Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
5943f48
feat(cli): run standalone validation after scan
mldangelo-oai Sep 23, 2026
87e0b77
Merge main into scan validation branch
mldangelo-oai Sep 30, 2026
66b3920
fix(cli): reuse scan runtime for follow-up validation
mldangelo-oai Sep 30, 2026
798b0af
fix(cli): preserve validation progress on workflow retries
mldangelo-oai Sep 30, 2026
da4189c
Merge main and verify committed-diff validation preflight
mldangelo-oai Oct 5, 2026
31e5a0f
fix: preserve Cyber selection during scan validation
mldangelo-oai Oct 5, 2026
724cc93
Merge main into PR branch
mldangelo-oai Oct 5, 2026
9b700bb
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
8f7a5b7
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
5db2482
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
85293d8
Merge latest main into PR branch
mldangelo-oai Oct 6, 2026
f8e5b16
Merge commit 'refs/codex/pr-repair/latest-main' into HEAD
mldangelo-oai Oct 6, 2026
dee0ddb
fix(sdk): recheck cached validation source and target
mldangelo-oai Oct 6, 2026
796ff84
fix(validation): preserve prepared Git context and escaped diagnostics
mldangelo-oai Oct 6, 2026
ca77cea
Merge commit 'refs/codex/pr-repair/1007-git-context-main' into HEAD
mldangelo-oai Oct 6, 2026
396e751
Keep home-relative CLI fixtures on the home drive
mldangelo-oai Oct 6, 2026
47822c7
Merge commit 'refs/codex/author-1007-current-main' into HEAD
mldangelo-oai Oct 6, 2026
008f444
fix(ci): preserve the existing Markdown formatting scope
mldangelo-oai Oct 6, 2026
b2fd9ae
Merge commit 'refs/codex/pr-repair/1007/current-main' into HEAD
mldangelo-oai Oct 6, 2026
b7ad323
Retain current main Markdown coverage checks
mldangelo-oai Oct 6, 2026
9c48536
fix(validation): bind cached assessments to selected instructions
mldangelo-oai Oct 6, 2026
5869d40
Merge commit 'a7b3618f4c5e63aa70f4defa4e72bbe8e0c4cdb1' into HEAD
mldangelo-oai Oct 6, 2026
2f30173
fix(validation): bind cached reviews to required guidance
mldangelo-oai Oct 6, 2026
6bf9883
Merge commit 'refs/codex/pr-repair/1007/current-main' into HEAD
mldangelo-oai Oct 6, 2026
938d5ed
Merge commit 'refs/codex/pr-repair/1007/current-main' into HEAD
mldangelo-oai Oct 6, 2026
4fcfb96
fix(validation): rerun file-backed native instructions
mldangelo-oai Oct 6, 2026
4b8111a
Merge commit 'refs/codex/pr-repair/author1007/current-main' into HEAD
mldangelo-oai Oct 6, 2026
0e0929f
fix(validation): retain supplied context and optional caching
mldangelo-oai Oct 6, 2026
f132a87
Keep optional validation caches compatible with native guidance
mldangelo-oai Oct 6, 2026
ae5a1a8
fix(validation): leave unbound instruction inputs to Codex
mldangelo-oai Oct 6, 2026
3df7358
Merge observed main and preserve validation cache controls
mldangelo-oai Oct 7, 2026
d14fccc
Preserve validation method in imported CLI failure fixture
mldangelo-oai Oct 7, 2026
cbe31af
Keep imported validation failure fixture return contract
mldangelo-oai Oct 7, 2026
32dadd1
Preserve validation outcome when temporary input cleanup fails
mldangelo-oai Oct 7, 2026
02cba60
Merge commit '07429cf69631f1d3e560b3fee412c7036db3fcf3' into HEAD
mldangelo-oai Oct 7, 2026
da5d7d7
Respect native project configuration in validation cache eligibility
mldangelo-oai Oct 7, 2026
068c4c5
Merge commit '08de98715805b18a7c588e334d218ca6e6f60718' into HEAD
mldangelo-oai Oct 7, 2026
158390e
Merge commit '2ec54a087d3e473ace6594056f84931e02d6712d' into HEAD
mldangelo-oai Oct 7, 2026
0ebd120
Merge commit '7ac5a8bec74e915c1ce39bc201516b300afa299e' into HEAD
mldangelo-oai Oct 7, 2026
029af65
fix: check cached empty scan targets before validation
mldangelo-oai Oct 7, 2026
cb659e4
Merge commit 'eb73cc0fa64f25f432bace6f8ff3f475038bfc71' into HEAD
mldangelo-oai Oct 7, 2026
475aba4
Merge main into optional scan validation
mldangelo-oai Oct 7, 2026
db76012
Preserve Git settings when validating empty scans
mldangelo-oai Oct 7, 2026
20e88bb
Merge main into optional scan validation
mldangelo-oai Oct 7, 2026
9c08365
Merge main into scan validation
mldangelo-oai Oct 8, 2026
59f04bb
Merge commit '7eba9000353aa1c58aae4941645f1bad5a37bc27' into mdangelo…
mldangelo-oai Oct 8, 2026
0e42ec0
fix(cli): retain selected Python during target checks
mldangelo-oai Oct 8, 2026
e93aff0
Merge commit 'e9aceb43b719cb16d54d7ee79e76a1112a7e9373' into mdangelo…
mldangelo-oai Oct 8, 2026
30262ad
Merge remote-tracking branch 'origin/main' into mdangelo/codex/repair…
mldangelo-oai Oct 8, 2026
3ff32e3
Merge remote-tracking branch 'origin/main' into mdangelo/codex/repair…
mldangelo-oai Oct 8, 2026
c7e53c7
fix(validation): preserve target compatibility and recovery guidance
mldangelo-oai Oct 8, 2026
d858e6f
Merge remote-tracking branch 'origin/main' into mdangelo/codex/repair…
mldangelo-oai Oct 8, 2026
11d5694
Preserve validation context and stabilize recovery fixture
mldangelo-oai Oct 8, 2026
056d5bf
Merge main into scan validation
mldangelo-oai Oct 8, 2026
06a0511
Keep validation snapshots internal in published declarations
mldangelo-oai Oct 8, 2026
5b1eef8
Reuse captured knowledge roots during validation
mldangelo-oai Oct 8, 2026
0af670f
Merge main and reconcile workflow snapshot options
mldangelo-oai Oct 8, 2026
e2bd11e
Preserve revision-only checks for legacy scan validation
mldangelo-oai Oct 8, 2026
c76d451
Preserve runtime selections in validation follow-up checks
mldangelo-oai Oct 8, 2026
8ba406b
Keep the trusted Git fixture original PATH
mldangelo-oai Oct 8, 2026
15b660a
Merge current main into scan validation
mldangelo-oai Oct 9, 2026
1fcc711
Validate cached assessment evidence before reuse
mldangelo-oai Oct 9, 2026
e877193
Merge pinned main and disambiguate validation test titles
mldangelo-oai Oct 9, 2026
413e369
fix: respect validation evidence destinations on workflow retries
mldangelo-oai Oct 9, 2026
585a898
fix: verify validation source after reading evidence
mldangelo-oai Oct 9, 2026
6cf9b2d
Merge main into scan validation branch
mldangelo-oai Oct 10, 2026
662e686
fix(validation): keep storage outside knowledge inputs
mldangelo-oai Oct 10, 2026
7bc64dd
chore: merge main into scan validation
mldangelo-oai Oct 11, 2026
910ef40
Bind cached validation assessments to the requested safety identifier
mldangelo-oai Oct 12, 2026
205a697
fix(validation): preserve selected plugins and late guidance changes
mldangelo-oai Oct 12, 2026
7831283
test(validation): compare canonical Python and plugin paths
mldangelo-oai Oct 12, 2026
f2fb676
Merge main and preserve standalone scan validation contracts
codex Oct 16, 2026
2f8b3e7
Preserve validation failures in full-output envelopes
codex Oct 16, 2026
fc5c4a8
fix: preserve validation results through temporary cleanup
codex Oct 16, 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
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,13 @@ for (const layout of ["standard", "diff", "worker"] as const) {
f.write(input),
);
await f.write(f.draft({ deferred: [pending] }));
const checkpoints = path.join(f.root, "checkpoints");
for (const name of await readdir(checkpoints))
await utimes(path.join(checkpoints, name), 1, 1);
for (const name of layout === "worker"
? ["result.json", "checkpoint-head.json"]
: ["coverage.json", "scan-manifest.json", "findings.json"])
await utimes(path.join(f.root, name), 1, 1);
await fail(closing);
await f.write(f.draft({}, true));
assert.deepEqual((await f.read()).deferred, []);
Expand Down
10 changes: 10 additions & 0 deletions plugins/codex-security/mcp-app/tests/test_deep_scan_executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2365,6 +2365,16 @@ async function testWorkerCyberAccessSettings() {
api_key_model_discovery: true,
},
},
{
name: "red",
configuration:
'[codex_security]\ncyber_access_program = "daybreak_red"\n[features]\napi_key_cyber_access_programs = true\napi_key_model_discovery = true\n',
program: "daybreak_red",
features: {
api_key_cyber_access_programs: true,
api_key_model_discovery: true,
},
},
{
name: "explicit-false",
configuration:
Expand Down
1 change: 1 addition & 0 deletions plugins/codex-security/scripts/workbench_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def add_command(name: str, *required: str) -> argparse.ArgumentParser:

get_scan = add_command("get-scan", "--scan-id")
get_scan.add_argument("--occurrence-id")
get_scan.add_argument("--check-target", action="store_true")

add_command("rename-scan", "--scan-id", "--name")

Expand Down
3 changes: 3 additions & 0 deletions plugins/codex-security/scripts/workbench_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@
require_git_worktree_head,
require_remediation_target,
require_scan_target_identity,
require_scan_target_unchanged,
restore_directory_junctions,
scan_target_warning,
worktree_content_digest,
Expand Down Expand Up @@ -3441,6 +3442,8 @@ def main(*, before_archive: Callable[[], None] | None = None) -> None:
elif args.command == "start-headless-standard-scan":
result = _start_prompt_driven_scan(connection, args, headless_standard=True)
elif args.command == "get-scan":
if args.check_target:
require_scan_target_unchanged(require_scan(connection, args.scan_id))
result = scan_context(connection, args.scan_id, args.occurrence_id)
elif args.command == "rename-scan":
result = scan_history.rename_scan(
Expand Down
60 changes: 51 additions & 9 deletions plugins/codex-security/scripts/workbench_finding_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
import hashlib
import json
import sqlite3
import stat
import sys
from pathlib import Path
from typing import Any

sys.path.insert(0, str(Path(__file__).resolve().parent))
from workbench_target import directory_content_digest, git_bytes, git_revision
from workbench_target import (
UnreadableLocalFile,
UnsupportedLocalFileType,
directory_content_digest,
git_bytes,
git_revision,
)

WORKFLOW_BINDINGS = {
"repositoryPath": "repository_path",
Expand Down Expand Up @@ -123,7 +130,24 @@ def finding_workflow(
if payload["action"] == "get":
return {"workflow": read_workflow(connection, workflow_id)}
if payload["action"] == "source":
target = Path(payload["repository"]).resolve(strict=True)
evidence = payload.get("evidence") is True
if evidence:
try:
target = Path(payload["repository"])
metadata = target.lstat()
if (
not target.is_absolute()
or not stat.S_ISDIR(metadata.st_mode)
or getattr(metadata, "st_reparse_tag", 0) & 0x20000000
or target.resolve(strict=True) != target
):
raise UnreadableLocalFile("The validation evidence directory was replaced.")
except (OSError, RuntimeError, UnreadableLocalFile):
if payload.get("optional") is True:
return {"source": None}
raise
else:
target = Path(payload["repository"]).resolve(strict=True)
private_paths = payload.get("privateStatePaths", [])
if not isinstance(private_paths, list) or any(
not isinstance(path, str) or not Path(path).is_absolute() for path in private_paths
Expand All @@ -133,18 +157,31 @@ def finding_workflow(
excluded = tuple(path for path in excluded if path.is_relative_to(target))
if target in excluded:
raise SystemExit("Private runtime storage must not exclude the repository root.")
try:
content = directory_content_digest(
target,
excluded=excluded,
include_ignored=True,
include_git_metadata=evidence,
)
except (UnsupportedLocalFileType, UnreadableLocalFile, OSError):
if payload.get("optional") is True:
return {"source": None}
raise
return {
"source": {
"repository": str(target),
"revision": "unversioned" if payload.get("gitDisabled") else git_revision(target),
"revision": (
"unversioned"
if evidence or payload.get("gitDisabled")
else git_revision(target)
),
"refsDigest": hashlib.sha256(
b""
if payload.get("gitDisabled")
if evidence or payload.get("gitDisabled")
else (git_bytes(target, "show-ref") or b"").strip()
).hexdigest(),
"content": directory_content_digest(
target, excluded=excluded, include_ignored=True
),
"content": content,
**({"privateStatePaths": [str(path) for path in excluded]} if excluded else {}),
}
}
Expand All @@ -158,15 +195,20 @@ def finding_workflow(
binding = payload["binding"]
source = binding["source"]
scope = binding["scope"]
on_conflict = (
"DO UPDATE SET result_json = excluded.result_json, created_at = excluded.created_at"
if payload.get("replace") is True and binding.get("stage") == "scan-validation"
else "DO NOTHING"
)
with connection:
connection.execute(
"""INSERT INTO finding_workflow_reviews
f"""INSERT INTO finding_workflow_reviews
(workflow_id, review_key, review_contract_version, codex_version,
source_repository_path, source_revision, source_refs_digest, source_content_digest,
scope_repository_id, scope_all_repositories, model, effort, settings_digest,
prompt_digest, contract_digest, result_json, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(workflow_id, review_key) DO NOTHING""",
ON CONFLICT(workflow_id, review_key) {on_conflict}""",
(
workflow_id,
payload["key"],
Expand Down
67 changes: 57 additions & 10 deletions plugins/codex-security/scripts/workbench_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
_WINDOWS = os.name == "nt"


class UnreadableLocalFile(SystemExit):
"""Source contents are unavailable for an optional review cache."""


class UnsupportedLocalFileType(SystemExit):
"""An entry cannot be read as source content for a review cache."""


def committed_diff_snapshot_digest(kind: str, base_revision: str, head_revision: str) -> str:
digest = hashlib.sha256(
f"codex-security-diff/v1\0{kind}\0{base_revision}\0{head_revision}".encode()
Expand Down Expand Up @@ -418,15 +426,18 @@ def worktree_content_digest(target: Path) -> str:


def remediation_checkout_snapshot(
scan: sqlite3.Row, *, expected_revision: str | None = None
scan: sqlite3.Row,
*,
expected_revision: str | None = None,
head_changed_message: str = (
"Repository HEAD changed. Regenerate the remediation patch against the current checkout."
),
) -> tuple[str, str | None]:
target = require_scan_target_identity(scan)
revision = git_revision(target)
required_revision = expected_revision or scan["target_revision"]
if revision != required_revision:
raise SystemExit(
"Repository HEAD changed. Regenerate the remediation patch against the current checkout."
)
raise SystemExit(head_changed_message)
content_digest = (
worktree_content_digest(target)
if revision != "unversioned"
Expand All @@ -435,6 +446,36 @@ def remediation_checkout_snapshot(
return revision, content_digest


def require_scan_target_unchanged(scan: sqlite3.Row) -> None:
head_changed_message = "Repository HEAD changed. Run a new scan before validating its findings."
if (
scan["diff_target_kind"] not in {"working_tree", "commit", "range"}
and scan["target_snapshot_digest"] is None
and scan["mode"] != "diff"
and scan["target_revision"] != "unversioned"
):
# Legacy Git records bind only the revision, not working-tree contents.
target = require_scan_target_identity(scan)
if git_revision(target) != scan["target_revision"]:
raise SystemExit(head_changed_message)
require_clean_submodule_worktrees(target)
return
_, current_digest = remediation_checkout_snapshot(
Comment thread
mldangelo-oai marked this conversation as resolved.
scan, head_changed_message=head_changed_message
)
if scan["diff_target_kind"] == "working_tree":
expected_digest = scan["diff_content_digest"]
elif scan["diff_target_kind"] in {"commit", "range"}:
expected_digest = clean_worktree_content_digest()
else:
expected_digest = scan["target_snapshot_digest"]
if current_digest != expected_digest:
Comment thread
mldangelo-oai marked this conversation as resolved.
raise SystemExit(
"Scan target contents changed since the scan. "
"Run a new scan before validating its findings."
)


def worktree_content_digest_for_context(
repository: Path,
pathspec: str,
Expand Down Expand Up @@ -800,6 +841,7 @@ def source_directory_snapshot_paths(
target: Path,
excluded: tuple[Path, ...] = (),
*,
include_git_metadata: bool = False,
onerror: Callable[[OSError], None] | None = None,
) -> list[Path]:
paths: list[Path] = []
Expand All @@ -809,15 +851,17 @@ def source_directory_snapshot_paths(
parent = Path(directory)
for name in directories[:]:
path = parent / name
if name == ".git" or path in excluded:
if (name == ".git" and not include_git_metadata) or path in excluded:
directories.remove(name)
continue
paths.append(path)
# Name-surrogate reparse points include Windows directory junctions.
if getattr(path.lstat(), "st_reparse_tag", 0) & 0x20000000:
directories.remove(name)
paths.extend(
parent / name for name in files if name != ".git" and parent / name not in excluded
parent / name
for name in files
if (name != ".git" or include_git_metadata) and parent / name not in excluded
)
return sorted(paths)

Expand All @@ -827,6 +871,7 @@ def directory_content_digest(
*,
excluded: tuple[Path, ...] = (),
include_ignored: bool = False,
include_git_metadata: bool = False,
) -> str:
def raise_walk_error(error: OSError) -> None:
raise error
Expand All @@ -835,7 +880,9 @@ def raise_walk_error(error: OSError) -> None:
path.relative_to(target) for path in excluded if path.is_relative_to(target)
]
paths = (
source_directory_snapshot_paths(target, excluded, onerror=raise_walk_error)
source_directory_snapshot_paths(
target, excluded, include_git_metadata=include_git_metadata, onerror=raise_walk_error
)
if include_ignored
else git_directory_snapshot_paths(target)
)
Expand All @@ -850,7 +897,7 @@ def raise_walk_error(error: OSError) -> None:
try:
metadata = path.lstat()
except OSError as exc:
raise SystemExit(f"Could not read local file: {relative_path}") from exc
raise UnreadableLocalFile(f"Could not read local file: {relative_path}") from exc
mode = metadata.st_mode
link_target = (
os.readlink(path)
Expand All @@ -874,12 +921,12 @@ def raise_walk_error(error: OSError) -> None:
content_digest.update(chunk)
content_size += len(chunk)
except OSError as exc:
raise SystemExit(f"Could not read local file: {relative_path}") from exc
raise UnreadableLocalFile(f"Could not read local file: {relative_path}") from exc
update_digest_field(digest, b"kind", b"file")
update_digest_field(digest, b"size", str(content_size).encode())
update_digest_field(digest, b"content-sha256", content_digest.digest())
else:
raise SystemExit(f"Unsupported local file type: {relative_path}")
raise UnsupportedLocalFileType(f"Unsupported local file type: {relative_path}")
return f"codex-security-snapshot/v1:sha256:{digest.hexdigest()}"


Expand Down
Loading
Loading