Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b57e42d
fix(workbench): bind finding excerpts to selected source paths
mldangelo-oai Aug 24, 2026
9ea43e0
fix(workbench): constrain persisted source authority
mldangelo-oai Aug 24, 2026
76eed7c
fix(workbench): reject divergent excerpt views
mldangelo-oai Aug 24, 2026
047538b
test(workbench): keep malformed metadata at boundary
mldangelo-oai Aug 24, 2026
8bfb4b9
fix(workbench): prepare excerpt authority outside hot paths
mldangelo-oai Aug 24, 2026
669203d
fix(workbench): capture source authority before writes
mldangelo-oai Aug 24, 2026
7426e4b
fix(workbench): revalidate deep scan source identity
mldangelo-oai Aug 24, 2026
76841a9
fix(workbench): seal excerpt tree authority
mldangelo-oai Aug 24, 2026
712438a
fix(workbench): index selected excerpt scopes
mldangelo-oai Aug 24, 2026
23d6047
fix(workbench): resolve excerpt scopes on demand
mldangelo-oai Aug 24, 2026
238a16b
refactor(workbench): select deepest excerpt scope
mldangelo-oai Aug 24, 2026
b2a852c
refactor(workbench): resolve excerpt path once
mldangelo-oai Aug 24, 2026
f356440
refactor(workbench): store scope membership only
mldangelo-oai Aug 24, 2026
1e48917
fix(workbench): preserve excerpt scope semantics
mldangelo-oai Aug 24, 2026
8977d5b
fix(workbench): stream excerpt tree lookups
mldangelo-oai Aug 24, 2026
1dc8572
fix(workbench): bound excerpt tree parsing
mldangelo-oai Aug 24, 2026
3923670
fix(workbench): stream source excerpt blobs
mldangelo-oai Aug 24, 2026
ce22ca2
test(workbench): align streamed excerpt coverage
mldangelo-oai Aug 24, 2026
1175240
fix(workbench): bound source authority checks
mldangelo-oai Aug 24, 2026
6eb08e9
fix(workbench): bound replacement ref checks
mldangelo-oai Aug 24, 2026
756e2bc
test(workbench): simplify replacement probe coverage
mldangelo-oai Aug 24, 2026
566f5c4
fix(workbench): honor replacement ref bases
mldangelo-oai Aug 24, 2026
58fcda0
fix(workbench): preserve excerpts for valid Git trees
mldangelo-oai Aug 24, 2026
a1d6efa
Merge reviewed scan budget attribution fixes
mldangelo-oai Aug 25, 2026
8ff61b6
Merge refreshed reviewed scan budget ancestry
mldangelo-oai Aug 25, 2026
cdcb5d1
fix(workbench): preserve distinct case-sensitive source paths
mldangelo-oai Aug 25, 2026
03af8b7
Merge reviewed Windows-safe local diff ancestry
mldangelo-oai Aug 25, 2026
2951311
fix(workbench): reuse source trees and avoid writer lock contention
mldangelo-oai Aug 25, 2026
6fd4128
Merge reviewed Windows sharing-safe scan budget ancestry
mldangelo-oai Aug 25, 2026
8baae67
fix(workbench): preserve subtree roots and committed scope authority
mldangelo-oai Aug 25, 2026
b0965bb
chore: refresh finding excerpt scope with main
mldangelo-oai Aug 26, 2026
89207b3
fix(workbench): refresh source scope and simplify excerpt checks
mldangelo-oai Aug 27, 2026
6fa2a11
chore: merge final release packaging updates from parent
mldangelo-oai Aug 27, 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
2 changes: 1 addition & 1 deletion sdk/typescript/_bundled_plugin/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codex-security",
"version": "0.1.78",
"version": "0.1.79",
"description": "Codex Security workflows for security scans, analysis, and investigation.",
"author": {
"name": "OpenAI"
Expand Down
37 changes: 31 additions & 6 deletions sdk/typescript/_bundled_plugin/scripts/deep_scan_workbench.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from filesystem_identity import serialize_filesystem_identity
from finalize_scan_contract import _read_scan_local_json
from workbench.handoff import require_current_continuation
from workbench_source_excerpt import capture_source_scopes
from workbench_target import (
directory_content_digest,
directory_snapshot_regular_file_count,
Expand Down Expand Up @@ -775,9 +776,32 @@ def begin_deep_scan_for_target(
)
target_device = serialize_filesystem_identity(target_metadata.st_dev)
target_inode = serialize_filesystem_identity(target_metadata.st_ino)
target_identity = (revision, target_snapshot_digest, target_device, target_inode)
source_scopes = capture_source_scopes(
target,
target_identity,
[scope],
)
scope_file_count = directory_snapshot_regular_file_count(
target if scope == "." else target / scope
)
current_target = require_remediation_target(target_path)
current_metadata = current_target.stat()
current_revision = git_revision(current_target)
current_snapshot_digest = (
directory_content_digest(current_target)
if current_revision == "unversioned"
else worktree_content_digest(current_target)
)
if (
current_revision,
current_snapshot_digest,
serialize_filesystem_identity(current_metadata.st_dev),
serialize_filesystem_identity(current_metadata.st_ino),
) != target_identity:
raise SystemExit(
"The selected scan target changed while the scan was starting. Try again."
)
connection.execute("BEGIN IMMEDIATE")
try:
existing = existing_deep_scan_for_target(connection, thread_id, target_path, scope)
Expand All @@ -799,10 +823,10 @@ def begin_deep_scan_for_target(
)
current_target = require_remediation_target(target_path)
current_metadata = current_target.stat()
if (current_metadata.st_dev, current_metadata.st_ino) != (
target_metadata.st_dev,
target_metadata.st_ino,
):
if (
serialize_filesystem_identity(current_metadata.st_dev),
serialize_filesystem_identity(current_metadata.st_ino),
) != (target_device, target_inode):
raise SystemExit(
"The selected scan target changed while the scan was starting. Try again."
)
Expand Down Expand Up @@ -870,10 +894,10 @@ def begin_deep_scan_for_target(
"""
INSERT INTO scans (
id, workspace_id, target_id, target_path, target_revision, target_snapshot_digest,
target_device, target_inode, scope, mode, user_context,
target_device, target_inode, source_scopes_json, scope, mode, user_context,
deep_scan_owner_thread_id, scan_dir, model, reasoning_effort, status, phase,
handoff_status, started_at, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'deep', ?, ?, ?, ?, ?,
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'deep', ?, ?, ?, ?, ?,
'running', 'preflight', 'delivered', ?, ?, ?)
""",
(
Expand All @@ -885,6 +909,7 @@ def begin_deep_scan_for_target(
target_snapshot_digest,
target_device,
target_inode,
json.dumps(source_scopes, allow_nan=False, separators=(",", ":"), sort_keys=True),
scope,
user_context,
thread_id,
Expand Down
90 changes: 82 additions & 8 deletions sdk/typescript/_bundled_plugin/scripts/workbench_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,13 @@
from workbench_schema import (
sql_statements as sql_statements,
)
from workbench_source_excerpt import finding_source_excerpt, safe_source_path
from workbench_source_excerpt import (
SourceContext,
capture_source_scopes,
finding_source_excerpt_from_context,
safe_source_path,
source_excerpt_context,
)
from workbench_target import (
clean_worktree_content_digest,
committed_diff_content_snapshot,
Expand Down Expand Up @@ -1291,6 +1297,12 @@ def active_scan() -> sqlite3.Row | None:
diff_target,
metadata=target_metadata,
)
source_scopes = capture_source_scopes(
target,
target_identity,
[scope],
diff_target_kind=diff_target["kind"] if diff_target is not None else None,
)
match_committed_diff_identity: (
Callable[[Path, dict[str, str], str], bool] | None
) = None
Expand Down Expand Up @@ -1400,6 +1412,7 @@ def target_matches_initial_snapshot() -> bool:
target_root=target_root,
target_summary=target_summary,
scope_file_count=scope_file_count,
source_scopes=source_scopes,
timestamp=timestamp,
model=args.model,
reasoning_effort=args.reasoning_effort,
Expand Down Expand Up @@ -1455,6 +1468,12 @@ def _start_prompt_driven_scan(
)
diff_identity = scan_diff_identity(diff_target)
target_identity = scan_target_identity(target, diff_target)
source_scopes = capture_source_scopes(
target,
target_identity,
[scope],
diff_target_kind=diff_target["kind"] if diff_target is not None else None,
)
match_committed_diff_identity: (
Callable[[Path, dict[str, str], str], bool] | None
) = None
Expand Down Expand Up @@ -1616,6 +1635,7 @@ def target_matches_initial_snapshot() -> bool:
target_root=target_root,
target_summary=target_summary,
scope_file_count=scope_file_count,
source_scopes=source_scopes,
timestamp=timestamp,
handoff_status="delivered",
model=args.model,
Expand Down Expand Up @@ -2243,6 +2263,12 @@ def register_cli_scan(connection: sqlite3.Connection, args: argparse.Namespace)
) = committed_diff_content_snapshot(repository, base, head)
mode = "diff" if diff_target is not None else recipe["mode"]
target_identity = scan_target_identity(repository, diff_target)
source_scopes = capture_source_scopes(
repository,
target_identity,
paths or ["."],
diff_target_kind=diff_target["kind"] if diff_target is not None else None,
)
scope_file_count = (
directory_snapshot_regular_file_count(repository)
if not paths
Expand Down Expand Up @@ -2357,6 +2383,7 @@ def target_matches_initial_snapshot() -> bool:
timestamp=timestamp,
handoff_status="delivered",
scan_dir=scan_dir,
source_scopes=source_scopes,
)
connection.execute(
"UPDATE scans SET recipe_json = ?, parent_scan_id = ?, user_context = ? WHERE id = ?",
Expand Down Expand Up @@ -3870,7 +3897,7 @@ def list_findings(connection: sqlite3.Connection, args: argparse.Namespace) -> d
next_offset = args.offset + len(rows)
return {
"findingsPage": {
"findings": [finding_result(connection, scan, row) for row in rows],
"findings": finding_results(connection, scan, rows),
"limit": limit,
"nextOffset": next_offset if next_offset < total else None,
"offset": args.offset,
Expand Down Expand Up @@ -3969,7 +3996,7 @@ def scan_result(
"contract": scan_contract(scan),
"continuationThreadId": scan["continuation_thread_id"],
"failureMessage": scan["failure_message"],
"findings": [finding_result(connection, scan, row) for row in occurrence_rows],
"findings": finding_results(connection, scan, occurrence_rows),
"findingCount": finding_count,
"findingsTruncated": finding_count > len(occurrence_rows),
"severityCounts": severity_counts,
Expand Down Expand Up @@ -4112,21 +4139,59 @@ def legacy_finding_matches(row: sqlite3.Row, finding: Any) -> bool:
)


def scan_source_excerpt_context(
scan: sqlite3.Row,
) -> tuple[Path | None, SourceContext | None]:
try:
target = require_scan_target_identity(scan)
except SystemExit:
return None, None
try:
selected_paths = requested_scan_paths(scan)
except (IndexError, KeyError, TypeError, ValueError):
selected_paths = []
if not isinstance(selected_paths, list) or not all(
isinstance(path, str) for path in selected_paths
):
selected_paths = []
return target, source_excerpt_context(scan, target, selected_paths)


def finding_results(
connection: sqlite3.Connection,
scan: sqlite3.Row,
occurrences: list[sqlite3.Row],
) -> list[dict[str, Any]]:
if not occurrences:
return []
target, source_context = scan_source_excerpt_context(scan)
return [
finding_result(
connection,
scan,
occurrence,
target=target,
source_context=source_context,
)
for occurrence in occurrences
]


def finding_result(
connection: sqlite3.Connection,
scan: sqlite3.Row,
occurrence: sqlite3.Row,
*,
target: Path | None,
source_context: SourceContext | None,
) -> dict[str, Any]:
details = bounded_finding_details(read_finding_details(occurrence["details_json"]))
confidence = details.get("confidence")
confidence = confidence if isinstance(confidence, dict) else {}
severity = details.get("severity")
severity = severity if isinstance(severity, dict) else {}
excerpt_locations = []
locations = []
try:
target = require_scan_target_identity(scan)
except SystemExit:
target = None
for row in connection.execute(
"""
SELECT relative_path, start_line, end_line, role
Expand All @@ -4137,6 +4202,13 @@ def finding_result(
""",
(occurrence["id"], FINDING_LOCATIONS_LIMIT),
):
excerpt_locations.append(
{
"endLine": row["end_line"],
"path": row["relative_path"],
"startLine": row["start_line"],
}
)
absolute_path = safe_source_path(target, row["relative_path"]) if target else None
location = {
"endLine": row["end_line"],
Expand Down Expand Up @@ -4181,7 +4253,9 @@ def finding_result(
result["knownSince"] = known_since
result["knownScanIds"] = known_scan_ids
result.pop("artifactPaths", None)
source_excerpt = finding_source_excerpt(scan, target, locations)
source_excerpt = finding_source_excerpt_from_context(
source_context, excerpt_locations
)
if source_excerpt:
result["sourceExcerpt"] = source_excerpt
artifact_paths = finding_artifact_paths(Path(scan["scan_dir"]), details)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from collections.abc import Callable
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

# Some plugin hosts launch Python with safe-path isolation enabled.
sys.path.insert(0, str(Path(__file__).resolve().parent))
Expand Down Expand Up @@ -160,6 +161,7 @@ def insert_running_scan(
target_root: Path,
target_summary: str | None,
scope_file_count: int,
source_scopes: dict[str, Any],
timestamp: str,
handoff_status: str = "pending",
model: str | None = None,
Expand All @@ -180,11 +182,11 @@ def insert_running_scan(
"""
INSERT INTO scans (
id, workspace_id, target_id, target_path, target_revision, target_snapshot_digest,
target_device, target_inode, scope, mode, user_context,
target_device, target_inode, source_scopes_json, scope, mode, user_context,
deep_scan_owner_thread_id, diff_target_kind, diff_base_revision,
diff_head_revision, diff_content_digest, target_summary, scan_dir, model,
reasoning_effort, status, phase, handoff_status, started_at, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
'running', 'preflight', ?, ?, ?, ?)
""",
(
Expand All @@ -193,6 +195,7 @@ def insert_running_scan(
workspace["target_id"],
str(target),
*target_identity,
json.dumps(source_scopes, allow_nan=False, separators=(",", ":"), sort_keys=True),
scope,
workspace["default_mode"],
user_context,
Expand Down
9 changes: 9 additions & 0 deletions sdk/typescript/_bundled_plugin/scripts/workbench_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,13 @@
ALTER TABLE deep_scan_runs ADD COLUMN publication_error_message TEXT;
""",
),
(
33,
"persist selected source excerpt authority",
"""
ALTER TABLE scans ADD COLUMN source_scopes_json TEXT;
""",
),
)


Expand Down Expand Up @@ -777,6 +784,8 @@ def apply_migrations(
repair_thread_scoped_workspaces_migration(connection)
elif version == 16:
should_backfill_targets = repair_stable_targets_migration(connection)
elif version == 33:
add_column_if_missing(connection, "scans", "source_scopes_json", "TEXT")
else:
for statement in sql_statements(sql):
connection.execute(statement)
Expand Down
Loading
Loading