Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a147e46
fix(cost): enforce verifiable scan spending limits
mldangelo-oai Aug 15, 2026
95b3782
fix(cost): reject incomplete final session usage
mldangelo-oai Aug 15, 2026
ce460fe
fix(cost): verify delegated worker completion and ownership
mldangelo-oai Aug 15, 2026
23e2682
Merge commit '5d1afcd312933121e36dc892dcb12e8d2e3e1de3' into mdangelo…
mldangelo-oai Aug 16, 2026
55f73bd
fix(cost): require completed root evidence for scan budgets
mldangelo-oai Aug 16, 2026
1f072db
fix(cost): preserve definitive budget overages
mldangelo-oai Aug 16, 2026
830b696
fix(api): retain definitive budget failure classification
mldangelo-oai Aug 16, 2026
f04345e
fix(cost): retain overages discovered during cleanup
mldangelo-oai Aug 16, 2026
b9f37a9
fix(cost): retain highest usage through budget cleanup
mldangelo-oai Aug 16, 2026
9eb6500
fix(cost): retain verifiable per-session accounting
mldangelo-oai Aug 16, 2026
11a702e
fix(cost): respect proven worker ownership
mldangelo-oai Aug 16, 2026
b2fdfa4
fix(cost): exclude unrelated historical accounting evidence
mldangelo-oai Aug 16, 2026
ff1827d
fix(cost): reverify late final accounting
mldangelo-oai Aug 16, 2026
59b2598
fix(cost): retain fresh usage from missing-session reads
mldangelo-oai Aug 16, 2026
4c6c807
fix(cost): preserve unpriced completed usage
mldangelo-oai Aug 16, 2026
b6f45b6
fix(cost): preserve finalization and usage boundaries
mldangelo-oai Aug 16, 2026
392a195
fix(cost): reject unusable owned accounting
mldangelo-oai Aug 16, 2026
4e5e34c
fix(cost): accumulate reset-aware session usage
mldangelo-oai Aug 16, 2026
4443643
Merge main into verifiable scan budget repair
mldangelo-oai Aug 17, 2026
7eaa2f1
fix(cost): preserve final accounting and worker shutdown
mldangelo-oai Aug 17, 2026
8e21385
Merge main session setup into scan budget repair
mldangelo-oai Aug 17, 2026
3c25018
fix(sdk): exclude internal helpers from public declarations
mldangelo-oai Aug 17, 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
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.20",
"version": "0.1.25",
"description": "Codex Security workflows for security scans, analysis, and investigation.",
"author": {
"name": "OpenAI"
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.
72 changes: 62 additions & 10 deletions sdk/typescript/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ export class CodexSecurity {
let releaseCredentialHome: (() => Promise<void>) | null = null;
let scanFailure = false;
let completionCost: ScanCost | null = null;
let signaledCostUsage: unknown;
let budgetRecovery: {
expectation: ScanExpectation;
pluginRoot: string;
Expand Down Expand Up @@ -692,7 +693,7 @@ export class CodexSecurity {
onCost:
options.onCost === undefined && options.maxCostUsd === undefined
? undefined
: (cost) => {
: (cost, usage) => {
notifyObserver(
"onCost",
options.onCost,
Expand All @@ -701,8 +702,10 @@ export class CodexSecurity {
);
if (
options.maxCostUsd !== undefined &&
cost.estimatedUsd > options.maxCostUsd
cost.estimatedUsd > options.maxCostUsd &&
!costAbortController.signal.aborted
) {
signaledCostUsage = usage;
costAbortController.abort(
new ScanCostLimitExceededError(
options.maxCostUsd,
Expand Down Expand Up @@ -993,11 +996,22 @@ export class CodexSecurity {
}
},
onFinalize: async (usage) => {
const snapshot = await tracker.stop(usage).catch((error: unknown) => {
if (options.maxCostUsd !== undefined) throw error;
reportTrackingError(error);
return { usage, cost: estimateScanCost(model, usage) };
});
const snapshot = await tracker
.stop(usage)
.catch(async (error: unknown) => {
if (options.maxCostUsd !== undefined) {
throwIfAborted(signal, scanDir);
try {
return await tracker.stop(usage);
} catch {
runPostScan = null;
throwIfAborted(signal, scanDir);
throw error;
}
}
reportTrackingError(error);
return { usage, cost: estimateScanCost(model, usage) };
});
throwIfAborted(signal, scanDir);
if (options.maxCostUsd !== undefined && snapshot.cost === null) {
notifyObserver(
Expand Down Expand Up @@ -1201,11 +1215,48 @@ export class CodexSecurity {
// Recorded first: everything below can throw a different error for this same failed
// scan, and cleanup must treat all of those as a failure it is not allowed to mask.
scanFailure = true;
const snapshot = await costTracker?.stop().catch(() => null);
const failure =
const trackedSnapshot = await costTracker?.stop().catch(() => null);
const signaledOverage =
signal.reason instanceof ScanCostLimitExceededError
? signal.reason
: error;
: null;
const trackedCost = trackedSnapshot?.cost;
const snapshot =
signaledOverage !== null &&
(trackedCost === undefined ||
trackedCost === null ||
signaledOverage.cost.estimatedUsd > trackedCost.estimatedUsd)
? {
cost: signaledOverage.cost,
usage: signaledCostUsage ?? {
input_tokens: signaledOverage.cost.inputTokens,
cached_input_tokens: signaledOverage.cost.cachedInputTokens,
cache_write_input_tokens:
signaledOverage.cost.cacheWriteInputTokens,
output_tokens: signaledOverage.cost.outputTokens,
reasoning_output_tokens: 0,
},
}
: trackedSnapshot;
const knownCost = snapshot?.cost;
let failure: unknown = signaledOverage ?? error;
if (
options.maxCostUsd !== undefined &&
knownCost !== undefined &&
knownCost !== null &&
knownCost.estimatedUsd > options.maxCostUsd &&
(signaledOverage !== null ||
(!this.#abortController.signal.aborted &&
options.signal?.aborted !== true)) &&
(signaledOverage === null ||
knownCost.estimatedUsd > signaledOverage.cost.estimatedUsd)
) {
failure = new ScanCostLimitExceededError(
options.maxCostUsd,
knownCost,
scanDir,
);
}
if (
failure instanceof ScanCostLimitExceededError &&
budgetRecovery !== null &&
Expand Down Expand Up @@ -2232,6 +2283,7 @@ interface ScanEventRunOptions {
onObserverError?: (observer: ScanObserverName, error: unknown) => void;
}

/** @internal */
export async function runScanEvents(
options: ScanEventRunOptions,
): Promise<ScanResult> {
Expand Down
Loading
Loading