Repository navigation
fix: 80 validated findings from the 2026-10-12 bug hunt - #317
Merged
Merged
Conversation
… tools do The card classified diff by its path argument only (path_a/path_b take precedence) and http_request/browser by method/action only, so a decoy path or an internal URL stayed off the card and batch trust auto-approved the real prompt. Now diff takes the worst of the paths it reads, http_request/browser navigate include the URL class, and browser click/back/snapshot withhold batch trust. Covers loop-1 and loop-2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A replacement check was installed without bumping the epoch, so a sibling call that ran earlier in the same batch was recorded as its passing evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
shellWords split on whitespace inside $(...), ${...} and backtick assignment
values, so a fragment of the value was reported as the program name. Those
substitutions now count as part of one word; unterminated ones are dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bg_start runs shell commands but was ignored by recordMutation, so a reply denying all actions after a background rm/make got no consistency notice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rowser URLs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quotes and parentheses inside a $( ... ) substitution (and double-quoted substitutions) still split the word, leaking a value fragment as argv0. A substitution is now consumed as a unit with nested quotes, parens and substitutions; an unterminated one drops the rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
snapshot fetches nothing, so it no longer withholds batch trust; click and back stay unclassifiable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A provider STT backend uploads the audio but the card showed only the local path class; it now takes the worse of path class and network_egress. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bufio.Scanner raises its limit to the initial buffer capacity, so a max_response_bytes below 64 KiB was silently not enforced on stdio. The initial buffer is now bounded by the configured cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After readLoop returned (oversized line or server exit) later calls registered a waiter nobody routed and blocked for the full server timeout. readLoop now records a sticky error, call() returns it immediately, and an oversized line closes stdin and kills the server as documented. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uncated Trailing text items were appended after the capped envelope and the whole string was cut from the end, dropping the artifact metadata lines. Trailing text is now folded into the envelope text so the single capping pass shrinks text first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Duplicate names became duplicate registry entries and panicked tool.NewRegistry at agent construction. Discover now fails closed on a repeated name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pattern up to 512 runes was kept verbatim, letting a server place a sentence of instructions outside the untrusted boundary. Patterns with line breaks, control characters or more than three spaces now move to the wrapped docs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A FIFO named AGENTS.md blocked os.ReadFile forever and hung every agent construction in that directory. LoadProjectFile now requires a regular file, opens non-blocking, re-checks the handle and reads at most the size cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mapResult kept only the joined reasoning text and the last signature, so redacted_thinking blocks were lost and multi-block turns replayed text that no longer matched its signature. Blocks are now carried through CallResult into session.Message (persisted, redacted, cloned) with the signing provider, and replayed verbatim only to the same API format; a cross-provider continue drops them. Covers both the redacted-block and the multi-block findings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Save redacted ThinkingBlock text but kept the signature, so a block that held a secret replayed as altered text with the original signature and the provider rejected every continue. The turn's blocks, signature and provider are now dropped when redaction changes the signed text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spaceless or escape-separated prose passed the space-count check. A kept pattern is now at most 128 runes with at most two runs of six or more letters, counting escapes such as x20 and s as separators; structural regexes stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is never acknowledged HandleResponse reported a match for any id still pending and silently dropped a second answer, so the REST bridge acked a deny after the command had been approved. Delivery now removes the pending entry; later answers report not found. Existing HandleResponse tests that re-read the pending map after delivery now hold the channel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the execution lock handlePrompt acquired the per-session lock (creating a lock file) before loading or authenticating the session, so every unknown id left a permanent lock file. The session is now loaded and token-checked first and again under the lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…bels stay valid UTF-8 shorten sliced bytes, so a multi-byte rune straddling the limit left invalid UTF-8 in new session titles and run labels. It now cuts on a rune boundary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-safe Tool names (model-supplied) and the renamable title were embedded raw in headings and metadata lines, so a newline forged document structure. They now pass through SanitizeInline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DELETE/POST /api/sessions/{id}, /api/cancel and the jobs/artifacts/uploads/subagents endpoints answered 404 vs 401 without the per-IP limiter that GET and recovery use, an unthrottled session-id oracle. All rejections now share the lookup limiter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without an index, Store.List loaded each full session and cleared only Messages, so listings leaked every session's memory buffer and decision receipts to instance-token holders. The fallback now builds rows from the same fields as the index path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…files With the workspace at $HOME, @.odek/secrets.env, @.odek/sessions/... or @.ssh/id_rsa expanded straight into the prompt with no approval. FileResolver.Load now refuses any target classified system_write or higher. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Search listed names and sizes under ~/.odek, credential directories and other system_write targets when the workspace is $HOME. Matches now pass the same classifier gate as Load. Adds a long-s alias test that skips until the classifier case-fold fix is merged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…okup budget startServeRun's invalid-token 401 for a known session id was unthrottled. It now goes through the same limiter as the other session-scoped endpoints. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to_approve mcpExecEqual ignored url/token_env for command servers, so a project could redirect an auto-approved stdio server to a remote URL with the operator's token. Every branch now compares command, args, env, url, token_env, limits and roots. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es before the pillar Blockquote and list-prefixed headings escaped imitation stripping, and an unterminated code fence made the rest of the identity opaque and swallowed the appended pillar. Quote markers are now peeled for detection, an unclosed opener no longer shields content, and any open fence is closed before the authoritative pillar is appended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iter A cloned repo could set max_concurrency to any value and lift the delegate limiter past the operator ceiling. Project values are clamped to the global value (default 3) and the fallback limiter is capped at 8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
promote keyed the registry by directory name while the loader looks it up by the skill's frontmatter name, so a skill whose directory differs from its name stayed NeedsReview after a successful promote. Record under the loaded name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
printf -v'a[$(cmd)]' x hid the NAME inside the fused option word; the spaced form was already classified. Fused -vNAME is now read the same way. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…vironment dumps awk/gawk/mawk/nawk ENVIRON and PROCINFO, yq env()/strenv() and readonly -p with no operand disclose the environment like env. jq's lowercase $env is an ordinary variable and stays safe. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ts as broadcast [a-z], \\w, [[:alnum:]] match every process name like '.', and -u root / -U 0 signal every system process; all are system_west. An uncompilable pattern stays system_write (prompt) rather than unknown (deny). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e non-default homes --sign matched as an abbreviation of --sign-key and became system_write. Complete options now win. An explicit --homedir or GNUPGHOME other than ~/.gnupg reads that directory's gpg.conf, which can name a photo viewer, so it is code_execution. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…gawk -E PROMPT_COMMAND, PS0/PS1/PS2/PS4 substitutions and BASH_FUNC_name%% bodies given as assignments run as code in the shell the command starts, so their values are classified as commands. gawk -E/--exec FILE is gated like -f. GNUPGHOME=DIR is checked like --homedir. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…rompts These render blank or invisible but are printable by category. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…E in fuzz targets Helpers are now rhAtLeast/rhSafe. The monotonicity fuzz targets set a real non-root HOME (a real directory, since /home is an automount point on macOS and made every lookup slow) so they pass as root and in CI. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…p additions Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Refuse trust anchors, home credential dirs, rc files, credential-shaped names and system secrets; ordinary files under /opt, /srv, /tmp load again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The batch card and the tools now share one classifier; unmappable hosts fail closed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop lower/UPPER pwd as bare keys (connection-string Pwd stays) and reject path-shaped and Go test-name values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jobSchedule takes the zone as a parameter instead of reloading config per job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restore bare pwd/PWD keys; secrets that start with / or Test are redacted again under every other key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Directory rules apply only at or below the workspace root; .docker, *.key and .env.*.example are narrowed; .envrc, *.env, shell history and keyrings added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Variable and env references, member chains, paths, ARNs, timestamps and lowercase resource names are no longer redacted as bare-key credentials. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… @-reference gate IsSensitiveReadPath now uses the same home-sensitive prefixes as ClassifyPath so the two cannot drift. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Standalone, appended, exported and env -S assignment forms now classify their value as a command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stic processes Escapes like \w, \S, / and a space, and user selectors without a name pattern, now escalate; a literal \. no longer does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Documents such as capital-plan.key or presentation-client.key load; server.key, tls.key, client.key and key directories stay protected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Polling tabs on a deleted or tokenless session no longer spend the shared session-lookup budget. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew source files Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wn layer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eferences Broad parents (.config, .docker, .cargo, .gem, .terraform.d) are narrowed to their credential children; ClassifyPath keeps the whole shared prefixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…variables Values stored into PROMPT_COMMAND/PS*/BASH_FUNC_* by printf -v or a here-string read are classified as commands; other input sources are unknown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rd-segment paths Accept one 20+ char letters+digits run (lowercase or UPPER) incl. quoted JSON/YAML. A value with '/' is a path when any segment is word-only (len>=3, no digit or base64 chars); slash-prefixed base64 like /aB3d... stays redacted. UUIDs and separator-joined groups stay visible. 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 | e64b679 | Commit Preview URL Branch Preview URL |
Oct 12 2026, 12:59 PM |
… check Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nt-independent The git default test compared against the default policy, which depends on whether the checkout running it is an armed repository; it now compares against git push outside any repo and checks a git push denylist entry. The linearity test checks 4x scaling and keeps its absolute bound off -race/-short. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 of 7 tasks
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 80 validated findings from the 2026-10-12 functional + security bug hunt over v2.34.2 (
ce0bbfef): 18 high, 42 medium, 20 low, none rejected.Process per area (danger, loop, memory, telegram, serve, tools, config, runtime, ops): an Opus hunter proved each finding with a failing
TestRED_*test; a Sonnet fixer re-validated it on the unmodified base, then RED → GREEN → coverage → docs, one commit per finding (a few related findings share a commit); a Sonnet adversarial review per area, with a second fix round where it found gaps. The nine branches were cherry-picked together, then two full-branch Opus adversarial reviews (4 + 7 issues) and two Sonnet re-reviews of those fixes ran, each with its findings fixed.High-severity highlights
let 'x[$(rm -rf ~)]=1'and other arithmetic/subscript substitutions auto-allowed; env dumps (jq -n env,ps eww,awk ENVIRON,export -p) classified safe;gpg --photo-viewer/--exec-pathcode execution classified safe.127.0.0.1) in browser/http_request (UTS-46 mapping now insidedanger.ClassifyURL, shared by tools and the batch card); APFS/NTFS case-fold aliases (~/.odek/ſecrets.env,~/.ſsh) bypassing trust-anchor and home-credential protection (danger.FoldPathCase).diffbypathonly while the tool readpath_a/path_b, hiding a/etc/master.passwdread behind batch trust../odek.jsoncould addurl/token_envto an operator auto-approved MCP server and send the operator token to an attacker URL; sub-agent artifacts readable across sessions; sub-agents of a sandboxed parent ran unsandboxed; project config${VAR}expansion of host env.session_searchcould read other chats' and the operator's sessions.@-references loaded~/.odek/secrets.env, SSH keys and.envinto the prompt with no approval (now refused bydanger.IsSensitiveReadPath; search filtered the same way)./plan_resumecontent entered chat sessions without taint;MEDIA:path parsing andFallbackTransportbody replay.Medium/low fixes span MCP stdio response caps and dead-client fail-fast, Anthropic thinking-block persistence (redaction-safe, provider-scoped), redaction gaps (bare
token/secret/pass/pwdkeys with a credential-shape check, netrc/XML/arrow forms, non-Bearer Authorization, Dockerauth) without redacting ordinary code, untrusted-tag look-alike neutralising (linear), audit divergence and terminal escaping, REST approval double-answer, rate limits, run-lock files and the stale-inode race, MarkdownV2 delivery failures, schedule timezone, data races.Per-finding detail:
.claude/bughunt-2026-10-12/(gitignored) —fix-input-*.json,fix-reports/,review-reports/.Coverage (before → after)
Test plan
go build ./...,go vet ./...,gofmt -l,go mod tidy -diffcleango test ./...(all packages)go test -raceon every changed packageHOME=/home/user go test -race ./internal/danger/FuzzSeparatorThenWipe,FuzzPipeIntoShell,FuzzHarmlessPrefixKeepsRank,FuzzAnalyzeBounded30s eachgolang.org/x/textviagolang.org/x/net/idna)Known residuals (documented, not fixed)
v='a[$(cmd)]'; let "$v"(substitution held in a variable);git $xwith a truly dynamic verb; standalonePROMPT_COMMAND+=classifiesunknown(deny) rather than destructive.@-reference gate.token=stay visible; legacy sessions with a thinking signature but no recorded provider replay as before.🤖 Generated with Claude Code