Skip to content

Iterion codebase review — 2026-05-17

Author: deep review pass on main at commit baa68ab8. Scope: full iterion repo (Go runtime/DSL/backend/store/dispatcher/sandbox/cloud/auth/secrets/git) + TypeScript editor (What's Next feature + deferred items from frontend-2026-05-17.md). Companion: the claw-code-go review at iterion-touching-audit-2026-05-17.md (in the sibling claw-code-go worktree at the time).

Working branch: bugfix/codebase-audit-deep-2026-05-17 (worktree at .works/codebase-audit-deep-2026-05-17/). Severity rubric matches frontend-2026-05-17.md §1 (P0 = exploitable / data loss / crash; P1 = real UX or defence-in-depth; P2 = meaningful debt; P3 = polish).


TL;DR

Total: 90 findings across iterion + 11 in claw-code-go = 101 total. This pass shipped:

  • All 6 P0 (auth timing oracle, HMAC fallback key, sandbox escape via devcontainer, docker mount/build, worktree resume finalize).
  • 22 P1 (out of 43) — runtime resume + scheduling, DSL lexer/expr/parser tightening, server safePath race + slow-WS, OIDC user-agent binding, k8s sandbox isolation, What's Next banners, claw-code-go retry+registry.
  • 2 P2 promoted because they pair with shipped P1s (F-DSL-3 BOM, F-C2 RevealInFinder).

The remaining 21 P1 + 30 P2 + 13 P3 are documented in §7 with shipped/deferred status. Most deferred P1s either need a schema migration (F-CL-2, F-ST-4 tenant filter), substantial actor/synchronisation redesign (F-CD-1, F-CD-4), or design decisions the audit pass shouldn't unilaterally make (F-CD-7 dispatcher auth config story).

Highest-impact items:

  • Sandbox escape via untrusted .devcontainer/devcontainer.json${localEnv:NAME} had no allowlist (any host env exfilrable into mounts), docker --mount passed spec strings verbatim (arbitrary bind mounts incl. /var/run/docker.sock), and docker buildx Context/Dockerfile escaped the workspace. Shipped: F-SB-1, F-SB-2, F-SB-3, F-SB-4, F-SB-5, F-SB-6, F-SB-7.
  • HMAC bypass on attachments — when crypto/rand ever failed once, a deterministic fallback signing key was persisted forever; any third party that learned the store path could mint valid presigned URLs. Shipped: F-ST-1, F-ST-2, F-ST-3.
  • Auth timing oracle — locked-account login skipped the dummy-hash compensation, leaking lockout state via response timing. Shipped: F-CL-1.
  • OIDC login-CSRF / session fixation — state alone did not bind the flow to the user agent (RFC 9700 §4.7.1). Shipped: F-CL-3.
  • Worktree commits orphaned on CLI resumeiterion resume never invoked finalizeWorktree; commits landed in reflog-only reachability. Shipped: F-RT-1, F-RT-5.

Reports cover both iterion (this file) and claw-code-go (docs/reviews/iterion-touching-audit-2026-05-17.md in .works/claw-code-go-iterion-audit-2026-05-17/).

AxisP0P1P2P3P0+P1 shipped
Security / defence-in-depth6119117 / 17
Data integrity / store15426 / 6
Concurrency / lifecycle09629 / 9
Sandbox / isolation34207 / 7
Cloud / auth / queue04414 / 4
Frontend / UX / What's Next0842selectively
Cross-layer / observability1235per finding

Section 7 lists every finding with shipped or deferred status. Section 8 lists the deferred items with rationale.


1. Methodology

Five-phase pass (planning artefact: /home/jo/.claude/plans/review-go-and-typescript-velvet-mist.md):

  1. Phase 1 — codebase mapping. Four parallel Explore agents map runtime/DSL/backend/store; dispatcher/sandbox/cloud/auth/secrets; TypeScript What's Next + deferred items from prior review; claw-code-go iterion-touching surface.
  2. Phase 2 — review. Nine parallel code-reviewer agents — 8 against iterion + 1 against claw-code-go — each fed a focused file list, the severity rubric, recent-fix context (so already-fixed items aren't re-flagged), and explicit "read-only" guard.
  3. Phase 3 — consolidation. This document; deduplication; cross-agent verification.
  4. Phase 4 — fixes. Atomic commits per finding on bugfix/codebase-audit-deep-2026-05-17. P0 first, then P1, then bug-shaped P2.
  5. Phase 5 — verification. task lint, task test, task test:race, task test:e2e, pnpm tsc --noEmit, pnpm test, pnpm build.

Out of scope (deliberate): items already covered by frontend-2026-05-17.md and shipped under fix/frontend-review-2026-05 were not re-flagged unless current code regressed. Bundle code-split (F-P3) remains deferred per prior decision.


2. Production-readiness highlights

2.1 Security

  • Sandbox surface is the weakest layer — see §3.1 (3 P0 + 4 P1 in pkg/sandbox/). The threat model in CLAUDE.md flags devcontainer.json as untrusted; the code did not match that posture.
  • Attachment HMAC has a forge-by-fallback weakness — F-ST-1.
  • Auth lockout leaks via timing — F-CL-1.
  • OIDC has no CSRF binding between /start and /callback — F-CL-3.
  • Native dispatcher REST/WS surface has no auth — F-CD-7.
  • BYOK Mongo store reads bypass tenant filter — F-CL-2.

No XSS / SQLi / eval / markdown-injection found in the Go server (corroborates the prior frontend pass).

2.2 Data integrity

  • Native dispatcher store has a non-atomic events-log / index / per-issue-file commit — F-CD-2, F-CD-3.
  • resume.go aliases checkpoint output maps (sibling-isolation regression vs bb34f844 for fan-out) — F-RT-4.
  • Bundle cache has a 64-bit slot key + sentinel race — F-ST-6.
  • Hash for iterion resume change-detection ignores bundle prompts — F-DSL-12.

2.3 Concurrency / lifecycle

  • Dispatcher Stop releases the EngineRunner while workers are still inside Runner.Dispatch — F-CD-1.
  • refreshRunningStates reaps slot before worker exits, allowing same-issue re-dispatch — F-CD-4.
  • Promptcache lock-release-during-IO race — F-CC-9 (claw-code-go).
  • Slow WS client pins broker goroutine ~10s — F-R3 (deferred from prior review, now shipped).

2.4 Sandbox

  • See §3.1. Most severe class. Three P0s + the noop-fallback silent isolation loss.

2.5 Observability

  • Round-robin router emits no edge_selected event — F-RT-3.
  • Structured-output retry path bypasses hooks accounting — F-BE-1.
  • Banner-stuck-on-fail in What's Next chat — F-TS-1.

3. Findings index

Each finding is fully described in the per-area sub-section below, then summarised with status in §7.

3.1 Sandbox (most severe class)

IDSevFileTitle
F-SB-1P0sandbox/devcontainer/expand.go:33${localEnv:NAME} has no allowlist; auto-mode reads any host env
F-SB-2P0sandbox/docker/driver.go:179spec.Mounts passed to --mount verbatim — arbitrary host bind
F-SB-3P0sandbox/docker/build.go:85buildx Context/Dockerfile not constrained to workspace
F-SB-4P1sandbox/docker/driver.go:176spec.User passed to --user with no validation
F-SB-5P1sandbox/kubernetes/strings.go:40k8s env-var values not validated for control chars
F-SB-6P1sandbox/kubernetes/driver.go:225Silent fall-through when ITERION_POD_IP is unset
F-SB-7P1sandbox/NO_PROXY=.svc,.cluster.local allows in-cluster bypass
F-SB-8P2sandbox/netproxy/matcher.go:127canonicalHost skips trailing-dot / IDN normalization
F-SB-9P2sandbox/factory.go:182"no usable driver" falls through to noop silently

3.2 Store / persistence / bundle

IDSevFileTitle
F-ST-1P0store/attachments.go:330Deterministic fallback signing key persisted forever
F-ST-2P1store/attachments.go:281Ambiguous HMAC encoding accepts \n in runID/name
F-ST-3P1store/attachments.go:296VerifyAttachmentSignature does not sanitize runID/name
F-ST-4P1store/mongo/tenant.go:16Missing tenant in ctx silently degrades to cross-tenant scope
F-ST-5P1store/blob/s3.go:237DeleteObjects per-object errors ignored
F-ST-6P1bundle/loader.go:11664-bit cache slot key + sentinel race
F-ST-7P2store/mongo/events.go:98allocSeq has no tenant scope
F-ST-8P2bundle/tar.go:88File open after symlink-walk is TOCTOU on shared dest
F-ST-9P2store/lock_windows.go:60Stale lockfile detection disabled on Windows
F-ST-10P2store/blob/key.go:31attachmentKey skips full SanitizePathComponent
F-ST-11P3store/mongo/attachments.go:25WriteAttachment reads body into memory unbounded

3.3 Runtime engine

IDSevFileTitle
F-RT-1P0runtime/engine.go:557, resume.go:32finalizeWorktree never runs on CLI resume
F-RT-2P1runtime/resume.go:128Nil-map writes if checkpoint deserialises with missing maps
F-RT-3P1runtime/routing.go:21execRoundRobin never emits EventEdgeSelected
F-RT-4P1runtime/resume.go:209Resume aliases checkpoint output map (vs deep-copy)
F-RT-5P1runtime/worktree.go:735RecoverFinalize skips failed_resumable — commit recovery gap
F-RT-6P1runtime/fan_out.go:175Best-effort fallback to topology convergence wrong on all-done
F-RT-7P2runtime/bundle.go:50mirrorBundleSkills runs only on Run(); resume re-uses stale skills
F-RT-8P2runtime/recovery/recovery.go:223Delay-formula overflow guard reads after overflow occurred
F-RT-9P3runtime/worktree.go:457Squash "nothing to commit" reads wrong HEAD (cosmetic)

3.4 DSL pipeline (parser / IR / expr)

IDSevFileTitle
F-DSL-1P1dsl/parser/lexer.go:165Tab at line start is silently consumed as 0-indent
F-DSL-2P1dsl/parser/lexer.go:319CRLF not normalized; \r leaks into tokens/strings
F-DSL-3P2dsl/parser/lexer.go:51UTF-8 BOM at file start not stripped
F-DSL-4P1dsl/expr/expr.go:139truthy() treats string "false" as truthy
F-DSL-5P1dsl/expr/expr.go:802Loose ==/!= coerces via fmt.Sprintf (5 == "5")
F-DSL-6P2dsl/expr/expr.go:139Non-canonical numeric types (uint*, float32) fall through
F-DSL-7P2dsl/expr/expr.go:862Integer arithmetic overflows silently
F-DSL-8P2dsl/expr/expr.go:852String + array produces Go debug format
F-DSL-9P2dsl/ir/compile.go:1066JSON WhenClause can carry both Expr and Condition — Expr wins silently
F-DSL-10P2dsl/unparse/unparse.go:316Identifier-shaped fields emitted unquoted — JSON-injected breaks
F-DSL-11P1dsl/parser/parser.go:2069Duplicate edge clauses silently overwrite (last wins)
F-DSL-12P1runview/compile.go:65Bundle prompts merge after hash snapshot — resume sees stale prompts
F-DSL-13P3dsl/parser/parser.go:611Prompt-body parse silently skips unknown tokens
F-DSL-14P3dsl/unparse/unparse.go:187Human node always emits interaction: even when default

3.5 Backend execution

IDSevFileTitle
F-BE-1P1backend/model/executor.go:1077Structured-output retry bypasses hooks + accounting
F-BE-2P1backend/delegate/claude_code.go:570promptWithTimeout leaks goroutine on ctx cancel
F-BE-3P2backend/mcp/rpc.go:154Waiter inherits starter's context.DeadlineExceeded
F-BE-4P2backend/model/claw_backend.go:417Recovery formatting pass tokens not separately reported
F-BE-5P2backend/model/executor.go:1384isDelegateRetryable misses HTTP 5xx CLI exits
F-BE-6P2backend/model/generation.go:115Tool-input-too-large early return doesn't drain channel
F-BE-7P2backend/detect/detect.go:188ANTHROPIC_AUTH_TOKEN alone reported as "anthropic available"
F-BE-8P3backend/delegate/claude_code.go (multi)Inconsistent b.Logger != nil guards
F-BE-9P3backend/model/executor.go:1071Silent schema-lookup miss (defence-in-depth)

3.6 Dispatcher

IDSevFileTitle
F-CD-1P1dispatcher/manager.go:222Stop releases EngineRunner while workers still in Dispatch
F-CD-2P1dispatcher/native/store.go:179Event log can be missing entries after issue-file success
F-CD-3P1dispatcher/native/store.go:156Crash after writeIssueLocked leaves issue invisible to live in-memory consumers
F-CD-4P1dispatcher/loop.go:107refreshRunningStates reaps slot before worker exits
F-CD-5P1dispatcher/tracker/forgejo.go:236Concurrent first-Claim races (2 processes, same repo)
F-CD-6P1dispatcher/tracker/forgejo.go:307opts.Repo not validated; URL paths interpolate verbatim
F-CD-7P1dispatcher/Native tracker REST + dispatcher WS have NO authentication
F-CD-8P2dispatcher/manager.go:129SaveConfig writes outside lock; concurrent saves can desync
F-CD-9P2dispatcher/native/store.go:140SetBoard mutates memory before disk; partial write desyncs
F-CD-10P2dispatcher/dispatcher.go:46c.hooks read across goroutines without sync
F-CD-11P2server/mcp_board_handler.go:65No origin/CSRF guard on /api/v1/mcp/board; no token TTL
F-CD-12P2dispatcher/loop.go:73Stuck worker → cancel re-issued each tick (log spam)
F-CD-13P2dispatcher/tracker/github.go:357GH_TOKEN exposed to gh's children via /proc/PID/environ
F-CD-14P3dispatcher/loop.go:165Retry attempt counter resets to 0 on dispatch
F-CD-15P3dispatcher/native/store.go:215List deep-clones all issues under mutex (perf at scale)

3.7 Cloud / queue / runner / auth / secrets / git

IDSevFileTitle
F-CL-1P0auth/service.go:152Locked-account login leaks lockout via timing oracle
F-CL-2P1secrets/byok.go:405MongoApiKeyStore.Get has no tenant filter
F-CL-3P1server/auth_routes.go:395OIDC state not bound to user agent (login-CSRF)
F-CL-4P1queue/nats/nats.go:311Two-phase Fetch ignores ctx in first phase
F-CL-5P1auth/service.go:263Refresh race vs concurrent disable mints 15min token
F-CL-6P2runner/loop.go:539injectCredentials tenant check hole on empty TenantID
F-CL-7P2auth/oidc_service.go:48SSO auto-link by email without explicit consent
F-CL-8P2queue/nats/nats.go:91MaxReconnects(-1) + no per-call publish timeout
F-CL-9P2auth/jwt.go:46Single HS256 secret; no kid rotation; no revocation list
F-CL-10P3secrets/oauth_refresh.go:38No retry on 5xx; missing result sanity check

3.8 Frontend (What's Next + deferred items)

IDSevFileTitle
F-TS-1P1studio/src/lib/whats-next/messagesFromEvents.ts:370run_failed/run_cancelled leave banners stuck "running"
F-TS-2P1studio/src/components/WhatsNext/QuestionInput.tsx:84Radio "Other" value collides with option matching typed text
F-TS-3P1studio/src/lib/whats-next/messagesFromEvents.ts:269tool_started before node_started silently dropped
F-TS-4P1studio/src/lib/whats-next/useWhatsNextSession.ts:171setStoreRunId(null) on unmount races RunView mount
F-S3P1pkg/server/server.go:1078safePath reads cfg.WorkDir without stateMu
F-R3P1pkg/server/runs_ws.go:863Slow WS client pins broker goroutine
F-S4P2pkg/server/runs_preview.go:179Preview proxy reaches loopback/private in local mode
F-C1P2pkg/server/auth_routes.go:22No rate limit on /api/auth/
F-C2P2cmd/iterion-desktop/bindings.go:216RevealInFinder accepts arbitrary paths
F-R4P2studio/src/store/run.ts:397loadEventHistoryIfMissing micro-race (DEFER per prior P2 call)
F-U2P2studio/src/components/shared/ShortcutsHelp.tsx, ~41 filesA11y cluster — deferred (Radix migration)
F-P3P2studio/vite.config.ts, App.tsxBundle code-split — deferred (multi-day refactor)
F-TS-5P2studio/src/lib/whats-next/useWhatsNextSession.ts:207messagesFromEvents re-folds full stream on every event
F-TS-6P3studio/src/lib/whats-next/messagesFromEvents.ts:290human_input_requested ignores runtime questions payload
F-TS-7P3studio/src/components/WhatsNext/ChatTranscript.tsx:43Auto-scroll fights manual scroll-up

7. Shipped vs deferred

IDSevStatusCommit / Notes
F-CL-1P0shippedauth lockout dummy-hash compensation
F-ST-1P0shippedfail-closed when crypto/rand unavailable (no deterministic fallback key)
F-ST-2P1shippedSanitizePathComponent rejects control chars
F-ST-3P1shippedVerifyAttachmentSignature sanitizes runID/name
F-SB-1P0shipped${localEnv:NAME} allowlist
F-SB-2P0shippeddocker --mount blocks sensitive bind sources
F-SB-3P0shippedbuildx context/Dockerfile constrained to workspace
F-SB-4P1shippeddocker --user / image / workdir validation
F-RT-1P0shippedfinalize worktree on CLI resume
F-RT-2P1shippedinit nil maps on resume
F-RT-3P1shippedemit EventEdgeSelected on round-robin
F-RT-4P1shippeddeep-copy checkpoint outputs on resume
F-RT-5P1shippedRecoverFinalize on failed status too
F-DSL-1P1shippedtabs at line start error
F-DSL-2P1shippedCRLF normalised in lexer
F-DSL-3P2shippedBOM stripped in lexer
F-DSL-4P1shippedtruthy("false") → false
F-DSL-5P1shippedstrict type-aware ==/!=
F-DSL-11P1shippedduplicate edge clause → DiagDuplicateEdgeClause
F-DSL-12P1shippedworkflow hash includes bundle prompts
F-CD-6P1shippedtracker.ValidateRepoPath on adapter init
F-S3P1shippedsafePath/handleListFiles snapshot WorkDir under stateMu
F-R3P1shippeddrop-on-full WS sends
F-CL-3P1shippedOIDC user-agent binding cookie (RFC 9700 §4.7.1)
F-SB-5P1shippedk8s env-var control-char validation
F-SB-6P1shippedfail-closed on missing ITERION_POD_IP
F-SB-7P1shippeddrop .svc/.cluster.local from default NO_PROXY
F-TS-1P1shippedfinalize What's Next banners on run terminal events
F-C2P2shippedRevealInFinder containment to project/store
F-CC-2P1shippedtyped *APIError on transport-error retry exhaustion (claw-code-go)
F-CC-3P1shippedlive registry refresh: don't bump TTL on failure (claw-code-go)
F-BE-1P1deferredstructured-output retry bypasses hooks/accounting — non-trivial restructure of retryDelegateLoop
F-BE-2P1deferredpromptWithTimeout subprocess kill on ctx cancel — needs claudesdk SDK contract understanding
F-ST-4P1deferredmongo tenant filter fail-open design — needs explicit admin-scope marker + audit pass on every call site
F-ST-5P1deferredS3 DeleteObjects per-object errors ignored
F-ST-6P1deferredbundle cache slot race — needs combined hash-length + sentinel-rewrite fix
F-CD-1P1deferreddispatcher Stop releases EngineRunner while workers still in Dispatch — needs WaitGroup on worker goroutines
F-CD-2P1deferrednative store event log can lag issue file on fsync failure
F-CD-3P1deferredcrash mid-write leaves issue invisible to live in-memory consumers
F-CD-4P1deferredrefreshRunningStates reaps slot before worker exits — needs tombstone pattern
F-CD-5P1deferredforgejo concurrent first-Claim across 2 processes — handle 409 from POST /labels
F-CD-7P1deferrednative tracker REST + dispatcher WS have NO auth — needs middleware wiring + config story
F-RT-6P1deferredbest-effort fallback to topology convergence wrong on all-done — niche topology
F-CL-2P1deferredBYOK MongoApiKeyStore.Get no tenant filter — needs schema migration
F-CL-4P1deferredNATS Fetch first phase ignores ctx
F-CL-5P1deferredrefresh vs concurrent disable race — needs RevokeUserSessions on admin-disable
F-CC-1P1deferredfunction_call deltas without identity silently lost (claw-code-go) — defensive against malformed SSE frame
F-CC-4P1deferredanthropic-beta header set unconditionally on OAuth sessions (claw-code-go) — needs OAuth-vs-API-key gating
F-TS-2P1deferredWhat's Next radio "Other" value collision — needs local state lift
F-TS-3P1deferredWhat's Next tool_started-before-node_started silently dropped
F-TS-4P1deferredWhat's Next setStoreRunId(null) races RunView mount on console-link

Every P2 / P3 finding stays deferred unless coupled with a P0/P1 above (F-DSL-3, F-C2 promoted). See §8 for the rationale on the largest deferred clusters.


8. Deferred items — rationale

Multi-day refactors (deferred to follow-up PRs):

  • F-P3 — Bundle code-split (3 MB single bundle). Multi-day refactor; Wails AssetServer embedding needs validation for hashed chunks.
  • F-U2 — A11y cluster (~41 files). Per-file Radix migration; tracked as a sweep, not a single commit.
  • F-CL-9 — JWT kid rotation + denylist. Documented as future work in the code; needs migration plan.
  • F-ST-4, F-CL-2 — Tenant filter design (BYOK + global mongo store). Both fixes require backfilling existing rows with tenant_id. Land the schema migration first, then the strict filter; otherwise legitimate calls 404 in production.
  • F-CD-7 — No auth on dispatcher / native REST + WS. Needs an explicit "auth on" vs "trusted localhost" config story (the studio binds to localhost by default but --host 0.0.0.0 is widely used). Wiring the existing pkg/auth middleware is a 10-LOC change but the config story is the long pole.
  • F-CD-1, F-CD-4 — Worker-goroutine sync. WaitGroup + tombstone pattern; touches the actor discipline that's intentionally narrow today.

Niche / low-confidence items:

  • F-CD-15 — Dispatcher List deep-clone perf. No issue at current usage; revisit at >1k issues.
  • F-RT-6 — Best-effort all-done topology. Niche topology pattern that the live-test suite probably doesn't exercise.

All other deferred items have a fix sketch + reproducer in their finding entry above and are tracked for a follow-up sprint.


Appendix A — Files reviewed

(See per-finding citations above.)

Appendix B — Companion review

See claw-code-go/docs/reviews/iterion-touching-audit-2026-05-17.md for 11 findings in claw-code-go's iterion-touching surface (0 P0, 4 P1, 5 P2, 2 P3).