Summary
ScanCostTracker (sdk/typescript/src/cost.ts) and readScanLogs (sdk/typescript/src/scan-logs.ts) both decide "does this Codex session belong to this scan?" using the deep-worker output directory layout but they use different checks, and the cost one is weaker. As a result ScanCostTracker attributes token usage from sessions that readScanLogs deliberately excludes. Because cost also gates --max-cost, this can both misreport cost and abort a scan early against its budget.
Environment
@openai/codex-security 0.1.16 (repo main @ 97d2c83)
- OS: platform-independent (path logic); one trigger is Windows-specific
- Area:
sdk/typescript/src/cost.ts, sdk/typescript/src/scan-logs.ts
Details
readScanLogs → belongsToScan requires the session working directory to be exactly <scanDir>/artifacts/deep_discovery/workers/<id>/output:
const directory = relative(workers, session.workingDirectory);
const components = directory.split(sep);
if (
!isAbsolute(directory) &&
components.length === 2 &&
components[0] !== ".." &&
relative(join(workers, components[0]!, "output"), session.workingDirectory) === ""
) { return true; }
ScanCostTracker.#readSessions only checks that the relative path splits into two components whose second is "output", dropping the !isAbsolute, components[0] !== "..", and exact-path guards:
const workerDirectory = relative(
join(artifactsDirectory, "deep_discovery", "workers"),
session.workingDirectory,
).split(sep);
if (
session.workingDirectory === artifactsDirectory ||
(workerDirectory.length === 2 && workerDirectory[1] === "output")
) { included.add(session.threadId); }
Two working directories that belongsToScan rejects but #readSessions accepts:
- Sibling of
workers/ (any OS): <scanDir>/artifacts/deep_discovery/output
→ relative(workers, cwd) = ../output → ["..","output"] → length 2, [1] === "output".
belongsToScan rejects it via components[0] !== "..".
- Windows cross-drive:
D:\output while the scan directory is on C:
→ relative() returns the absolute D:\output → ["D:","output"] → length 2, [1] === "output".
belongsToScan rejects it via !isAbsolute(directory).
Reproduction
Running the two predicates' verbatim source expressions on identical inputs (node, path.win32):
scanDir = C:\srv\scans\run1
wd = C:\srv\scans\run1\artifacts\deep_discovery\workers\w0\output cost=include logs=include (agree)
wd = C:\srv\scans\run1\artifacts\deep_discovery\output cost=include logs=EXCLUDE (diverge)
wd = D:\output cost=include logs=EXCLUDE (diverge)
End-to-end, a session running in <scanDir>/artifacts/deep_discovery/output (started after the scan) with, say, 1,000,000 input / 1,000,000 output tokens is folded into the scan's reported usage, even though its events are not part of the scan's log view.
Expected vs. observed
- Expected: cost attribution matches log attribution, only the scan thread, real
workers/<id>/output sessions, the artifacts dir, and their child threads are counted.
- Observed:
ScanCostTracker additionally counts sessions in sibling directories under deep_discovery/, and (on Windows) any X:\output directory on a different drive, inflating cost and token totals and potentially tripping --max-cost early.
Suggested fix
Have #readSessions use the same guarded, exact-path check as belongsToScan (add isAbsolute import; require !isAbsolute, components[0] !== "..", and the relative(join(workers, components[0]!, "output"), cwd) === "" re-check). With that change the two predicates agree on every input, real worker/artifacts directories are still counted, and the bystander directories are excluded. Happy to open a focused PR if useful.
Summary
ScanCostTracker(sdk/typescript/src/cost.ts) andreadScanLogs(sdk/typescript/src/scan-logs.ts) both decide "does this Codex session belong to this scan?" using the deep-worker output directory layout but they use different checks, and the cost one is weaker. As a resultScanCostTrackerattributes token usage from sessions thatreadScanLogsdeliberately excludes. Because cost also gates--max-cost, this can both misreport cost and abort a scan early against its budget.Environment
@openai/codex-security0.1.16(repomain@97d2c83)sdk/typescript/src/cost.ts,sdk/typescript/src/scan-logs.tsDetails
readScanLogs→belongsToScanrequires the session working directory to be exactly<scanDir>/artifacts/deep_discovery/workers/<id>/output:ScanCostTracker.#readSessionsonly checks that the relative path splits into two components whose second is"output", dropping the!isAbsolute,components[0] !== "..", and exact-path guards:Two working directories that
belongsToScanrejects but#readSessionsaccepts:workers/(any OS):<scanDir>/artifacts/deep_discovery/output→
relative(workers, cwd)=../output→["..","output"]→ length 2,[1] === "output".belongsToScanrejects it viacomponents[0] !== "..".D:\outputwhile the scan directory is onC:→
relative()returns the absoluteD:\output→["D:","output"]→ length 2,[1] === "output".belongsToScanrejects it via!isAbsolute(directory).Reproduction
Running the two predicates' verbatim source expressions on identical inputs (
node,path.win32):End-to-end, a session running in
<scanDir>/artifacts/deep_discovery/output(started after the scan) with, say, 1,000,000 input / 1,000,000 output tokens is folded into the scan's reported usage, even though its events are not part of the scan's log view.Expected vs. observed
workers/<id>/outputsessions, theartifactsdir, and their child threads are counted.ScanCostTrackeradditionally counts sessions in sibling directories underdeep_discovery/, and (on Windows) anyX:\outputdirectory on a different drive, inflating cost and token totals and potentially tripping--max-costearly.Suggested fix
Have
#readSessionsuse the same guarded, exact-path check asbelongsToScan(addisAbsoluteimport; require!isAbsolute,components[0] !== "..", and therelative(join(workers, components[0]!, "output"), cwd) === ""re-check). With that change the two predicates agree on every input, real worker/artifacts directories are still counted, and the bystander directories are excluded. Happy to open a focused PR if useful.