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
37def7c
fix(windows): support long paths in scan tooling
faizan-oai Aug 14, 2026
f3ccf52
fix(windows): finalize scans through long paths
faizan-oai Aug 14, 2026
6444017
Merge remote-tracking branch 'origin/main' into agent/support-windows…
faizan-oai Aug 14, 2026
0d34df1
fix(windows): support long deep scan paths
faizan-oai Aug 14, 2026
8dd0409
fix(windows): query history through long paths
faizan-oai Aug 14, 2026
4c26065
fix(windows): open long workbench state paths
faizan-oai Aug 14, 2026
c571062
fix(windows): read long rollout paths
faizan-oai Aug 14, 2026
82f54aa
fix(windows): exclude Python bytecode from packages
mldangelo-oai Aug 15, 2026
054f72a
Merge remote-tracking branch 'origin/main' into HEAD
mldangelo-oai Aug 15, 2026
b4d2f04
fix(windows): preserve long paths through scan results
mldangelo-oai Aug 15, 2026
6dc0fd8
test(windows): run long-path coverage with bundled ripgrep
mldangelo-oai Aug 15, 2026
9a7f97c
fix(windows): handle legacy scan paths and scoped inventories
mldangelo-oai Aug 15, 2026
1fa404f
fix(windows): reconcile legacy target and Unicode paths
mldangelo-oai Aug 15, 2026
189a49d
Merge remote-tracking branch 'origin/main' into HEAD
mldangelo-oai Aug 15, 2026
6391332
test(windows): assert persisted target identity at its contract boundary
mldangelo-oai Aug 15, 2026
e04622e
fix(windows): preserve long-path ranking and sealed scan history
mldangelo-oai Aug 15, 2026
ff8b06b
fix(windows): retain legacy scan roots in workbench history
mldangelo-oai Aug 15, 2026
4641e9b
fix(windows): normalize deep scan children and legacy targets
mldangelo-oai Aug 15, 2026
5c9c09f
fix(windows): extend generated temporary scan artifact paths
mldangelo-oai Aug 15, 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
16 changes: 10 additions & 6 deletions sdk/typescript/_bundled_plugin/scripts/config_preflight.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
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 windows_paths import filesystem_path, portable_path

try:
import tomllib
except ModuleNotFoundError: # pragma: no cover - Python 3.10 only
Expand Down Expand Up @@ -121,7 +125,7 @@ def parse_args() -> argparse.Namespace:

