Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 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
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
5 changes: 5 additions & 0 deletions sdk/typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ make one extra model call, even with a cost limit.
Keep results outside the repository and restrict access: reports can contain
source code, vulnerability details, and reproduction steps.

Saved-history excerpts use scanned Git objects without fetching missing data.
Working-tree diff scans omit excerpts, as do historical scans whose source or
scope cannot be established. Unambiguous older scans remain readable. An omitted
excerpt does not remove the finding.

### Validate an existing finding

```ts
Expand Down
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.60",
"version": "0.1.65",
"description": "Codex Security workflows for security scans, analysis, and investigation.",
"author": {
"name": "OpenAI"
Expand Down
11 changes: 9 additions & 2 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_scopes import capture_source_scopes
from workbench_target import (
directory_content_digest,
directory_snapshot_regular_file_count,
Expand Down Expand Up @@ -823,6 +824,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 @@ -870,10 +876,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 +891,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
52 changes: 47 additions & 5 deletions sdk/typescript/_bundled_plugin/scripts/workbench_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@
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, safe_source_path
from workbench_target import (
clean_worktree_content_digest,
copy_directory_excluding,
Expand Down Expand Up @@ -444,6 +445,12 @@ def requested_scan_paths(scan: sqlite3.Row) -> list[str]:
return [scan["scope"]]


def public_scan_recipe(scan: sqlite3.Row) -> dict[str, Any]:
recipe = json.loads(scan["recipe_json"], parse_constant=reject_non_finite_json)
recipe.pop("_codexSecurityFileScopes", None)
return recipe


def scan_contract(scan: sqlite3.Row) -> dict[str, Any]:
target = Path(scan["target_path"])
target_contract = {
Expand Down Expand Up @@ -849,6 +856,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 @@ -897,6 +910,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 @@ -953,6 +967,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 @@ -1052,6 +1072,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 @@ -1660,6 +1681,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 @@ -1677,6 +1699,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 @@ -1734,6 +1762,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 All @@ -1744,7 +1773,9 @@ def register_cli_scan(connection: sqlite3.Connection, args: argparse.Namespace)
connection.execute(
"UPDATE scans SET recipe_json = ?, parent_scan_id = ?, user_context = ? WHERE id = ?",
(
json.dumps(recipe, allow_nan=False, separators=(",", ":"), sort_keys=True),
json.dumps(
recipe, allow_nan=False, separators=(",", ":"), sort_keys=True
),
parent_scan_id,
user_context,
scan_id,
Expand Down Expand Up @@ -1832,7 +1863,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 @@ -3157,7 +3188,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 @@ -3443,6 +3474,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 @@ -3457,6 +3489,14 @@ def finding_result(
""",
(occurrence["id"], FINDING_LOCATIONS_LIMIT),
):
excerpt_locations.append(
{
"endLine": row["end_line"],
"path": row["relative_path"],
"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 @@ -3501,7 +3541,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(
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
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,9 @@ 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
22 changes: 22 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;
""",
),
(
34,
"persist authorized source excerpt scopes",
"""
ALTER TABLE scans ADD COLUMN source_scopes_json TEXT;
""",
),
)


Expand Down Expand Up @@ -772,11 +779,15 @@ def apply_migrations(
"publication_error_message",
"TEXT",
)
elif version == 34:
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 == 34:
add_column_if_missing(connection, "scans", "source_scopes_json", "TEXT")
else:
for statement in sql_statements(sql):
connection.execute(statement)
Expand Down Expand Up @@ -911,6 +922,17 @@ def normalize_pre_release_execution_profile_migrations(


def normalize_pre_release_migrations(connection: sqlite3.Connection, timestamp: str) -> None:
source_scope_migration = connection.execute(
"SELECT name FROM schema_migrations WHERE version = 34"
).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."
)

completion_warning_migration = connection.execute(
"SELECT name FROM schema_migrations WHERE version = 25"
).fetchone()
Expand Down
Loading
Loading