openai / openai/codex-security

multiscan cleanup failure masks the scan outcome and skips the repository receipt

Open
#211 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:bulk-scan area:reports bug priority:p1
Dominant language
TypeScript
Stars
10.8k
Forks
801
Avg merge
1d 8h
Merged PRs (30d)
257

Description

Summary

The per-repository worker in runMultiscan awaits checkout removal in a
finally block before it appends the attempt receipt:

} catch (error) {
  if (options.signal?.aborted === true) options.signal.throwIfAborted();
  failure = redactedErrorMessage(error);
} finally {
  await rm(checkout, { recursive: true, force: true });
}
const status = failure === undefined ? "completed" : "failed";
await appendReceipt(...);

If rm rejects, its filesystem error replaces the outcome already captured by
the worker and execution never reaches appendReceipt. force: true ignores a
missing path, but it does not suppress EACCES, EPERM, or EBUSY.

This is still present on main at
a8fc00984b07f10f5b607a9521c2f05aa57c5107:

https://github.com/openai/codex-security/blob/a8fc00984b07f10f5b607a9521c2f05aa57c5107/sdk/typescript/src/multiscan.ts#L192-L199

Environment
  • Windows
  • Node.js 24.18.0
  • pnpm 11.9.0
  • Bun 1.3.13
Reproduction

No API request is required. The reproduction creates a one-commit local Git
repository and a one-row multiscan inventory. The injected security client
denies delete access to its checkout with icacls, then throws
ORIGINAL_SCAN_FAILURE.

The complete deterministic harness is available as
repro-multiscan-cleanup.ts in the accompanying review evidence. Its relevant
injection is:

createSecurity: () => ({
  async run(repository: string) {
    const acl = spawnSync(
      "icacls",
      [repository, "/deny", `${account}:(D,DC)`],
      { encoding: "utf8", windowsHide: true },
    );
    if (acl.status !== 0) throw new Error(`icacls failed: ${acl.stderr}`);
    throw new Error("ORIGINAL_SCAN_FAILURE");
  },
  async close() {},
}),

Observed result:

{
  "surfaced": "Error: EACCES: permission denied, rm '<checkout>'",
  "preservedOriginalFailure": false,
  "receiptWritten": false
}

The harness restores the ACL and removes its temporary fixture in a finally
block. It exits successfully only when the cleanup error surfaces, the original
failure is absent, and no receipt contains that failure.

Expected behavior

Checkout cleanup should not replace the scan outcome. Every attempted
repository should receive a durable receipt before a secondary cleanup failure
can terminate or warn.

Actual behavior

The caller receives the checkout rm error, the original scan failure is lost,
and results.jsonl contains no receipt for the attempt. The same ordering also
discards a successful scan result if cleanup fails.

Impact
  • The real scan failure is unavailable to the caller.
  • A successful result can be changed into a campaign failure.
  • The durable campaign ledger no longer records every attempt.
  • Resume and operational diagnosis cannot distinguish an unattempted repository
    from one whose cleanup failed after scanning.
Duplication check
  • #38 and #127 cover the analogous single-scan cleanup path in api.ts; the
    fix now on main does not change multiscan.ts.
  • #98 and #196 change multiscan lock publication and recovery, not the worker
    checkout finally or receipt ordering.
  • #30 concerns resume accepting existing corrupted artifacts, not a missing
    receipt caused by checkout cleanup.

Searches across open, closed, and merged issue/PR states did not find the same
multiscan behavior.

Suggested direction

Capture the attempt outcome first and treat checkout removal as best-effort
cleanup. Persist the receipt before a cleanup error can terminate the worker,
and report cleanup failure as secondary information. Regression coverage should
include both:

  1. scan failure plus checkout cleanup failure;
  2. scan success plus checkout cleanup failure.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read sdk/typescript/src/multiscan.ts around the runMultiscan worker and its checkout cleanup and receipt ordering. Run the repro-multiscan-cleanup.ts harness, then add regression coverage for both scan failure and scan success when cleanup fails. Done means the scan outcome is preserved, every attempt gets a receipt, and cleanup errors remain secondary.

Written by the indexing model from the issue text.

Assessment

Tech stack
nodejs, typescript
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.