def read_toml(path: Path, *, required: bool) -> dict[str, Any]:
try:
with path.open("rb") as file:
with filesystem_path(path).open("rb") as file:
return tomllib.load(file)
except FileNotFoundError:
if required:
Expand Down Expand Up @@ -231,7 +235,7 @@ def resolve_project_root(cwd: Path, config_layers: list[tuple[Path, dict[str, An
if not markers:
return cwd
for candidate in (cwd, *cwd.parents):
if any((candidate / marker).exists() for marker in markers):
if any(filesystem_path(candidate / marker).exists() for marker in markers):
return candidate
return cwd

Expand All @@ -243,7 +247,7 @@ def project_trust_level(
projects = config.get("projects")
if not isinstance(projects, dict):
continue
project = projects.get(str(project_root))
project = projects.get(str(portable_path(project_root)))
if not isinstance(project, dict):
continue
trust_level = project.get("trust_level")
Expand All @@ -265,7 +269,7 @@ def project_config_paths(project_root: Path, cwd: Path) -> list[Path]:
def discover_config_paths(
*, cwd: Path, profile_layer_path: Path | None
) -> tuple[list[Path], dict[str, Any]]:
resolved_cwd = cwd.expanduser().resolve()
resolved_cwd = filesystem_path(cwd.expanduser()).resolve()

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 Extend project config paths after appending filenames

When a trusted Windows project root is shorter than 248 UTF-16 units but appending /.codex/config.toml pushes the path past the legacy limit, converting only resolved_cwd here leaves project_config_paths() returning ordinary paths, which read_toml() opens directly. The project-local configuration is then ignored or preflight fails despite the directory itself being valid; apply filesystem_path to each fully constructed config filename.

AGENTS.md reference: sdk/typescript/AGENTS.md:L24-L24

Useful? React with 👍 / 👎.

if not resolved_cwd.is_dir():
raise ValueError(f"cwd must be a directory, got {str(resolved_cwd)!r}")

Expand All @@ -279,8 +283,8 @@ def discover_config_paths(
if trust_level == "trusted":
paths.extend(project_config_paths(project_root, resolved_cwd))
return paths, {
"cwd": str(resolved_cwd),
"project_root": str(project_root),
"cwd": str(portable_path(resolved_cwd)),
"project_root": str(portable_path(project_root)),
"project_trust_level": trust_level,
"project_layers_loaded": trust_level == "trusted",
}
Expand Down
115 changes: 73 additions & 42 deletions sdk/typescript/_bundled_plugin/scripts/deep_scan_workbench.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
worktree_content_digest,
)
from workbench_validation import optional_text, require_uuid, user_text
from windows_paths import extended_path, filesystem_path, portable_path

DEEP_SCAN_WORKER_KINDS = ("setup", "discovery", "dedup")
DEEP_SCAN_WORKER_STATUSES = ("queued", "running", "succeeded", "failed", "canceled")
Expand Down Expand Up @@ -196,6 +197,10 @@ def require_canonical_scan_directory(scan_dir: Path) -> Path:
return dependencies().require_canonical_scan_directory(scan_dir)


def scan_directory_path(scan: sqlite3.Row, *parts: str) -> Path:
return filesystem_path(Path(scan["scan_dir"]).joinpath(*parts))


def safe_segment(value: str) -> str:
return dependencies().safe_segment(value)

Expand Down Expand Up @@ -264,25 +269,28 @@ def deep_scan_path(
if not supplied.is_absolute():
raise SystemExit(f"{label} must be an absolute path inside the scan directory.")
try:
resolved = supplied.resolve(strict=True)
scan_dir = require_canonical_scan_directory(Path(scan["scan_dir"]))
resolved.relative_to(scan_dir)
resolved = type(supplied)(filesystem_path(supplied)).resolve(strict=True)
scan_dir = require_canonical_scan_directory(scan_directory_path(scan))
portable_resolved = portable_path(resolved)
portable_resolved.relative_to(portable_path(scan_dir))
except (OSError, RuntimeError, ValueError) as exc:
raise SystemExit(f"{label} must be an existing path inside the scan directory.") from exc
if os.path.normcase(resolved) != os.path.normcase(supplied.absolute()):
if os.path.normcase(portable_resolved) != os.path.normcase(
portable_path(supplied.absolute())
):
raise SystemExit(f"{label} must be a canonical non-symlink path.")
if kind == "file" and not resolved.is_file():
raise SystemExit(f"{label} must be a regular file.")
if kind == "directory" and not resolved.is_dir():
raise SystemExit(f"{label} must be a directory.")
return str(resolved)
return str(portable_resolved)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep discovery paths compatible with the extended scan root

When a Windows scan directory requires the \\?\ spelling and a Deep Scan exceeds --max-cost, canonical_discovery_artifacts() now returns portable artifact paths here, while complete_budget_exhausted_scan() passes an extended scan_dir to budget_exhausted_candidates(). Its inventory.relative_to(scan_dir) and ledger.relative_to(scan_dir) calls then compare different anchors and raise ValueError, so the budget-exhausted scan cannot preserve or complete its discovery results. Convert both operands to the same spelling before deriving the scan-local relative paths.

AGENTS.md reference: sdk/typescript/AGENTS.md:L24-L24

Useful? React with 👍 / 👎.



def deep_scan_output_path(scan: sqlite3.Row, value: str, label: str) -> str:
supplied = Path(value).expanduser()
if not supplied.is_absolute():
raise SystemExit(f"{label} must be an absolute path inside the scan directory.")
if supplied.exists():
if filesystem_path(supplied).exists():
return deep_scan_path(scan, str(supplied), label, kind="file")
parent = Path(deep_scan_path(scan, str(supplied.parent), label, kind="directory"))
output = parent / supplied.name
Expand All @@ -291,12 +299,16 @@ def deep_scan_output_path(scan: sqlite3.Row, value: str, label: str) -> str:
return str(output)


def temporary_artifact_path(path: Path, suffix: str) -> Path:
return filesystem_path(path.with_name(f".{path.name}.{uuid.uuid4()}.{suffix}"))


def promote_staged_file(staged_path: str, output_path: str) -> tuple[Path, Path, Path | None]:
staged = Path(staged_path)
output = Path(output_path)
staged = filesystem_path(Path(staged_path))
output = filesystem_path(Path(output_path))
if staged == output:
raise SystemExit("A staged Deep Scan artifact must not be its published output path.")
backup = output.with_name(f".{output.name}.{uuid.uuid4()}.backup") if output.exists() else None
backup = temporary_artifact_path(output, "backup") if output.exists() else None
if backup is not None:
os.replace(output, backup)
try:
Expand All @@ -323,10 +335,13 @@ def finish_staged_file(promotion: tuple[Path, Path, Path | None]) -> None:


def canonical_discovery_artifacts(scan: sqlite3.Row) -> dict[str, str]:
discovery_dir = Path(scan["scan_dir"]) / "artifacts" / "02_discovery"
artifacts = {
"inScopeFilesPath": discovery_dir / "in_scope_files.txt",
"candidateLedgerPath": discovery_dir / "candidate_ledger.jsonl",
"inScopeFilesPath": scan_directory_path(
scan, "artifacts", "02_discovery", "in_scope_files.txt"
),
"candidateLedgerPath": scan_directory_path(
scan, "artifacts", "02_discovery", "candidate_ledger.jsonl"
),
}
labels = {
"inScopeFilesPath": "Canonical in-scope inventory path",
Expand Down Expand Up @@ -364,15 +379,19 @@ def deep_scan_state(connection: sqlite3.Connection, scan_id: str) -> dict[str, A
run["canonical_inventory_path"] is None
and run["status"] == "succeeded"
and run["manifest_path"] is not None
and run["manifest_path"] != str(Path(scan["scan_dir"]) / "scan-manifest.json")
and (Path(scan["scan_dir"]) / "artifacts" / "02_discovery" / "in_scope_files.txt").exists()
and run["manifest_path"]
!= str(portable_path(scan_directory_path(scan, "scan-manifest.json")))
and scan_directory_path(scan, "artifacts", "02_discovery", "in_scope_files.txt").exists()
):
canonical_artifacts = canonical_discovery_artifacts(scan)
if (
run["terminal_reason"] == "capped"
and run["completion_sequence"] == 0
and deep_scan_deadline_reached(run)
and Path(canonical_artifacts["candidateLedgerPath"]).stat().st_size != 0
and filesystem_path(Path(canonical_artifacts["candidateLedgerPath"]))
.stat()
.st_size
!= 0
):
raise SystemExit(
"A capped Deep Scan without completed discoveries requires an empty "
Expand Down Expand Up @@ -691,7 +710,7 @@ def begin_deep_scan_for_target(
target = require_target(args.target_path)
require_scannable_target(target)
scope = require_scope(args.scope, "deep", target)
target_path = str(target)
target_path = str(portable_path(target))
existing = existing_deep_scan_for_target(connection, thread_id, target_path, scope)
if existing is not None:
return begin_deep_scan_for_scan(connection, existing["id"], thread_id, args)
Expand Down Expand Up @@ -757,10 +776,17 @@ def begin_deep_scan_for_target(
if workflow_version is None:
raise SystemExit("workflow-version is required.")
root = (
Path(args.scan_root).expanduser().resolve() if args.scan_root else state_dir() / "scans"
filesystem_path(Path(args.scan_root).expanduser()).resolve()
if args.scan_root
else state_dir() / "scans"
)
target_root = (root / safe_segment(target.name)).resolve()
if target_root == target or target in target_root.parents:
target_root = filesystem_path(root / safe_segment(target.name)).resolve()
portable_target = portable_path(target)
portable_target_root = portable_path(target_root)
if (
portable_target_root == portable_target
or portable_target in portable_target_root.parents
):
raise SystemExit("The scan artifact directory must be outside the selected target.")
target_root.mkdir(parents=True, exist_ok=True)
user_context = user_text(args.user_context)
Expand All @@ -770,10 +796,12 @@ def begin_deep_scan_for_target(
scan_id = str(uuid.uuid4())
timestamp = now()
target_id = ensure_security_target(connection, target_path)
scan_dir = Path(
tempfile.mkdtemp(
prefix=f"{safe_segment(revision)}_{compact_timestamp()}_",
dir=target_root,
scan_dir = filesystem_path(
Path(
tempfile.mkdtemp(
prefix=f"{safe_segment(revision)}_{compact_timestamp()}_",
dir=extended_path(target_root),
)
)
).resolve()
connection.execute(
Expand Down Expand Up @@ -817,7 +845,7 @@ def begin_deep_scan_for_target(
scope,
user_context,
thread_id,
str(scan_dir),
str(portable_path(scan_dir)),
model,
reasoning_effort,
timestamp,
Expand Down Expand Up @@ -886,11 +914,11 @@ def coordinator_lease_is_live(
seconds=DEEP_SCAN_LEGACY_COORDINATOR_GRACE_SECONDS
)
heartbeat_time = datetime.fromisoformat(str(run["updated_at"]))
heartbeat_path = (
Path(scan["scan_dir"])
/ "artifacts"
/ "deep_discovery"
/ f"coordinator-heartbeat-{run['coordinator_generation']}.json"
heartbeat_path = scan_directory_path(
scan,
"artifacts",
"deep_discovery",
f"coordinator-heartbeat-{run['coordinator_generation']}.json",
)
try:
heartbeat = json.loads(heartbeat_path.read_text(encoding="utf-8"))
Expand Down Expand Up @@ -1047,7 +1075,7 @@ def recover_expired_coordinator(

def recover_candidate_ledger_publication(connection: sqlite3.Connection, scan_id: str) -> None:
scan = require_scan(connection, scan_id)
ledger = Path(scan["scan_dir"]) / "artifacts" / "02_discovery" / "candidate_ledger.jsonl"
ledger = scan_directory_path(scan, "artifacts", "02_discovery", "candidate_ledger.jsonl")
backups = sorted(
ledger.parent.glob(f".{ledger.name}.*.backup"),
key=lambda backup: backup.stat().st_mtime_ns,
Expand All @@ -1066,7 +1094,7 @@ def recover_candidate_ledger_publication(connection: sqlite3.Connection, scan_id
(scan_id,),
)
for reducer in reducers:
snapshot = Path(reducer["artifact_dir"]) / "canonical" / ledger.name
snapshot = filesystem_path(Path(reducer["artifact_dir"]) / "canonical" / ledger.name)
if not snapshot.exists():
continue
published = ledger.exists() and ledger.samefile(snapshot)
Expand Down Expand Up @@ -1531,7 +1559,7 @@ def commit_deep_scan_dedup_locked(
"Staged candidate ledger path",
kind="file",
)
discovery_dir = Path(scan["scan_dir"]) / "artifacts" / "02_discovery"
discovery_dir = scan_directory_path(scan, "artifacts", "02_discovery")
deep_scan_path(
scan,
str(discovery_dir / "in_scope_files.txt"),
Expand Down Expand Up @@ -1567,11 +1595,9 @@ def commit_deep_scan_dedup_locked(
if not inputs or any(row["merge_state"] != "merging" for row in inputs):
raise SystemExit("Dedup inputs are not in the claimed merging state.")
if candidate_ledger_path and canonical_candidate_ledger_path:
canonical_path = Path(canonical_candidate_ledger_path)
publication_copy = canonical_path.with_name(
f".{canonical_path.name}.{uuid.uuid4()}.publish"
)
os.link(candidate_ledger_path, publication_copy)
canonical_path = filesystem_path(Path(canonical_candidate_ledger_path))
publication_copy = temporary_artifact_path(canonical_path, "publish")
os.link(filesystem_path(Path(candidate_ledger_path)), publication_copy)
promotion = promote_staged_file(
str(publication_copy),
canonical_candidate_ledger_path,
Expand Down Expand Up @@ -1651,7 +1677,9 @@ def finish_deep_scan_locked(
scan, args.manifest_path, "Deep Scan coordinator manifest path", kind="file"
)
)
standard_scan_manifest = manifest_path == str(Path(scan["scan_dir"]) / "scan-manifest.json")
standard_scan_manifest = manifest_path == str(
portable_path(scan_directory_path(scan, "scan-manifest.json"))
)

failure_capped = False
if (
Expand All @@ -1662,12 +1690,12 @@ def finish_deep_scan_locked(
for artifact_name in ("scan-manifest.json", "findings.json", "coverage.json"):
deep_scan_path(
scan,
str(Path(scan["scan_dir"]) / artifact_name),
str(scan_directory_path(scan, artifact_name)),
f"Canonical parent {artifact_name}",
kind="file",
)
coverage = _read_scan_local_json(
Path(scan["scan_dir"]), "coverage.json", "Canonical parent coverage.json"
scan_directory_path(scan), "coverage.json", "Canonical parent coverage.json"
)
deferred = coverage.get("deferred")
failure_capped = (
Expand Down Expand Up @@ -1766,7 +1794,7 @@ def finish_deep_scan_locked(
for artifact_name in ("scan-manifest.json", "findings.json", "coverage.json"):
deep_scan_path(
scan,
str(Path(scan["scan_dir"]) / artifact_name),
str(scan_directory_path(scan, artifact_name)),
f"Canonical parent {artifact_name}",
kind="file",
)
Expand All @@ -1792,7 +1820,10 @@ def finish_deep_scan_locked(
and (
standard_scan_manifest
or canonical_artifacts is not None
and Path(canonical_artifacts["candidateLedgerPath"]).stat().st_size == 0
and filesystem_path(Path(canonical_artifacts["candidateLedgerPath"]))
.stat()
.st_size
== 0
)
)
if successful_reducer is None and not zero_discovery_deadline:
Expand Down
16 changes: 10 additions & 6 deletions sdk/typescript/_bundled_plugin/scripts/finalize_scan_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
from typing import Any, TextIO
from urllib.parse import quote, urlsplit

# Some plugin hosts launch Python with safe-path isolation enabled.
sys.path.insert(0, str(Path(__file__).resolve().parent))
from windows_paths import filesystem_path

SCHEMA_VERSION = "1.0"
PRODUCER_NAME = "codex-security-plugin"
FINGERPRINT_ALGORITHM = "codex-security/v1"
Expand Down Expand Up @@ -86,7 +90,7 @@ def _loads_json(value: str | bytes) -> Any:

def _read_json(path: Path) -> dict[str, Any]:
try:
payload = _loads_json(path.read_text(encoding="utf-8"))
payload = _loads_json(filesystem_path(path).read_text(encoding="utf-8"))
_require_safe_json_value(payload, str(path))
except FileNotFoundError as exc:
raise ContractError(f"missing required contract artifact: {path}") from exc
Expand Down Expand Up @@ -230,7 +234,7 @@ def _require_safe_relative_path(value: str, context: str, *, allow_dot: bool = F


def _require_scan_directory(scan_dir: Path) -> Path:
scan_dir = scan_dir.absolute()
scan_dir = filesystem_path(scan_dir.absolute())
try:
metadata = scan_dir.lstat()
except OSError as exc:
Expand Down Expand Up @@ -1643,7 +1647,7 @@ def _github_primary_location_line_hash(
return None
primary_location = _sarif_primary_location(finding)
try:
source_root = source_root.resolve(strict=True)
source_root = filesystem_path(source_root).resolve(strict=True)
except (OSError, RuntimeError):
return None
relative_path = _require_safe_relative_path(primary_location["path"], "SARIF source location")
Expand All @@ -1665,7 +1669,7 @@ def _github_line_hash_cache(
if source_root is None:
return {}
try:
source_root = source_root.resolve(strict=True)
source_root = filesystem_path(source_root).resolve(strict=True)
except (OSError, RuntimeError):
return {}
requested_lines_by_path: dict[str, set[int]] = {}
Expand Down Expand Up @@ -1909,7 +1913,7 @@ def build_sarif_projection(
) -> dict[str, Any]:
if source_root is not None:
try:
source_root = source_root.resolve(strict=True)
source_root = filesystem_path(source_root).resolve(strict=True)
source_root_is_directory = source_root.is_dir()
except (OSError, RuntimeError):
source_root_is_directory = False
Expand Down Expand Up @@ -2052,7 +2056,7 @@ def write_export_output(scan_dir: Path, output: Path, export_format: str, conten
if export_format not in EXPORT_PATHS:
raise ContractError(f"unsupported export format: {export_format}")
scan_dir = _require_scan_directory(scan_dir)
output = Path(os.path.abspath(output))
output = filesystem_path(Path(os.path.abspath(output)))
try:
relative_output = output.relative_to(scan_dir).as_posix()
except ValueError:
Expand Down
Loading
Loading