Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
914c381
fix(plugin): require evidence before completing scans
mldangelo-oai Oct 23, 2026
3f30f71
test(plugin): complete the shared scan evidence fixture
mldangelo-oai Oct 23, 2026
5fd282c
fix(plugin): preserve import and unfinished scan compatibility
mldangelo-oai Oct 23, 2026
9edf1a0
test(sdk): retain validation proof in saved finding fixtures
mldangelo-oai Oct 23, 2026
371bf4d
fix(plugin): allow repair after incomplete evidence errors
mldangelo-oai Oct 23, 2026
534d592
fix(plugin): validate default-complete discovery handoffs
mldangelo-oai Oct 23, 2026
aef9ed4
test(plugin): cover checkpoint recovery after rejected handoffs
mldangelo-oai Oct 23, 2026
cc6f844
fix(plugin): validate evidence before discovery workers finish
mldangelo-oai Oct 23, 2026
aa9d7cb
test(plugin): complete reducer and admission evidence fixtures
mldangelo-oai Oct 23, 2026
3d4df7e
fix: repair accepted Deep evidence when resuming
mldangelo-oai Oct 23, 2026
0af2d56
fix: resume evidence repair before sealing Deep reports
mldangelo-oai Oct 23, 2026
d6b3777
fix: preserve Deep report cutoffs during evidence repair
mldangelo-oai Oct 23, 2026
db55f82
Merge main and preserve evidence checks during checkpoint recovery
mldangelo-oai Oct 23, 2026
3e1d19d
fix: preserve saturation across late discovery failures
mldangelo-oai Oct 23, 2026
7a7f9e7
test: complete saved threat-model recovery evidence
mldangelo-oai Oct 23, 2026
de35c94
Merge main into scan evidence completion
mldangelo-oai Oct 24, 2026
4162bb4
fix: preserve selected Deep Scan results during recovery
mldangelo-oai Oct 24, 2026
1ac50d6
fix: preserve independently reviewed Deep Scan surfaces
mldangelo-oai Oct 24, 2026
6e71e31
fix: check canceled worker evidence after deadline reconciliation
mldangelo-oai Oct 24, 2026
c358227
test: supply evidence when completing revised sibling findings
mldangelo-oai Oct 24, 2026
7b5b801
fix: keep corroborated evidence across deadline checkpoints
mldangelo-oai Oct 24, 2026
535fe0f
fix: bind deadline follow-up to its worker and authored proof
mldangelo-oai Oct 24, 2026
47ed468
fix: preserve selected scan findings and coverage across recovery
mldangelo-oai Oct 24, 2026
609402f
fix: skip checkpoint metadata for canonical-only findings
mldangelo-oai Oct 24, 2026
8476d30
fix: match retained evidence to its original finding
mldangelo-oai Oct 24, 2026
ed0b193
Merge main into the pull request
mldangelo-oai Oct 25, 2026
02fc216
Merge main and preserve reducer paging during evidence repair
mldangelo-oai Oct 25, 2026
0cde27b
test: align Deep Scan publication fixtures with active runs
mldangelo-oai Oct 25, 2026
a194682
test: supply evidence for completed recovery findings
mldangelo-oai Oct 25, 2026
c45caea
Merge main and preserve finding evidence during scan recovery
mldangelo-oai Oct 28, 2026
af490c0
Merge main into finding evidence checks
mldangelo-oai Oct 28, 2026
40baa8e
test: use current evidence when sealing scan fixtures
mldangelo-oai Oct 28, 2026
20d1603
fix: bind deferred validation to the registered scan recipe
mldangelo-oai Oct 28, 2026
b30f877
fix: preserve selected reports when the repair checkout changes
mldangelo-oai Oct 28, 2026
2933299
fix: preserve the original deadline when deferring incomplete findings
mldangelo-oai Oct 28, 2026
b7a60ae
Merge main and preserve evidence repair across scan recovery
mldangelo-oai Nov 3, 2026
5e0a422
test(evals): retain source evidence in completed findings
mldangelo-oai Nov 3, 2026
e3701f2
Merge main into scan completion evidence checks
mldangelo-oai Nov 3, 2026
eb62279
Merge main to include Deep Scan caller tool settings
mldangelo-oai Nov 3, 2026
6ccd07e
fix(plugin): repair historical evidence on host iterations
mldangelo-oai Nov 4, 2026
e3ad1f7
Merge remote-tracking branch 'origin/main' into HEAD
mldangelo-oai Nov 4, 2026
67c1bee
test(plugin): distinguish completed findings from deadline checkpoints
mldangelo-oai Nov 4, 2026
89eab62
Merge main into mdangelo/codex/evidence-completeness
mldangelo-oai Nov 4, 2026
e8a0677
Merge main into mdangelo/codex/evidence-completeness
mldangelo-oai Nov 4, 2026
37405db
Merge main into mdangelo/codex/evidence-completeness
mldangelo-oai Nov 4, 2026
9d2f6d1
Merge main into mdangelo/codex/evidence-completeness
mldangelo-oai Nov 4, 2026
eddd444
ci: retry managed Code Quality after GitHub installation rate limit
mldangelo-oai Nov 4, 2026
41bbc41
Merge main and reuse publication fixtures for required evidence
mldangelo-oai Nov 4, 2026
cdb4f1f
Merge main and checkpoint discovery evidence before returning
mldangelo-oai Nov 4, 2026
2ef8511
Merge main while preserving evidence repair and comparison isolation
mldangelo-oai Nov 4, 2026
070f841
test(deep-scan): import the coordinator stop error fixture
mldangelo-oai Nov 4, 2026
5ca47ac
test(workbench): bind retained validation to the renamed worker excerpt
mldangelo-oai Nov 4, 2026
eec6c6f
Merge commit 'refs/workers-integ/main' into HEAD
mldangelo-oai Nov 4, 2026
093e882
fix(deep-scan): retain resumed evidence and account for repair sessions
mldangelo-oai Nov 4, 2026
697f35e
Merge main and preserve both worker report admission checks
mldangelo-oai Nov 4, 2026
71b12bb
Reconcile evidence repair with current scan recovery APIs
mldangelo-oai Nov 4, 2026
3573951
Integrate current coverage publication and deadline preflight
mldangelo-oai Nov 5, 2026
15b00e3
test(deep-scan): exercise the evidence repair child process
mldangelo-oai Nov 5, 2026
d7ced30
test(deep-scan): retain complete evidence in repair assignment fixture
mldangelo-oai Nov 5, 2026
f0fe5c2
Integrate worker usage receipts and permission boundaries for evidenc…
mldangelo-oai Nov 5, 2026
10434bb
Merge current main and regenerate completed example with saved evidence
mldangelo-oai Nov 5, 2026
b1aec77
Merge branch 'main' into mdangelo/codex/evidence-completeness
mldangelo-oai Nov 6, 2026
e61606a
chore(plugin): merge main and retain both recovery test fixtures
mldangelo-oai Nov 7, 2026
8a64e04
Merge main and retain Windows evidence and resume protections
mldangelo-oai Nov 10, 2026
2597efa
Merge main while preserving sealed evidence completeness repair
mldangelo-oai Nov 11, 2026
418ef5c
fix(deep-scan): recheck checkout ownership after completion lock
mldangelo-oai Nov 11, 2026
0eb4656
fix(scan): preserve selected recovery source associations
mldangelo-oai Nov 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions evals/standard-time-budget/test_validation_survival.mts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,15 @@ for (const deferLast of [false, true]) {
const accepted = (deferLast ? candidates.slice(0, -1) : candidates).map(
(candidate) => ({
...candidate,
rootCause: `The ${candidate.ruleId} route omits the corresponding safe control.`,
attackPath: {
dataflow: `Request input reaches the cited sink in ${candidate.locations[0]!.path}.`,
},
validation: {
disposition: "reportable",
method: "Static review",
summary: `Verified ${candidate.ruleId} against its cited sink.`,
evidenceRefs: [candidate.ruleId],
limitations: ["Synthetic review."],
},
provenance: {
Expand Down Expand Up @@ -96,6 +101,8 @@ for (const deferLast of [false, true]) {
for (const field of [
"identity",
"locations",
"rootCause",
"attackPath",
"validation",
"codeEvidence",
] as const)
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex-security/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codex-security",
"version": "0.1.95",
"version": "0.1.96",
Comment thread
mldangelo-oai marked this conversation as resolved.
Resolved
"description": "Codex Security workflows for security scans, analysis, and investigation.",
"author": {
"name": "OpenAI"
Expand Down
20 changes: 20 additions & 0 deletions plugins/codex-security/examples/completed-scan/findings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@
"summary": "The caller must be able to supply an archive for extraction. The service account must have write access to the destination."
}
},
"codeEvidence": [
{
"code": "destination = output_dir / entry.name\nwith archive.open(entry) as source:\n destination.parent.mkdir(parents=True, exist_ok=True)\n destination.write_bytes(source.read())",
"endLine": 44,
"explanation": "The destination incorporates an untrusted archive entry name and is written without a containment check.",
"id": "archive-write",
"label": "Unchecked archive write",
"path": "src/extract.py",
"startLine": 41
}
],
"confidence": {
"level": "high",
"rationale": "The destination passed to the file writer is visible in the reviewed code."
Expand Down Expand Up @@ -42,6 +53,9 @@
"remediationTests": [
"Assert that extracting an archive entry named `../escape.txt` fails without writing outside the extraction root."
],
"rootCause": {
"summary": "The extractor joins the entry name to the output directory without checking whether the destination stays inside it."
},
"ruleId": "path-traversal.archive-extraction",
"severity": {
"changeConditions": "A filesystem policy that confines writes to a disposable import directory would limit the impact.",
Expand All @@ -59,6 +73,12 @@
},
"title": "Unsafe archive extraction can escape the output directory",
"validation": {
"evidence": [
"The untrusted entry name reaches the filesystem write without normalization or a containment check."
],
"limitations": [
"The extraction route was not exercised dynamically."
],
"method": "Static source review",
"summary": "The extractor combines the output directory with the entry name and passes the resulting path to the writer. It never checks that the destination stays inside the output directory."
}
Expand Down
10 changes: 10 additions & 0 deletions plugins/codex-security/examples/completed-scan/report.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,22 @@ No explicit canonical threat-model summary was recorded.

Archive extraction can overwrite files outside the directory chosen for the import.

#### Root Cause

The extractor joins the entry name to the output directory without checking whether the destination stays inside it.

#### Validation

The extractor combines the output directory with the entry name and passes the resulting path to the writer. It never checks that the destination stays inside the output directory.

Validation method: Static source review

Evidence:
- The untrusted entry name reaches the filesystem write without normalization or a containment check.

Limitations:
- The extraction route was not exercised dynamically.

#### Dataflow

The archive supplies an entry name. The extractor uses that name to select the file it writes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{
"mediaType": "application/json",
"path": "findings.json",
"sha256": "3c63586ea38b07b1d5ad9004ba2afc27f67e79ed164da508c7beae72648eb3b7"
"sha256": "c6a4d31e66342a60bf427e71f57b318236ac4c822d92303a716dd516165e0dd1"
}
],
"completedAt": "2026-05-31T18:09:00Z",
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex-security/mcp-app/deep-scan-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export async function runCliDeepScan(): Promise<void> {
if (controller.signal.aborted)
throw new Error("Deep Scan canceled before coordinator startup.");
let terminal = run;
if (run.status === "running") {
if (run.status === "running" || run.evidenceRepairRequired) {
const started = await startDeepScanEngine({
run,
store,
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex-security/mcp-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codex-security-mcp-app",
"version": "0.1.161",
"version": "0.1.162",
"type": "module",
"private": true,
"scripts": {
Expand Down
20 changes: 18 additions & 2 deletions plugins/codex-security/mcp-app/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1568,7 +1568,7 @@ export function createCodexSecurityServer(): McpServer {
{
title: "Complete Codex Security Scan",
description:
"Finalization only: validate and seal already-authored scan-manifest.json and findings.json, generate report.md, index findings, and mark the scan complete. For an app-backed running scan, scan-manifest.json is an unsealed draft and must omit scan.sealedAt and scan.artifacts; this tool supplies the exact workbench timestamps, seal, artifact digests, and derived finding identities. Call only after those canonical files exist; this tool does not create missing artifacts or run skipped phases. If it fails, surface the exact error and stop the current response without retrying completion or returning a final, no-findings, structured, or benchmark response.",
"Finalization only: validate and seal already-authored scan-manifest.json and findings.json, generate report.md, index findings, and mark the scan complete. For an app-backed running scan, scan-manifest.json is an unsealed draft and must omit scan.sealedAt and scan.artifacts; this tool supplies the exact workbench timestamps, seal, artifact digests, and derived finding identities. Call only after those canonical files exist; this tool does not create missing artifacts or run skipped phases. If completion requests a corrected draft, repair the same scan and retry. For other failures, surface the exact error and stop the current response without retrying completion or returning a final, no-findings, structured, or benchmark response.",
inputSchema: completeScanSchema,
annotations: writingAnnotations,
_meta: modelActionMeta,
Expand Down Expand Up @@ -2206,6 +2206,7 @@ function deepScanTerminalResult(
run: DeepScanRunState,
completion?: JsonObject,
) {
if (run.evidenceRepairRequired) return undefined;
if (run.status === "succeeded") {
if (!run.manifestPath) return undefined;
const outcome =
Expand Down Expand Up @@ -2417,9 +2418,24 @@ function failureDiagnostic(error: unknown): string {
}

function completionFailureMessage(error: unknown): string {
const diagnostic = failureDiagnostic(error);
if (
diagnostic.startsWith(
"Scan result contains invalid findings; repair the finding records before completion.",
) ||
diagnostic ===
"The latest saved scan draft is incomplete; continue the scan before completing it."
) {
return [
"Codex Security scan completion needs a corrected draft.",
diagnostic,
"Continue the same scan, repair the named evidence gaps using actual source or validation evidence, and retry completion after saving the corrected draft. Preserve valid findings and deferred work; do not invent evidence or start another scan.",
"Do not return a final, no-findings, structured, or benchmark response until completion succeeds.",
].join("\n");
}
return [
"Codex Security scan completion failed.",
failureDiagnostic(error),
diagnostic,
"Stop the current response and surface this exact MCP error.",
"Do not retry completion or return a final, no-findings, structured, or benchmark response.",
].join("\n");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createHash } from "node:crypto";
import { dirname, join, posix, relative, sep } from "node:path";
import { isDeepStrictEqual } from "node:util";
import { asRecord } from "../record.js";
import { asRecord, isNonEmptyString } from "../record.js";
import type { JsonObject } from "../types.js";
import { artifactSourcePath, type ArtifactContext } from "../artifact-io.js";
import {
Expand Down Expand Up @@ -91,8 +91,11 @@ export async function validateDiscoveryArtifacts(
throw new Error(
"Standard scan worker wrote only a checkpoint; its audit is not complete.",
);
// Preserve the existing publication of previously accepted historical payloads.
if (admission === "new") validateWorkerReport(result);
// Historical worker inputs are immutable. The host repairs their evidence on resume.
if (admission === "new") {
validateWorkerReport(result);
validateCompletedFindingEvidence(result.findings);
}
validateRetainedCoverage(result.reviewState);
// Check the worker's own task IDs before qualifying active and closed tasks.
const closures = result.reviewState.resolvedDeferred ?? [];
Expand Down Expand Up @@ -296,3 +299,48 @@ export function parseStoredScanDraft<Result extends ScanUnion>(
}
return parsed;
}

/** Match the finalizer's evidence gate while worker validation can still retry. */
export function validateCompletedFindingEvidence(findings: JsonObject[]): void {
for (const [index, finding] of findings.entries()) {
const requireText = (value: unknown, field: string) => {
if (!isNonEmptyString(value))
throw new Error(
`findings[${index}].${field}: include the supporting analysis before completing the worker.`,
);
};
if (
!Array.isArray(finding.codeEvidence) ||
finding.codeEvidence.length === 0
)
throw new Error(
`findings[${index}].codeEvidence: include the source snippets used as proof.`,
);
requireText(
asRecord(finding.rootCause)?.summary ?? finding.rootCause,
"rootCause",
);
const dataflow = asRecord(finding.attackPath)?.dataflow;
const flow = asRecord(dataflow);
if (flow) {
if (!flow.summary) {
requireText(flow.source, "attackPath.dataflow.source");
requireText(flow.sink, "attackPath.dataflow.sink");
}
} else {
requireText(dataflow, "attackPath.dataflow");
}
const validation = asRecord(finding.validation);
requireText(validation?.method, "validation.method");
if (!validation?.result)
requireText(validation?.summary, "validation.summary");
const hasEvidence = [validation?.evidence, validation?.evidenceRefs].some(
(value) =>
Array.isArray(value) ? value.length > 0 : isNonEmptyString(value),
);
if (!hasEvidence)
throw new Error(
`findings[${index}].validation: include observed evidence or evidenceRefs to the source proof.`,
);
}
}
Loading
Loading