fix(security): harden trace teardown and gallery error responses

- CodeQL #1202 (tests/e2e/teardown.js): replace execSync shell execution with
  execFileSync, passing python executable and sanitizer arguments separately with
  shell: false. Preserve ODYSSEUS_TEST_PYTHON support, timeout handling, and
  fail-closed deletion.
- CodeQL #1203 (routes/gallery/gallery_routes.py): prevent exception information
  exposure in /api/image/edit-progress by returning a generic rejection response
  without leaking internal hostnames, IPs, resolver errors, or credentials.
- Add regression tests covering paths with spaces, metacharacters, synthetic command
  substitutions, process failure modes, fail-closed artifact purge, and SSRF
  rejection confidentiality.
This commit is contained in:
Alexandre Teixeira
2026-10-28 21:10:12 +00:00
parent cce5892eeb
commit de8f151632
4 changed files with 381 additions and 8 deletions
+8 -6
View File
@@ -1,6 +1,6 @@
const fs = require('node:fs');
const path = require('node:path');
const { execSync } = require('node:child_process');
const { execFileSync } = require('node:child_process');
module.exports = async () => {
const dataDirectory = process.env.PHOTO_EDITOR_E2E_DATA_DIR;
@@ -13,8 +13,8 @@ module.exports = async () => {
// Secure default tracing: never publish trace archives in CI or without explicit opt-in.
// When traces are not opted-in, purge all archives unconditionally.
// When local opt-in tracing is active, run the strict fail-closed structured sanitizer.
const resultsDir = path.join(__dirname, '..', '..', 'test-results');
const reportDir = path.join(__dirname, '..', '..', 'playwright-report');
const resultsDir = process.env.ODYSSEUS_TEST_RESULTS_DIR || path.join(__dirname, '..', '..', 'test-results');
const reportDir = process.env.ODYSSEUS_TEST_REPORT_DIR || path.join(__dirname, '..', '..', 'playwright-report');
if (fs.existsSync(reportDir)) {
try {
fs.rmSync(reportDir, { recursive: true, force: true });
@@ -65,12 +65,14 @@ module.exports = async () => {
purgeSensitiveArtifacts(resultsDir);
} else {
const python = process.env.ODYSSEUS_TEST_PYTHON || 'python';
const sanitizerScript = path.join(__dirname, '..', '..', 'scripts', 'sanitize_trace_artifacts.py');
const sanitizerScript = process.env.ODYSSEUS_TEST_SANITIZER_SCRIPT || path.join(__dirname, '..', '..', 'scripts', 'sanitize_trace_artifacts.py');
const timeout = Number(process.env.ODYSSEUS_TEST_SANITIZER_TIMEOUT_MS) || 30_000;
let sanitizerSucceeded = false;
try {
execSync(`${JSON.stringify(python)} ${JSON.stringify(sanitizerScript)} ${JSON.stringify(resultsDir)}`, {
execFileSync(python, [sanitizerScript, resultsDir], {
stdio: 'ignore',
timeout: 30_000,
timeout,
shell: false,
});
sanitizerSucceeded = true;
} catch (_) {