Skip to content

Commit 86483fc

Browse files
ianw-oaicopyberry
authored andcommitted
chore(codex-security): sync public projection
- [codex-security] simplify standard security scan (#1154250) GitOrigin-Timestamp=2026-07-27T12:20:24-07:00 GitOrigin-RevId: 081b5a80efb844fdc5f22f99e45f7a3e38eda352
1 parent 10e7a22 commit 86483fc

17 files changed

Lines changed: 163 additions & 492 deletions

sdk/typescript/_bundled_plugin/references/final-report.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ Set the finding category and CWE from the primary broken control. Do not add sec
5353

5454
Examples that should normally become separate final findings include SQL API modes such as `execute`, `executemany`, and `executescript`; deserializer variants such as `pickle.load`, `pickle.loads`, `yaml.load`, and `yaml.load_all`; distinct path/file helper calls; SSRF modes with different destination controls; and missing-auth protected actions such as create, delete, reset, admin, and job-trigger endpoints.
5555

56-
Before completing canonical JSON, reconcile each final finding against its candidate-ledger path from `scan-artifacts.md`, the saved validation closure table, and the repository coverage ledger when those artifacts exist. Every final candidate finding must have discovery, validation, and attack-path receipts for the same candidate id, or an explicit follow-up-needed reason for the missing proof. Start from validated rows marked `reportable` or `survives: yes`, not only from the most polished candidate narrative. Every `reportable` seeded or root-control ledger row must become a canonical finding with the same root-control file:line. Rows closed as `suppressed`, `not_applicable`, or `deferred` should appear in canonical coverage surfaces using public-facing outcomes such as `Rejected`, `Not applicable`, or `Needs follow-up`. Do not silently drop a seeded/root-control row because a same-family neighboring finding survived. If attack-path analysis omitted a reportable validation row, populate a concise canonical attack path from the validation evidence and threat model rather than dropping the row.
56+
For a standard repository or scoped-path scan, assemble the canonical JSON from the enriched `<discovery_dir>/candidate_ledger.jsonl`. Map each nested `validation` record into the finding's validation fields, map its confidence and rationale into top-level `confidence.level` and `confidence.rationale`, and map each nested `attack_path` record into dataflow, reachability, severity, and change conditions.
57+
58+
Apply row outcomes in this order: validation disposition `reportable` plus attack-path decision `reportable` becomes a finding with its distinct instance and all relevant entrypoint, root-control, sink, and supporting locations; otherwise, a `deferred` result from either phase becomes `needs_follow_up` coverage and a `coverage.deferred` entry using the recorded uncertainty or proof gap; otherwise, validation disposition `not_applicable` becomes `not_applicable` coverage; otherwise, validation disposition `suppressed` or attack-path decision `ignore` becomes `rejected` coverage. A missing required phase record leaves the candidate unresolved and prevents complete coverage. Do not require phase receipts, per-candidate narratives, or another reconciliation pass.
59+
60+
Diff, deep, and resumed legacy scans may still provide per-candidate ledgers, validation closure tables, and repository coverage ledgers. When those artifacts exist, retain their traceability: start from reportable/surviving rows, preserve exact affected locations, and map suppressed, not-applicable, or deferred rows to public-facing coverage outcomes. Do not silently drop a seeded row because a same-family neighbor survived.
5761

5862
## Report Structure
5963

sdk/typescript/_bundled_plugin/references/sarif-adapter.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The adapter:
1010
- uses stable `ruleId` values
1111
- keeps rule descriptors stable across scans
1212
- emits repository-relative POSIX paths
13-
- uses one root-control location for GitHub annotation when available and keeps remaining evidence locations under `relatedLocations`
13+
- keeps the root-control location first for GitHub annotation when available and emits every distinct affected or code-evidence location in `locations`, so vulnerable sinks remain matchable
1414
- preserves the semantic fingerprint under `codexSecurity/v1`
1515
- emits GitHub's source-line `primaryLocationLineHash` when it can safely hash a bounded regular non-symlink source file inside the available source root
1616
- maps categorical severity to SARIF `level`

sdk/typescript/_bundled_plugin/references/scan-artifacts.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,19 @@ End each repository-scoped threat model with these two lines:
3838

3939
## Finding Discovery (Phase 2) Paths
4040

41+
### Standard Repository Or Scoped-Path Scan
42+
43+
- Deterministic in-scope file list: `<discovery_dir>/in_scope_files.txt`
44+
- Compact combined candidate ledger: `<discovery_dir>/candidate_ledger.jsonl`
45+
- The combiner reads one or more temporary raw candidate sources, validates them against the in-scope file list, merges rows with the same CWE ids, locations, and optional instance, preserves their text, and assigns deterministic `candidate_id` values. This is the sole durable standard candidate artifact.
46+
- After normalization, compact validation adds exactly one `validation` object to every row with `disposition` (`reportable`, `suppressed`, `not_applicable`, or `deferred`), `method`, `confidence` (`high`, `medium`, or `low`), `confidence_rationale`, concise `rubric` and `evidence`, `counterevidence_or_proof_gap`, `remaining_uncertainty`, and optional `artifact_paths`. Add `source`, `control`, `sink`, or `preconditions` only when they clarify or differ from the discovery fields.
47+
- Compact attack-path analysis adds exactly one `attack_path` object to each validation row marked `reportable` or `deferred`, with `decision` (`reportable`, `ignore`, or `deferred`), `dataflow`, `reachability`, `counterevidence`, `impact` and `likelihood` (`high`, `medium`, `low`, `ignore`, or `unknown`), `severity` (`critical`, `high`, `medium`, `low`, `ignore`, or `unknown`), `severity_rationale`, `change_conditions`, and `proof_gap` when deferred. A `reportable` decision requires severity `critical`, `high`, `medium`, or `low`; `ignore` requires severity `ignore`; `deferred` uses a provisional reportable severity or `unknown`.
48+
- Preserve all discovery fields and row order during enrichment, rewrite atomically, and do not pass the enriched ledger back to `normalize_candidates.py`.
49+
- Optional compact validation evidence: `<discovery_dir>/validation_artifacts/<candidate_id>/`
50+
- Create this directory only for actual PoCs, crafted inputs, or logs and reference those paths from the row's `validation` object. Do not create placeholder per-candidate directories or narrative reports.
51+
52+
The legacy ranking, raw/deduped candidate, per-finding receipt, and phase-report paths below are for diff/deep or resumed legacy workflows. A compact standard scan uses the enriched ledger instead.
53+
4154
### Coverage Planning
4255

4356
- Advisory seed research: `<context_dir>/seed_research.md`
@@ -69,12 +82,16 @@ End each repository-scoped threat model with these two lines:
6982

7083
## Validation (Phase 3) Paths
7184

85+
Compact standard scans use the nested `validation` record and optional compact evidence path above. Other scan modes use these paths:
86+
7287
- Scan-level validation summary: `<findings_dir>/validation_summary.md` if applicable
7388
- Per-finding validation report: `<findings_dir>/<candidate_id>/validation_report.md`
7489
- Per-finding validation artifacts: `<findings_dir>/<candidate_id>/validation_artifacts/`
7590

7691
## Attack-Path Analysis (Phase 4) Paths
7792

93+
Compact standard scans use the nested `attack_path` record above. Other scan modes use these paths:
94+
7895
- Scan-level attack-path analysis report: `<findings_dir>/attack_path_analysis_report.md` if applicable
7996
- Per-finding attack-path analysis report: `<findings_dir>/<candidate_id>/attack_path_analysis_report.md`
8097

@@ -95,5 +112,5 @@ End each repository-scoped threat model with these two lines:
95112

96113
- Put scan phase outputs and supporting evidence under the numbered artifact subdirectories above.
97114
- Keep fix-finding outputs outside the numbered scan phases because fix-finding can run standalone or against an existing scan.
98-
- Do not author the final `report.md` directly. Put complete scan-level report semantics in the canonical JSON files and detailed per-finding prose in `findings/<slug>/<slug>.md`. Put derived design guidance under `hardening/`. Finalization deterministically writes the unsealed `report.md` projection and links any recorded write-ups and hardening portfolio. Do not add these derived documents to the sealed artifact list.
115+
- Do not author the final `report.md` directly. Put complete scan-level report semantics in the canonical JSON files. Detailed per-finding prose in `findings/<slug>/<slug>.md` and derived design guidance under `hardening/` are optional for a standard scan. Finalization deterministically writes the unsealed `report.md` projection and links any recorded write-ups and hardening portfolio. Do not add these derived documents to the sealed artifact list.
99116
- Keep the full scan bundle together under `scan_dir`.

sdk/typescript/_bundled_plugin/references/scan-contract.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Do not put line numbers in `identity.anchor`. When two sibling vulnerabilities s
6060

6161
Fingerprint matching is a reconciliation signal, not proof that two findings are equivalent. Treat ambiguous matches as unresolved.
6262

63-
When a finding has multiple affected locations, label the vulnerable control location `root_control` when one is known. Adapters use the first `root_control` location as the primary annotation location and otherwise fall back to the first affected location. Preserve supporting entrypoint, wrapper, sink, and concrete-implementation locations as additional evidence.
63+
When a finding has multiple affected locations, label the vulnerable control location `root_control` when one is known. Adapters keep the first `root_control` location first and otherwise fall back to the first affected location, while preserving every distinct entrypoint, wrapper, sink, concrete-implementation, and code-evidence occurrence as a matchable location.
6464

6565
## Rule ID Policy
6666

sdk/typescript/_bundled_plugin/references/shared-hard-rules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Shared Hard Rules
22

3-
Apply these rules for every top-level Codex Security scan workflow before the scan-mode-specific hard rules in that workflow:
3+
Apply these rules for diff, deep, and resumed legacy Codex Security scans before the scan-mode-specific hard rules in that workflow:
44

55
- Keep the phases separate.
66
- Follow the execution plan in order.

sdk/typescript/_bundled_plugin/scripts/finalize_scan_contract.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,32 @@ def _sarif_primary_location(finding: dict[str, Any]) -> dict[str, Any]:
14781478
)
14791479

