mind-self-run: redact URL userinfo from Self result text #50

Merged
eureka merged 6 commits from eureka-body/self-run-redact-url-userinfo into main 2026-11-11 14:21:28 +00:00
Owner

Follow-up eureka-body:follow_up:chat-s5922-5922. A Soul transcript printed a forge token embedded in a git remote URL; nothing redacted URL credentials in what the actuators carry out.

State changes

  • One function, redact_url_userinfo (crates/mind-body/src/self_run.rs). Call sites, one per place a CLI-produced string enters the journal or her mind: result_text (Self result: journal line and admitted Reply, redacted before the 3800-byte chunking) and persona::structured_output (Persona and Sleep results, redacted string by string so JSON structure is never inside a candidate).

Final rule

  • Every scheme://, and every protocol-relative // at a word start (after start, ASCII whitespace, a quote, bracket, = or ,; a//b@c is not one), opens an authority, read as git, curl and RFC 3986 read it. It ends at the first / ? #, backslash, quote, backtick, parenthesis, bracket, angle bracket or ASCII whitespace.
  • Within the authority the userinfo is everything before the LAST @ (a raw @ in a password still redacts); non-ASCII characters, NBSP and U+2028 included, stay inside it. Output: scheme://<redacted>@host.
  • With a colon: always redacted. Without: redacted on http, https, ftp, ftps, protocol-relative and an unparseable scheme; kept on any other scheme (ssh://git@host is a username). Percent-encoded forms are redacted like any other.
  • A @ after the authority is path and is left byte-identical: github.com/@user, medium.com/@name, registry.npmjs.org/@scope/pkg, h/pkg@1.2.3, a//b@c, ?mail=a@b.c. Compact JSON of two URLs redacts only the credentialed one.
  • Scheme-less: only word:secret@host.tld/ at the start of an ASCII-whitespace-delimited word without //.
  • Scanning is char-boundary safe.

NOT covered (rotation of any exposed token stays required)

  • A password holding raw /, ?, #, a quote or ASCII whitespace: no URL parser reads it as userinfo, so the text after the delimiter is path/query and is not redacted.
  • scp-style user:secret@host:path and any scheme-less form beyond the narrow shape; tokens in query strings or headers; bare tokens outside URLs.
  • claude stderr (Stdio::inherit, self_run.rs ~1344) is NOT redacted.
  • The kept transcript .jsonl copies (move_out_transcripts ~1225) and Claude Code's own transcripts are NOT redacted and still hold tool output verbatim.
  • Object keys of structured output; other claude-produced strings not routed through the two call sites.

Verification

  • Tests in self_run/tests/reply.rs (canary strings only): form table; canary absent from journal and admitted Reply; split by the reply limit (asserts both pieces exist); raw @, percent-encoded, NBSP, U+2028 and non-ASCII inside userinfo redacted; the pinned limits above; commonplace URLs byte-identical; compact JSON; protocol-relative; each guard of the scheme-less shape; multibyte inputs plus every substring of a multibyte seed never panic; Persona/Sleep structured output.
  • Hand mutation, each fails a test: identity function; old char-slicing scan; no redaction in structured_output; host-dot rule removed; host-character rule removed; username-character rule removed; non-empty-secret guard removed; / dropped from authority ends; first @ instead of last; Unicode whitespace as break; word-start guard of // removed; ssh/git added to the bare-token schemes; scheme-less pass removed.
  • unshare -r cargo test -p mind-body --no-fail-fast: lib 902 passed, 0 failed, 1 ignored. 5 environmental failures: control.rs x3 (non-root cannot be simulated under unshare -r), persona_bin x2 (the mind could not be asked: unavailable, no mind daemon). Clippy: no new lints vs base.
  • Not merged, deployed or installed.

🤖 Generated with Claude Code

Follow-up eureka-body:follow_up:chat-s5922-5922. A Soul transcript printed a forge token embedded in a git remote URL; nothing redacted URL credentials in what the actuators carry out. ## State changes - One function, `redact_url_userinfo` (`crates/mind-body/src/self_run.rs`). Call sites, one per place a CLI-produced string enters the journal or her mind: `result_text` (Self result: journal line and admitted Reply, redacted before the 3800-byte chunking) and `persona::structured_output` (Persona and Sleep results, redacted string by string so JSON structure is never inside a candidate). ## Final rule - Every `scheme://`, and every protocol-relative `//` at a word start (after start, ASCII whitespace, a quote, bracket, `=` or `,`; `a//b@c` is not one), opens an authority, read as git, curl and RFC 3986 read it. It ends at the first `/ ? #`, backslash, quote, backtick, parenthesis, bracket, angle bracket or ASCII whitespace. - Within the authority the userinfo is everything before the LAST `@` (a raw `@` in a password still redacts); non-ASCII characters, NBSP and U+2028 included, stay inside it. Output: `scheme://<redacted>@host`. - With a colon: always redacted. Without: redacted on http, https, ftp, ftps, protocol-relative and an unparseable scheme; kept on any other scheme (`ssh://git@host` is a username). Percent-encoded forms are redacted like any other. - A `@` after the authority is path and is left byte-identical: `github.com/@user`, `medium.com/@name`, `registry.npmjs.org/@scope/pkg`, `h/pkg@1.2.3`, `a//b@c`, `?mail=a@b.c`. Compact JSON of two URLs redacts only the credentialed one. - Scheme-less: only `word:secret@host.tld/` at the start of an ASCII-whitespace-delimited word without `//`. - Scanning is char-boundary safe. ## NOT covered (rotation of any exposed token stays required) - A password holding raw `/`, `?`, `#`, a quote or ASCII whitespace: no URL parser reads it as userinfo, so the text after the delimiter is path/query and is not redacted. - scp-style `user:secret@host:path` and any scheme-less form beyond the narrow shape; tokens in query strings or headers; bare tokens outside URLs. - claude stderr (`Stdio::inherit`, self_run.rs ~1344) is NOT redacted. - The kept transcript .jsonl copies (`move_out_transcripts` ~1225) and Claude Code's own transcripts are NOT redacted and still hold tool output verbatim. - Object keys of structured output; other claude-produced strings not routed through the two call sites. ## Verification - Tests in `self_run/tests/reply.rs` (canary strings only): form table; canary absent from journal and admitted Reply; split by the reply limit (asserts both pieces exist); raw `@`, percent-encoded, NBSP, U+2028 and non-ASCII inside userinfo redacted; the pinned limits above; commonplace URLs byte-identical; compact JSON; protocol-relative; each guard of the scheme-less shape; multibyte inputs plus every substring of a multibyte seed never panic; Persona/Sleep structured output. - Hand mutation, each fails a test: identity function; old char-slicing scan; no redaction in `structured_output`; host-dot rule removed; host-character rule removed; username-character rule removed; non-empty-secret guard removed; `/` dropped from authority ends; first `@` instead of last; Unicode whitespace as break; word-start guard of `//` removed; ssh/git added to the bare-token schemes; scheme-less pass removed. - `unshare -r cargo test -p mind-body --no-fail-fast`: lib 902 passed, 0 failed, 1 ignored. 5 environmental failures: control.rs x3 (non-root cannot be simulated under `unshare -r`), persona_bin x2 (`the mind could not be asked: unavailable`, no mind daemon). Clippy: no new lints vs base. - Not merged, deployed or installed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
One function, redact_url_userinfo, owned by result_text: the journal line and the admitted
PipelineReply both read the result through it, and redaction runs before the reply is chunked.
scheme://user:tok@h and http(s)/ftp(s)://tok@h become scheme://<redacted>@h; ssh://git@h stays.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Candidate run is everything to the next whitespace, userinfo up to its last @; protocol-relative
// and the narrow word:secret@host.tld/ shape are covered; scanning no longer slices inside a
multibyte character. persona::structured_output redacts each string through the same function.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
NBSP, U+2028 and other Unicode whitespace stay inside userinfo, so a secret holding them is redacted whole.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Userinfo is everything before the last @ of the authority, which ends at / ? # backslash, quotes,
brackets, angle brackets or ASCII whitespace; a path @ (github.com/@user, pkg@1.2.3, a//b@c) is left
alone, non-ASCII stays inside userinfo. Protocol-relative // opens an authority only at a word start.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
eureka merged commit 6d6aea7059 into main 2026-11-11 14:21:28 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/Huginn!50
No description provided.