Skip to content
Merged
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 removed sdk/typescript/_bundled_plugin/mcp/mcp-app.html.br
Binary file not shown.
2 changes: 1 addition & 1 deletion sdk/typescript/_bundled_plugin/mcp/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const runtimeChunkNames = (await readdir(new URL("./", import.meta.url)))
.filter((name) => name.startsWith("server.mjs.br.part-"))
.sort();
if (!runtimeChunkNames.length) {
throw new Error("Missing compressed Codex Security MCP workspace runtime chunks.");
throw new Error("Missing compressed Codex Security MCP server runtime chunks.");
}
const compressedRuntime = Buffer.concat(
await Promise.all(runtimeChunkNames.map((name) => readFile(new URL(`./${name}`, import.meta.url))))
Expand Down
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.
10 changes: 4 additions & 6 deletions sdk/typescript/_bundled_plugin/references/config-preflight.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,9 @@ Do not warn merely because a user's value differs from the profile's suggested p

If a runtime capability is `unknown`, establish it from the current tool surface and rerun the helper with an explicit `--runtime-check`. Do not treat an `incomplete` result or unknown value as evidence that the capability is available.

## MCP App onboarding handoff
## Durable scan handoff

The onboarding workspace opens before capability preflight and does not display or enforce configuration capability results. If `open_codex_security_workspace` returns a workspace with `setup.submitted=false`, that is the app setup wait state. Do not run this helper, do not call `set_codex_security_capability_preflight`, do not create or adopt a scan goal, and do not reclassify the scan as terminal/chat fallback merely because no `scanId` exists yet. Stop and wait for the user to review setup and press Start scan.

After the user submits setup and the app-generated handoff provides a `scanId`, load the authoritative scan context with `get_codex_security_scan_context`, then run this preflight for the validated target and selected scan mode. The dedicated preflight worker described above is allowed and should finish before goal setup, threat modeling, scan/discovery worker creation, or other substantive analysis.
After a native handoff or direct conversation start provides a `scanId`, use its authoritative scan context and run this preflight for the validated target and selected scan mode. The dedicated preflight worker described above should finish before goal setup, threat modeling, scan/discovery worker creation, or other substantive analysis.

For standard and diff scans, the app handoff starts preflight without an item count. After every structured helper result, call `update_codex_security_scan_progress` without changing phase and set `preflightChecks` to every entry from the helper's `results` array, projecting each entry to only `capability`, `reason`, `severity`, and `status`. Do not send `phaseItemsTotal`, `phaseItemsCompleted`, or `phaseProgressUnit` with `preflightChecks`: the server derives the total from the array length, counts `pass` and `fail` as completed, excludes `unknown` from completed, and derives the visible `block` or `warn` attention items. Send the full fresh results array after a clean rerun so stale issues disappear. Do not interpret item-count completion as readiness: remain in preflight for every blocked, incomplete, or error result while remediation or retry remains pending, even when every returned check was evaluated. Only after a `ready` result has published its fresh `preflightChecks` should a separate progress call advance to `threat_model`. These counts and issues belong to the current scan rather than the legacy setup-time workspace preflight, and remain visible after the scan advances. Deep Scan preflight and discovery progress remain owned by `start_codex_security_deep_scan`.

Expand Down Expand Up @@ -112,6 +110,6 @@ In a non-interactive Codex session, do not leave the run waiting for an answer i

For any non-ready result, do not fail automatically. If an interactive chat fallback declines required remediation without choosing whether to cancel or leave the scan running, ask that follow-up before taking either action. If remediation is unavailable, the helper cannot run, the helper returns an error envelope, or a rerun remains blocked or incomplete, preserve a durable running scan and retry or hand off while recovery may still be possible. Call `fail_codex_security_scan` with the exact reasons only after the documented recovery path is exhausted and the blocker is confirmed unrecoverable, or when the user explicitly cancels.

Present applicable remediation in the Codex thread. Ask before editing persistent user configuration in interactive sessions; use the narrow automatic-remediation path above in non-interactive sessions. Do not pass capability preflight to `open_codex_security_workspace`, depend on the setup UI to display it, or require `set_codex_security_capability_preflight` before the user can start a scan.
Present applicable remediation in the Codex thread. Ask before editing persistent user configuration in interactive sessions; use the narrow automatic-remediation path above in non-interactive sessions.

Codex CLI and hosts without MCP Apps use the same prompt-based preflight before substantive work. This fallback applies only when the host cannot use the setup app at all; once an app workspace has opened, remain on the app handoff path until the user submits setup or cancels it. Explain the exact reasons and remediation in chat. Interactive CLI sessions ask before editing persistent config; non-interactive CLI sessions use the narrow automatic-remediation path above and continue only after a `ready` rerun.
Codex CLI and other hosts use the same prompt-based preflight before substantive work. Explain the exact reasons and remediation in chat. Interactive CLI sessions ask before editing persistent config; non-interactive CLI sessions use the narrow automatic-remediation path above and continue only after a `ready` rerun.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Use these shared path conventions for Codex Security scan workflows unless the u
- `reconciliation_dir=<artifacts_dir>/04_reconciliation`
- `findings_dir=<artifacts_dir>/05_findings`

The MCP app resolves the platform temporary directory automatically. For a manual workflow, use the active process temporary directory (for example, `%TEMP%` on Windows or `$TMPDIR` when configured on Unix-like hosts) instead of hardcoding `/tmp`.
The plugin resolves the platform temporary directory automatically. For a manual workflow, use the active process temporary directory (for example, `%TEMP%` on Windows or `$TMPDIR` when configured on Unix-like hosts) instead of hardcoding `/tmp`.

Resolve `<python_command>` to the configured Python interpreter (`$PYTHON` when one is provided), otherwise use `python` on Windows and `python3` on Unix-like hosts.

Expand Down
34 changes: 0 additions & 34 deletions sdk/typescript/_bundled_plugin/scripts/deep_scan_workbench.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,32 +588,6 @@ def terminal_deep_scan_for_target_snapshot(
).fetchone()


def pending_deep_workspace_for_target(
connection: sqlite3.Connection, thread_id: str, target_path: str, scope: str
) -> sqlite3.Row | None:
return connection.execute(
"""
SELECT *
FROM workspaces
WHERE thread_id = ?
AND target_path = ?
AND default_scope = ?
AND default_mode = 'deep'
AND active_scan_id IS NULL
ORDER BY updated_at DESC, created_at DESC, id
LIMIT 1
""",
(thread_id, target_path, scope),
).fetchone()


def setup_ui_opt_out_enabled(connection: sqlite3.Connection) -> bool:
row = connection.execute(
"SELECT skip_setup_ui FROM setup_preferences WHERE singleton = 1"
).fetchone()
return row is not None and bool(row["skip_setup_ui"])


def begin_deep_scan_for_scan(
connection: sqlite3.Connection,
scan_id: str,
Expand Down Expand Up @@ -737,14 +711,6 @@ def begin_deep_scan_for_target(
existing["id"],
start_disposition="joined" if existing_run is not None else "created",
)
pending_workspace = pending_deep_workspace_for_target(
connection, thread_id, target_path, scope
)
if pending_workspace is not None and not setup_ui_opt_out_enabled(connection):
raise SystemExit(
"A matching Codex Security setup workspace is waiting for Start scan. "
"Finish that setup and retry with its scanId."
)
current_target = require_remediation_target(target_path)
current_metadata = current_target.stat()
if (current_metadata.st_dev, current_metadata.st_ino) != (
Expand Down
50 changes: 0 additions & 50 deletions sdk/typescript/_bundled_plugin/scripts/workbench_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,13 @@ def parse_args(description: str) -> argparse.Namespace:
parser = argparse.ArgumentParser(description=description)
subparsers = parser.add_subparsers(dest="command", required=True)

subparsers.add_parser("get-setup-preference")

create_workspace = subparsers.add_parser("create-workspace")
create_workspace.add_argument("--workspace-id", required=True)
create_workspace.add_argument("--thread-id")
create_workspace.add_argument("--target-path")
create_workspace.add_argument("--target-title")
create_workspace.add_argument("--target-summary")
create_workspace.add_argument("--user-context")
create_preflight = create_workspace.add_mutually_exclusive_group()
create_preflight.add_argument("--capability-preflight-json")
create_preflight.add_argument("--capability-preflight-json-file", type=Path)
create_workspace.add_argument("--scope")
create_workspace.add_argument("--mode", choices=MODES, default="standard")
create_workspace.add_argument("--diff-target-kind", choices=DIFF_TARGET_KINDS)
Expand All @@ -51,14 +46,6 @@ def parse_args(description: str) -> argparse.Namespace:
get_workspace.add_argument("--workspace-id", required=True)
get_workspace.add_argument("--thread-id")

get_latest_workspace = subparsers.add_parser("get-latest-workspace")
get_latest_workspace.add_argument("--thread-id", required=True)

list_workspace_scans = subparsers.add_parser("list-workspace-scans")
list_workspace_scans.add_argument("--workspace-id", required=True)
list_workspace_scans.add_argument("--offset", type=non_negative_int, default=0)
list_workspace_scans.add_argument("--limit", type=workspace_scan_limit, default=20)

inspect_target = subparsers.add_parser("inspect-target")
inspect_target.add_argument("--target-path", required=True)

Expand All @@ -71,25 +58,6 @@ def parse_args(description: str) -> argparse.Namespace:
inspect_setup.add_argument("--diff-head-revision")
inspect_setup.add_argument("--diff-content-digest")

begin_diff_resolution = subparsers.add_parser("begin-diff-resolution")
begin_diff_resolution.add_argument("--workspace-id", required=True)
begin_diff_resolution.add_argument("--request-id", required=True)
begin_diff_resolution.add_argument("--target-path", required=True)
begin_diff_resolution.add_argument("--user-context")

cancel_diff_resolution = subparsers.add_parser("cancel-diff-resolution")
cancel_diff_resolution.add_argument("--workspace-id", required=True)
cancel_diff_resolution.add_argument("--request-id", required=True)

set_diff_target = subparsers.add_parser("set-diff-target")
set_diff_target.add_argument("--workspace-id", required=True)
set_diff_target.add_argument("--request-id", required=True)
set_diff_target.add_argument("--target-summary", required=True)
set_diff_target.add_argument("--diff-target-kind", choices=DIFF_TARGET_KINDS, required=True)
set_diff_target.add_argument("--diff-base-revision")
set_diff_target.add_argument("--diff-head-revision")
set_diff_target.add_argument("--diff-content-digest")

save_workspace = subparsers.add_parser("save-workspace")
save_workspace.add_argument("--workspace-id", required=True)
save_workspace.add_argument("--target-path", required=True)
Expand All @@ -102,23 +70,12 @@ def parse_args(description: str) -> argparse.Namespace:
save_workspace.add_argument("--diff-head-revision")
save_workspace.add_argument("--diff-content-digest")

set_capability_preflight = subparsers.add_parser("set-capability-preflight")
set_capability_preflight.add_argument("--workspace-id", required=True)
set_capability_preflight.add_argument("--checked-target-path", required=True)
set_capability_preflight.add_argument("--checked-mode", choices=MODES, required=True)
set_preflight = set_capability_preflight.add_mutually_exclusive_group(required=True)
set_preflight.add_argument("--capability-preflight-json")
set_preflight.add_argument("--capability-preflight-json-file", type=Path)

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)

start_prompt_only_scan = subparsers.add_parser("start-prompt-only-scan")
start_prompt_only_scan.add_argument("--thread-id", required=True)
start_prompt_only_scan.add_argument("--target-path", required=True)
Expand Down Expand Up @@ -356,12 +313,5 @@ def positive_int(value: str) -> int:
return parsed


def workspace_scan_limit(value: str) -> int:
parsed = positive_int(value)
if parsed > 100:
raise argparse.ArgumentTypeError("expected an integer between 1 and 100")
return parsed


if __name__ == "__main__":
parse_args(__doc__)
2 changes: 0 additions & 2 deletions sdk/typescript/_bundled_plugin/scripts/workbench_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@
FINDING_LOCATION_ROLE_BYTES = 128
FINDING_ABSOLUTE_PATH_BYTES = 4_096
FINDING_LEVEL_BYTES = 128
MAX_CAPABILITY_PREFLIGHT_INPUT_JSON_BYTES = 160_000
MAX_CAPABILITY_PREFLIGHT_PERSISTED_JSON_BYTES = 180_000
GIT_REPOSITORY_ENVIRONMENT = (
"GIT_ALTERNATE_OBJECT_DIRECTORIES",
"GIT_CEILING_DIRECTORIES",
Expand Down
Loading
Loading