Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e2e177b
fix(workbench): keep finding source excerpts within requested paths
mldangelo-oai Aug 16, 2026
d0faf1c
fix(workbench): reject inaccessible selected source scopes
mldangelo-oai Aug 16, 2026
70bddea
fix(workbench): authenticate missing historical source aliases
mldangelo-oai Aug 16, 2026
8764b0c
fix(workbench): preserve authenticated historical Unicode aliases
mldangelo-oai Aug 16, 2026
54d4721
Preserve explicit source scope for historical scans
mldangelo-oai Aug 16, 2026
33fb8f7
Reject unauthenticated source path collisions
mldangelo-oai Aug 16, 2026
113dbef
Merge main into finding excerpt scope fix
mldangelo-oai Aug 17, 2026
8f3834c
Bind finding previews to saved Git source objects
mldangelo-oai Aug 17, 2026
0f19739
Preserve immutable source reads across scan entrypoints
mldangelo-oai Aug 17, 2026
b06c353
Preserve historical preview scope and replacement consistency
mldangelo-oai Aug 17, 2026
709cca5
Index source scopes and align previews with displayed locations
mldangelo-oai Aug 17, 2026
042a88f
Authenticate requested source scopes before canonical aliases
mldangelo-oai Aug 17, 2026
90df0d4
Avoid worktree execution when authorizing source previews
mldangelo-oai Aug 17, 2026
ed88560
Close source-preview fixture databases before cleanup
mldangelo-oai Aug 17, 2026
d526d48
Merge main into finding source excerpt scope
mldangelo-oai Aug 18, 2026
cad99e0
Merge main into finding source excerpt scope
mldangelo-oai Aug 22, 2026
543d2ec
chore: merge main into finding source scopes
mldangelo-oai Aug 26, 2026
7af8716
fix(workbench): verify ambiguous excerpts and simplify selection
mldangelo-oai Aug 27, 2026
8651c09
chore: merge final release packaging updates
mldangelo-oai Aug 27, 2026
4d228cb
Merge main into source-scope excerpts
mldangelo-oai Aug 31, 2026
62dd525
fix(plugin): follow the source-helper help convention
mldangelo-oai Aug 31, 2026
66a858f
test: avoid synchronous Windows subprocess timeouts
mldangelo-oai Aug 31, 2026
11e3b93
chore: resolve PR #488 conflicts with main
mldangelo-oai Sep 3, 2026
565639d
chore: resolve PR #488 conflicts with main
mldangelo-oai Sep 3, 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
1 change: 1 addition & 0 deletions plugins/codex-security/plugin-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"scripts/workbench_schema.py",
"scripts/workbench_severity.py",
"scripts/workbench_source_excerpt.py",
"scripts/workbench_source_scopes.py",
"scripts/workbench_target.py",
"scripts/workbench_target_state.py",
"scripts/workbench_validation.py",
Expand Down
11 changes: 9 additions & 2 deletions plugins/codex-security/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_scopes import capture_source_scopes
from workbench_target import (
directory_content_digest,
directory_snapshot_regular_file_count,
Expand Down Expand Up @@ -828,6 +829,11 @@ def begin_deep_scan_for_target(
terminal["id"],
start_disposition="joined",
)
source_scopes = capture_source_scopes(
target,
(revision, target_snapshot_digest, target_device, target_inode),
[scope],
)
config = effective_deep_scan_config(args)
workflow_version = optional_text(args.workflow_version, maximum=256)
if workflow_version is None:
Expand Down Expand Up @@ -875,10 +881,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 @@ -890,6 +896,7 @@ def begin_deep_scan_for_target(
target_snapshot_digest,
target_device,
target_inode,
json.dumps(source_scopes),
scope,
user_context,
thread_id,
Expand Down
74 changes: 45 additions & 29 deletions plugins/codex-security/scripts/workbench_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,14 @@
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 finding_source_excerpt
from workbench_source_scopes import (
capture_source_scopes,
expected_target_kinds,
public_scan_recipe,
requested_scan_paths,
safe_source_path,
)
from workbench_target import (
clean_worktree_content_digest,
copy_directory_excluding,
Expand Down Expand Up @@ -138,6 +145,7 @@
optional_text,
parse_scan_cost,
path_within_scope,
reject_non_finite_json,
require_close_note,
require_occurrence,
require_uuid,
Expand Down Expand Up @@ -432,27 +440,6 @@ def require_scannable_target(target: Path) -> None:
)


def expected_target_kinds(scan: sqlite3.Row) -> list[str]:
if scan["mode"] == "diff":
return ["git_diff"]
if scan["target_revision"] == "unversioned":
return ["directory_snapshot"]
if scan["target_snapshot_digest"] is None:
return ["git_worktree", "git_revision"]
if scan["target_snapshot_digest"] == clean_worktree_content_digest():
return ["git_revision"]
return ["git_worktree"]


def requested_scan_paths(scan: sqlite3.Row) -> list[str]:
if "recipe_json" in scan.keys() and scan["recipe_json"] is not None:
recipe = json.loads(scan["recipe_json"], parse_constant=reject_non_finite_json)
target = recipe["target"]
if target["kind"] == "paths":
return target["paths"]
return [scan["scope"]]


def scan_contract(scan: sqlite3.Row) -> dict[str, Any]:
target = Path(scan["target_path"])
target_contract = {
Expand Down Expand Up @@ -858,6 +845,12 @@ def start_scan(connection: sqlite3.Connection, args: argparse.Namespace) -> dict
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,
)
target_root = scan_target_root(args.scan_root, target)
target_root.mkdir(parents=True, exist_ok=True)
if manages_transaction:
Expand Down Expand Up @@ -906,6 +899,7 @@ def start_scan(connection: sqlite3.Connection, args: argparse.Namespace) -> dict
scope=scope,
diff_target=diff_target,
target_identity=target_identity,
source_scopes=source_scopes,
target_root=target_root,
target_summary=target_summary,
scope_file_count=scope_file_count,
Expand Down Expand Up @@ -962,6 +956,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,
)
target_root = scan_target_root(args.scan_root, target)

connection.execute("BEGIN IMMEDIATE")
Expand Down Expand Up @@ -1064,6 +1064,7 @@ def _start_prompt_driven_scan(
scope=scope,
diff_target=diff_target,
target_identity=target_identity,
source_scopes=source_scopes,
target_root=target_root,
target_summary=target_summary,
scope_file_count=scope_file_count,
Expand Down Expand Up @@ -1676,6 +1677,7 @@ def register_cli_scan(connection: sqlite3.Connection, args: argparse.Namespace)
recipe = parse_scan_recipe(recipe_json, repository)
requested_target = recipe["target"]
paths = requested_target["paths"]
recipe.pop("_codexSecurityFileScopes", None)
scope = paths[0] if len(paths) == 1 else "."
diff_target = None
if requested_target["kind"] in {"refs", "working_tree"}:
Expand All @@ -1693,6 +1695,12 @@ def register_cli_scan(connection: sqlite3.Connection, args: argparse.Namespace)
diff_target["contentDigest"] = worktree_content_digest(repository)
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 @@ -1750,6 +1758,7 @@ def register_cli_scan(connection: sqlite3.Connection, args: argparse.Namespace)
scope=scope,
diff_target=diff_target,
target_identity=target_identity,
source_scopes=source_scopes,
target_root=scan_dir.parent,
target_summary=None,
scope_file_count=scope_file_count,
Expand Down Expand Up @@ -1875,7 +1884,7 @@ def get_scan_recipe(connection: sqlite3.Connection, args: argparse.Namespace) ->
raise SystemExit("This scan does not have a saved launch recipe.")
return {
"parentScanId": scan["parent_scan_id"],
"recipe": json.loads(scan["recipe_json"], parse_constant=reject_non_finite_json),
"recipe": public_scan_recipe(scan),
"scanId": scan["id"],
}

Expand Down Expand Up @@ -2721,7 +2730,7 @@ def scan_context(
}
if scan["recipe_json"] is not None:
context["parentScanId"] = scan["parent_scan_id"]
context["recipe"] = json.loads(scan["recipe_json"], parse_constant=reject_non_finite_json)
context["recipe"] = public_scan_recipe(scan)
return context


Expand Down Expand Up @@ -3022,6 +3031,7 @@ def finding_result(
severity = details.get("severity")
severity = severity if isinstance(severity, dict) else {}
locations = []
excerpt_locations = []
try:
target = require_scan_target_identity(scan)
except SystemExit:
Expand All @@ -3036,6 +3046,14 @@ def finding_result(
""",
(occurrence["id"], FINDING_LOCATIONS_LIMIT),
):
excerpt_locations.append(
{
"endLine": row["end_line"],
"path": row["relative_path"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Omit excerpts when the displayed path is truncated

When a valid repository-relative path exceeds FINDING_LOCATION_PATH_BYTES, location.path is truncated for the response while excerpt_locations retains the full database value, so sourceExcerpt is read from a file that the reported relative location no longer identifies; two deep paths with the same first 2,048 bytes can even display identically while showing different source. Preserve the prior alignment by omitting the excerpt when bounding changes the path, or perform the lookup using the exact path returned to the caller.

AGENTS.md reference: AGENTS.md:L30-L33

Useful? React with 👍 / 👎.

"role": row["role"],
"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 @@ -3082,7 +3100,9 @@ def finding_result(
if related:
result["related"] = related
result.pop("artifactPaths", None)
source_excerpt = finding_source_excerpt(scan, target, locations)
source_excerpt = finding_source_excerpt(
scan, target, excerpt_locations, requested_scan_paths(scan)
)
if source_excerpt:
result["sourceExcerpt"] = source_excerpt
artifact_paths = finding_artifact_paths(Path(scan["scan_dir"]), details)
Expand Down Expand Up @@ -3376,10 +3396,6 @@ def read_json_object(path: Path) -> dict[str, Any]:
return payload


def reject_non_finite_json(value: str) -> None:
raise ValueError(f"non-finite JSON number {value!r} is not supported")


_WORKBENCH_PUBLICATION_CONTEXT = publication.WorkbenchPublicationContext(
ARTIFACTS=ARTIFACTS,
artifact_path=artifact_path,
Expand Down
7 changes: 5 additions & 2 deletions plugins/codex-security/scripts/workbench_scan_start.py
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 @@ -157,6 +158,7 @@ def insert_running_scan(
scope: str,
diff_target: dict[str, str] | None,
target_identity: tuple[str, str | None, int | str, int | str],
source_scopes: dict[str, Any],
target_root: Path,
target_summary: str | None,
scope_file_count: int,
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
25 changes: 25 additions & 0 deletions plugins/codex-security/scripts/workbench_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,13 @@
);
""",
),
(
42,
"persist authorized source excerpt scopes",
"""
ALTER TABLE scans ADD COLUMN source_scopes_json TEXT;
""",
),
)


Expand Down Expand Up @@ -1011,11 +1018,15 @@ def apply_migrations(
"publication_error_message",
"TEXT",
)
elif version == 42:
add_column_if_missing(connection, "scans", "source_scopes_json", "TEXT")
continue
if version == 6:
repair_thread_scoped_workspaces_migration(connection)
elif version == 16:
should_backfill_targets = repair_stable_targets_migration(connection)
elif version == 42:
add_column_if_missing(connection, "scans", "source_scopes_json", "TEXT")
else:
for statement in sql_statements(sql):
connection.execute(statement)
Expand Down Expand Up @@ -1148,6 +1159,20 @@ def normalize_pre_release_execution_profile_migrations(

def normalize_pre_release_migrations(connection: sqlite3.Connection, timestamp: str) -> None:
normalize_mirror_lineage_migrations(connection)
connection.execute(
"UPDATE schema_migrations SET version = 42 WHERE version IN (34, 40, 41) AND name = ?",
("persist authorized source excerpt scopes",),
)
source_scope_migration = connection.execute(
"SELECT name FROM schema_migrations WHERE version = 42"
).fetchone()
if (
source_scope_migration is not None
and source_scope_migration["name"] != "persist authorized source excerpt scopes"
):
raise SystemExit(
"The Codex Security database has an unsupported source-scope migration history."
)
connection.execute(
"UPDATE schema_migrations SET version = 40 WHERE version = 33 AND name = ?",
("index finding identity and comparison history",),
Expand Down
Loading
Loading