Development record
scanner.RedactRefusal does not seal a token glued behind a word character. The scanner's bundled secret patterns anchor their start on a leading word boundary, and '_' and every letter or digit are word characters, so a token that sits right after one has no boundary and is never matched: the implement loop's laneFileGap (internal/core/implement/loop/receipt.go) returns the receipt's path as notes_ followed by a GitHub PAT and .md, does not exist, with the token raw, and memory's ValidateDistilledPage (internal/core/memory/schema.go) returns unknown key(s) [notes_ followed by the token] raw, while notes- and sub/ spellings seal. A letter on both sides (x, token, y) is missed the same way. Every RedactRefusal caller shares the gap (scribe, release, ideate, lifeboat, reading, intent, memory, implement loop), so a host payload's key or path carries a credential to the terminal and the transcript through a refusal. It is the refusal-side twin of the page-filename gap iss-2609290411321963 closed with filenameJudgeTexts. Found by the security review of lane drainEcho3. Fix direction: in the primitive, not the callers; re-find the secret patterns with their leading boundary removed, through the scanner's own linear adjacency machinery, and seal every glued match byte for byte through Redact; fail closed as before. Detector: a refusal naming a key or a path in which a well-formed token sits behind an underscore or a letter carries the token sealed.
scanner.RedactRefusal does not seal a token glued behind a word character. The scanner's bundled secret patterns anchor their start on a leading word boundary, and '' and every letter or digit are word characters, so a token that sits right after one has no boundary and is never matched: the implement loop's laneFileGap (internal/core/implement/loop/receipt.go) returns the receipt's path as notes followed by a GitHub PAT and .md, does not exist, with the token raw, and memory's ValidateDistilledPage (internal/core/memory/schema.go) returns unknown key(s) [notes_ followed by the token] raw, while notes- and sub/ spellings seal. A letter on both sides (x, token, y) is missed the same way. Every RedactRefusal caller shares the gap (scribe, release, ideate, lifeboat, reading, intent, memory, implement loop), so a host payload's key or path carries a credential to the terminal and the transcript through a refusal. It is the refusal-side twin of the page-filename gap iss-2609290411321963 closed with filenameJudgeTexts. Found by the security review of lane drainEcho3. Fix direction: in the primitive, not the callers; re-find the secret patterns with their leading boundary removed, through the scanner's own linear adjacency machinery, and seal every glued match byte for byte through Redact; fail closed as before. Detector: a refusal naming a key or a path in which a well-formed token sits behind an underscore or a letter carries the token sealed.
Grounds
- pursued: a refusal naming a key or a path in which a well-formed token sits behind an underscore or a letter carries the token sealed and the rest of the name readable; a refusal carrying the token, or a linear-cost guard over long underscore-joined runs failing, would show it wrong
Frontmatter
| id | iss-2609290541525428 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | minor |
.abcd/work/issues/resolved/iss-2609290541525428-redactrefusal-does-not-seal-a-token.md
open on GitHub ↗ · commit history ↗
mentions3
- iss-2609290411321963The memory writer's page-filename refusal quotes the token it refuses. judgeFilename (internal/core/memory/redact.go, called from writer.go at the write boundary) refuses a page whose host-chosen type, domain or slug carries a hard_fail secret, and its message names the whole filename raw: a slug of ghp_ and forty characters is refused with the token in the error, so it reaches the terminal and the transcript, the class iss-2609290218032954 and iss-2609290300464268 closed elsewhere. The echo is deliberate and pinned: TestWriteRefusesASecretShapedFilename and the separator-spelling cases in writer_filename_test.go assert the refusal names the page, because the filename is the write's identity and the operator needs to know which page to repair. scanner.RedactRefusal cannot square the two: an underscore is a word character, so a token joined behind topic_auth_ has no word boundary and is not redacted in the joined name, and a token can straddle the type, domain and slug separators. Found by lane drainEcho3's sweep for iss-2609290300464268. Fix direction: seal, byte for byte in the joined name, every span the hard_fail findings over filenameJudgeTexts matched, and keep the rest of the name. That keeps both requirements, the page named and the token not echoed, so it needs no ruling; describing the whole name would give up the identity the test pins. Detector: a page whose slug, or a spelling across the separators, is a well-formed token is refused, nothing is written, and the refusal names the page with the token sealed.
- iss-2609290551363398The canonical scanner's ScanText does not find a secret token glued behind a letter, a digit or an underscore, so the launch scan, the memory writer's page-name bar (judgeFilename) and every store-before-commit redactor share the blind spot. Every bundled secret pattern opens on a leading word boundary and a word character before the token removes it: judgeFilename accepts topic_auth_x followed by a GitHub PAT and y, .md, because no underscore suffix of filenameJudgeTexts starts at the token, and the history, memory and capture redactors leave the same spelling raw in what they store. scanAllPatterns' adjacency probes recover a token that abuts a token already found, never one that abuts ordinary text; its own comment names the limitation as accepted. Found by the security review of lane drainEcho3 (INFO), beside iss-2609290541525428. Measured before deciding: the glued sweep that closes the refusal gap, run over all 4605 tracked text files of the repository, adds 0 findings to ScanText's 13 hard_fail secret findings, the launch dry-run's finding count stays 1, and the sweep costs 7 to 10 per cent of the bounded scan's time. Fix direction: run the glued sweep inside scanText, so every consumer inherits it. Detector: ScanText reports a token glued behind a letter, a digit or an underscore at its own byte span, and the memory writer refuses a page whose slug carries one.
- iss-2609290743362554The scanner's glued-token sweep (glued.go, iss-2609290541525428) runs on the raw line only: decodedLineFindings and viewFindings (percent.go) scan the percent-decoded and JSON-unescaped views with the bounded patterns alone, whose leading word boundary cannot hold behind a word byte. A glued token whose own bytes are escaped (a percent-encoded letter of its prefix, or a JSON unicode escape of its first byte, behind an underscore or a letter) therefore survives ScanText, Redact and RedactRefusal raw. Reach is narrow, since no encoder escapes an ASCII letter of a token, but the decoded layers exist to close spelling variants. A second half: ScanText gives no signal when the sweep cannot be built in full (a configured pattern whose leading boundary carries a quantifier); only RedactRefusal reads the sweep's completeness, so every other consumer runs a silently narrower sweep, and Unavailable does not say so.