Skip to content

Standard scan completes but cannot save: manifest.scan.id must match the workbench scan #292

Description

@qwertfyghjbn

Summary

A standard repository scan completed threat modeling, all-file review, finding generation, validation, coverage, manifest sealing, and report generation, but the CLI exited 2 during final workbench save:

Could not save the Codex Security scan: manifest.scan.id: must match the workbench scan

This appears related to #50 (same completed-analysis / failed-save family), but the contract mismatch here is the scan ID rather than target kind.

Environment

  • @openai/codex-security: 0.1.7 (npm latest at time of test)
  • Bundled plugin: 0.1.15
  • Bundled Codex SDK: 0.144.6
  • Node: v22.22.2
  • Python: 3.10.12 with tomli in an isolated copied venv
  • OS: WSL2 Linux
  • Mode: standard
  • Target: clean 3-file local Git repository
  • External provider: OpenRouter with deepseek/deepseek-v4-pro

No API key is included in this report.

Reproduction

Use a new output directory outside a clean Git repository:

npx -y @openai/codex-security scan /path/to/repository \
  --provider openrouter \
  --model deepseek/deepseek-v4-pro \
  --mode standard \
  --headless \
  --output-dir /tmp/codex-security-output \
  --python /path/to/copied-venv/bin/python \
  --format json

The fixture contains a Flask route where request.args.get("host") is concatenated into os.popen("ping -c 1 " + host).

Observed behavior

The scan ran for about 8 minutes. CLI progress reached:

Scan phase: preflight (0/3 files)
Preflight: worker delegation supported (up to 6 worker slots)
Scan phase: reviewing files (3/3 files)

The output directory contained:

artifacts/01_context/threat_model.md
scan-manifest.json
findings.json
coverage.json
report.md

The manifest had status: "completed"; coverage was complete; the findings file contained one reportable high-confidence CWE-78 command-injection finding with static validation and attack-path fields. All canonical files used the same generated scan ID (77fbdc60-bb3a-4aaf-ba4b-b2e3ea6ffad6).

Despite those artifacts, finalization failed with the error above and exit code 2.

Root-cause evidence

The SDK builds basePrompt before it registers the workbench scan. After registration it has the authoritative scanId, but the prompt sent to the model still says:

Use exactly "$CODEX_SECURITY_SCAN_ID" as the scan ID in the manifest, findings, and coverage.

The SDK exposes CODEX_SECURITY_SCAN_ID only in the runtime environment. In this standard scan, the model completed the scan semantics but authored a different UUID in the canonical artifacts, which the workbench rejected during complete-scan.

A robust fix is to interpolate the registered scanId (and similarly authoritative target values) into the post-registration prompt before thread.runStreamed(prompt), rather than relying on an LLM to resolve shell-style placeholders. This is analogous to the literal placeholder issue described in #290 for deep scans.

Expected behavior

The agent-authored manifest scan ID must match the scan ID registered by the workbench, or the finalizer should reconcile the value safely. A scan with complete canonical artifacts and coverage should save successfully and return a success exit code.

Impact

This prevents use of the CLI exit code as a formal evaluation/CI gate. The on-disk artifacts are useful for diagnosis, but the run cannot be treated as a clean completed scan while workbench persistence fails.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions