Repository navigation
fix: 77 validated findings from the 2026-10-13 bug hunt - #322
Merged
Merged
Conversation
…bodies
argv0 returned a leading '(' word or a whole $( ) / backtick substitution verbatim,
leaking assignment values into the event stream. Words carrying shell
metacharacters now yield an empty program name. Adds the red-hunt tests for this area.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sion and plan titles Byte slicing at fixed offsets split multibyte characters and sent U+FFFD to the model. Cuts now use cutUTF8 and advance the tail to the next rune start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cache breakpoints A resumed run inserted a fresh memory block without removing the one persisted by the previous run, so copies and cache breakpoints accumulated past Anthropic's limit of 4. Persisted memory blocks are now dropped before the current one is injected, and only the last cache-marked later system block keeps its breakpoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… user turn on Anthropic System messages hoisted into the top-level system field left the superseded draft as the final message, an assistant prefill. Trailing system messages after the last assistant turn now go out as one trailing user message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The metacharacter rejection blanked ordinary commands such as 'ls;rm x' or 'cat<f'. The name now ends at the first unquoted operator; a prefix opening a subshell, group or substitution still yields an empty name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erator 'K=v;curl x' reported a later argument as the program name. The name is now unknown when the assignment word contains an unquoted operator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rompt A pending clarify waited on a channel closed only when the turn returned, and the approver was never cancelled, so cancelling the turn left it blocked for minutes. The run context now cancels the approver and releases clarify. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A case-variant spelling of ~/.odek/media skipped the per-chat scope check on case-insensitive filesystems. Containment is now compared case-folded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ResumeSession reuses the canonical tg-<chat> ledger key but kept the replaced conversation's reads; it now forgets them. Includes the case-fold helper test and docs for the telegram fixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
/new locked the chat mutex inline, stalling every chat behind a running turn. Resets now cancel the turn and run on a goroutine; later turns wait on a per-chat reset gate so they land in the new session. /resume also cancels the turn, drops background jobs and forgets the read ledger after the turn unwinds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cancel branch left the Approve/Deny keyboard live; it now edits the prompt as the timeout branch does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folding everywhere made media/CHAT<id> count as the chat's own subdirectory on Linux. The fold decision is now a platform variable; helper tests cover both modes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e values The run tail recorded full tool output per frame (count-bounded only), pinning hundreds of MiB per run. Frames are now clipped (8 KiB head+tail per value) and the tail is capped at 4 MiB, dropping oldest events first. All RED tests for the serve area live in the shared redhunt file added here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e throttled lookup path An unknown session_id was accepted with 202 and a full agent was built before the turn failed. It now returns 404 via rejectSessionLookup before any build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ation Large values overflowed int64 nanoseconds and wrapped to a sub-second timeout. The integer seconds are compared against the 600s cap first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Validation failures after PromptsStarted (unknown @-session, attachments, expanded-prompt cap, ...) returned without bumping prompts_failed. The failure is now counted once in the turn's deferred outcome block for any non-completed started prompt, replacing the two ad hoc increments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is rejected A 503/500 after store.Create left an orphan 'Uploaded files' session whose id and token were never returned. The new session is now removed on every non-success path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pong came from the connect-time snapshot and kept the original model after a per-prompt switch, so the health popover reverted every 20s. It now reads the lock-protected connection model. The existing pong test pinned the stale value and now accepts the connect-time or switched model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The preview returned three zero times for a cron like 0 0 30 2 *, while saving the same schedule is refused. It now returns 400 as Job.Validate does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
requireLocalHost compared the raw [::1] to ::1 when SplitHostPort failed and returned 403. Brackets are now stripped in that case, matching the origin check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…refusal Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eview tests Trimming used utf8.ValidString over the whole half, so a single invalid byte dropped most of the head or tail. Only the partial rune at each cut is trimmed now. Adds tests for multibyte and invalid-byte clipping, nested and unmarshalable values, and the never-matching cron preview. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sub-agent's delegate_tasks envelope carried only the grandchild's own declared max_risk/profile, so a capped child could delegate past its cap. The envelope now stamps parent_max_risk and the child clamps to it. (Shared RED test file also holds the audit bare-IP test.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ResourcesIn only knew URLs, paths and name.tld tokens, so nc/ssh/curl to a bare IP after untrusted ingest was never flagged. IPv4 and bracketed IPv6 literals are now resources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
net/http compares host names only, so the token_env token followed an https-to-http downgrade and cross-port redirects. The redirect guard now removes Authorization (with Mcp-Session-Id) unless scheme, host and port match the configured URL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(gh_, issues) and (gh, _issues) both registered gh___issues, panicking the registry or shadowing a tool. Ambiguous joins now always carry the hash suffix and loadMCPTools errors on any remaining duplicate registered name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wn and missing caps Allowlist entries won over class denials, so a grandchild picking an allowlisting profile escaped the cap; they are now dropped when the cap denies them. A fully locked-down sub-agent stamps cap "none" (deny all), a depth>=2 task file without parent_max_risk is refused, and tests drive subagentCmd itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
https://h and https://h:443 (http/:80) are the same origin, so the token is no longer dropped on a same-origin redirect that spells the default port. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…xed names The duplicate check is a small helper with its own test; docs note that allow-list entries for ambiguous names need the hash suffix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Classifying each nested find -exec payload in full was quadratic in chain depth. The walk now descends to the innermost action commands, examines each once, and fails closed (unknown) past 64 of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rted as hidden Flagging every control byte as a hidden character rejected ordinary terminal output and MCP schema text. NormalizeForScan still removes them so a control byte inside a keyword cannot hide a phrase; only invisible format characters are reported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ar/jar/pax/dpkg-deb extraction, hg/svn updates, submodule/sparse-checkout/ worktree verbs, ed/ex, and directory-merging cp/mv/rsync (globs, trailing slash, recursive) now void the licence of later scripts in the command. Read-only variants keep it: stash list/show, merge --abort, clean -n, a new branch at HEAD, reset --soft/--mixed, restore --staged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-Bm/-um clusters, versioned interpreter names (python3.11) and a package's __init__.py/__main__.py (and parent packages) are gated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
COOKIE, WEBHOOK and AUTH_SOCK names are secret-shaped, and a nameref target built from an expansion is treated as secret (fail closed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…olds regional indicators 0x1C-0x1F and CSI sequences read as a space or as nothing, so the scan matches both readings; regional-indicator letters fold to ASCII. Adds guard tests (coverage 84.9%). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… package-manager shell settings, ext:: remotes xargs/parallel options are read with the option grammar (-0a, -ta, bare parallel -a, shell -c with no payload); with fully dynamic argv only a read-only allowlist stays safe. yarn/pnpm/bun/nvm/fnm/pyenv/rbenv runners and 'bundle e' classify the command they run. More start-time files are persistence (IPython profiles, *rc.d, .pdbrc, .irbrc, Jupyter config, Emacs init, vim/vimrc, .vscode/tasks.json). npm/yarn/pnpm config set of script-shell/node-options is persistence and the flags on a run are code_execution. git ext::/fd:: remotes are code_execution. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Options after -m NAME are the module's argv, not interpreter options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…icence checkout -f -b / -B / switch -C / --discard-changes discard local edits and merge --abort restores files; only a plain new branch at HEAD stays read-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… space and as nothing OSC/DCS/APC/PM/SOS strings, two-byte escapes, U+009B and every C0/C1 control between words are read both ways (OSC payload text stays visible in the space reading). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-manager set keys, dynamic-argv allowlist, yarn scripts url.ext::/url.fd:: config and protocol.*.allow=always are code_execution; parallel's value-taking options no longer hide the command; every word after a package-manager set is checked as a key; sed/sort/uniq/file leave the dynamic-argv allowlist; bare 'yarn WORD' matches npm run/pnpm and yarn's read-only builtins are recognised. Docs updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n JSON-encoded text In plain shell text a double quote or a literal \t / \n inside a password ended redaction early. The command tokenizer and operand scanner now apply JSON string rules only when the command sits inside a JSON string (odd unescaped quote count on its line, tracked incrementally); plain text redacts to the next blank. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ving Lines above the limit are discarded while the rest loads; only a real read error disables persistence, with a one-line warning on stderr. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hild Filtering against the final config also stripped allowlisted unknown-class commands from trusted, uncapped children. Entries are now dropped only when the trust lockdown, task max_risk or parent cap denies their class; operator defaults and the operator profile envelope keep them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A run of ESC P / ESC ] introducers was rescanned from every position, making ScanInjection quadratic; an unterminated string now runs to the end of input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ec end, runner options, dry-run publish, hook files as write targets, AUTH_SOCK
parallel options come from GNU parallel's documented list with exact names
(boolean options no longer swallow the command, unknown options never do);
dynamic argv uses the command's worst-case class (readers safe, local writers
local_write, curl/wget upload) and options/placeholders are not fixed
arguments; find -exec ends only at ; or {} +; nvm/bun options before the
command are skipped; publish --dry-run is not an upload; start-time hook
files are persistence only as write targets; SSH_AUTH_SOCK is not secret.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An option missing from the table was read as a flag, so a value it takes would become the command and hide the real one. Unknown long or short options now classify the whole parallel command as unknown; listed options are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
odek | d97454a | Commit Preview URL Branch Preview URL |
Oct 14 2026, 09:34 AM |
…lt thresholds Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… at a hostname boundary Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ecting JSON context A " inside a single-quoted shell span flipped the tracker into JSON mode, so a later literal \t or \n in a password cut redaction short. Single-quoted spans outside JSON strings are now skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing wins; patch/uniq/split write targets Program-valued parallel options (--compress-program, --decompress-program, --rpl, -S/--sshlogin) run their value (judged as a command, floor code_execution) and path-valued ones (--results, --joblog, --return, --basefile, --tmpdir, --workdir, --transferfile) are write targets; a sensitive or system target stays unknown as before. A later false dry-run setting makes a publish an upload again. The file patch rewrites, patch -o, and the output operand of uniq/split count as start-time hook write targets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cter Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes 77 findings from the 2026-10-13 bug hunt over v2.34.7 (7 high, 44 medium, 26 low). Each finding has a
TestRED_*regression test (redhunt_2026_10_13_*_test.go). The fixes went through per-area Sonnet reviews and two Opus full-branch reviews.High severity
xargs/parallel,curl -Kandfd -xin short-flag clusters,curl --variable %SECRET,declare -naliases of secret variables, and unclassifiedfind -execinner commands.Other areas: danger/guard, redaction, config, loop, memory, Telegram, serve, tools, runtime/MCP and CLI. The commit list has the per-finding details.
Behavior changes
bundle exec,rails,toxandyarn <bin>xargswith commands that aren't read-onlynpm config set script-shellgit pull && ./build.shasks again before running a script that was read earlier._or whose tool starts with_now get a hash suffix (see docs/MCP.md).POST /api/promptwith an unknownsession_idreturns 404.Tests
go build,go vet,gofmtandgolangci-lintare clean.go test -short -racepasses on every touched package.internal/dangerandinternal/guardwere also run withHOME=/home/user.paralleloptions that main denied by accident; they now prompt.Coverage
No touched package dropped.
cmd/odekwent from 77.4% to 77.7%, andinternal/agentfrom 83.6% to 85.1%.🤖 Generated with Claude Code