14801480

1481+
def _sarif_locations(finding: dict[str, Any]) -> list[dict[str, Any]]:
1482+
primary = _sarif_primary_location(finding)
1483+
locations = [
1484+
primary,
1485+
*(location for location in finding["locations"] if location is not primary),
1486+
]
1487+
locations.extend(
1488+
{
1489+
"path": evidence["path"],
1490+
"startLine": evidence["startLine"],
1491+
"endLine": evidence.get("endLine", evidence["startLine"]),
1492+
"role": f"evidence:{evidence['id']}",
1493+
}
1494+
for evidence in finding.get("codeEvidence", [])
1495+
)
1496+
unique: dict[tuple[str, int, int], dict[str, Any]] = {}
1497+
for location in locations:
1498+
key = (
1499+
location["path"],
1500+
location["startLine"],
1501+
location.get("endLine", location["startLine"]),
1502+
)
1503+
unique.setdefault(key, location)
1504+
return list(unique.values())
1505+
1506+
14811507
def _github_primary_location_line_hash(
14821508
finding: dict[str, Any],
14831509
source_root: Path | None,
@@ -1579,23 +1605,15 @@ def _sarif_result(
15791605
line_hash = _github_primary_location_line_hash(finding, source_root, line_hash_cache)
15801606
if line_hash is not None:
15811607
partial_fingerprints["primaryLocationLineHash"] = line_hash
1582-
primary_location = _sarif_primary_location(finding)
1583-
related_locations = [
1584-
_sarif_location(location, index)
1585-
for index, location in enumerate(finding["locations"])
1586-
if location is not primary_location
1587-
]
15881608
result = {
15891609
"ruleId": finding["ruleId"],
15901610
"ruleIndex": rule_index,
15911611
"level": SARIF_LEVELS[finding["severity"]["level"]],
15921612
"message": {"text": finding["summary"]},
1593-
"locations": [_sarif_location(primary_location)],
1613+
"locations": [_sarif_location(location) for location in _sarif_locations(finding)],
15941614
"partialFingerprints": partial_fingerprints,
15951615
"properties": properties,
15961616
}
1597-
if related_locations:
1598-
result["relatedLocations"] = related_locations
15991617
return result
16001618

16011619

sdk/typescript/_bundled_plugin/skills/attack-path-analysis/SKILL.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ If the user explicitly provides a different path for a required input or output,
1616
If a required input is still missing, stop and ask the user for it before continuing.
1717
Use the shared scan artifact path conventions in `../../references/scan-artifacts.md`.
1818

19+
### Compact Standard-Scan Mode
20+
21+
When `$security-scan` explicitly invokes this skill in compact standard-scan mode, load the per-scan threat model and the enriched `<discovery_dir>/candidate_ledger.jsonl`. Analyze, in one invocation, every row whose validation disposition is `reportable` or `deferred`. Add one nested `attack_path` record to each row that enters the phase, using the compact record shape in `../../references/scan-artifacts.md`, while preserving every discovery and validation field and the original row order.
22+
23+
In this mode, the nested record replaces the per-finding attack-path report and receipt. Rewrite the ledger atomically. Keep attack-path facts, counterevidence, severity calibration, and policy adjustment as separate reasoning steps even though their output is compact. All reachability, instance-preservation, and evidence requirements still apply; only the artifact packaging changes.
24+
1925
## Workflow
2026

2127
1. Load the per-scan threat model path from `../../references/scan-artifacts.md` as the repo-specific threat-model source of truth. Start from this along with the potential findings. Both inputs are required for this workflow.
@@ -31,9 +37,9 @@ Use the shared scan artifact path conventions in `../../references/scan-artifact
3137
4. Before finalizing scope or reportability-driving facts, identify the strongest repository counterevidence against the key scoping fields and explain why it is or is not dispositive.
3238
5. Calibrate impact and likelihood from the repository evidence.
3339
6. Apply a separate final policy-adjustment pass mechanically using those facts and the calibrated severity.
34-
7. Drop any candidate whose final policy decision is `ignore`.
35-
8. Save that finding's visible attack-path report to its per-finding attack-path analysis report path from `../../references/scan-artifacts.md`.
36-
9. Append one attack-path receipt per candidate id to that finding's candidate ledger path from `../../references/scan-artifacts.md`. The receipt must record the candidate id, attack-path reportability decision, attack-path facts or exact proof gap, and attack-path artifact/report reference for that candidate finding.
40+
7. Record final policy decision `ignore` explicitly. Outside compact standard-scan mode, drop it from the surviving finding set; in compact mode, retain the ledger row for coverage mapping.
41+
8. In compact standard-scan mode, add the nested `attack_path` record to every candidate that entered the phase and atomically replace the ledger.
42+
9. Outside compact standard-scan mode, save that finding's visible attack-path report and append one attack-path receipt per candidate id at the default paths from `../../references/scan-artifacts.md`. The receipt must record the candidate id, attack-path reportability decision, attack-path facts or exact proof gap, and attack-path artifact/report reference for that candidate finding.
3743

3844
## Scope and Attack Path Checklist
3945

@@ -76,6 +82,10 @@ Apply severity and policy calibration using `references/severity-policy.md`.
7682

7783
## Output Contract
7884

85+
In compact standard-scan mode, use the nested record defined in `../../references/scan-artifacts.md`. Every validation row with disposition `reportable` or `deferred` must receive exactly one attack-path decision. The record is the phase closure for this mode; do not also create a narrative report or receipt.
86+
87+
Outside compact standard-scan mode, use the following report contract.
88+
7989
For each surviving finding include:
8090

8191
- title
@@ -94,13 +104,13 @@ Render attack-path facts using `references/attack-path-facts.md`.
94104

95105
- Prefer repository evidence first, but use network connectivity when it materially helps confirm deployment context, reachable surfaces, or other reportability-relevant facts.
96106
- Do not invent attack chains that the code does not support.
97-
- Do not leave candidate coverage implicit. Every candidate finding that reaches attack-path analysis must leave an attack-path receipt in its candidate-ledger path from `../../references/scan-artifacts.md`, even when the final policy decision is `ignore` or the path remains deferred.
107+
- Do not leave candidate coverage implicit. In compact standard-scan mode, every candidate that reaches attack-path analysis must receive a nested `attack_path` record, even when the final policy decision is `ignore` or `deferred`. In other modes, every such candidate must leave an attack-path receipt in its candidate-ledger path from `../../references/scan-artifacts.md`.
98108
- Do not drop exact affected locations while converting validated findings into attack paths. Repository-wide seeded/root-control rows that survive validation must keep their root-control file:line even when a wrapper, route, or transport is easier to explain.
99109
- Do not skip a reportable validation row because a neighboring same-family finding has a cleaner story. Either produce attack-path facts for that exact row or make an explicit final policy decision with repository counterevidence.
100110
- Missing public-ingress evidence is not by itself dispositive counterevidence.
101111
- Keep attack-path analysis, severity calibration, and final policy suppression as separate sub-stages.
102112
- Use the final policy-adjustment matrix mechanically rather than re-arguing severity from scratch after the facts are set.
103-
- Save a final visible report for each candidate finding using that finding's attack-path analysis report path from `../../references/scan-artifacts.md`.
113+
- Outside compact standard-scan mode, save a final visible report for each candidate finding using that finding's attack-path analysis report path from `../../references/scan-artifacts.md`. Compact standard scans use the nested phase record instead.
104114

105115
-- Considerations for attack path --
106116
- A bug matters if evidence shows an attacker could exploit it.

0 commit comments

Comments
 (0)