Skip to content

fix(publish-gate): scanLeak misses credentials after a JSON-escaped newline or tab #12562

Closed

Description

@kyle-sexton

Problem

scanLeak (lib/publish-gate.mjs) misses \b-anchored credential shapes when the scanned file is JSON-encoded and the token follows an escaped newline or tab. The escape leaves the letter n or t directly before the token, so no word boundary exists there. normalize keeps those sequences, so its folded pass misses too.

Measured on main with a fake GitHub-token shape built at runtime:

Input scanLeak
Plain text, token at line start blocks
JSON.stringify, token after a space blocks
JSON.stringify, token at line start {}
JSON.stringify, token after a tab {}

Who is affected

  • ci-workflows pr-review-cli.yml writes the review handoff with JSON.stringify, scans it through scan-upload, and uploads it unencrypted for one day. A token at the start of a line in model text passes that scan.
  • ci-workflows#969 scans the run-claude-cli execution file the same way before sealing it. There the data stays encrypted, but a missed credential raises no rotation notice.
  • upload-lane-transcript in this repository passes raw bytes to the same gate. UNVERIFIED: whether its execution files have this one-line JSON form.

Likelihood is limited by the lanes' scrubbed subprocess environment and read-only tools; a token would have to reach the model through a file it can read.

Acceptance criteria

  • scanLeak blocks the two {} rows above, for every \b-anchored shape in SECRET_PATTERNS, with a test for each.
  • Text that is clean today stays clean. normalize's comment says a tracked-tree test limits which escapes it may fold, so the fix either passes that test or adds a separate decoded pass.
  • The gate's plugin copies stay in sync the way they do today.
  • Once released, the ci-workflows scan-upload result for a JSON file with a line-start token is withheld (it fetches the gate at tip).

Refs: #7676, melodic-software/ci-workflows#969

Activity

  1. github-actions commented on Nov 3, 2026

    @github-actions
    Contributor

    Proposed work class: work-class: scoped
    Basis: automated intake triage; the lanes App applied this class.

    Size: Medium
    Complexity: Medium
    Uncertainty: Known
    Flags: none
    Blocked by: none

    Understood: scanLeak in lib/publish-gate.mjs misses \b-anchored credential shapes in JSON-encoded text when the token follows an escaped newline or tab. The n or t left before the token removes the word boundary, and normalize keeps those escapes. The fix needs to block those two cases for every \b-anchored shape, keep clean text clean, and keep the plugin copies in sync.

    The existing refinement comment already has acceptance criteria. The last item in your body, the ci-workflows scan-upload result, can only be checked after release, outside this repository.

    No likely duplicates found among the open issues. #8295 is related but covers a different gap.

    Automated triage, applied by the lanes App.

  2. added
    status: readyTriaged, unblocked, and fully specified; eligible to pick up.
    work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.
    on Nov 3, 2026
  3. melodic-automation-lanes commented on Nov 3, 2026

    @melodic-automation-lanes
    Contributor

    Not admitted to the issue lane yet: no trusted-human acceptance criteria: a listed human must put the confirmed - [ ] criteria in the body or post them as their own comment, or, on an issue a listed human filed, intake's newest refinement comment must hold them unedited; intake's refinement of this body is due and may supply them.

  4. melodic-automation-lanes commented on Nov 3, 2026

    @melodic-automation-lanes
    Contributor

    Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.

    Intended outcome: scanLeak blocks a credential that follows a JSON-escaped newline, tab or carriage return in JSON-encoded text, for every \b-anchored SECRET_PATTERNS shape, with clean text staying clean and the plugin copies in sync.

    Acceptance criteria

    • In lib/publish-gate.mjs, scanLeak(JSON.stringify(text)) returns a block when a fake GitHub-token shape built at runtime sits at the start of a line (after an escaped newline).
    • scanLeak(JSON.stringify(text)) also returns a block when the token follows an escaped tab.
    • lib/publish-gate.test.mjs has one test per \b-anchored shape in SECRET_PATTERNS (AWS access key, GitHub token, OpenAI key, Slack token, Stripe key, Cloudflare API token, upload token, npm token, Google API key, JSON Web Token, Pulumi access token, and the \b-anchored netrc, assignment and R2 pairs where they apply), each asserting a block for the after-newline and after-tab JSON forms.
    • The existing rows still pass: plain text with the token at line start blocks, and JSON.stringify with the token after a space blocks.
    • Text that is clean today stays clean: the existing publish-gate tests, including the tracked-tree test that limits which escapes normalize may fold, pass unchanged.
    • The fix either changes normalize in a way the tracked-tree test still accepts, or adds a separate decoded pass (for example a JSON-escape-decoded copy checked against BLOCKING only) used by scanLeak.
    • The backslash-n and backslash-t sequences are decoded only for the leak-scan credential check, so no new HIDDEN or assignment warn behavior appears for plain escaped JSON that blocks nothing today.
    • plugins/{review,visualization,work-items,harness-ops,source-control}/lib/publish-gate.mjs are byte-identical to lib/publish-gate.mjs, matching scripts/shared-copies.txt, and the repo's shared-copy sync check passes.
    • The affected plugins' CHANGELOG entries note the fix, following how the earlier publish-gate changes (#​8806, #​8811) were recorded.

    External state: none

    Dependencies

    • The last acceptance criterion in the issue, that the ci-workflows scan-upload result is withheld for a JSON file with a line-start token, only holds once this change is released and ci-workflows fetches the gate at tip. It is a follow-up check outside this repository, not part of the change.

    Related work

    Sources read

    • lib/publish-gate.mjs:34-66: SECRET_PATTERNS, with CREDENTIAL_SHAPES, holds the \b-anchored shapes (AWS, GitHub, OpenAI, Slack, Stripe, Cloudflare, upload token, npm, Google, JWT, Pulumi, netrc, assignment).
    • lib/publish-gate.mjs:124-138: normalize keeps backslash before letters and before / . " ', so an escaped newline or tab leaves n or t before the token; the comment at line 129 cites the tracked-tree test in publish-gate.test.mjs.
    • lib/publish-gate.mjs:278-289: scanLeak runs classify, deeperLeak, then a UTF-8 pass with firstHit(BLOCKING, decoded); no pass decodes \n or \t.
    • scripts/shared-copies.txt:465-469: lib/publish-gate.mjs is copied to five plugins (review, visualization, work-items, harness-ops, source-control).
    • scripts/cross-plugin-source-registry.txt:490: lib/publish-gate.mjs -> plugins/*/lib/publish-gate.mjs.

    Automated refinement by the lanes App. Intake triages the issue again with it.

  5. removed
    needs-triageNot yet classified. Floor until a type and one priority tier are set.
    on Nov 3, 2026
  6. added
    admission-deferredAdmitted but waiting for WIP room; the hourly work-unblock sweep labels it agent-ready.
    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.
    and removed
    admission-deferredAdmitted but waiting for WIP room; the hourly work-unblock sweep labels it agent-ready.
    on Nov 3, 2026
  7. github-actions commented on Nov 3, 2026

    @github-actions
    Contributor
    No description provided.
  8. github-actions commented on Nov 3, 2026

    @github-actions
    Contributor
    No description provided.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.priority: mediumReal value, no hard deadline; normal backlog flow.status: readyTriaged, unblocked, and fully specified; eligible to pick up.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    Fields

    Work status

    agent-ready

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions