Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file modified sdk/typescript/_bundled_plugin/mcp/mcp-app.html.br
Binary file not shown.
Binary file modified sdk/typescript/_bundled_plugin/mcp/server.mjs.br.part-000
Binary file not shown.
Binary file modified sdk/typescript/_bundled_plugin/mcp/server.mjs.br.part-001
Binary file not shown.
37 changes: 32 additions & 5 deletions sdk/typescript/_bundled_plugin/scripts/deep_scan_workbench.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def register_subcommands(subparsers: Any, positive_int: Callable[[str], int]) ->
begin_deep_scan.add_argument("--user-context")
begin_deep_scan.add_argument("--scan-root")
begin_deep_scan.add_argument("--claim-token")
begin_deep_scan.add_argument("--model")
begin_deep_scan.add_argument("--reasoning-effort")
begin_deep_scan.add_argument("--available-parallelism", type=positive_int)
begin_deep_scan.add_argument("--workflow-version", default=DEEP_SCAN_WORKFLOW_VERSION)

Expand Down Expand Up @@ -324,7 +326,7 @@ def independent_review_progress(
connection: sqlite3.Connection, scan_id: str
) -> dict[str, int | str] | None:
run = connection.execute(
"SELECT completion_sequence, updated_at FROM deep_scan_runs WHERE scan_id = ?",
"SELECT completion_sequence, phase, updated_at FROM deep_scan_runs WHERE scan_id = ?",
(scan_id,),
).fetchone()
if run is None:
Expand All @@ -342,6 +344,7 @@ def independent_review_progress(
return {
"active": int(active),
"completed": int(run["completion_sequence"]),
"consolidating": run["phase"] == "reducing",
"updatedAt": str(run["updated_at"]),
}

Expand Down Expand Up @@ -569,6 +572,18 @@ def begin_deep_scan_for_scan(
)
if scan["mode"] != "deep":
raise SystemExit("Deep Scan orchestration requires a scan in deep mode.")
model = optional_text(args.model, maximum=200)
reasoning_effort = optional_text(args.reasoning_effort, maximum=32)
if model is not None or reasoning_effort is not None:
connection.execute(
"""
UPDATE scans
SET model = COALESCE(?, model), reasoning_effort = COALESCE(?, reasoning_effort)
WHERE id = ?

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 Preserve model metadata on terminal Deep Scan rejoins

When an idempotent start_codex_security_deep_scan call rejoins an already completed, failed, or canceled scan, require_owned_scan and the continuation check do not require the parent scan to be running, and this unrestricted update executes before the existing run is returned. A later retry from a turn using different model settings therefore rewrites the historical scan's model and reasoning_effort even though no scan work runs. Restrict this update to running scans so terminal execution metadata remains immutable.

Useful? React with 👍 / 👎.

""",
(model, reasoning_effort, scan_id),
)
connection.commit()
existing = connection.execute(
"SELECT scan_id FROM deep_scan_runs WHERE scan_id = ?", (scan_id,)
).fetchone()
Expand Down Expand Up @@ -681,6 +696,8 @@ def begin_deep_scan_for_target(
raise SystemExit("The scan artifact directory must be outside the selected target.")
target_root.mkdir(parents=True, exist_ok=True)
user_context = optional_text(args.user_context)
model = optional_text(args.model, maximum=200)
reasoning_effort = optional_text(args.reasoning_effort, maximum=32)
workspace_id = str(uuid.uuid4())
scan_id = str(uuid.uuid4())
timestamp = now()
Expand Down Expand Up @@ -715,10 +732,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,
deep_scan_owner_thread_id, scan_dir, status, phase, handoff_status,
started_at, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'deep', ?, ?, ?, 'running', 'preflight',
'delivered', ?, ?, ?)
deep_scan_owner_thread_id, scan_dir, model, reasoning_effort, status, phase,
handoff_status, started_at, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'deep', ?, ?, ?, ?, ?,
'running', 'preflight', 'delivered', ?, ?, ?)
""",
(
scan_id,
Expand All @@ -733,6 +750,8 @@ def begin_deep_scan_for_target(
user_context,
thread_id,
str(scan_dir),
model,
reasoning_effort,
timestamp,
timestamp,
timestamp,
Expand Down Expand Up @@ -1114,6 +1133,14 @@ def claim_deep_scan_dedup(
"UPDATE deep_scan_runs SET phase = 'reducing', updated_at = ? WHERE scan_id = ?",
(timestamp, scan_id),
)
connection.execute(
"""
UPDATE scan_progress
SET deep_review_pass = COALESCE(deep_review_pass, 0) + 1, updated_at = ?
WHERE scan_id = ?
""",
(timestamp, scan_id),
)
connection.commit()
except BaseException:
connection.rollback()
Expand Down
6 changes: 6 additions & 0 deletions sdk/typescript/_bundled_plugin/scripts/workbench_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ def parse_args(description: str) -> argparse.Namespace:
start_scan = subparsers.add_parser("start-scan")
start_scan.add_argument("--workspace-id", required=True)
start_scan.add_argument("--scan-root")
start_scan.add_argument("--model")
start_scan.add_argument("--reasoning-effort")

disable_setup_ui = subparsers.add_parser("disable-setup-ui")
disable_setup_ui.add_argument("--workspace-id", required=True)
Expand All @@ -129,6 +131,8 @@ def parse_args(description: str) -> argparse.Namespace:
start_prompt_only_scan.add_argument("--diff-head-revision")
start_prompt_only_scan.add_argument("--diff-content-digest")
start_prompt_only_scan.add_argument("--scan-root")
start_prompt_only_scan.add_argument("--model")
start_prompt_only_scan.add_argument("--reasoning-effort")

deep_scan.register_subcommands(subparsers, positive_int)

Expand Down Expand Up @@ -209,6 +213,8 @@ def parse_args(description: str) -> argparse.Namespace:
update_progress.add_argument("--reportable-findings-count", type=non_negative_int)
update_progress.add_argument("--deep-review-pass", type=positive_int)
update_progress.add_argument("--claim-token")
update_progress.add_argument("--model")
update_progress.add_argument("--reasoning-effort")

prepare_scan_completion = subparsers.add_parser("prepare-scan-completion")
prepare_scan_completion.add_argument("--scan-id", required=True)
Expand Down
33 changes: 10 additions & 23 deletions sdk/typescript/_bundled_plugin/scripts/workbench_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
SQLITE_RETRY_ATTEMPTS,
)
from workbench_feedback import get_scan_feedback
from workbench_remediation import remediation_claim_is_active
from workbench_scan_start import (
archive_scan,
compact_timestamp,
Expand Down Expand Up @@ -137,23 +138,6 @@ def stale_claim_before(seconds: int = CLAIM_LEASE_SECONDS) -> str:
)


def remediation_claim_is_active(remediation: sqlite3.Row) -> bool:
if remediation["pending_action_claim_token"] is None:
return False
delivered_at = remediation["pending_action_delivered_at"]
claimed_at = delivered_at or remediation["pending_action_claimed_at"]
if not isinstance(claimed_at, str):
return True
try:
parsed = datetime.fromisoformat(claimed_at)
if parsed.tzinfo is None:
return True
except ValueError:
return True
lease_seconds = DELIVERED_ACTION_LEASE_SECONDS if delivered_at else CLAIM_LEASE_SECONDS
return parsed > datetime.now(timezone.utc) - timedelta(seconds=lease_seconds)


def state_dir() -> Path:
state_dir = os.environ.get("CODEX_SECURITY_STATE_DIR")
if state_dir:
Expand Down Expand Up @@ -1172,6 +1156,8 @@ def start_scan(connection: sqlite3.Connection, args: argparse.Namespace) -> dict
target_summary=target_summary,
scope_file_count=scope_file_count,
timestamp=timestamp,
model=args.model,
reasoning_effort=args.reasoning_effort,
Comment on lines +1159 to +1160

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 Populate model metadata for setup-UI scans

For the normal app-backed setup flow, the bundled MCP app invokes start_codex_security_scan with only sessionId, while the server handler ignores request metadata and forwards only its optional input fields. Consequently both values passed here are always None, and scans started through the setup UI still store null model and reasoning_effort even though the originating model turn provides them. Retain the metadata from the opening/waiting turn or read it in the start handler so this primary scan path is included.

Useful? React with 👍 / 👎.

)
if manages_transaction:
connection.commit()
Expand Down Expand Up @@ -1309,6 +1295,8 @@ def start_prompt_only_scan(
scope_file_count=scope_file_count,
timestamp=timestamp,
handoff_status="delivered",
model=args.model,
reasoning_effort=args.reasoning_effort,
)
connection.commit()
except BaseException:
Expand Down Expand Up @@ -3004,6 +2992,7 @@ def scan_result(
progress_result["independentReviews"] = {
"active": independent_reviews["active"],
"completed": independent_reviews["completed"],
"consolidating": independent_reviews["consolidating"],
}
return {
"artifacts": artifacts,
Expand All @@ -3024,8 +3013,10 @@ def scan_result(
"handoffClaimToken": scan["handoff_claim_token"],
"handoffStatus": scan["handoff_status"],
"mode": scan["mode"],
"model": scan["model"],
"diffTarget": stored_diff_target(scan),
"progress": progress_result,
"reasoningEffort": scan["reasoning_effort"],
"remediationAvailable": remediation_available,
"remediationUnavailableReason": remediation_unavailable_reason,
"reportAvailable": "markdownReport" in artifacts,
Expand Down Expand Up @@ -3647,13 +3638,9 @@ def main() -> None:
read_coverage=coverage_for_comparison,
)
elif args.command == "list-global-findings":
result = native_indexes.list_global_findings(
connection, args, read_coverage=coverage_for_comparison
)
result = native_indexes.list_global_findings(connection, args)
elif args.command == "list-repositories":
result = native_indexes.list_repositories(
connection, args, read_coverage=coverage_for_comparison
)
result = native_indexes.list_repositories(connection, args)
elif args.command == "list-findings":
result = list_findings(connection, args)
elif args.command == "update-progress":
Expand Down
54 changes: 5 additions & 49 deletions sdk/typescript/_bundled_plugin/scripts/workbench_native_indexes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sqlite3
import sys
from collections import Counter
from collections.abc import Callable, Iterator
from collections.abc import Iterator
from itertools import islice
from pathlib import Path
from typing import Any
Expand All @@ -19,14 +19,12 @@
def list_global_findings(
connection: sqlite3.Connection,
args: argparse.Namespace,
*,
read_coverage: Callable[[sqlite3.Row], dict[str, Any]],
) -> dict[str, Any]:
limit = min(args.limit, FINDINGS_PAGE_MAX)
query = args.query.strip().casefold() if args.query else ""
findings = (
row
for row in _active_findings(connection, read_coverage)
for row in _indexed_findings(connection)

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 Exclude findings resolved by later covered scans

When a later completed scan covers a finding's location but no longer reports that finding, _indexed_findings still yields the latest historical occurrence because it only ranks stored occurrences and never examines subsequent scan coverage. Consequently list-global-findings continues presenting the finding as open, and list-repositories also keeps it in openFindingsCount; the removed coverage-aware filtering previously handled this exact resolution case. Restore that filtering or persist an equivalent resolved state when scans complete.

Useful? React with 👍 / 👎.

if (args.target_id is None or row["target_id"] == args.target_id)
and (args.severity is None or row["severity"] == args.severity)
and (args.status is None or row["status"] == args.status)
Expand Down Expand Up @@ -72,23 +70,8 @@ def list_global_findings(
}


def _active_findings(
connection: sqlite3.Connection,
read_coverage: Callable[[sqlite3.Row], dict[str, Any]],
) -> Iterator[sqlite3.Row]:
completed_scans_by_target: dict[str, list[sqlite3.Row]] = {}
for scan in connection.execute(
"""
SELECT *
FROM scans
WHERE status = 'complete' AND seal_manifest_digest IS NOT NULL
ORDER BY started_at DESC, id DESC
"""
):
completed_scans_by_target.setdefault(scan["target_id"], []).append(scan)

coverage_by_scan_id: dict[str, dict[str, Any]] = {}
rows = connection.execute(
def _indexed_findings(connection: sqlite3.Connection) -> Iterator[sqlite3.Row]:
yield from connection.execute(
"""
WITH ranked_findings AS (
SELECT
Expand All @@ -97,7 +80,6 @@ def _active_findings(
occurrences.severity,
occurrences.created_at,
scans.id AS scan_id,
scans.started_at AS scan_started_at,
scans.target_id,
targets.current_path AS target_path,
scans.scope,
Expand Down Expand Up @@ -146,35 +128,11 @@ def _active_findings(
selected_findings.occurrence_id
""",
)
for row in rows:
resolved = False
for scan in completed_scans_by_target.get(row["target_id"], ()):
if (scan["started_at"], scan["id"]) <= (
row["scan_started_at"],
row["scan_id"],
):
break
coverage = coverage_by_scan_id.get(scan["id"])
if coverage is None:
coverage = read_coverage(scan)
coverage_by_scan_id[scan["id"]] = coverage
if scan_history.scan_covers_path(
scan,
target_id=row["target_id"],
path=row["location_path"],
coverage=coverage,
):
resolved = True
break
if not resolved:
yield row


def list_repositories(
connection: sqlite3.Connection,
args: argparse.Namespace | None = None,
*,
read_coverage: Callable[[sqlite3.Row], dict[str, Any]],
) -> dict[str, Any]:
scans = scan_history.list_scans(connection)["scans"]
scans_by_id = {scan["scanId"]: scan for scan in scans}
Expand All @@ -190,9 +148,7 @@ def list_repositories(
latest_scan_by_target.setdefault(row["target_id"], scans_by_id[row["id"]])

open_findings_by_target = Counter(
row["target_id"]
for row in _active_findings(connection, read_coverage)
if row["status"] == "open"
row["target_id"] for row in _indexed_findings(connection) if row["status"] == "open"
)
targets = {row["id"]: row for row in connection.execute("SELECT * FROM security_targets")}
repositories = [
Expand Down
9 changes: 6 additions & 3 deletions sdk/typescript/_bundled_plugin/scripts/workbench_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
sys.path.insert(0, str(Path(__file__).resolve().parent))
from workbench.handoff import require_current_continuation
from workbench_constants import PHASES
from workbench_validation import require_uuid
from workbench_validation import optional_text, require_uuid

MAX_PREFLIGHT_ISSUES_JSON_BYTES = 64 * 1024
MAX_PREFLIGHT_ISSUES = 32
Expand Down Expand Up @@ -86,6 +86,8 @@ def update_progress(
scan_context: Callable[[sqlite3.Connection, str], dict[str, Any]],
) -> dict[str, Any]:
scan_id = require_uuid(args.scan_id, "scan-id")
model = optional_text(args.model, maximum=200)
reasoning_effort = optional_text(args.reasoning_effort, maximum=32)
serialized_preflight_issues = preflight_issues_json(args.preflight_issues_json)
connection.execute("BEGIN IMMEDIATE")
try:
Expand Down Expand Up @@ -189,10 +191,11 @@ def update_progress(
updated = connection.execute(
"""
UPDATE scans
SET phase = COALESCE(?, phase), updated_at = ?
SET phase = COALESCE(?, phase), model = COALESCE(?, model),
reasoning_effort = COALESCE(?, reasoning_effort), updated_at = ?
WHERE id = ? AND status = 'running'
""",
(args.phase, timestamp, scan["id"]),
(args.phase, model, reasoning_effort, timestamp, scan["id"]),
)
if updated.rowcount != 1:
raise SystemExit("Only a running scan can update progress.")
Expand Down
20 changes: 19 additions & 1 deletion sdk/typescript/_bundled_plugin/scripts/workbench_remediation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,33 @@
import argparse
import sqlite3
import sys
from datetime import datetime, timezone
from datetime import datetime, timedelta, 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))
from workbench_constants import CLAIM_LEASE_SECONDS, DELIVERED_ACTION_LEASE_SECONDS
from workbench_validation import require_occurrence, require_uuid


def remediation_claim_is_active(remediation: sqlite3.Row) -> bool:
if remediation["pending_action_claim_token"] is None:
return False
delivered_at = remediation["pending_action_delivered_at"]
claimed_at = delivered_at or remediation["pending_action_claimed_at"]
if not isinstance(claimed_at, str):
return True
try:
parsed = datetime.fromisoformat(claimed_at)
if parsed.tzinfo is None:
return True
except ValueError:
return True
lease_seconds = DELIVERED_ACTION_LEASE_SECONDS if delivered_at else CLAIM_LEASE_SECONDS
return parsed > datetime.now(timezone.utc) - timedelta(seconds=lease_seconds)


def register_cancel_finding_remediation_request(subparsers: Any) -> None:
parser = subparsers.add_parser("cancel-finding-remediation-request")
parser.add_argument("--occurrence-id", required=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ def list_scans(
"findingCount": row["finding_count"],
"handoffStatus": row["handoff_status"],
"mode": row["mode"],
"model": row["model"],

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 Persist model metadata for SDK-registered scans

For scans started through the public TypeScript SDK or CLI, api.ts invokes register-cli-scan, whose insert_running_scan call still supplies neither model nor reasoning_effort; this path also never uses the MCP progress endpoint that can backfill them. Although the saved recipe already contains the effective model and model_reasoning_effort, every such scan therefore exposes null in these newly added history fields. Pass the configured values during registration or extract them from the validated recipe.

Useful? React with 👍 / 👎.

"parentScanId": row["parent_scan_id"],
"progress": {
"candidates": {"reportable": row["reportable_findings_count"]},
Expand All @@ -238,6 +239,7 @@ def list_scans(
"updatedAt": row["progress_updated_at"],
},
"recipeAvailable": row["recipe_json"] is not None,
"reasoningEffort": row["reasoning_effort"],
"scanDir": row["scan_dir"],
"scanId": row["id"],
"scope": row["scope"],
Expand Down
Loading
Loading