From 5d99d550159588fa579de5756fb9af18c90e56b5 Mon Sep 17 00:00:00 2001 From: Zaki Manian Date: Mon, 20 Apr 2026 23:55:31 +0900 Subject: [PATCH 1/4] fix(gate): handle orphaned approval gates when thread deleted (#2347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(gate): handle orphaned approval gates when thread is deleted (#2323) When a thread is deleted while an approval gate is pending, the gate becomes orphaned -- unresolvable on backend and undismissable on frontend. This fixes three root causes: 1. execute_pending_gate_action now detects missing threads and emits a gate_resolved event with resolution "expired" instead of erroring, so the frontend can dismiss the stale card. 2. PendingGateStore gains discard_for_thread() for bulk cleanup of all gates tied to a specific thread, regardless of user. 3. Frontend handleGateResolved now handles "expired" resolution to remove stale approval cards and re-enable chat input. Co-Authored-By: Claude Opus 4.6 (1M context) * fix(gate): split Ok(None)/Err(_) arms and wire discard_for_thread into production Address review feedback on #2347: 1. Split the conflated `Ok(None) | Err(_)` match arm in `execute_pending_gate_action`: `Ok(None)` means the thread was genuinely deleted (emit "expired" gate resolution), while `Err(_)` indicates a transient DB failure (propagate the error so the caller can retry instead of permanently discarding the gate). 2. Wire `discard_for_thread()` into the conversation clear path (`clear_engine_conversation`), replacing the per-user `discard()` call. This ensures all pending gates for a thread are cleaned up regardless of which user created them, preventing orphaned gates. Co-Authored-By: Claude Opus 4.6 (1M context) * fix(gate): pre-flight thread check before persisting AlwaysAllow Address review feedback on #2347: 1. **codex P2 — AlwaysAllow silent commit on thread-delete race.** When `resolve_gate` hit `Approved { always: true }`, it persisted `AlwaysAllow` to DB *before* calling `execute_pending_gate_action`. The rollback branch only fires on `result.is_err()`, but the thread-missing path now returns `Ok(BridgeOutcome::Respond(...))` for graceful `expired` UX — so the preference would stick even though the tool never ran. Added a pre-flight `load_thread` check in the `Approved` arm that short-circuits with `emit_gate_expired_dismissal` before any auto-approve / DB write occurs. 2. **Rebase onto staging (`scope_thread_id: Option`).** The PR predated #2561/#2473's identity-type tightening. The inline `pending.scope_thread_id.clone().or_else(|| Some(pending.thread_id.to_string()))` no longer type-checks against `Option` on the wire field. Replaced with `Some(pending.effective_wire_thread_id())` — matches the five other `GateResolved` emit sites and satisfies the "don't re-derive identity values" invariant in `src/bridge/CLAUDE.md`. 3. **Extracted `emit_gate_expired_dismissal`** so the expired-SSE path is shared between the pre-flight check and `execute_pending_gate_action`'s `Ok(None)` arm. Documented the pre-flight contract on the helper itself. 4. **Regression test** `resolve_gate_approved_with_missing_thread_emits_expired_and_skips_persist` drives `resolve_gate` at the caller level with `Approved { always: true }` and no thread in the store. Asserts the first SSE event is `expired` (not `approved_always`), satisfying `.claude/rules/testing.md` → "Test Through the Caller, Not Just the Helper". Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: Illia Polosukhin --- .../static/js/core/onboarding.js | 2 + src/bridge/router.rs | 160 ++++++++++++++++-- src/gate/store.rs | 83 +++++++++ 3 files changed, 232 insertions(+), 13 deletions(-) diff --git a/crates/ironclaw_gateway/static/js/core/onboarding.js b/crates/ironclaw_gateway/static/js/core/onboarding.js index 64caa78db8b..7c12e582bff 100644 --- a/crates/ironclaw_gateway/static/js/core/onboarding.js +++ b/crates/ironclaw_gateway/static/js/core/onboarding.js @@ -358,6 +358,8 @@ function handleGateResolved(data) { ) { removeAuthCard(); enableChatInput(); + } else if (data.resolution === 'expired') { + enableChatInput(); } } diff --git a/src/bridge/router.rs b/src/bridge/router.rs index 5dce208e095..41442280195 100644 --- a/src/bridge/router.rs +++ b/src/bridge/router.rs @@ -949,6 +949,41 @@ async fn resume_lease_for_pending_gate( .await } +/// Broadcast a `GateResolved { resolution: "expired" }` event and return the +/// dismissal outcome. Used when the target thread has been deleted between +/// `take_verified` and resume, so there's no live thread to execute against. +/// +/// Callers that persist side effects (e.g. `Approved { always }` writing +/// `AlwaysAllow` to settings) MUST pre-flight with `state.store.load_thread` +/// and call this helper *before* persisting, so a missing thread doesn't +/// silently commit a long-lived preference for a tool that never ran (#2347). +fn emit_gate_expired_dismissal( + state: &EngineState, + message: &IncomingMessage, + pending: &PendingGate, +) -> BridgeOutcome { + tracing::debug!( + thread_id = %pending.thread_id, + gate = %pending.gate_name, + action = %pending.action_name, + "thread not found for pending gate; emitting expired resolution" + ); + if let Some(ref sse) = state.sse { + sse.broadcast_for_user( + &message.user_id, + AppEvent::GateResolved { + request_id: pending.request_id.to_string(), + gate_name: pending.gate_name.clone(), + tool_name: pending.action_name.clone(), + resolution: "expired".into(), + message: "Thread no longer exists.".into(), + thread_id: Some(pending.effective_wire_thread_id()), + }, + ); + } + BridgeOutcome::Respond("Thread no longer exists. Approval dismissed.".into()) +} + async fn execute_pending_gate_action( agent: &Agent, state: &EngineState, @@ -957,12 +992,15 @@ async fn execute_pending_gate_action( approval_already_granted: bool, approval_event: Option<(String, bool)>, ) -> Result { - let thread = state - .store - .load_thread(pending.thread_id) - .await - .map_err(|e| engine_err("load thread", e))? - .ok_or_else(|| engine_err("load thread", "thread not found"))?; + let thread = match state.store.load_thread(pending.thread_id).await { + Ok(Some(t)) => t, + Ok(None) => return Ok(emit_gate_expired_dismissal(state, message, pending)), + Err(e) => { + // Transient DB failure -- propagate so the caller can retry + // rather than permanently discarding the gate. + return Err(engine_err("load thread", e)); + } + }; let resolved_call_id = resolved_or_synthetic_call_id_for_pending_action(state, pending).await?; let lease = resume_lease_for_pending_gate(pending, &state.thread_manager.leases) @@ -2412,6 +2450,22 @@ pub async fn resolve_gate( // auto-approval that bypasses every subsequent gate. The gate's // own `allow_always` is the authoritative server-side policy. let always = clamp_always_to_resume_kind(always, &pending.resume_kind); + + // Pre-flight thread check before committing `AlwaysAllow` + // persistence (#2347): if the thread was deleted between + // `take_verified` and now, persisting auto-approve would leave + // a permanent preference behind for a tool that never ran. The + // rollback at the bottom of this branch only fires on `Err`, so + // execute_pending_gate_action's graceful `Ok(Respond)` on + // missing-thread would bypass it. Short-circuit here instead. + match state.store.load_thread(pending.thread_id).await { + Ok(Some(_)) => {} + Ok(None) => { + return Ok(emit_gate_expired_dismissal(state, message, &pending)); + } + Err(e) => return Err(engine_err("load thread", e)), + } + if let Some(ref sse) = state.sse { sse.broadcast_for_user( &message.user_id, @@ -3101,13 +3155,9 @@ async fn clear_engine_conversation(agent: &Agent, message: &IncomingMessage) -> .stop_thread(*tid, &message.user_id) .await; } - let _ = state - .pending_gates - .discard(&PendingGateKey { - user_id: message.user_id.clone(), - thread_id: *tid, - }) - .await; + // Discard all pending gates for this thread regardless of user, + // preventing orphaned gates that can never be resolved (#2323). + state.pending_gates.discard_for_thread(*tid).await; } } @@ -8015,6 +8065,90 @@ mod tests { outcome.expect("router no-auth-backend failure test"); } + /// Regression for #2323: when the target thread is deleted between + /// `take_verified` and resume, an `Approved` resolution must emit + /// `GateResolved { resolution: "expired" }` (not just generic error) and + /// must *not* persist `AlwaysAllow` — otherwise the caller's rollback + /// (`result.is_err()` branch) would be skipped, leaving a permanent + /// auto-approve preference behind for a tool that never ran. Covers + /// both the `always: false` SSE contract and the pre-flight thread + /// check that gates `persist_always_allow`. + #[tokio::test] + async fn resolve_gate_approved_with_missing_thread_emits_expired_and_skips_persist() { + let _guard = ENGINE_STATE_TEST_LOCK.lock().await; + let lock = ENGINE_STATE.get_or_init(|| RwLock::new(None)); + *lock.write().await = None; + + let outcome = async { + let store = Arc::new(TestStore::new()); + let sse = Arc::new(SseManager::new()); + let mut event_stream = Box::pin( + sse.subscribe_raw(Some("alice".to_string()), false) + .expect("subscribe raw"), + ); + + let mut state = make_expected_test_state(store); + state.sse = Some(Arc::clone(&sse)); + + // Thread deleted / never saved — `state.store.load_thread(tid)` + // returns `Ok(None)`, mimicking the #2323 race. + let thread_id = ironclaw_engine::ThreadId::new(); + let pending = sample_pending_gate( + "alice", + thread_id, + ironclaw_engine::ResumeKind::Approval { allow_always: true }, + ); + state + .pending_gates + .insert(pending.clone()) + .await + .expect("insert pending gate"); + + *lock.write().await = Some(state); + + let (agent, _statuses) = make_router_test_agent(Some(Arc::clone(&sse))).await; + let message = + IncomingMessage::new("web", "alice", "approve").with_thread(thread_id.to_string()); + + let result = resolve_gate( + &agent, + &message, + thread_id, + pending.request_id, + ironclaw_engine::GateResolution::Approved { always: true }, + ) + .await + .expect("resolve gate"); + + // Graceful dismissal, not an error. + assert!(matches!( + result, + BridgeOutcome::Respond(ref text) + if text == "Thread no longer exists. Approval dismissed." + )); + + // The first (and only) SSE event on this subscription must be + // `expired`. Critically it must NOT be `approved_always` — a + // prior implementation emitted that first, then discovered the + // missing thread and committed AlwaysAllow before anyone could + // roll it back. + let event = event_stream.next().await.expect("gate event"); + assert!( + matches!( + &event, + AppEvent::GateResolved { resolution, .. } if resolution == "expired" + ), + "expected expired gate resolution (pre-flight short-circuit), got: {event:?}" + ); + + Ok::<(), crate::error::Error>(()) + } + .await; + + *lock.write().await = None; + outcome.expect("router orphaned-approved-gate expired test"); + } + /// Unit test for the extension-manager branch of /// `submit_pending_auth_credential`. The caller-level regression is /// `resolve_gate_uses_extension_manager_without_auth_manager_for_auth_resume`; diff --git a/src/gate/store.rs b/src/gate/store.rs index 1548266971d..dbd4e248e0b 100644 --- a/src/gate/store.rs +++ b/src/gate/store.rs @@ -215,6 +215,43 @@ impl PendingGateStore { .collect() } + /// Remove all gates for a given thread, regardless of user. + /// + /// Returns the gates that were removed. Used when a thread is deleted or + /// becomes unreachable while gates are still pending — prevents orphaned + /// gates that can never be resolved. + pub async fn discard_for_thread( + &self, + thread_id: ironclaw_engine::ThreadId, + ) -> Vec { + let removed = { + let mut inner = self.inner.lock().await; + let keys: Vec = inner + .by_key + .iter() + .filter(|(k, _)| k.thread_id == thread_id) + .map(|(k, _)| k.clone()) + .collect(); + let mut gates = Vec::with_capacity(keys.len()); + for key in &keys { + if let Some(gate) = inner.by_key.remove(key) { + inner.by_request_id.remove(&gate.request_id); + gates.push(gate); + } + } + (keys, gates) + }; + let (keys, gates) = removed; + if let Some(ref persistence) = self.persistence { + for key in &keys { + if let Err(e) = persistence.remove(key).await { + tracing::debug!(error = %e, "failed to remove orphaned gate from persistence"); + } + } + } + gates + } + /// Remove a gate by key without verification. /// /// Used for cleanup paths like conversation clears or explicit cancel flows. @@ -669,6 +706,52 @@ mod tests { assert!(store.peek(&key_expired).await.is_none()); } + // ── Thread-scoped bulk discard ────────────────────────── + + #[tokio::test] + async fn test_discard_for_thread_removes_all_gates() { + // Regression: #2323 — orphaned gates when thread deleted + let store = PendingGateStore::in_memory(); + let orphan_tid = ThreadId::new(); + let other_tid = ThreadId::new(); + + // Two gates on the orphan thread (different users) + let g1 = sample_gate_with("alice", orphan_tid, "web", 300); + let g2 = sample_gate_with("bob", orphan_tid, "telegram", 300); + // One gate on a different thread + let g3 = sample_gate_with("alice", other_tid, "web", 300); + + store.insert(g1).await.unwrap(); + store.insert(g2).await.unwrap(); + store.insert(g3).await.unwrap(); + + let removed = store.discard_for_thread(orphan_tid).await; + assert_eq!(removed.len(), 2, "should remove both gates for the thread"); + + // Orphan thread gates are gone + assert!( + store + .list_all() + .await + .iter() + .all(|g| g.thread_id != orphan_tid) + ); + + // Other thread gate still present + let key = PendingGateKey { + user_id: "alice".into(), + thread_id: other_tid, + }; + assert!(store.peek(&key).await.is_some()); + } + + #[tokio::test] + async fn test_discard_for_thread_returns_empty_when_no_gates() { + let store = PendingGateStore::in_memory(); + let removed = store.discard_for_thread(ThreadId::new()).await; + assert!(removed.is_empty()); + } + // ── Reserved channel names ─────────────────────────────── #[test] From c725366e70ee8db1438322a9b2118e4a3b811118 Mon Sep 17 00:00:00 2001 From: Illia Polosukhin Date: Tue, 21 Apr 2026 00:03:29 +0900 Subject: [PATCH 2/4] docs(rules): add review-driven guidance for Claude Code (#2714) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(rules): add review-driven guidance for Claude Code Synthesizes recurring patterns from ~30 merged PRs, 147 bot review comments (Copilot/Gemini), human reviews, and ~50 issues filed in the past 2 weeks. Each rule cites the motivating PR/issue numbers. New files: - error-handling.md — silent-failure taxonomy (unwrap_or_default, .ok()?, poisoned caches), persist-then-reload atomicity, channel-edge error mapping. (#2526, #2633, #2653, #2673, #2546, #2407, #2408) - agent-evidence.md — side-effect claims must cite tool evidence, empty-fast outputs are errors, external-effect tools must read back, setup UI round-trip. (#2544, #2580, #2582, #2541, #2545, #2411, #2543, #2586) - lifecycle.md — discovery vs. activation, terminal auth rejection, list_installed vs. list_active, deactivation unwinds, snapshot rehydrate must re-validate. (#2556, #2557, #2558, #2564, #2419, PR #2617, PR #2631) Extended: - types.md — from_trusted boundary rule, validated-newtype template with shared validate(&str), serde(try_from) required for validated types, wire-stable enums (no Debug; serde alias for migrations), canonical wire-contract field naming. (PR #2685, #2681, #2687, #2678, #2669, #2665, #2683, #2702) - safety-and-sandbox.md — every new ingress scans pre-transform/pre- injection, bounded resources (interners/streams/fan-out caps), cache keys must be complete. (#2491, #2676, #2470, #2633, #2673, #2710, PR #2702) - review-discipline.md — PR scope discipline, guardrail scripts are code (regression tests, grouped-import parsing, CI has_code inclusion), absolute-path ban in committed docs, stale comments after refactors. (PR #2668, #2628, #2680, #2687, #2647, #2689, #2701) All new files carry paths: frontmatter so they auto-load only on matching files. Co-Authored-By: Claude Opus 4.7 (1M context) * refactor(rules): split agent-evidence into prompt + code rule agent-evidence.md mixed two concerns: runtime agent instruction (what the LLM should do when concluding a turn) and code-enforcement rules (what the dispatcher, engine, and tools must implement). Rules under .claude/rules/ only guide Claude Code when editing the repo — the runtime agent never reads them. Splits the two: - crates/ironclaw_engine/prompts/codeact_postamble.md — new section "Evidence before claiming side effects". Sits next to the existing "FINAL() answer quality" guidance; loaded via include_str! in executor/prompt.rs (no Rust change needed). - .claude/rules/tool-evidence.md — renamed from agent-evidence.md, keeps only the code invariants (engine v2 side-effect gate, empty-fast ToolError::EmptyResult, external-effect tools must read back, setup UI round-trip). Prompt tests pass unchanged; the postamble addition is pure text. Co-Authored-By: Claude Opus 4.7 (1M context) * prompt: tighten evidence rule to FINAL() claims only, not tool use Live-test validation of the "Evidence before claiming side effects" section (added in the prior commit) showed it inhibited legitimate tool use. With the original wording, `zizmor_scan_v2` live-recording timed out at 302s with zero responses; reverting the postamble restored healthy behavior (88s run, 8 shell calls including `cargo install zizmor` and full workflow analysis). The original phrasing conflated two things: what the agent should claim and what tools it should call. The rule is only about the claim. Re-tunes the section to: - Open with an explicit "this does not restrict tool calls" scope. - Drop the "<1ms = failure" heuristic (too broad — normal tools like `tool_info(schema)` are legitimately fast). - Drop the full enumeration of forbidden side-effect verbs; keep the rule narrower and clearer. - Shorten the code example (remove redundant early-return). Re-tuned run: agent is active (shell calls, real reasoning), live recording completes in ~9s. The remaining test failure is a pre-existing assertion bug (exact `t == "shell"` match against tool strings that now carry arguments like `"shell(cmd)"`) — reproduces with the old postamble too. Co-Authored-By: Claude Opus 4.7 (1M context) * test(live): fix tool-name assertions + re-record zizmor traces The two `zizmor_scan*` live tests had four broken tool-name assertions that silently failed to match: `tools.iter().any(|t| t == "shell")` against a tool list that now contains `"shell(cmd)"` strings (tool events carry args via `format_action_display_name` in `src/bridge/router.rs`). Two of the four were negative assertions checking for the absence of `tool_install` recovery loops — those silently passed even when a recovery loop actually ran. `sandbox_live_e2e.rs:203` already used the correct `t == "shell" || t.starts_with("shell(")` pattern; applied it consistently to all four sites. Verified live: - `IRONCLAW_LIVE_TEST=1 cargo test --test e2e_live -- zizmor_scan --ignored --test-threads=1` → 2 passed, 0 failed, 51.78s. Agent installs and runs zizmor end-to-end, producing real findings (exit code 14, dangerous triggers, excessive permissions, etc.). Traces re-recorded with the tuned postamble (commit 50d85175) and scrubbed: replaced `/home/illia/.cargo/bin/zizmor` with `/home/user/.cargo/bin/zizmor` per the developer-local-path ban in `.claude/rules/review-discipline.md`. No credentials, PII, or high-entropy secrets in either trace (only git SHAs from zizmor's workflow analysis output). Replay still passes: `cargo test --test e2e_live -- zizmor_scan --ignored` → 2/2 ok. Co-Authored-By: Claude Opus 4.7 (1M context) * test(replay): update zizmor_scan_v2 insta snapshot The engine v2 replay-snapshot gate (`engine_v2_tests::snapshot_zizmor_scan_v2`) failed against the re-recorded trace from 1691efec because the old snapshot encoded a broken run: - final_state: Failed - Missing Assistant message role - 3 issues: thread_failure (error), no_response (warning), llm_error (error) - 6 tool calls that never produced a final answer The new trace completes cleanly: - final_state: Done - System / User / Assistant roles present - 1 issue: mixed_mode (info) - 3 shell tool calls + successful `FINAL()` with real findings The snapshot was pinning a regression. Regenerated with `INSTA_UPDATE=always cargo test --test e2e_engine_v2 -- snapshot_zizmor_scan_v2`; passes on replay. Co-Authored-By: Claude Opus 4.7 (1M context) * docs(rules): address PR #2714 review feedback - review-discipline: reword "Doc Absolute Paths" as a review convention (pre-commit only scans .rs; the rule misleadingly claimed enforcement). - safety-and-sandbox: broaden `paths:` frontmatter to include the actual ingress owners (`bridge`, `channels`, `workspace`, `agent`, engine crate) so the rule auto-loads where it applies. - tool-evidence: mark the side-effect gate, empty-fast rule, and `unverified` flag as target/aspirational invariants — neither `ToolError::EmptyResult`, an `unverified` field on `ToolOutput`, nor a byte-count field on `ActionRecord` exist today. Point at concrete interim conventions (`ToolError::ExecutionFailed`, `unverified: true` in the JSON result body). - types: scope "Validated newtypes must gate Deserialize" to *new* types, document the `CredentialName`/`ExtensionName` exception (they intentionally use `#[serde(transparent)]` + derived `Deserialize` under the `serde_does_not_revalidate` test). Clarify the `from_trusted` trust boundary (trusted = typed upstream, untrusted = raw JSON field even if the field *name* is "registry entry"). Switch `new` template to `impl Into` to avoid an unnecessary clone when an owned `String` is passed. Co-Authored-By: Claude Opus 4.7 (1M context) * docs(rules): simplify types.md + split doc-hygiene; address review round 2 - types: collapse two templates into one canonical validated-newtype shape. New types use `#[serde(try_from = "String")]` with a shared `validate(&str)` helper — no more dual "transparent for some / try_from for others" guidance. `CredentialName`/`ExtensionName` are documented as the sole legacy exception (locked in by the `serde_does_not_revalidate` test); new code must not copy their `transparent` + `from_trusted` pattern. Removes the long "Using `from_trusted` safely" section and the separate "Validated newtypes must gate Deserialize" subsection that contradicted the Don'ts list. - doc-hygiene: new tiny rule file scoped to `**/*.md`, `**/*.py`, `docs/**` that carries the "no developer-local absolute paths in committed docs" convention. Removed from review-discipline.md where its `src/**/*.rs` scope meant the rule never loaded on the files it governed. Co-Authored-By: Claude Opus 4.7 (1M context) * test(live): match hyphenated tool-install in attempted_relevant_tool The engine records `action_name` as the raw string the LLM emitted (`crates/ironclaw_engine/src/executor/structured.rs:381`), and the registry's lookup canonicalization only affects dispatch — not the name that reaches `StatusUpdate::ToolStarted`. The two other predicates in this file (`bad_recovery` at :420, `phase_b_recovery` at :531) already defend against both forms; this one should too, for consistency. Addresses PR #2714 review. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .claude/rules/doc-hygiene.md | 15 ++ .claude/rules/error-handling.md | 51 +++++ .claude/rules/lifecycle.md | 29 +++ .claude/rules/review-discipline.md | 21 ++ .claude/rules/safety-and-sandbox.md | 34 +++ .claude/rules/tool-evidence.md | 37 +++ .claude/rules/types.md | 211 +++++++++++------- .../prompts/codeact_postamble.md | 19 ++ tests/e2e_live.rs | 36 ++- .../fixtures/llm_traces/live/zizmor_scan.json | 172 +++++++++----- .../fixtures/llm_traces/live/zizmor_scan.log | 189 ++++++---------- .../llm_traces/live/zizmor_scan_v2.json | 102 +++------ .../llm_traces/live/zizmor_scan_v2.log | 89 ++++++-- tests/snapshots/replay__zizmor_scan_v2.snap | 72 ++---- 14 files changed, 683 insertions(+), 394 deletions(-) create mode 100644 .claude/rules/doc-hygiene.md create mode 100644 .claude/rules/error-handling.md create mode 100644 .claude/rules/lifecycle.md create mode 100644 .claude/rules/tool-evidence.md diff --git a/.claude/rules/doc-hygiene.md b/.claude/rules/doc-hygiene.md new file mode 100644 index 00000000000..233d1274a5e --- /dev/null +++ b/.claude/rules/doc-hygiene.md @@ -0,0 +1,15 @@ +--- +paths: + - "**/*.md" + - "**/*.py" + - "docs/**" +--- +# Doc Hygiene + +## Absolute Paths + +Committed `.md` and `.py` files (outside `tests/` and `scripts/`) +must not contain developer-local absolute paths (`/home//`, +`/Users//`, `/tmp/`). This is a review convention, not +pre-commit-enforced — grep before merging a docs-touching PR. +Reference: PR #2689. diff --git a/.claude/rules/error-handling.md b/.claude/rules/error-handling.md new file mode 100644 index 00000000000..3e56f613874 --- /dev/null +++ b/.claude/rules/error-handling.md @@ -0,0 +1,51 @@ +--- +paths: + - "src/**/*.rs" + - "crates/**/*.rs" +--- +# Error Handling + +Existing rules forbid `.unwrap()` / `.expect()` in production. The footguns below are equally dangerous and equally banned on DB, IO, workspace, and settings reads. + +## Silent-Failure Anti-Patterns + +- `.unwrap_or_default()` on a `Result` — collapses errors into empty state. Masks DB outages, migration failures, schema drift. (#2526 `list_projects`, #2653 `.env` scan.) +- `.ok()?` on `Result` — drops the error entirely. +- `let Ok(x) = ... else { return None }` / `else { return }` — same shape, structured. +- `if let Err(e) = ... { warn!(...) }` followed by caching / inserting / continuing — poisons downstream state with a half-initialized value and hides the failure forever. (#2633 `seed_if_empty` cache.) + +**Required pattern — fail loud by default:** + +```rust +let projects = store.list_projects(&owner_id).await?; +``` + +**When fallback is genuinely acceptable** — must be justified inline and name the operation: + +```rust +let rows = store.list_agent_jobs().await.unwrap_or_default(); // silent-ok: dashboard refresh, next poll retries +``` + +Review flag: added lines containing `unwrap_or_default()`, `.ok()?`, or `else { return` / `else { return None }` on a DB/IO/workspace call must carry a `// silent-ok: ` comment or be rejected. + +## Persist-Then-Reload Atomicity + +A write that triggers a runtime rebuild (provider chain reload, settings reload, credential reinjection) is multi-step. The DB row may commit while the rebuild fails — do NOT leave split-brain state. + +Two acceptable patterns: + +- **Pre-validate** — attempt the rebuild on the new value *without persisting*; only persist on success. +- **Snapshot + rollback** — snapshot the old value, write, attempt rebuild; on rebuild failure, restore the snapshot and return the error. + +Reference: PR #2673 `reload_llm_after_settings_change`. + +## Error Boundaries at the Channel Edge + +No internal identifier, traceback, or transport error may cross a channel boundary to the user. Map at the source: + +- `LlmError::BadGateway` / raw HTTP 5xx → "provider temporarily unavailable" +- `LlmError::ContextOverflow` / HTTP 413 → "message too large — summarizing" (every direct-HTTP provider must detect 413) +- Filesystem / workspace errors → "can't access your workspace file" (never expose paths) +- Orchestrator worker tracebacks → "internal task failed" + opaque job id for correlation + +Forbidden in user-facing output: raw 5xx codes, Python tracebacks, absolute paths (`/workspace/...`, `/home/...`), internal file names (`.system/`, `AGENTS.md`, `HEARTBEAT.md`, `BOOTSTRAP.md`), wire-format prefixes (`message{content:`, literal `\n`). References: #2546, #2407, #2408, #2489, #2584. diff --git a/.claude/rules/lifecycle.md b/.claude/rules/lifecycle.md new file mode 100644 index 00000000000..2ab14aa7d88 --- /dev/null +++ b/.claude/rules/lifecycle.md @@ -0,0 +1,29 @@ +--- +paths: + - "src/channels/**" + - "src/tools/wasm/**" + - "src/tools/mcp/**" + - "src/bridge/**" +--- +# Discovery vs. Activation + +**Installed is not active.** These are distinct states with distinct triggers: + +- **Discovery** — boot-time scan that enumerates what the user has installed (WASM channels, MCP servers, extensions). Produces a manifest. Side-effect-free. +- **Activation** — explicit state transition that brings an installed thing into a running state (channel opened, WS connected, hooks registered, credentials bound). + +A bug shape has recurred 5× on the WASM channel surface (#2556, #2557, #2558, #2564, #2419): activation-level behavior bound to the discovery scan. + +## Rules + +1. **Registration of runtime effects happens at activation, not discovery.** Hook registration, websocket spawn, poll task spawn, reconnect loops, long-lived state — none of these may run from `discover()`, `list_installed()`, or boot-time iteration of the manifest. + +2. **Auth rejection is terminal until credentials change.** A WASM/MCP channel that fails auth on connect MUST NOT retry in a reconnect loop. It transitions to `AuthFailed` and stays there until a credential-change event (new OAuth token, new bot token) triggers re-activation. + +3. **`list_installed` vs. `list_active` are separate queries.** Status surfaces and dispatch paths must use the query that matches their intent. A dashboard saying "N channels" must specify which. + +4. **Deactivation unwinds everything activation set up.** When a user disables or uninstalls an extension, every runtime effect must be reversed: hooks removed, WS closed, poll task cancelled, in-flight reconnect aborted, snapshot state dropped. No orphaned tasks. + +5. **Discovery is idempotent and side-effect-free.** Repeated discovery scans produce the same manifest and must not start tasks, open connections, or touch the network. + +6. **Snapshot rehydrate must re-validate.** When restoring cached state across a restart (pending auth prompts, leases, gate state), re-run the type's `::new()` constructor AND check domain invariants (not-revoked, not-expired, `thread_id` matches). Stale snapshots cause "ghost" leases and replayed auth. References: PR #2617 `restore_selected_auth_prompt`, PR #2631 paused-lease rehydrate. diff --git a/.claude/rules/review-discipline.md b/.claude/rules/review-discipline.md index 74ace30a907..45e75bf1286 100644 --- a/.claude/rules/review-discipline.md +++ b/.claude/rules/review-discipline.md @@ -46,3 +46,24 @@ cargo check --all-features # all features - `grep -rn 'super::' ` -- prefer `crate::` for cross-module imports (`super::` OK in tests/intra-module) - If you fixed a pattern bug, `grep` for other instances across `src/` - Run `scripts/pre-commit-safety.sh` to catch UTF-8, case-sensitivity, hardcoded /tmp, and logging issues + +## PR Scope Discipline + +A PR's title and body must match its diff. + +- If the title describes one change ("fix auth cancel") but the diff spans multiple layers (provider → bridge → orchestrator → Python), retitle, split, or explicitly call out the scope expansion in the body. Reference: zmanian's review on #2668 (+590/-72 under a title advertising ~10 lines). +- **Move-only refactors** must state "no behavior change" in the body and file a follow-up issue for every pre-existing correctness/perf concern surfaced during the move. Don't silently fix things mid-move — it's unreviewable. Pattern across #2628, #2680, #2687. +- After a refactor that relocates or renames code, grep for `.md` and `CLAUDE.md` references to the moved paths and update them in the same PR. `web/CLAUDE.md` pointing at `server.rs` after its contents moved (#2687) is a review fail. + +## Guardrail Scripts Are Code + +Lint/boundary/safety scripts under `scripts/` are enforcement infrastructure. They must: + +- **Have regression tests** exercising every documented exemption (e.g. `dispatch-exempt`, `silent-ok`, `#[cfg(test)]` skip). +- **Be included in the CI `has_code` / diff-filter** that gates required checks — a guardrail that isn't run on changes to itself can be weakened without anyone noticing. Reference: PR #2647. +- **Parse grouped / multiline Rust syntax** when inspecting imports. Line-based regex misses `use crate::channels::web::{handlers::auth::...}` and shim re-exports. +- **Actually enforce their documented skips** — if the exemption says "skips `#[cfg(test)]` blocks", the scanner must track brace nesting, not match a regex on the first line. + +## Stale Comments After Refactors + +Doc strings and inline comments are part of the contract. A comment that says "strips trailing punctuation + whitespace" while the code only strips periods (#2701 `src/bridge/router.rs`) is a bug report waiting to happen. When you change behavior in a function, re-read its docstring and adjacent comments — update or delete them in the same change. diff --git a/.claude/rules/safety-and-sandbox.md b/.claude/rules/safety-and-sandbox.md index 50e1135e0ba..4ce91432fb3 100644 --- a/.claude/rules/safety-and-sandbox.md +++ b/.claude/rules/safety-and-sandbox.md @@ -4,6 +4,11 @@ paths: - "src/sandbox/**" - "src/secrets/**" - "src/tools/wasm/**" + - "src/bridge/**" + - "src/channels/**" + - "src/workspace/**" + - "src/agent/**" + - "crates/ironclaw_engine/**" --- # Safety Layer & Sandbox Rules @@ -32,3 +37,32 @@ The shell tool scrubs sensitive env vars before executing commands. The sanitize ## Zero-Exposure Credential Model Secrets are stored encrypted on the host and injected into HTTP requests by the proxy at transit time. Container processes never see raw credential values. + +## Every New Ingress Scans Before Storage or LLM + +Mirror of CLAUDE.md's "Everything Goes Through Tools" rule: every new surface that accepts external data into the system — user messages, webhook payloads, memory writes, URL fetches, file ingestion — must run the matching safety scan on the **pre-transform, pre-injection** payload before the data reaches the LLM or the database. + +Recurring bug shape: a new code path is added, and the safety scan is skipped, applied post-injection (too late), or applied to the wrong stage. References: #2491 Engine v2 inbound, #2676 WASM URL post-injection, #2470 memory write layer. + +Rules: + +- **Inbound user text** → `safety_layer.scan_inbound_for_secrets()` before engine dispatch. +- **Tool output** (existing) → sanitize + leak detector before LLM, wrapped in `` XML. +- **LLM response** → leak detector before user delivery. +- **Memory / workspace writes** → injection scan on the pre-storage value. Never on the transformed/rendered value. +- **URL fetches** → leak-pattern scan on the resolved URL **before** credential injection; not on the post-injection URL. + +A newly added ingress handler (HTTP route, webhook receiver, `Channel::send_message` impl) that reaches an LLM call or DB write without calling a `safety_layer.*` function on the payload is a review-blocker. + +## Bounded Resources + +User-controlled inputs must not grow unbounded. Apply caps at the boundary: + +- **Interners, caches, accumulators** — hard size limit (entries + total bytes), eviction policy documented. PR #2673 model-name interner: 256-byte value cap, 1024-entry cap. +- **File reads in HTTP handlers** — stream with `tokio::fs::File::open` + `ReaderStream`; never `tokio::fs::read`, which buffers the whole file. Reference: #2633 item 3. +- **Fan-out scans (portfolio addresses, batch tool calls)** — position cap + O(n) algorithm required, not O(n²). Tool-specific fuel limits, not global raises. Reference: #2710 portfolio tool. +- **Tokio task fan-out** — in-flight dedup or bounded semaphore on spawns driven by user input (PR #2702). + +## Cache Keys Must Be Complete + +A cache whose stored value depends on input X must include a stable representation of X in its key. `WorkspacePool` keyed on `user_id` but applying token-specific `workspace_read_scopes` before caching (#2633 item 1) froze the first token's scopes for every subsequent request — canonical example. Rule: if `get_or_create(a, b)` inserts using only `a` but `b` affects the stored value, that is a bug. diff --git a/.claude/rules/tool-evidence.md b/.claude/rules/tool-evidence.md new file mode 100644 index 00000000000..0d62dba953f --- /dev/null +++ b/.claude/rules/tool-evidence.md @@ -0,0 +1,37 @@ +--- +paths: + - "src/agent/**" + - "src/tools/**" + - "src/channels/web/**" + - "crates/ironclaw_engine/**" +--- +# Tool Evidence and Side-Effect Verification + +The most dangerous user-visible bug class is **claim/evidence drift**: the agent narrates "message sent" / "file attached" / "tool installed" with no corresponding side effect. The agent-facing half of this rule lives in `crates/ironclaw_engine/prompts/codeact_postamble.md` ("Claims in FINAL() need tool evidence"). This file documents the *target* code invariants that make the rule enforceable at the tool layer. Several of these are aspirational — where that's the case, it's called out inline so new contributors don't assume an enforcement mechanism exists. + +## Engine v2 Side-Effect Gate (target invariant) + +Engine v2 should classify user turns for side-effect intent (send / save / install / schedule / post / write / delete) and a model-final turn that lacks at least one successful tool call matching the intent should be rejected before it reaches the user — surfacing "action not performed" instead of the agent's narration. + +**Current state:** only a soft tool-intent *nudge* exists in `crates/ironclaw_engine/src/executor/loop_engine.rs`; there is no hard rejection gate. Adding one belongs on the engine roadmap. Until it lands, the prompt-side guidance in `codeact_postamble.md` is the primary defence. Reference: #2544, #2580, #2582, #2541, #2447. + +## Empty-Fast Outputs Are Errors (tool-author convention) + +A tool that completes in `< 1 ms` **and** returns empty content is almost always a silent failure. Tool authors must treat this shape as an error at the tool implementation: return a descriptive `ToolError::ExecutionFailed("empty result from : …")` (or the closest matching variant in `src/tools/tool.rs`) rather than a successful empty `ToolOutput`. + +**Current state:** the dispatcher does not today enforce a generic "fast + empty = error" rule, and `ToolOutput` / `ActionRecord` do not carry a dedicated byte-count field — timing is captured on `ToolOutput.duration` and content size is implicit in the serialized `result`. A future enforcement path (dedicated `ToolError::EmptyResult` variant, explicit byte-count on `ActionRecord`, UI suppression of the success checkmark on zero-byte output) is desirable; when adding those, update this rule to cite the concrete APIs. Reference: #2545. + +## External-Effect Tools Must Read Back + +A tool whose side effect is visible only to an external system (Telegram send, Slack post, file write, extension install, OAuth completion) MUST read back the effect before returning success: + +- `telegram_send` → capture and return `message_id` from the API response; error if the response lacks one. +- `file_write` → re-stat and return the actual byte count; error on mismatch. +- `extension_install` → call `extensions_list` and assert the new extension is present and active. +- OAuth completion → perform a minimal authenticated read against the provider before declaring success. + +A tool without a read-back path is claim-only. There is no canonical `unverified` field on `ToolOutput` today — when you write a claim-only tool, include an `unverified: true` key in the JSON `result` body and a clear hedge in the text output ("submitted; delivery not confirmed") so downstream layers and the user can see it. If/when a first-class field lands on `ToolOutput`, migrate to it. References: #2411 Telegram token Save, #2543 Linear MCP OAuth, #2586 Slack Install. + +## Setup UI Round-Trip + +Save / Install / Connect buttons in the setup UI must issue a read-back verification immediately after the write succeeds and render the read-back value (or explicit error) to the user — not a local optimistic checkmark. Install actions dispatch through `ToolDispatcher::dispatch` and surface the resulting `ActionRecord`. A UI success state with no corresponding backend read-back is the same bug class as agent claim drift. References: #2411, #2534, #2543, #2586. diff --git a/.claude/rules/types.md b/.claude/rules/types.md index 7d64b5e40c7..45738bb8262 100644 --- a/.claude/rules/types.md +++ b/.claude/rules/types.md @@ -6,26 +6,23 @@ paths: --- # Typed Internals — No Stringly-Typed Values Inside the System -**Internal values must have types that reflect what they mean.** Raw `String` -is the boundary type — accepted from user input, JSON/HTTP payloads, the -database, and untrusted external APIs — and it should be converted to a -domain type as soon as possible. Everything that moves between internal -modules should carry a type that makes misuse a compile error. - -Concretely: - -- **Identifiers** → newtypes (`CredentialName`, `ExtensionName`, `ThreadId`, - `UserId`). Never `String`, `&str`, or `uuid::Uuid` alone. +**Every domain value gets a specialized type.** Raw `String` is a boundary +format — accepted from user input, JSON/HTTP payloads, the database, +and untrusted external APIs — and converted to a domain type at the +earliest opportunity. Everything flowing between internal modules must +carry a type that makes misuse a compile error. + +- **Identifiers** → newtypes (`CredentialName`, `ExtensionName`, + `ThreadId`, `UserId`). Never `String`, `&str`, or `uuid::Uuid` alone. - **Fixed small sets** → enums with `#[serde(rename_all = "snake_case")]` or explicit `#[serde(rename = "...")]`. Never compare strings like `status == "in_progress"`. - **Units, shapes, modes** → enums (`SandboxPolicy`, `ExecutionMode`, `ThreadState`). Never booleans-plus-magic-strings. -If two values have the same shape (`String`, `u64`, whatever) but different -meanings, they must be different types. The compiler is the only durable -enforcement of "don't mix these up" — comments, naming, and code review -are not. +Two values with the same shape but different meanings must be +different types. The compiler is the only durable enforcement — +comments, naming, and code review are not. ## Why @@ -38,94 +35,152 @@ Identity confusion has shipped four times in recent history: | #2512 | Slack relay OAuth | state lookup compared strings across two callers that had diverged | | #2574 | auth-gate display | inline fallback re-derived extension name, returned `telegram_bot_token` where `telegram` was expected | -All four bugs are the same shape: a string-typed value passes through more -than one layer, one layer treats it as one meaning, another treats it as a -different meaning, and the compiler has nothing to say. Newtypes would -have made each of these a type error. +Same shape every time: a string-typed value passes through more than +one layer, one layer treats it as one meaning, another as a different +meaning, and the compiler has nothing to say. Newtypes would have +turned each into a compile error. -## The Extension/Auth identity invariant +## Extension/Auth identity invariant -See `CLAUDE.md` → "Extension/Auth Invariants" for the routing rules. The +See `CLAUDE.md` → "Extension/Auth Invariants" for routing rules. The types live in `crates/ironclaw_common/src/identity.rs`: -- [`CredentialName`] — backend secret identity (e.g. `telegram_bot_token`, - `google_oauth_token`). Used for secrets-store keys, gate resume - payloads, credential injection. +- [`CredentialName`] — backend secret identity (e.g. + `telegram_bot_token`, `google_oauth_token`). Used for secrets-store + keys, gate resume payloads, credential injection. - [`ExtensionName`] — user-facing installed extension/channel identity (e.g. `telegram`, `gmail`). Used for onboarding UI, setup/configure routing, Python action dispatch. Hyphens fold to underscores at - construction time because extensions are invoked as Python attribute - accesses. + construction time. Never cast between them. Never recompute one from the other by string -manipulation — resolve through `AuthManager::resolve_extension_name_for_auth_flow`. - -## When to add a newtype - -Add one when **all** of the following are true: +manipulation — resolve through +`AuthManager::resolve_extension_name_for_auth_flow`. -1. The value is a *name*, *id*, or *key* — something whose shape is - incidental to its meaning. -2. It flows through **more than one module** or crosses a **type - boundary** (struct field, function parameter, return type). -3. Mixing it up with another same-shape value would be a **silent - runtime bug**, not a compile error. +## Canonical newtype template -If all three hold, make it a newtype. Put it in -`crates/ironclaw_common/src/identity.rs` (or a module-local spot if its -blast radius is genuinely one crate). - -### Newtype template - -Use `#[serde(transparent)]` so on-wire and on-disk representation stays a -plain string — legacy persisted rows must keep deserializing cleanly. -Validate at explicit construction sites (`new`, `try_from`, `from_str`), -not on the wire. Provide a `from_trusted(String)` escape hatch for values -sourced from a typed upstream (DB row, registry entry) where the caller -already trusts the shape. +New newtypes use this single shape. Validation happens on the wire +(`try_from`) and at explicit construction (`::new`), both routed +through a shared `validate(&str)`: ```rust #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(transparent)] +#[serde(try_from = "String")] pub struct MyId(String); impl MyId { - pub fn new(raw: impl AsRef) -> Result { ... } - pub fn from_trusted(raw: String) -> Self { Self(raw) } + fn validate(s: &str) -> Result<(), MyIdError> { /* ... */ } + + pub fn new(raw: impl Into) -> Result { + let s = raw.into(); + Self::validate(&s)?; + Ok(Self(s)) + } + pub fn as_str(&self) -> &str { &self.0 } + pub fn into_inner(self) -> String { self.0 } } -impl AsRef for MyId { ... } // explicit via `.as_ref()` -impl TryFrom for MyId { ... } // validating -impl From for String { ... } // infallible -// Deliberately no `From` or `From<&str>` — infallible +impl TryFrom for MyId { + type Error = MyIdError; + fn try_from(value: String) -> Result { + Self::validate(&value)?; + Ok(Self(value)) + } +} + +impl AsRef for MyId { + fn as_ref(&self) -> &str { &self.0 } +} + +impl fmt::Display for MyId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } +} + +impl From for String { + fn from(id: MyId) -> Self { id.0 } +} +// Deliberately no `From` / `From<&str>` — infallible // conversion would silently bypass validation. // Deliberately no `Deref` — auto-deref would let -// `&my_id` silently coerce to `&str`, which is the implicit-conversion -// pattern this whole module exists to prevent. Use `.as_str()` / -// `.as_ref()` at the call site so the boundary is visible. +// `&id` silently coerce to `&str`, which is the implicit-conversion +// pattern this rule exists to prevent. ``` -## Don'ts - -- **Don't add `From<&str>` or `From` for an identity newtype.** - That relaxes the invariant. If a caller has a raw string, they should - have to choose `new` (validate) or `from_trusted` (documented opt-out) - — the choice itself is the audit trail. -- **Don't compare a newtype against a format-string-built `String`.** - If you find yourself writing `format!("{}_token", extension_name) == - credential_name.as_str()`, you've rebuilt the bug #2574 fixed. Route - through the shared resolver instead. -- **Don't use `#[serde(try_from = "String")]` for identity newtypes - without a migration plan.** Existing persisted rows may not satisfy the - current rule; `transparent` + explicit validation at construction - preserves them while still gaining type distinctness. -- **Don't match on string literals for a value that should be an enum.** - `match status.as_str() { "ready" => ... }` means status should be an - enum. Fix the type. -- **Don't downgrade a typed value back to `String` except at a system - boundary.** A `String` returned from an internal function is a - regression — return the type. +Rules baked into the template: + +- `#[serde(try_from = "String")]` — wire validation matches + construction; do not use `#[serde(transparent)]` on a newly added + validated newtype. +- Shared `validate(&str)` — one source of truth for the invariant. +- `impl Into` on `new` — avoids a clone for owned-`String` + callers; still accepts `&str`. +- Explicit `as_str()` / `as_ref()` / `into_inner()` — every boundary + crossing is visible in the source. +- Match-on-string-literals means the type should be an enum. Fix the + type. +- Don't return `String` from an internal function — return the newtype. +- Don't compare a newtype against a format-string-built `String`. + `format!("{}_token", extension_name) == credential_name.as_str()` + rebuilds the bug #2574 fixed — route through the shared resolver. + +## Legacy exception — `#[serde(transparent)]` identity types + +`CredentialName` and `ExtensionName` predate this template. They use +`#[serde(transparent)]` + derived `Deserialize` and deliberately do +*not* revalidate on the wire — the `serde_does_not_revalidate` test +in `identity.rs` locks that contract in, because legacy persisted rows +may not satisfy the current rule. + +Don't "migrate" them to `try_from` — you will break rehydration of +pre-existing DB rows. New code must still construct them through +`::new()`. The `from_trusted(String)` helper on those two types is a +legacy escape hatch for values handed over from a typed upstream (DB +row, parsed `ExtensionManifest` field); do not copy that pattern onto +new newtypes. + +Review flag: `#[serde(transparent)]` on a newly added validated +newtype, or a `from_trusted` helper on anything other than the two +legacy identity types. References: PR #2685, PR #2681, PR #2687. + +## Byte-length vs. character-length + +A validator using `s.len()` measures bytes. If the error message says +"N characters", switch to `s.chars().count()`. Pick one and match the +message. + +## Wire-stable enums + +Enums serialized over the network or persisted to the DB are part of +the public contract. + +Derive `Serialize` + `Deserialize` with +`#[serde(rename_all = "snake_case")]`. Add enum helper methods for +wire/UI rendering — never `format!("{:?}", ...)`. `format!("{:?}", +status)` emits `"InProgress"` while snake_case serde emits +`"in_progress"`; the drift has already shipped (#2669 `mission_list` +vs `mission_complete`). + +**Migrations from `String` must preserve every historical value.** +When replacing a stringly-typed wire field with an enum, add +`#[serde(alias = "...")]` for every value any running producer still +emits. Grep the tree; check staging/production logs. Add a round-trip +deserialization test with raw legacy JSON. Reference: PR #2678 +`JobResultStatus` rejected `"error"` / `"stuck"` / case variants on +rollout. + +## Wire-contract field naming + +A boolean or enum exposed to the web UI has exactly one canonical +snake_case name on the wire (`engine_v2_enabled`) and one canonical +JS accessor (`window.bootstrap.engineV2Enabled`). Reading the same +value from ad-hoc `data.engine_v2` inside a surface file is a bug — +it will diverge. Delete duplicate fields in response structs (PR +#2665 shipped both `engine_v2` and `engine_v2_enabled` in one struct). +Frontend reads the flag from bootstrap globals, not from response +bodies. References: PR #2683, PR #2702. ## Applies to diff --git a/crates/ironclaw_engine/prompts/codeact_postamble.md b/crates/ironclaw_engine/prompts/codeact_postamble.md index 5cdca91b3c2..95c0f975225 100644 --- a/crates/ironclaw_engine/prompts/codeact_postamble.md +++ b/crates/ironclaw_engine/prompts/codeact_postamble.md @@ -76,3 +76,22 @@ proposals with their APY, gain, and cost — not a count. Build up the answer string with real data from tool results (`proposal["rationale"]`, `proposal["projected_annual_gain_usd"]`, etc.), then call `FINAL()` once with the complete Markdown. + +## Claims in FINAL() need tool evidence + +This rule is only about what your `FINAL()` answer asserts — it does not +restrict tool calls. Call as many tools as the task needs. + +If `FINAL()` says you did something — "sent", "saved", "installed", +"posted", "scheduled", "wrote", "deleted" — the same answer must cite +the tool result that proves it (e.g. `message_id`, `bytes_written`, +`external_id`, `job_id`). If no tool produced that evidence, say what +actually happened instead: "Tried to install X, cargo returned error Y." + +```repl +result = await telegram_send(chat_id=chat, text=body) +if result and result.get("message_id"): + FINAL(f"Sent (message_id={result['message_id']}).") +else: + FINAL(f"Tried to send but Telegram did not confirm delivery: {result}") +``` diff --git a/tests/e2e_live.rs b/tests/e2e_live.rs index 4516f59f09c..ed7c3ca6efc 100644 --- a/tests/e2e_live.rs +++ b/tests/e2e_live.rs @@ -53,8 +53,13 @@ mod live_tests { ); // The agent should have used the shell tool to install/run zizmor. + // Tool events now carry args (e.g. `"shell(cmd)"`) via + // `format_action_display_name` in `src/bridge/router.rs`, so match both + // the bare name and the argument-prefixed form. assert!( - tools.iter().any(|t| t == "shell"), + tools + .iter() + .any(|t| t == "shell" || t.starts_with("shell(")), "Expected shell tool to be used for running zizmor, got: {tools:?}" ); @@ -131,10 +136,16 @@ mod live_tests { // V2 without auto-approve hits an approval gate for shell/tool_install. // The response may be the approval prompt itself rather than agent output. - // Verify the agent at least attempted a relevant action. + // Verify the agent at least attempted a relevant action. Tool events + // carry args (e.g. `"shell(cmd)"`) via `format_action_display_name`, so + // accept either the bare name or the argument-prefixed form. let attempted_relevant_tool = tools.iter().any(|t| { t == "shell" + || t.starts_with("shell(") || t == "tool_install" + || t.starts_with("tool_install(") + || t == "tool-install" + || t.starts_with("tool-install(") || t.starts_with("tool_search") || t.starts_with("skill_search") }); @@ -399,10 +410,18 @@ mod live_tests { // The agent must NOT have run a tool_install / tool_activate // recovery loop — that's the bad behaviour the post-flight - // detector eliminates. - let bad_recovery = phase_a_tools - .iter() - .any(|t| t == "tool_install" || t == "tool_activate" || t == "tool-install"); + // detector eliminates. Match both bare names and the + // argument-prefixed `"(args)"` form emitted by + // `format_action_display_name`; an exact-match check would silently + // miss `"tool_install(foo)"` and turn this into a false negative. + let bad_recovery = phase_a_tools.iter().any(|t| { + t == "tool_install" + || t.starts_with("tool_install(") + || t == "tool_activate" + || t.starts_with("tool_activate(") + || t == "tool-install" + || t.starts_with("tool-install(") + }); assert!( !bad_recovery, "Phase A: agent ran a tool_install/tool_activate recovery loop instead \ @@ -507,11 +526,16 @@ mod live_tests { "[DriveAuthGate][Phase B] Tools attempted ({}): {phase_b_tools:?}", phase_b_tools.len() ); + // Match bare and argument-prefixed names; see the Phase A comment. let phase_b_recovery = phase_b_tools.iter().any(|t| { t == "tool_install" + || t.starts_with("tool_install(") || t == "tool-install" + || t.starts_with("tool-install(") || t == "tool_activate" + || t.starts_with("tool_activate(") || t == "secret_list" + || t.starts_with("secret_list(") || t.starts_with("tool_search") }); assert!( diff --git a/tests/fixtures/llm_traces/live/zizmor_scan.json b/tests/fixtures/llm_traces/live/zizmor_scan.json index 9166a2a7cd9..62d9d507aa7 100644 --- a/tests/fixtures/llm_traces/live/zizmor_scan.json +++ b/tests/fixtures/llm_traces/live/zizmor_scan.json @@ -16,15 +16,17 @@ "type": "tool_calls", "tool_calls": [ { - "id": "chatcmpl-tool-8917b4d21dbf0f03", - "name": "tool_search", + "id": "call_b21eb60bdc2d485083cddf80", + "name": "shell", "arguments": { - "query": "zizmor" + "command": "which zizmor || cargo install --version 1.11.1 zizmor 2>&1 || echo \"Not found, checking alternatives...\"", + "timeout": 180, + "workdir": "" } } ], - "input_tokens": 14524, - "output_tokens": 172 + "input_tokens": 19610, + "output_tokens": 104 } }, { @@ -36,21 +38,23 @@ "type": "tool_calls", "tool_calls": [ { - "id": "chatcmpl-tool-8e89a891c7087c33", + "id": "call_ed47a9d7681c4037abc75019", "name": "shell", "arguments": { - "command": "which zizmor || command -v zizmor || echo \"not found\"" + "command": "cd /tmp && git clone --depth 1 https://github.com/zizmorcore/zizmor.git 2>&1", + "timeout": 120, + "workdir": "" } } ], - "input_tokens": 14633, - "output_tokens": 142 + "input_tokens": 19717, + "output_tokens": 127 }, "expected_tool_results": [ { - "tool_call_id": "chatcmpl-tool-8917b4d21dbf0f03", - "name": "tool_search", - "content": "\n{\n \"count\": 0,\n \"results\": [],\n \"searched_online\": false\n}\n" + "tool_call_id": "call_b21eb60bdc2d485083cddf80", + "name": "shell", + "content": "{'exit_code': 0, 'output': '/home/user/.cargo/bin/zizmor\\n', 'sandboxed': False, 'success': True}" } ] }, @@ -60,16 +64,26 @@ "min_message_count": 6 }, "response": { - "type": "text", - "content": "\n\nGreat! zizmor is already installed at `/Users/coder/.cargo/bin/zizmor`. \n\nTo run it, I need to know what you'd like to audit:\n1. **Current directory** - audit the GitHub Actions workflows in this repo\n2. **Specific repository** - audit another GitHub repo (e.g., `github:user/repo`)\n3. **Local workflow files** - audit specific `.yml` files\n\nWhich would you like to do? Or if you're in a repo with GitHub Actions workflows, I can just run it on the current directory.\n[Called tool `list_dir` with arguments: {\"path\":\".\"}]", - "input_tokens": 14778, - "output_tokens": 188 + "type": "tool_calls", + "tool_calls": [ + { + "id": "call_237e079bdafc4345a4347875", + "name": "shell", + "arguments": { + "command": "zizmor --version", + "timeout": 30, + "workdir": "" + } + } + ], + "input_tokens": 19827, + "output_tokens": 91 }, "expected_tool_results": [ { - "tool_call_id": "chatcmpl-tool-8e89a891c7087c33", + "tool_call_id": "call_ed47a9d7681c4037abc75019", "name": "shell", - "content": "\n{\n \"exit_code\": 0,\n \"output\": \"/Users/coder/.cargo/bin/zizmor\\n\",\n \"sandboxed\": false,\n \"success\": true\n}\n" + "content": "{'exit_code': 128, 'output': \"fatal: destination path 'zizmor' already exists and is not an empty directory.\\n\", 'sandboxed': False, 'success': False}" } ] }, @@ -79,16 +93,26 @@ "min_message_count": 8 }, "response": { - "type": "text", - "content": "\n\nI can see there's a `.github/` directory which likely contains GitHub Actions workflows. Let me check what's in there and then run zizmor on this repository.\n[Called tool `list_dir` with arguments: {\"path\":\".github\"}]", - "input_tokens": 16007, - "output_tokens": 86 + "type": "tool_calls", + "tool_calls": [ + { + "id": "call_d2cd4b080ac24056a0c124b8", + "name": "shell", + "arguments": { + "command": "ls -la /tmp/zizmor", + "timeout": 30, + "workdir": "" + } + } + ], + "input_tokens": 19905, + "output_tokens": 152 }, "expected_tool_results": [ { - "tool_call_id": "00000001B", - "name": "list_dir", - "content": "\n{\n \"count\": 70,\n \"entries\": [\n \".claude/\",\n \".git/\",\n \".githooks/\",\n \".github/\",\n \"channels-src/\",\n \"crates/\",\n \"deploy/\",\n \"docker/\",\n \"docs/\",\n \"fuzz/\",\n \"migrations/\",\n \"registry/\",\n \"scripts/\",\n \"skills/\",\n \"src/\",\n \"target/\",\n \"tests/\",\n \"tools-src/\",\n \"wit/\",\n \"wix/\",\n \".dockerignore (67B)\",\n \".env.example (12.5KB)\",\n \".gitattributes (50B)\",\n \".gitignore (593B)\",\n \"AGENTS.md (5.4KB)\",\n \"CHANGELOG.md (69.2KB)\",\n \"CLAUDE.md (14.7KB)\",\n \"CONTRIBUTING.md (5.3KB)\",\n \"COVERAGE_PLAN.md (32.2KB)\",\n \"Cargo.lock (227.0KB)\",\n \"Cargo.toml (9.1KB)\",\n \"Dockerfile (2.3KB)\",\n \"Dockerfile.test (1.5KB)\",\n \"Dockerfile.worker (2.3KB)\",\n \"FEATURE_PARITY.md (30.5KB)\",\n \"LICENSE-APACHE (10.5KB)\",\n \"LICENSE-MIT (1.0KB)\",\n \"README.ja.md (16.7KB)\",\n \"README.md (13.7KB)\",\n \"README.ru.md (20.4KB)\",\n \"README.zh-CN.md (13.3KB)\",\n \"build.rs (8.5KB)\",\n \"clippy.toml (537B)\",\n \"codecov.yml (219B)\",\n \"deny.toml (1.8KB)\",\n \"docker-compose.yml (524B)\",\n \"engine_trace_20260405T100717.json (20.9KB)\",\n \"ironclaw.bash (100.3KB)\",\n \"ironclaw.fish (69.9KB)\",\n \"ironclaw.png (266.7KB)\",\n \"ironclaw.zsh (84.0KB)\",\n \"providers.json (12.8KB)\",\n \"release-plz.toml (39B)\",\n \"trace_20260309T172815.json (8.8KB)\",\n \"trace_20260310T075912.json (4.8KB)\",\n \"trace_20260311T002753.json (4.8KB)\",\n \"trace_20260311T130928.json (4.8KB)\",\n \"trace_20260311T141121.json (9.1KB)\",\n \"trace_20260311T141138.json (11.7KB)\",\n \"trace_20260311T141430.json (9.1KB)\",\n \"trace_20260311T181338.json (11.8KB)\",\n \"trace_20260311T183957.json (9.2KB)\",\n \"trace_20260311T185836.json (9.2KB)\",\n \"trace_20260311T191112.json (95.2KB)\",\n \"trace_20260311T191641.json (156.7KB)\",\n \"trace_20260311T194203.json (9.8KB)\",\n \"trace_20260311T194240.json (272.4KB)\",\n \"trace_20260311T214436.json (4.7KB)\",\n \"trace_20260311T214457.json (133.1KB)\",\n \"trace_20260311T221512.json (7.6KB)\"\n ],\n \"path\": \"/Users/coder/ironclaw8\",\n \"truncated\": false\n}\n" + "tool_call_id": "call_237e079bdafc4345a4347875", + "name": "shell", + "content": "{'exit_code': 0, 'output': 'zizmor 1.24.1\\n', 'sandboxed': False, 'success': True}" } ] }, @@ -101,21 +125,23 @@ "type": "tool_calls", "tool_calls": [ { - "id": "chatcmpl-tool-a3ed7db91efcad87", - "name": "list_dir", + "id": "call_75ca1709ad454a329f16ed39", + "name": "shell", "arguments": { - "path": ".github/workflows" + "command": "/tmp/zizmor --help", + "timeout": 30, + "workdir": "/tmp" } } ], - "input_tokens": 16172, - "output_tokens": 55 + "input_tokens": 20012, + "output_tokens": 125 }, "expected_tool_results": [ { - "tool_call_id": "00000001B", - "name": "list_dir", - "content": "\n{\n \"count\": 4,\n \"entries\": [\n \"scripts/\",\n \"workflows/\",\n \"labeler.yml (3.4KB)\",\n \"pull_request_template.md (1.7KB)\"\n ],\n \"path\": \"/Users/coder/ironclaw8/.github\",\n \"truncated\": false\n}\n" + "tool_call_id": "call_d2cd4b080ac24056a0c124b8", + "name": "shell", + "content": "{'exit_code': 0, 'output': '-rwxr-xr-x 1 illia illia 24729120 Mar 8 16:53 /tmp/zizmor\\n', 'sandboxed': False, 'success': True}" } ] }, @@ -125,16 +151,26 @@ "min_message_count": 12 }, "response": { - "type": "text", - "content": "\n\nPerfect! There are 14 GitHub Actions workflow files. Let me run zizmor to audit them for security issues:\n[Called tool `shell` with arguments: {\"command\":\"zizmor .github/workflows --format pretty\", \"timeout\": 120}]", - "input_tokens": 16453, - "output_tokens": 89 + "type": "tool_calls", + "tool_calls": [ + { + "id": "call_95972591c0fb4785bd8ddcdb", + "name": "glob", + "arguments": { + "max_results": 10, + "path": ".", + "pattern": "**/.github/workflows/*.yml" + } + } + ], + "input_tokens": 21614, + "output_tokens": 103 }, "expected_tool_results": [ { - "tool_call_id": "chatcmpl-tool-a3ed7db91efcad87", - "name": "list_dir", - "content": "\n{\n \"count\": 14,\n \"entries\": [\n \"claude-review.yml (5.3KB)\",\n \"code_style.yml (3.3KB)\",\n \"coverage.yml (6.9KB)\",\n \"docker.yml (4.3KB)\",\n \"e2e.yml (4.0KB)\",\n \"pr-label-classify.yml (683B)\",\n \"pr-label-scope.yml (385B)\",\n \"regression-test-check.yml (7.4KB)\",\n \"release-plz-batch-summary.yml (1.5KB)\",\n \"release-plz.yml (2.2KB)\",\n \"release.yml (23.3KB)\",\n \"staging-ci.yml (21.5KB)\",\n \"staging-promotion-metadata.yml (2.7KB)\",\n \"test.yml (7.8KB)\"\n ],\n \"path\": \"/Users/coder/ironclaw8/.github/workflows\",\n \"truncated\": false\n}\n" + "tool_call_id": "call_75ca1709ad454a329f16ed39", + "name": "shell", + "content": "{'exit_code': 0, 'output': 'Static analysis for GitHub Actions\\n\\nUsage: zizmor [OPTIONS] ...\\n\\nArguments:\\n ...\\n The inputs to audit.\\n \\n These can be individual workflow filenames, action definitions (typically `action.yml`), entire directories, or a `user/repo` slug for a GitHub repository. In the latter case, a `@ref` can be appended to audit the repository at a particular git reference state.\\n\\nOptions:\\n --lsp\\n Run in language server mode (EXPERIMENTAL).\\n \\n This flag cannot be used with any other flags.\\n\\n -p, --pedantic\\n Emit \\'pedantic\\' findings.\\n \\n This is an alias for --persona=pedantic.\\n\\n --persona \\n The persona to use while auditing\\n\\n Possible values:\\n - auditor: The \"auditor\" persona (false positives OK)\\n - pedantic: The \"pedantic\" persona (code smells OK)\\n - regular: The \"regular\" persona (minimal false positives)\\n \\n [default: regular]\\n\\n -o, --offline\\n Perform only offline operations.\\n \\n This disables all online audit rules, and prevents zizmor from auditing remote repositories.\\n \\n [env: ZIZMOR_OFFLINE=]\\n\\n --gh-token \\n The GitHub API token to use [env: GH_TOKEN or GITHUB_TOKEN or ZIZMOR_GITHUB_TOKEN]\\n\\n --gh-hostname \\n The GitHub Server Hostname. Defaults to github.com\\n \\n [env: GH_HOST=]\\n [default: github.com]\\n\\n --no-online-audits\\n Perform only offline audits.\\n \\n This is a weaker version of `--offline`: instead of completely forbidding all online operations, it only disables audits that require connectivity.\\n \\n [env: ZIZMOR_NO_ONLINE_AUDITS=]\\n\\n -v, --verbose...\\n Increase logging verbosity\\n\\n -q, --quiet...\\n Decrease logging verbosity\\n\\n --no-progress\\n Don\\'t show progress bars, even if the terminal supports them\\n\\n --format \\n The output format to emit. By default, cargo-style diagnostics will be emitted\\n\\n Possible values:\\n - plain: cargo-style output\\n - json: JSON-formatted output (currently v1)\\n - json-v1: \"v1\" JSON format\\n - sarif: SARIF-formatted output\\n - github: GitHub Actions workflow command-formatted output\\n \\n [default: plain]\\n\\n --render-links \\n Whether to render OSC 8 links in the output.\\n \\n This affects links under audit IDs, as well as any links produced by audit rules.\\n \\n Only affects `--format=plain` (the default).\\n\\n Possible values:\\n - auto: Render OSC 8 links in output if support is detected\\n - always: Always render OSC 8 links in output\\n - never: Never render OSC 8 links in output\\n \\n [env: ZIZMOR_RENDER_LINKS=]\\n [default: auto]\\n\\n --show-audit-urls \\n Whether to render audit URLs in the output, separately from any URLs embedded in OSC 8 links.\\n \\n Only affects `--format=plain` (the default).\\n\\n Possible values:\\n - auto: Render audit URLs in output automatically based on output format and runtime context\\n - always: Always render audit URLs in output\\n - never: Never render audit URLs in output\\n \\n [env: ZIZMOR_SHOW_AUDIT_URLS=]\\n [default: auto]\\n\\n --color \\n Control the use of color in output\\n\\n Possible values:\\n - auto: Use color output if the output supports it\\n - always: Force color output, even if the output isn\\'t a terminal\\n - never: Disable color output, even if the output is a compatible terminal\\n\\n -c, --config \\n The configuration file to load. This loads a single configuration file across all input groups, which may not be what you intend\\n \\n [env: ZIZMOR_CONFIG=]\\n\\n --no-config\\n Disable all configuration loading\\n\\n --no-exit-codes\\n Disable all error codes besides success and tool failure\\n\\n --min-severity \\n Filter all results below this severity\\n \\n [possible values: informational, low, medium, high]\\n\\n --min-confidence \\n Filter all results below this confidence\\n \\n [possible values: low, medium, high]\\n\\n --cache-dir \\n The directory to use for HTTP caching. By default, a host-appropriate user-caching directory will be used\\n\\n --collect ...\\n Control which kinds of inputs are collected for auditing.\\n \\n By default, all workflows and composite actions are collected, while honoring `.gitignore` files.\\n\\n Possible values:\\n - all: Collect all possible inputs, ignoring `.gitignore` files\\n - default: Collect all possible inputs, respecting `.gitignore` files\\n - workflows: Collect workflows\\n - actions: Collect action definitions (i.e. `action.yml`)\\n - dependabot: Collect Dependabot configuration files (i.e. `dependabot.yml`)\\n \\n [default: default]\\n\\n --strict-collection\\n Fail instead of warning on syntax and schema errors in collected inputs\\n\\n --completions \\n Generate tab completion scripts for the specified shell\\n\\n Possible values:\\n - bash: Bourne Again `SHell` (bash)\\n - elvish: Elvish shell\\n - fish: Friendly Interactive `SHell` (fish)\\n - nushell: Nushell\\n - powershell: `PowerShell`\\n - zsh: Z `SHell` (zsh)\\n\\n --fix[=]\\n Fix findings automatically, when available (EXPERIMENTAL)\\n\\n Possible values:\\n - safe: Apply only safe fixes (the default)\\n - unsafe-only: Apply only unsafe fixes\\n - all: Apply all fixes, both safe and unsafe\\n\\n --thanks\\n Emit thank-you messages for zizmor\\'s sponsors\\n\\n -h, --help\\n Print help (see a summary with \\'-h\\')\\n\\n -V, --version\\n Print version\\n', 'sandboxed': False, 'success': True}" } ] }, @@ -147,22 +183,23 @@ "type": "tool_calls", "tool_calls": [ { - "id": "chatcmpl-tool-90714484a7b01774", + "id": "call_e304703be12b45b2becab403", "name": "shell", "arguments": { - "command": "zizmor .github/workflows --format plain", - "timeout": 120 + "command": "zizmor .github/workflows/ --format json --persona regular", + "timeout": 120, + "workdir": "." } } ], - "input_tokens": 16616, - "output_tokens": 67 + "input_tokens": 21776, + "output_tokens": 92 }, "expected_tool_results": [ { - "tool_call_id": "00000001B", - "name": "shell", - "content": "\n{\n \"exit_code\": 2,\n \"output\": \"error: invalid value 'pretty' for '--format '\\n [possible values: plain, json, json-v1, sarif, github]\\n\\nFor more information, try '--help'.\\n\",\n \"sandboxed\": false,\n \"success\": false\n}\n" + "tool_call_id": "call_95972591c0fb4785bd8ddcdb", + "name": "glob", + "content": "{'count': 10, 'duration_ms': 205, 'files': ['.github/workflows/docker.yml', '.github/workflows/e2e.yml', '.github/workflows/pr-label-classify.yml', '.github/workflows/pr-label-scope.yml', '.github/workflows/rebuild-release-image.yml', '.github/workflows/regression-test-check.yml', '.github/workflows/release.yml', '.github/workflows/replay-gate.yml', '.github/workflows/staging-ci.yml', '.github/workflows/test.yml'], 'truncated': True}" } ] }, @@ -172,16 +209,43 @@ "min_message_count": 16 }, "response": { - "type": "text", - "content": "\n\nDone! I ran zizmor on your GitHub Actions workflows and found **security issues across all 14 workflow files**. Here's the summary:\n\n## 🔴 Critical Issues Found\n\n### 1. **Unpinned Action References** (Most Common)\nEvery workflow uses actions like `actions/checkout@v6` instead of pinned SHA hashes. This is a supply chain risk - the action could change without notice.\n\n**Example fixes needed:**\n```yaml\n# Before\nuses: actions/checkout@v6\n\n# After \nuses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v6\n```\n\n### 2. **Dangerous Triggers**\n- `pr-label-classify.yml`, `pr-label-scope.yml`, `release-plz-batch-summary.yml` use `pull_request_target` which is fundamentally insecure when combined with PR code execution.\n\n### 3. **Template Injection Risks**\nMultiple workflows have code injection vulnerabilities where user-controlled input can be expanded into shell commands:\n- `docker.yml` - workflow inputs directly in run blocks\n- `release.yml` - matrix args and tags in commands\n- `regression-test-check.yml` - PR base ref in git commands\n\n### 4. **Excessive Permissions**\n- `coverage.yml` has `id-token: write` at workflow level (too broad)\n- `release.yml` has `contents: write` at workflow level\n- Most workflows use default permissions instead of explicit least-privilege\n\n### 5. **Secrets Outside Environments**\n`release-plz.yml` and `claude-review.yml` access secrets (API keys, private keys) without using dedicated GitHub Environments for protection.\n\n### 6. **Artipacked (Credential Persistence)**\nNearly all `actions/checkout` steps don't set `persist-credentials: false`, leaving Git credentials in the runner artifact.\n\n## 📊 Severity Breakdown\n- **Errors**: 70+ (unpinned uses, dangerous triggers, template injection)\n- **Warnings**: 20+ (excessive permissions, secrets-outside-env)\n- **Help/Info**: 30+ (artipacked, superfluous-actions, template-injection)\n\nWould you like me to:\n1. Generate a SARIF report for GitHub Security tab integration?\n2. Create specific PRs to fix the highest-priority issues?\n3. Focus on one workflow file at a time?\n\n[\"Generate SARIF report for GitHub\", \"Fix unpinned actions in claude-review.yml\", \"Prioritize template injection fixes\"]", - "input_tokens": 34701, - "output_tokens": 611 + "type": "tool_calls", + "tool_calls": [ + { + "id": "call_b2ad3da87b6a4bb3a2b1c479", + "name": "echo", + "arguments": { + "message": "zizmor successfully ran and found security issues. Let me parse and summarize the findings." + } + } + ], + "input_tokens": 40348, + "output_tokens": 78 }, "expected_tool_results": [ { - "tool_call_id": "chatcmpl-tool-90714484a7b01774", + "tool_call_id": "call_e304703be12b45b2becab403", "name": "shell", - "content": "\n{\n \"exit_code\": 14,\n \"output\": \"help[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/claude-review.yml:23:9\\n |\\n23 | - uses: actions/checkout@v6\\n | _________^\\n24 | | with:\\n25 | | fetch-depth: 0\\n | |________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/claude-review.yml:23:15\\n |\\n23 | - uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/claude-review.yml:28:15\\n |\\n28 | uses: anthropics/claude-code-action@v1\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nwarning[secrets-outside-env]: secrets referenced without a dedicated environment\\n --> .github/workflows/claude-review.yml:30:34\\n |\\n18 | review:\\n | ------ this job\\n...\\n30 | anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^ secret is accessed outside of a dedicated environment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#secrets-outside-env\\n\\nhelp[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/code_style.yml:10:7\\n |\\n10 | - name: Checkout repository\\n | _______^\\n11 | | uses: actions/checkout@v6\\n | |_______________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nhelp[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/code_style.yml:23:7\\n |\\n23 | - name: Checkout repository\\n | _______^\\n24 | | uses: actions/checkout@v6\\n | |_______________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nhelp[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/code_style.yml:42:7\\n |\\n42 | - name: Checkout repository\\n | _______^\\n43 | | uses: actions/checkout@v6\\n | |_______________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nhelp[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/code_style.yml:69:7\\n |\\n69 | - name: Checkout repository\\n | _______^\\n70 | | uses: actions/checkout@v6\\n | |_______________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nhelp[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/code_style.yml:85:7\\n |\\n85 | - name: Checkout repository\\n | _______^\\n86 | | uses: actions/checkout@v6\\n87 | | with:\\n88 | | fetch-depth: 0\\n | |______________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/code_style.yml:1:1\\n |\\n 1 | / name: Code Style\\n 2 | | on:\\n 3 | | pull_request:\\n... |\\n112 | | exit 1\\n113 | | fi\\n | |_____________^ default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/code_style.yml:6:3\\n |\\n 6 | / format:\\n 7 | | name: Formatting\\n 8 | | runs-on: ubuntu-latest\\n 9 | | steps:\\n... |\\n16 | | - name: Check formatting\\n17 | | run: cargo fmt --all -- --check\\n | | ^\\n | | |\\n | |_____________________________________this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/code_style.yml:19:3\\n |\\n19 | / deny-check:\\n20 | | name: cargo-deny\\n21 | | runs-on: ubuntu-latest\\n22 | | steps:\\n... |\\n25 | | - name: Run cargo deny\\n26 | | uses: EmbarkStudios/cargo-deny-action@v2\\n | | ^\\n | | |\\n | |______________________________________________this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/code_style.yml:28:3\\n |\\n28 | / clippy:\\n29 | | name: Clippy (${{ matrix.name }})\\n30 | | runs-on: ubuntu-latest\\n31 | | strategy:\\n... |\\n51 | | - name: Check lints\\n52 | | run: cargo clippy --all --benches --tests --examples ${{ matrix.flags }} -- -D warnings\\n | | ^\\n | | |\\n | |_____________________________________________________________________________________________this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/code_style.yml:54:3\\n |\\n54 | / clippy-windows:\\n55 | | name: Clippy Windows (${{ matrix.name }})\\n56 | | if: github.base_ref == 'main'\\n57 | | runs-on: windows-latest\\n... |\\n78 | | - name: Check lints\\n79 | | run: cargo clippy --all --benches --tests --examples ${{ matrix.flags }} -- -D warnings\\n | | ^\\n | | |\\n | |_____________________________________________________________________________________________this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/code_style.yml:81:3\\n |\\n81 | / no-panics:\\n82 | | name: No panics in production code\\n83 | | runs-on: ubuntu-latest\\n84 | | steps:\\n... |\\n97 | | # Roll-up job for branch protection\\n | | ^\\n | | |\\n | |_____________________________________this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/code_style.yml:98:3\\n |\\n 98 | / code-style:\\n 99 | | name: Code Style (fmt + clippy + deny)\\n100 | | runs-on: ubuntu-latest\\n101 | | if: always()\\n... |\\n112 | | exit 1\\n113 | | fi\\n | | ^\\n | | |\\n | |_____________this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/code_style.yml:105:22\\n |\\n104 | - run: |\\n | --- this run block\\n105 | if [[ \\\"${{ needs.format.result }}\\\" != \\\"success\\\" || \\\"${{ needs.clippy.result }}\\\" != \\\"success\\\" || \\\"${{ needs.deny-check.res...\\n | ^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/code_style.yml:105:67\\n |\\n104 | - run: |\\n | --- this run block\\n105 | if [[ \\\"${{ needs.format.result }}\\\" != \\\"success\\\" || \\\"${{ needs.clippy.result }}\\\" != \\\"success\\\" || \\\"${{ needs.deny-check.res...\\n | ^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/code_style.yml:105:112\\n |\\n104 | ... - run: |\\n | --- this run block\\n105 | ... if [[ \\\"${{ needs.format.result }}\\\" != \\\"success\\\" || \\\"${{ needs.clippy.result }}\\\" != \\\"success\\\" || \\\"${{ needs.deny-check.result }}...\\n | ^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/code_style.yml:105:161\\n |\\n104 | ... - run: |\\n | --- this run block\\n105 | ... if [[ \\\"${{ needs.format.result }}\\\" != \\\"success\\\" || \\\"${{ needs.clippy.result }}\\\" != \\\"success\\\" || \\\"${{ needs.deny-check.result }}\\\" != \\\"success\\\" || \\\"${{ needs.no-panics.result }}...\\n | ^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/code_style.yml:110:22\\n |\\n104 | - run: |\\n | --- this run block\\n...\\n110 | if [[ \\\"${{ needs.clippy-windows.result }}\\\" != \\\"success\\\" && \\\"${{ needs.clippy-windows.result }}\\\" != \\\"skipped\\\" ]]; then\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/code_style.yml:110:22\\n |\\n104 | - run: |\\n | --- this run block\\n...\\n110 | if [[ \\\"${{ needs.clippy-windows.result }}\\\" != \\\"success\\\" && \\\"${{ needs.clippy-windows.result }}\\\" != \\\"skipped\\\" ]]; then\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/code_style.yml:111:46\\n |\\n104 | - run: |\\n | --- this run block\\n...\\n111 | echo \\\"Windows clippy failed: ${{ needs.clippy-windows.result }}\\\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:11:13\\n |\\n11 | uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:13:13\\n |\\n13 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:24:13\\n |\\n24 | uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:26:13\\n |\\n26 | uses: EmbarkStudios/cargo-deny-action@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:43:13\\n |\\n43 | uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:45:13\\n |\\n45 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:48:13\\n |\\n48 | - uses: Swatinem/rust-cache@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:70:13\\n |\\n70 | uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:72:13\\n |\\n72 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:75:13\\n |\\n75 | - uses: Swatinem/rust-cache@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:86:13\\n |\\n86 | uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/code_style.yml:89:13\\n |\\n89 | - uses: actions/setup-python@v5\\n | ^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nhelp[superfluous-actions]: action functionality is already included by the runner\\n --> .github/workflows/code_style.yml:13:13\\n |\\n12 | - name: Install Rust\\n | ------------------ this step\\n13 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nhelp[superfluous-actions]: action functionality is already included by the runner\\n --> .github/workflows/code_style.yml:45:13\\n |\\n44 | - name: Install Rust\\n | ------------------ this step\\n45 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nhelp[superfluous-actions]: action functionality is already included by the runner\\n --> .github/workflows/code_style.yml:72:13\\n |\\n71 | - name: Install Rust\\n | ------------------ this step\\n72 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nhelp[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/coverage.yml:70:9\\n |\\n70 | - uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nhelp[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/coverage.yml:129:9\\n |\\n129 | - uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nerror[excessive-permissions]: overly broad permissions\\n --> .github/workflows/coverage.yml:35:3\\n |\\n35 | id-token: write\\n | ^^^^^^^^^^^^^^^ id-token: write is overly broad at the workflow level\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/coverage.yml:223:22\\n |\\n222 | - run: |\\n | --- this run block\\n223 | if [[ \\\"${{ needs.coverage.result }}\\\" != \\\"success\\\" || \\\"${{ needs.e2e-coverage.result }}\\\" != \\\"success\\\" ]]; then\\n | ^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/coverage.yml:223:69\\n |\\n222 | - run: |\\n | --- this run block\\n223 | if [[ \\\"${{ needs.coverage.result }}\\\" != \\\"success\\\" || \\\"${{ needs.e2e-coverage.result }}\\\" != \\\"success\\\" ]]; then\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:70:15\\n |\\n70 | - uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:72:15\\n |\\n72 | - uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:77:15\\n |\\n77 | - uses: Swatinem/rust-cache@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:82:15\\n |\\n82 | uses: taiki-e/install-action@cargo-llvm-cov\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:116:15\\n |\\n116 | uses: codecov/codecov-action@v5\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:129:15\\n |\\n129 | - uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:131:15\\n |\\n131 | - uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:136:15\\n |\\n136 | - uses: Swatinem/rust-cache@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:141:15\\n |\\n141 | uses: taiki-e/install-action@cargo-llvm-cov\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:165:15\\n |\\n165 | - uses: actions/setup-python@v5\\n | ^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:200:15\\n |\\n200 | uses: codecov/codecov-action@v5\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/coverage.yml:210:15\\n |\\n210 | uses: actions/upload-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nhelp[superfluous-actions]: action functionality is already included by the runner\\n --> .github/workflows/coverage.yml:72:15\\n |\\n72 | - uses: dtolnay/rust-toolchain@stable\\n | - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n | _________|\\n | |\\n73 | | with:\\n74 | | components: llvm-tools-preview\\n75 | | targets: wasm32-wasip2\\n | |________________________________- this step\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nhelp[superfluous-actions]: action functionality is already included by the runner\\n --> .github/workflows/coverage.yml:131:15\\n |\\n131 | - uses: dtolnay/rust-toolchain@stable\\n | - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n | _________|\\n | |\\n132 | | with:\\n133 | | components: llvm-tools-preview\\n134 | | targets: wasm32-wasip2\\n | |________________________________- this step\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nwarning[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/docker.yml:37:9\\n |\\n37 | - name: Checkout\\n | _________^\\n38 | | uses: actions/checkout@v4\\n39 | | with:\\n40 | | ref: ${{ github.event_name == 'schedule' && 'staging' || '' }}\\n | |________________________________________________________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/docker.yml:52:24\\n |\\n51 | run: |\\n | --- this run block\\n52 | VERSION=\\\"${{ steps.version.outputs.version }}\\\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[template-injection]: code injection via template expansion\\n --> .github/workflows/docker.yml:77:25\\n |\\n51 | run: |\\n | --- this run block\\n...\\n77 | if [[ -n \\\"${{ inputs.tag }}\\\" ]]; then\\n | ^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[template-injection]: code injection via template expansion\\n --> .github/workflows/docker.yml:77:25\\n |\\n51 | run: |\\n | --- this run block\\n...\\n77 | if [[ -n \\\"${{ inputs.tag }}\\\" ]]; then\\n | ^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[template-injection]: code injection via template expansion\\n --> .github/workflows/docker.yml:77:25\\n |\\n51 | run: |\\n | --- this run block\\n...\\n77 | if [[ -n \\\"${{ inputs.tag }}\\\" ]]; then\\n | ^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/docker.yml:121:23\\n |\\n115 | run: |\\n | --- this run block\\n...\\n121 | echo \\\"${{ steps.tags.outputs.tags }}\\\" | tr ',' '\\\\n'\\n | ^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/docker.yml:126:23\\n |\\n115 | run: |\\n | --- this run block\\n...\\n126 | echo \\\"${{ steps.tags.outputs.worker_tags }}\\\" | tr ',' '\\\\n'\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/docker.yml:129:36\\n |\\n115 | run: |\\n | --- this run block\\n...\\n129 | echo \\\"- version: \\\\`${{ steps.version.outputs.version }}\\\\`\\\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/docker.yml:38:15\\n |\\n38 | uses: actions/checkout@v4\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/docker.yml:85:15\\n |\\n85 | uses: docker/setup-buildx-action@v3\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/docker.yml:88:15\\n |\\n88 | uses: docker/login-action@v3\\n | ^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/docker.yml:94:15\\n |\\n94 | uses: docker/build-push-action@v6\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/docker.yml:104:15\\n |\\n104 | uses: docke\\n\\n... [truncated 1244 bytes] ...\\n\\nd]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/e2e.yml:68:9\\n |\\n68 | - uses: actions/checkout@v6\\n | _________^\\n69 | | with:\\n70 | | ref: ${{ inputs.ref || github.sha }}\\n | |______________________________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/e2e.yml:1:1\\n |\\n 1 | / name: E2E Tests\\n 2 | | on:\\n 3 | | workflow_call:\\n 4 | | inputs:\\n... |\\n112 | | exit 1\\n113 | | fi\\n | |_____________^ default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/e2e.yml:21:3\\n |\\n21 | / build:\\n22 | | name: Build ironclaw (libsql)\\n23 | | runs-on: ubuntu-latest\\n24 | | timeout-minutes: 30\\n... |\\n49 | | # ── Step 2: run test slices in parallel ───────────────────────────────────\\n | | ^\\n | | |\\n | |______________________________________________________________________________this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/e2e.yml:50:3\\n |\\n 50 | / test:\\n 51 | | name: E2E (${{ matrix.group }})\\n 52 | | needs: build\\n 53 | | runs-on: ubuntu-latest\\n... |\\n102 | | # ── Roll-up for branch protection ────────────────────────────────────────\\n | | ^\\n | | |\\n | |_____________________________________________________________________________this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/e2e.yml:103:3\\n |\\n103 | / e2e:\\n104 | | name: E2E Tests\\n105 | | runs-on: ubuntu-latest\\n106 | | if: always()\\n... |\\n112 | | exit 1\\n113 | | fi\\n | | ^\\n | | |\\n | |_____________this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/e2e.yml:110:22\\n |\\n109 | - run: |\\n | --- this run block\\n110 | if [[ \\\"${{ needs.test.result }}\\\" != \\\"success\\\" ]]; then\\n | ^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/e2e.yml:26:15\\n |\\n26 | - uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/e2e.yml:30:15\\n |\\n30 | - uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/e2e.yml:32:15\\n |\\n32 | - uses: actions/cache@v4\\n | ^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/e2e.yml:43:15\\n |\\n43 | uses: actions/upload-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/e2e.yml:68:15\\n |\\n68 | - uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/e2e.yml:73:15\\n |\\n73 | uses: actions/download-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/e2e.yml:81:15\\n |\\n81 | - uses: actions/setup-python@v5\\n | ^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/e2e.yml:96:15\\n |\\n96 | uses: actions/upload-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nhelp[superfluous-actions]: action functionality is already included by the runner\\n --> .github/workflows/e2e.yml:30:15\\n |\\n30 | - uses: dtolnay/rust-toolchain@stable\\n | ------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\n | | |\\n | | use `rustup` and/or `cargo` in a script step\\n | this step\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nwarning[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/pr-label-classify.yml:16:9\\n |\\n16 | - name: Checkout base branch\\n | _________^\\n17 | | uses: actions/checkout@v4\\n18 | | with:\\n19 | | ref: ${{ github.event.pull_request.base.ref }}\\n | |________________________________________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nerror[dangerous-triggers]: use of fundamentally insecure workflow trigger\\n --> .github/workflows/pr-label-classify.yml:3:1\\n |\\n3 | / on:\\n4 | | pull_request_target:\\n5 | | types: [opened, synchronize, reopened]\\n | |__________________________________________^ pull_request_target is almost always used insecurely\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#dangerous-triggers\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/pr-label-classify.yml:17:15\\n |\\n17 | uses: actions/checkout@v4\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[dangerous-triggers]: use of fundamentally insecure workflow trigger\\n --> .github/workflows/pr-label-scope.yml:3:1\\n |\\n3 | / on:\\n4 | | pull_request_target:\\n5 | | types: [opened, synchronize, reopened]\\n | |__________________________________________^ pull_request_target is almost always used insecurely\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#dangerous-triggers\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/pr-label-scope.yml:15:15\\n |\\n15 | - uses: actions/labeler@v5\\n | ^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nwarning[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/regression-test-check.yml:11:9\\n |\\n11 | - name: Checkout repository\\n | _________^\\n12 | | uses: actions/checkout@v4\\n13 | | with:\\n14 | | fetch-depth: 0\\n | |________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> .github/workflows/regression-test-check.yml:7:3\\n |\\n 7 | / regression-test:\\n 8 | | name: Regression test enforcement\\n 9 | | runs-on: ubuntu-latest\\n 10 | | steps:\\n... |\\n183 | | exit 1\\n184 | |\\n | | ^\\n | | |\\n | |_this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nerror[template-injection]: code injection via template expansion\\n --> .github/workflows/regression-test-check.yml:18:32\\n |\\n17 | run: |\\n | --- this run block\\n18 | git fetch origin ${{ github.event.pull_request.base.ref }}\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[template-injection]: code injection via template expansion\\n --> .github/workflows/regression-test-check.yml:28:32\\n |\\n25 | run: |\\n | --- this run block\\n...\\n28 | BASE_REF=\\\"origin/${{ github.event.pull_request.base.ref }}\\\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/regression-test-check.yml:12:15\\n |\\n12 | uses: actions/checkout@v4\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nhelp[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/release-plz-batch-summary.yml:31:9\\n |\\n31 | - name: Checkout base branch\\n | _________^\\n32 | | uses: actions/checkout@v6\\n33 | | with:\\n34 | | ref: ${{ github.event_name == 'workflow_dispatch' && 'main' || github.event.pull_request.base.ref }}\\n35 | | fetch-depth: 0\\n36 | | fetch-tags: true\\n | |__________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nerror[dangerous-triggers]: use of fundamentally insecure workflow trigger\\n --> .github/workflows/release-plz-batch-summary.yml:3:1\\n |\\n 3 | / on:\\n 4 | | workflow_dispatch:\\n 5 | | inputs:\\n 6 | | pr_number:\\n... |\\n15 | | pull_request_target:\\n16 | | types: [opened, synchronize, reopened]\\n | |__________________________________________^ pull_request_target is almost always used insecurely\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#dangerous-triggers\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz-batch-summary.yml:32:15\\n |\\n32 | uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz.yml:20:15\\n |\\n20 | uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz.yml:26:15\\n |\\n26 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz.yml:27:15\\n |\\n27 | - uses: Swatinem/rust-cache@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz.yml:31:15\\n |\\n31 | uses: actions/create-github-app-token@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz.yml:39:15\\n |\\n39 | uses: release-plz/action@v0.5\\n | ^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz.yml:20:15\\n |\\n20 | uses: actions/checkout@v6\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz.yml:26:15\\n |\\n26 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz.yml:60:15\\n |\\n60 | - uses: Swatinem/rust-cache@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz.yml:62:15\\n |\\n62 | uses: actions/create-github-app-token@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release-plz.yml:68:15\\n |\\n68 | uses: release-plz/action@v0.5\\n | ^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nwarning[secrets-outside-env]: secrets referenced without a dedicated environment\\n --> .github/workflows/release-plz.yml:35:23\\n |\\n11 | release-plz-release:\\n | ------------------- this job\\n...\\n35 | app-id: ${{ secrets.GH_RELEASES_MANAGER_APP_ID }}\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ secret is accessed outside of a dedicated environment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#secrets-outside-env\\n\\nwarning[secrets-outside-env]: secrets referenced without a dedicated environment\\n --> .github/workflows/release-plz.yml:37:28\\n |\\n11 | release-plz-release:\\n | ------------------- this job\\n...\\n37 | private-key: ${{ secrets.GH_RELEASES_MANAGER_APP_PRIVATE_KEY }}\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ secret is accessed outside of a dedicated environment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#secrets-outside-env\\n\\nwarning[secrets-outside-env]: secrets referenced without a dedicated environment\\n --> .github/workflows/release-plz.yml:44:37\\n |\\n11 | release-plz-release:\\n | ------------------- this job\\n...\\n44 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ secret is accessed outside of a dedicated environment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#secrets-outside-env\\n\\nwarning[secrets-outside-env]: secrets referenced without a dedicated environment\\n --> .github/workflows/release-plz.yml:65:23\\n |\\n47 | release-plz-pr:\\n | -------------- this job\\n...\\n65 | app-id: ${{ secrets.GH_RELEASES_MANAGER_APP_ID }}\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ secret is accessed outside of a dedicated environment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#secrets-outside-env\\n\\nwarning[secrets-outside-env]: secrets referenced without a dedicated environment\\n --> .github/workflows/release-plz.yml:66:28\\n |\\n47 | release-plz-pr:\\n | -------------- this job\\n...\\n66 | private-key: ${{ secrets.GH_RELEASES_MANAGER_APP_PRIVATE_KEY }}\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ secret is accessed outside of a dedicated environment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#secrets-outside-env\\n\\nwarning[secrets-outside-env]: secrets referenced without a dedicated environment\\n --> .github/workflows/release-plz.yml:73:37\\n |\\n47 | release-plz-pr:\\n | -------------- this job\\n...\\n73 | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ secret is accessed outside of a dedicated environment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#secrets-outside-env\\n\\nhelp[superfluous-actions]: action functionality is already included by the runner\\n --> .github/workflows/release-plz.yml:26:15\\n |\\n25 | name: Install Rust toolchain\\n | ---------------------------- this step\\n26 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nhelp[superfluous-actions]: action functionality is already included by the runner\\n --> .github/workflows/release-plz.yml:26:15\\n |\\n25 | name: Install Rust toolchain\\n | ---------------------------- this step\\n26 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nwarning[artipacked]: credential persistence through GitHub Actions artifacts\\n --> .github/workflows/release.yml:467:9\\n |\\n467 | - uses: actions/checkout@v4\\n | _________^\\n468 | | with:\\n469 | | ref: main\\n | |___________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nerror[excessive-permissions]: overly broad permissions\\n --> .github/workflows/release.yml:18:3\\n |\\n18 | \\\"contents\\\": \\\"write\\\"\\n | ^^^^^^^^^^^^^^^^^^^ contents: write is overly broad at the workflow level\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nerror[template-injection]: code injection via template expansion\\n --> .github/workflows/release.yml:79:91\\n |\\n78 | run: |\\n | --- this run block\\n79 | dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-fo...\\n | ^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nwarning[template-injection]: code injection via template expansion\\n --> .github/workflows/release.yml:136:18\\n |\\n136 | run: ${{ matrix.install_dist.run }}\\n | --- ^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n | |\\n | this run block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nwarning[template-injection]: code injection via template expansion\\n --> .github/workflows/release.yml:181:15\\n |\\n180 | run: |\\n | --- this run block\\n181 | ${{ matrix.packages_install }}\\n | ^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/release.yml:185:26\\n |\\n183 | ... run: |\\n | --- this run block\\n184 | ... # Actually do builds and make zips and whatnot\\n185 | ... dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest....\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nwarning[template-injection]: code injection via template expansion\\n --> .github/workflows/release.yml:185:98\\n |\\n183 | run: |\\n | --- this run block\\n184 | # Actually do builds and make zips and whatnot\\n185 | dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifes...\\n | ^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/release.yml:238:26\\n |\\n237 | run: |\\n | --- this run block\\n238 | dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json \\\"--artifacts=global\\\" > dist-manifest.json\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/release.yml:419:25\\n |\\n418 | run: |\\n | --- this run block\\n419 | dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> .github/workflows/release.yml:450:34\\n |\\n446 | ... run: |\\n | --- this run block\\n...\\n450 | ... gh release create \\\"${{ needs.plan.outputs.tag }}\\\" --target \\\"$RELEASE_COMMIT\\\" $PRERELEASE_FLAG --title \\\"$ANNOUNCEMENT_TITLE\\\"...\\n | ^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:58:15\\n |\\n58 | - uses: actions/checkout@v4\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:68:15\\n |\\n68 | uses: actions/upload-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:84:15\\n |\\n84 | uses: actions/upload-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:120:15\\n |\\n120 | - uses: actions/checkout@v4\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:131:15\\n |\\n131 | - uses: swatinem/rust-cache@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:139:15\\n |\\n139 | uses: actions/download-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:201:15\\n |\\n201 | uses: actions/upload-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:218:15\\n |\\n218 | - uses: actions/checkout@v4\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:223:15\\n |\\n223 | uses: actions/download-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:230:15\\n |\\n230 | uses: actions/download-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:248:15\\n |\\n248 | uses: actions/upload-artifact@v4\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:263:15\\n |\\n263 | - uses: actions/checkout@v4\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:271:15\\n |\\n271 | - uses: swatinem/rust-cache@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> .github/workflows/release.yml:377:15\\n |\\n377 | uses: actions/upload-artifact@v4\\n \\n\\n--- stderr ---\\n🌈 zizmor v1.23.1\\n INFO audit: zizmor: 🌈 completed .github/workflows/claude-review.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/code_style.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/coverage.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/docker.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/e2e.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/pr-label-classify.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/pr-label-scope.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/regression-test-check.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/release-plz-batch-summary.yml\\n WARN audit: zizmor: one or more inputs contains YAML anchors; you may encounter crashes or unpredictable behavior\\n WARN audit: zizmor: for more information, see: https://docs.zizmor.sh/usage/#yaml-anchors\\n INFO audit: zizmor: 🌈 completed .github/workflows/release-plz.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/release.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/staging-ci.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/staging-promotion-metadata.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/test.yml\\n\",\n \"sandboxed\": false,\n \"success\": false\n}\n" + "content": "{'exit_code': 14, 'output': '[\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"High\",\\n \"severity\": \"High\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/code_style.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"clippy-matrix\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 0\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 75,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 87,\\n \"column\": 12\\n },\\n \"offset_span\": {\\n \"start\": 2750,\\n \"end\": 3471\\n }\\n },\\n \"feature\": \"id: set\\\\n run: |\\\\n FULL=\\'[{\\\\\"name\\\\\":\\\\\"all-features\\\\\",\\\\\"flags\\\\\":\\\\\"--all-features\\\\\"},{\\\\\"name\\\\\":\\\\\"default\\\\\",\\\\\"flags\\\\\":\\\\\"\\\\\"},{\\\\\"name\\\\\":\\\\\"libsql-only\\\\\",\\\\\"flags\\\\\":\\\\\"--no-default-features --features libsql\\\\\"}]\\'\\\\n SLIM=\\'[{\\\\\"name\\\\\":\\\\\"all-features\\\\\",\\\\\"flags\\\\\":\\\\\"--all-features\\\\\"}]\\'\\\\n\\\\n # Full matrix on push (cache-warming + verification across configs)\\\\n # and on PRs targeting main (final promotion gate). Other PRs use\\\\n # SLIM since lint findings are almost never feature-gated.\\\\n if [ \\\\\"${{ github.event_name }}\\\\\" = \\\\\"push\\\\\" ] || [ \\\\\"${{ github.base_ref }}\\\\\" = \\\\\"main\\\\\" ]; then\\\\n echo \\\\\"matrix=${FULL}\\\\\" >> \\\\\"$GITHUB_OUTPUT\\\\\"\\\\n else\\\\n echo \\\\\"matrix=${SLIM}\\\\\" >> \\\\\"$GITHUB_OUTPUT\\\\\"\\\\n fi\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/code_style.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"clippy-matrix\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 0\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 463,\\n \"fragment\": {\\n \"Raw\": \"github.base_ref\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 83,\\n \"column\": 63\\n },\\n \"end_point\": {\\n \"row\": 83,\\n \"column\": 78\\n },\\n \"offset_span\": {\\n \"start\": 3299,\\n \"end\": 3314\\n }\\n },\\n \"feature\": \"|\\\\n FULL=\\'[{\\\\\"name\\\\\":\\\\\"all-features\\\\\",\\\\\"flags\\\\\":\\\\\"--all-features\\\\\"},{\\\\\"name\\\\\":\\\\\"default\\\\\",\\\\\"flags\\\\\":\\\\\"\\\\\"},{\\\\\"name\\\\\":\\\\\"libsql-only\\\\\",\\\\\"flags\\\\\":\\\\\"--no-default-features --features libsql\\\\\"}]\\'\\\\n SLIM=\\'[{\\\\\"name\\\\\":\\\\\"all-features\\\\\",\\\\\"flags\\\\\":\\\\\"--all-features\\\\\"}]\\'\\\\n\\\\n # Full matrix on push (cache-warming + verification across configs)\\\\n # and on PRs targeting main (final promotion gate). Other PRs use\\\\n # SLIM since lint findings are almost never feature-gated.\\\\n if [ \\\\\"${{ github.event_name }}\\\\\" = \\\\\"push\\\\\" ] || [ \\\\\"${{ github.base_ref }}\\\\\" = \\\\\"main\\\\\" ]; then\\\\n echo \\\\\"matrix=${FULL}\\\\\" >> \\\\\"$GITHUB_OUTPUT\\\\\"\\\\n else\\\\n echo \\\\\"matrix=${SLIM}\\\\\" >> \\\\\"$GITHUB_OUTPUT\\\\\"\\\\n fi\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/code_style.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"clippy-matrix\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 0\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 76,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 76,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 2766,\\n \"end\": 2769\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"Low\",\\n \"severity\": \"Informational\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/code_style.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"code-style\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 0\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 269,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 303,\\n \"column\": 0\\n },\\n \"offset_span\": {\\n \"start\": 10691,\\n \"end\": 12078\\n }\\n },\\n \"feature\": \"run: |\\\\n # Docs-only PRs intentionally skip every gated job — that\\'s a pass.\\\\n if [[ \\\\\"${{ needs.changes.outputs.has_code }}\\\\\" == \\\\\"false\\\\\" ]]; then\\\\n echo \\\\\"No code changes — style checks skipped correctly\\\\\"\\\\n exit 0\\\\n fi\\\\n\\\\n # Always-required jobs.\\\\n for job_result in \\\\\\\\\\\\n \\\\\"format=${{ needs.format.result }}\\\\\" \\\\\\\\\\\\n \\\\\"gateway-js-syntax=${{ needs.gateway-js-syntax.result }}\\\\\" \\\\\\\\\\\\n \\\\\"clippy=${{ needs.clippy.result }}\\\\\" \\\\\\\\\\\\n \\\\\"deny-check=${{ needs.deny-check.result }}\\\\\" \\\\\\\\\\\\n \\\\\"gateway-boundaries=${{ needs.gateway-boundaries.result }}\\\\\"; do\\\\n name=\\\\\"${job_result%%=*}\\\\\"\\\\n result=\\\\\"${job_result##*=}\\\\\"\\\\n if [[ \\\\\"$result\\\\\" != \\\\\"success\\\\\" ]]; then\\\\n echo \\\\\"$name failed: $result\\\\\"\\\\n exit 1\\\\n fi\\\\n done\\\\n\\\\n # Conditional jobs: must succeed when run, may be skipped on\\\\n # events where their `if:` filter excludes them.\\\\n for job_result in \\\\\\\\\\\\n \\\\\"no-panics=${{ needs.no-panics.result }}\\\\\" \\\\\\\\\\\\n \\\\\"clippy-windows=${{ needs.clippy-windows.result }}\\\\\"; do\\\\n name=\\\\\"${job_result%%=*}\\\\\"\\\\n result=\\\\\"${job_result##*=}\\\\\"\\\\n if [[ \\\\\"$result\\\\\" != \\\\\"success\\\\\" && \\\\\"$result\\\\\" != \\\\\"skipped\\\\\" ]]; then\\\\n echo \\\\\"$name failed: $result\\\\\"\\\\n exit 1\\\\n fi\\\\n done\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/code_style.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"code-style\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 0\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 78,\\n \"fragment\": {\\n \"Raw\": \"needs.changes.outputs.has_code\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 271,\\n \"column\": 21\\n },\\n \"end_point\": {\\n \"row\": 271,\\n \"column\": 51\\n },\\n \"offset_span\": {\\n \"start\": 10799,\\n \"end\": 10829\\n }\\n },\\n \"feature\": \"|\\\\n # Docs-only PRs intentionally skip every gated job — that\\'s a pass.\\\\n if [[ \\\\\"${{ needs.changes.outputs.has_code }}\\\\\" == \\\\\"false\\\\\" ]]; then\\\\n echo \\\\\"No code changes — style checks skipped correctly\\\\\"\\\\n exit 0\\\\n fi\\\\n\\\\n # Always-required jobs.\\\\n for job_result in \\\\\\\\\\\\n \\\\\"format=${{ needs.format.result }}\\\\\" \\\\\\\\\\\\n \\\\\"gateway-js-syntax=${{ needs.gateway-js-syntax.result }}\\\\\" \\\\\\\\\\\\n \\\\\"clippy=${{ needs.clippy.result }}\\\\\" \\\\\\\\\\\\n \\\\\"deny-check=${{ needs.deny-check.result }}\\\\\" \\\\\\\\\\\\n \\\\\"gateway-boundaries=${{ needs.gateway-boundaries.result }}\\\\\"; do\\\\n name=\\\\\"${job_result%%=*}\\\\\"\\\\n result=\\\\\"${job_result##*=}\\\\\"\\\\n if [[ \\\\\"$result\\\\\" != \\\\\"success\\\\\" ]]; then\\\\n echo \\\\\"$name failed: $result\\\\\"\\\\n exit 1\\\\n fi\\\\n done\\\\n\\\\n # Conditional jobs: must succeed when run, may be skipped on\\\\n # events where their `if:` filter excludes them.\\\\n for job_result in \\\\\\\\\\\\n \\\\\"no-panics=${{ needs.no-panics.result }}\\\\\" \\\\\\\\\\\\n \\\\\"clippy-windows=${{ needs.clippy-windows.result }}\\\\\"; do\\\\n name=\\\\\"${job_result%%=*}\\\\\"\\\\n result=\\\\\"${job_result##*=}\\\\\"\\\\n if [[ \\\\\"$result\\\\\" != \\\\\"success\\\\\" && \\\\\"$result\\\\\" != \\\\\"skipped\\\\\" ]]; then\\\\n echo \\\\\"$name failed: $result\\\\\"\\\\n exit 1\\\\n fi\\\\n done\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/code_style.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"code-style\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 0\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 269,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 269,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 10691,\\n \"end\": 10694\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"Low\",\\n \"severity\": \"Informational\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 199,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 217,\\n \"column\": 37\\n },\\n \"offset_span\": {\\n \"start\": 7741,\\n \"end\": 8399\\n }\\n },\\n \"feature\": \"name: Summary\\\\n if: steps.check.outputs.skip != \\'true\\'\\\\n run: |\\\\n {\\\\n echo \\\\\"## Docker Images\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw-worker:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.worker_tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- sha: \\\\\\\\`${{ steps.source_sha.outputs.sha }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 83,\\n \"fragment\": {\\n \"Raw\": \"steps.tags.outputs.tags\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 207,\\n \"column\": 22\\n },\\n \"end_point\": {\\n \"row\": 207,\\n \"column\": 45\\n },\\n \"offset_span\": {\\n \"start\": 7963,\\n \"end\": 7986\\n }\\n },\\n \"feature\": \"|\\\\n {\\\\n echo \\\\\"## Docker Images\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw-worker:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.worker_tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- sha: \\\\\\\\`${{ steps.source_sha.outputs.sha }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 201,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 201,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 7810,\\n \"end\": 7813\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"Low\",\\n \"severity\": \"Informational\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 199,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 217,\\n \"column\": 37\\n },\\n \"offset_span\": {\\n \"start\": 7741,\\n \"end\": 8399\\n }\\n },\\n \"feature\": \"name: Summary\\\\n if: steps.check.outputs.skip != \\'true\\'\\\\n run: |\\\\n {\\\\n echo \\\\\"## Docker Images\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw-worker:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.worker_tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- sha: \\\\\\\\`${{ steps.source_sha.outputs.sha }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 203,\\n \"fragment\": {\\n \"Raw\": \"steps.tags.outputs.worker_tags\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 212,\\n \"column\": 22\\n },\\n \"end_point\": {\\n \"row\": 212,\\n \"column\": 52\\n },\\n \"offset_span\": {\\n \"start\": 8133,\\n \"end\": 8163\\n }\\n },\\n \"feature\": \"|\\\\n {\\\\n echo \\\\\"## Docker Images\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw-worker:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.worker_tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- sha: \\\\\\\\`${{ steps.source_sha.outputs.sha }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 201,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 201,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 7810,\\n \"end\": 7813\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"Low\",\\n \"severity\": \"Informational\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 199,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 217,\\n \"column\": 37\\n },\\n \"offset_span\": {\\n \"start\": 7741,\\n \"end\": 8399\\n }\\n },\\n \"feature\": \"name: Summary\\\\n if: steps.check.outputs.skip != \\'true\\'\\\\n run: |\\\\n {\\\\n echo \\\\\"## Docker Images\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw-worker:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.worker_tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- sha: \\\\\\\\`${{ steps.source_sha.outputs.sha }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 300,\\n \"fragment\": {\\n \"Raw\": \"steps.version.outputs.version\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 215,\\n \"column\": 35\\n },\\n \"end_point\": {\\n \"row\": 215,\\n \"column\": 64\\n },\\n \"offset_span\": {\\n \"start\": 8260,\\n \"end\": 8289\\n }\\n },\\n \"feature\": \"|\\\\n {\\\\n echo \\\\\"## Docker Images\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw-worker:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.worker_tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- sha: \\\\\\\\`${{ steps.source_sha.outputs.sha }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 201,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 201,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 7810,\\n \"end\": 7813\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"Low\",\\n \"severity\": \"Informational\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 199,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 217,\\n \"column\": 37\\n },\\n \"offset_span\": {\\n \"start\": 7741,\\n \"end\": 8399\\n }\\n },\\n \"feature\": \"name: Summary\\\\n if: steps.check.outputs.skip != \\'true\\'\\\\n run: |\\\\n {\\\\n echo \\\\\"## Docker Images\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw-worker:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.worker_tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- sha: \\\\\\\\`${{ steps.source_sha.outputs.sha }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 357,\\n \"fragment\": {\\n \"Raw\": \"steps.source_sha.outputs.sha\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 216,\\n \"column\": 31\\n },\\n \"end_point\": {\\n \"row\": 216,\\n \"column\": 59\\n },\\n \"offset_span\": {\\n \"start\": 8327,\\n \"end\": 8355\\n }\\n },\\n \"feature\": \"|\\\\n {\\\\n echo \\\\\"## Docker Images\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"**ironclaw-worker:**\\\\\"\\\\n echo \\'```\\'\\\\n echo \\\\\"${{ steps.tags.outputs.worker_tags }}\\\\\" | tr \\',\\' \\'\\\\\\\\n\\'\\\\n echo \\'```\\'\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- sha: \\\\\\\\`${{ steps.source_sha.outputs.sha }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 11\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 201,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 201,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 7810,\\n \"end\": 7813\\n }\\n },\\n \"feature\": \"run\",\\n \"comme\\n\\n... [truncated 1324 bytes] ...\\n\\n}\\n },\\n \"feature\": \"name: Summary (skipped)\\\\n if: steps.check.outputs.skip == \\'true\\'\\\\n run: |\\\\n {\\\\n echo \\\\\"## Docker Images — skipped\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"Current commit already built for \\\\\\\\`${IMAGE_NAME}:staging\\\\\\\\`.\\\\\"\\\\n echo \\\\\"- sha: \\\\\\\\`${{ steps.source_sha.outputs.sha }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 12\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 137,\\n \"fragment\": {\\n \"Raw\": \"steps.source_sha.outputs.sha\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 226,\\n \"column\": 31\\n },\\n \"end_point\": {\\n \"row\": 226,\\n \"column\": 59\\n },\\n \"offset_span\": {\\n \"start\": 8685,\\n \"end\": 8713\\n }\\n },\\n \"feature\": \"|\\\\n {\\\\n echo \\\\\"## Docker Images — skipped\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"Current commit already built for \\\\\\\\`${IMAGE_NAME}:staging\\\\\\\\`.\\\\\"\\\\n echo \\\\\"- sha: \\\\\\\\`${{ steps.source_sha.outputs.sha }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/docker.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 12\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 221,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 221,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 8488,\\n \"end\": 8491\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"dangerous-triggers\",\\n \"desc\": \"use of fundamentally insecure workflow trigger\",\\n \"url\": \"https://docs.zizmor.sh/audits/#dangerous-triggers\",\\n \"determinations\": {\\n \"confidence\": \"Medium\",\\n \"severity\": \"High\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/pr-label-classify.yml\"\\n }\\n },\\n \"annotation\": \"pull_request_target is almost always used insecurely\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"on\"\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 2,\\n \"column\": 0\\n },\\n \"end_point\": {\\n \"row\": 4,\\n \"column\": 42\\n },\\n \"offset_span\": {\\n \"start\": 48,\\n \"end\": 117\\n }\\n },\\n \"feature\": \"on:\\\\n pull_request_target:\\\\n types: [opened, synchronize, reopened]\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"dangerous-triggers\",\\n \"desc\": \"use of fundamentally insecure workflow trigger\",\\n \"url\": \"https://docs.zizmor.sh/audits/#dangerous-triggers\",\\n \"determinations\": {\\n \"confidence\": \"Medium\",\\n \"severity\": \"High\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/pr-label-scope.yml\"\\n }\\n },\\n \"annotation\": \"pull_request_target is almost always used insecurely\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"on\"\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 2,\\n \"column\": 0\\n },\\n \"end_point\": {\\n \"row\": 4,\\n \"column\": 42\\n },\\n \"offset_span\": {\\n \"start\": 26,\\n \"end\": 95\\n }\\n },\\n \"feature\": \"on:\\\\n pull_request_target:\\\\n types: [opened, synchronize, reopened]\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"High\",\\n \"severity\": \"High\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 109,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 120,\\n \"column\": 0\\n },\\n \"offset_span\": {\\n \"start\": 3709,\\n \"end\": 4220\\n }\\n },\\n \"feature\": \"name: Summary\\\\n run: |\\\\n {\\\\n echo \\\\\"## Rebuilt Docker Image\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- source ref: \\\\\\\\`${{ inputs.source_ref }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- source sha: \\\\\\\\`${{ steps.source.outputs.sha }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- runtime stage detected: \\\\\\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- image: \\\\\\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 70,\\n \"fragment\": {\\n \"Raw\": \"inputs.source_ref\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 114,\\n \"column\": 38\\n },\\n \"end_point\": {\\n \"row\": 114,\\n \"column\": 55\\n },\\n \"offset_span\": {\\n \"start\": 3851,\\n \"end\": 3868\\n }\\n },\\n \"feature\": \"|\\\\n {\\\\n echo \\\\\"## Rebuilt Docker Image\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- source ref: \\\\\\\\`${{ inputs.source_ref }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- source sha: \\\\\\\\`${{ steps.source.outputs.sha }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- runtime stage detected: \\\\\\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- image: \\\\\\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 110,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 110,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 3731,\\n \"end\": 3734\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"Low\",\\n \"severity\": \"Informational\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 109,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 120,\\n \"column\": 0\\n },\\n \"offset_span\": {\\n \"start\": 3709,\\n \"end\": 4220\\n }\\n },\\n \"feature\": \"name: Summary\\\\n run: |\\\\n {\\\\n echo \\\\\"## Rebuilt Docker Image\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- source ref: \\\\\\\\`${{ inputs.source_ref }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- source sha: \\\\\\\\`${{ steps.source.outputs.sha }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- runtime stage detected: \\\\\\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- image: \\\\\\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 122,\\n \"fragment\": {\\n \"Raw\": \"steps.source.outputs.sha\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 115,\\n \"column\": 38\\n },\\n \"end_point\": {\\n \"row\": 115,\\n \"column\": 62\\n },\\n \"offset_span\": {\\n \"start\": 3913,\\n \"end\": 3937\\n }\\n },\\n \"feature\": \"|\\\\n {\\\\n echo \\\\\"## Rebuilt Docker Image\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- source ref: \\\\\\\\`${{ inputs.source_ref }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- source sha: \\\\\\\\`${{ steps.source.outputs.sha }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- runtime stage detected: \\\\\\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- image: \\\\\\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 110,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 110,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 3731,\\n \"end\": 3734\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"Low\",\\n \"severity\": \"Informational\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 109,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 120,\\n \"column\": 0\\n },\\n \"offset_span\": {\\n \"start\": 3709,\\n \"end\": 4220\\n }\\n },\\n \"feature\": \"name: Summary\\\\n run: |\\\\n {\\\\n echo \\\\\"## Rebuilt Docker Image\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- source ref: \\\\\\\\`${{ inputs.source_ref }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- source sha: \\\\\\\\`${{ steps.source.outputs.sha }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- runtime stage detected: \\\\\\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- image: \\\\\\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 178,\\n \"fragment\": {\\n \"Raw\": \"steps.version.outputs.version\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 116,\\n \"column\": 35\\n },\\n \"end_point\": {\\n \"row\": 116,\\n \"column\": 64\\n },\\n \"offset_span\": {\\n \"start\": 3979,\\n \"end\": 4008\\n }\\n },\\n \"feature\": \"|\\\\n {\\\\n echo \\\\\"## Rebuilt Docker Image\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- source ref: \\\\\\\\`${{ inputs.source_ref }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- source sha: \\\\\\\\`${{ steps.source.outputs.sha }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- runtime stage detected: \\\\\\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- image: \\\\\\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 110,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 110,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 3731,\\n \"end\": 3734\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"Low\",\\n \"severity\": \"Informational\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 109,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 120,\\n \"column\": 0\\n },\\n \"offset_span\": {\\n \"start\": 3709,\\n \"end\": 4220\\n }\\n },\\n \"feature\": \"name: Summary\\\\n run: |\\\\n {\\\\n echo \\\\\"## Rebuilt Docker Image\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- source ref: \\\\\\\\`${{ inputs.source_ref }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- source sha: \\\\\\\\`${{ steps.source.outputs.sha }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- runtime stage detected: \\\\\\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- image: \\\\\\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 254,\\n \"fragment\": {\\n \"Raw\": \"steps.target.outputs.has_runtime_stage\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 117,\\n \"column\": 50\\n },\\n \"end_point\": {\\n \"row\": 117,\\n \"column\": 88\\n },\\n \"offset_span\": {\\n \"start\": 4065,\\n \"end\": 4103\\n }\\n },\\n \"feature\": \"|\\\\n {\\\\n echo \\\\\"## Rebuilt Docker Image\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- source ref: \\\\\\\\`${{ inputs.source_ref }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- source sha: \\\\\\\\`${{ steps.source.outputs.sha }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- runtime stage detected: \\\\\\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- image: \\\\\\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 110,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 110,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 3731,\\n \"end\": 3734\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"template-injection\",\\n \"desc\": \"code injection via template expansion\",\\n \"url\": \"https://docs.zizmor.sh/audits/#template-injection\",\\n \"determinations\": {\\n \"confidence\": \"High\",\\n \"severity\": \"High\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"this step\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Hidden\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 109,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 120,\\n \"column\": 0\\n },\\n \"offset_span\": {\\n \"start\": 3709,\\n \"end\": 4220\\n }\\n },\\n \"feature\": \"name: Summary\\\\n run: |\\\\n {\\\\n echo \\\\\"## Rebuilt Docker Image\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- source ref: \\\\\\\\`${{ inputs.source_ref }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- source sha: \\\\\\\\`${{ steps.source.outputs.sha }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- runtime stage detected: \\\\\\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- image: \\\\\\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"may expand into attacker-controllable code\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": {\\n \"Subfeature\": {\\n \"after\": 344,\\n \"fragment\": {\\n \"Raw\": \"inputs.tag\"\\n }\\n }\\n },\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 118,\\n \"column\": 55\\n },\\n \"end_point\": {\\n \"row\": 118,\\n \"column\": 65\\n },\\n \"offset_span\": {\\n \"start\": 4165,\\n \"end\": 4175\\n }\\n },\\n \"feature\": \"|\\\\n {\\\\n echo \\\\\"## Rebuilt Docker Image\\\\\"\\\\n echo \\\\\"\\\\\"\\\\n echo \\\\\"- source ref: \\\\\\\\`${{ inputs.source_ref }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- source sha: \\\\\\\\`${{ steps.source.outputs.sha }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- version: \\\\\\\\`${{ steps.version.outputs.version }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- runtime stage detected: \\\\\\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\\\\\`\\\\\"\\\\n echo \\\\\"- image: \\\\\\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\\\\\`\\\\\"\\\\n } >> \\\\\"$GITHUB_STEP_SUMMARY\\\\\"\\\\n\",\\n \"comments\": []\\n }\\n },\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/rebuild-release-image.yml\"\\n }\\n },\\n \"annotation\": \"this run block\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"jobs\"\\n },\\n {\\n \"Key\": \"build\"\\n },\\n {\\n \"Key\": \"steps\"\\n },\\n {\\n \"Index\": 9\\n },\\n {\\n \"Key\": \"run\"\\n }\\n ]\\n },\\n \"feature_kind\": \"KeyOnly\",\\n \"kind\": \"Related\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 110,\\n \"column\": 8\\n },\\n \"end_point\": {\\n \"row\": 110,\\n \"column\": 11\\n },\\n \"offset_span\": {\\n \"start\": 3731,\\n \"end\": 3734\\n }\\n },\\n \"feature\": \"run\",\\n \"comments\": []\\n }\\n }\\n ],\\n \"ignored\": false\\n },\\n {\\n \"ident\": \"dangerous-triggers\",\\n \"desc\": \"use of fundamentally insecure workflow trigger\",\\n \"url\": \"https://docs.zizmor.sh/audits/#dangerous-triggers\",\\n \"determinations\": {\\n \"confidence\": \"Medium\",\\n \"severity\": \"High\",\\n \"persona\": \"Regular\"\\n },\\n \"locations\": [\\n {\\n \"symbolic\": {\\n \"key\": {\\n \"Local\": {\\n \"prefix\": \".github/workflows/\",\\n \"given_path\": \".github/workflows/release-plz-batch-summary.yml\"\\n }\\n },\\n \"annotation\": \"pull_request_target is almost always used insecurely\",\\n \"route\": {\\n \"route\": [\\n {\\n \"Key\": \"on\"\\n }\\n ]\\n },\\n \"feature_kind\": \"Normal\",\\n \"kind\": \"Primary\"\\n },\\n \"concrete\": {\\n \"location\": {\\n \"start_point\": {\\n \"row\": 2,\\n \"column\": 0\\n \\n\\n--- stderr ---\\n INFO zizmor: 🌈 zizmor v1.24.1\\n INFO audit: zizmor: 🌈 completed .github/workflows/claude-review.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/code_style.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/coverage.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/docker.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/e2e.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/pr-label-classify.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/pr-label-scope.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/rebuild-release-image.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/regression-test-check.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/release-plz-batch-summary.yml\\n WARN audit: zizmor: one or more inputs contains YAML anchors; see https://docs.zizmor.sh/usage/#yaml-anchors for details\\n INFO audit: zizmor: 🌈 completed .github/workflows/release-plz.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/release.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/replay-gate.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/staging-ci.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/staging-promotion-metadata.yml\\n INFO audit: zizmor: 🌈 completed .github/workflows/test.yml\\n', 'sandboxed': False, 'success': False}" + } + ] + }, + { + "request_hint": { + "last_user_message_contains": "can we run https://github.com/zizmorcore/zizmor", + "min_message_count": 18 + }, + "response": { + "type": "text", + "content": "\n\nYes, zizmor can be run! It's already installed (v1.24.1) and I just ran it on the repository's GitHub workflows. Here's what it found:\n\n## Summary\n\n**Exit code 14** indicates security issues were detected.\n\n### Critical Findings\n\n#### 1. **Dangerous Triggers** (High Severity, Medium Confidence)\n- **`.github/workflows/pr-label-classify.yml`** - Uses `pull_request_target` trigger\n- **`.github/workflows/pr-label-scope.yml`** - Uses `pull_request_target` trigger\n- **`.github/workflows/release-plz-batch-summary.yml`** - Uses `pull_request_target` trigger\n\n`pull_request_target` is fundamentally insecure because it runs in the context of the base branch with write permissions, making it vulnerable to code injection from PRs.\n\n#### 2. **Template Injection** (High Severity, High Confidence)\nMultiple workflows have unsafe template expansions that could allow code injection:\n\n- **`.github/workflows/code_style.yml`** - Lines 83 (`github.base_ref`), 271 (`needs.changes.outputs.has_code`)\n- **`.github/workflows/rebuild-release-image.yml`** - Lines 114-118 (`inputs.source_ref`, `inputs.tag`, `steps.*.outputs.*`)\n\nThese inject user-controllable data directly into shell commands via `${{ }}` expressions.\n\n#### 3. **Template Injection** (Informational Severity, Low Confidence)\nSeveral `GITHUB_STEP_SUMMARY` injections in:\n- `.github/workflows/docker.yml` - Metadata output steps\n- `.github/workflows/rebuild-release-image.yml` - Summary generation\n\n### Recommendation\n\n1. **High Priority**: Replace `pull_request_target` with safer alternatives like `pull_request` + approved checks\n2. **Medium Priority**: Sanitize template injections by using environment variables or avoiding direct expansion of user input\n3. **Consider**: Running zizmor as a CI check on all PRs to catch new issues\n\nWould you like me to analyze specific workflow files in more detail or suggest fixes for these issues?", + "input_tokens": 40414, + "output_tokens": 553 + }, + "expected_tool_results": [ + { + "tool_call_id": "call_b2ad3da87b6a4bb3a2b1c479", + "name": "echo", + "content": "zizmor successfully ran and found security issues. Let me parse and summarize the findings." } ] } diff --git a/tests/fixtures/llm_traces/live/zizmor_scan.log b/tests/fixtures/llm_traces/live/zizmor_scan.log index cad8d83eb67..4db40494f8c 100644 --- a/tests/fixtures/llm_traces/live/zizmor_scan.log +++ b/tests/fixtures/llm_traces/live/zizmor_scan.log @@ -1,135 +1,84 @@ # Live Test Session: zizmor_scan -# Mode: Replay -# LLM calls: 8, Input tokens: 143884, Output tokens: 1410 -# Wall time: 0.9s, Cost: $0.0000 +# Mode: Live +# LLM calls: 9, Input tokens: 223223, Output tokens: 1425 +# Wall time: 32.3s, Cost: $0.0000 # ────────────────────────────────────────────────── -› can we run https://github.com/zizmorcore/zizmor - ○ Processing... - ○ Thinking (step 1)... - ○ Running tool_search... - ● tool_search - ✓ tool_search - tool_search → { - "count": 0, - "results": [], - "searched_online": false -} - ○ Thinking (step 2)... - ○ Running command... - ● shell - ✓ shell - shell → { - "exit_code": 0, - "output": "/Users/coder/.cargo/bin/zizmor\n", - "sandboxed": false, - "success": true -} - ○ Thinking (step 3)... - ○ Running list_dir... - ● list_dir - ✓ list_dir - list_dir → { - "count": 70, - "entries": [ - ".claude/", - ".git/", - ".githooks/", - ".github/", - "channels-src/", - "crates/", - "deploy/", - "docker/", - "docs/", - "fuzz/", - "migratio… - ○ Thinking (step 4)... - ○ Running list_dir... - ● list_dir - ✓ list_dir - list_dir → { - "count": 4, - "entries": [ - "scripts/", - "workflows/", - "labeler.yml (3.4KB)", - "pull_request_template.md (1.7KB)" - ], - "path": "/Users/coder/ironclaw8/.github", - "truncated": false… - ○ Thinking (step 5)... - ○ Running list_dir... - ● list_dir - ✓ list_dir - list_dir → { - "count": 14, - "entries": [ - "claude-review.yml (5.3KB)", - "code_style.yml (3.3KB)", - "coverage.yml (6.9KB)", - "docker.yml (4.3KB)", - "e2e.yml (4.0KB)", - "pr-label-classify.yml (… - ○ Thinking (step 6)... - ○ Running command... - ● shell - ✓ shell - shell → { - "exit_code": 2, - "output": "error: invalid value 'pretty' for '--format '\n [possible values: plain, json, json-v1, sarif, github]\n\nFor more information, try '--help'.\n", - "sandboxed… - ○ Thinking (step 7)... - ○ Running command... - ● shell - ✓ shell - shell → { - "exit_code": 14, - "output": "help[artipacked]: credential persistence through GitHub Actions artifacts\n --> .github/workflows/claude-review.yml:23:9\n |\n23 | - uses: actions/checkout… - ○ Thinking (step 8)... - … Done +## Turn 1 +[USER] › can we run https://github.com/zizmorcore/zizmor ──────────────────────────────────────────────────── -Done! I ran zizmor on your GitHub Actions workflows and found **security issues across all 14 workflow files**. Here's the summary: -## 🔴 Critical Issues Found -### 1. **Unpinned Action References** (Most Common) -Every workflow uses actions like `actions/checkout@v6` instead of pinned SHA hashes. This is a supply chain risk - the action could change without notice. +Yes, zizmor can be run! It's already installed (v1.24.1) and I just ran it on the repository's GitHub workflows. Here's what it found: + +## Summary + +**Exit code 14** indicates security issues were detected. -**Example fixes needed:** -```yaml -# Before -uses: actions/checkout@v6 +### Critical Findings -# After -uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v6 -``` +#### 1. **Dangerous Triggers** (High Severity, Medium Confidence) +- **`.github/workflows/pr-label-classify.yml`** - Uses `pull_request_target` trigger +- **`.github/workflows/pr-label-scope.yml`** - Uses `pull_request_target` trigger +- **`.github/workflows/release-plz-batch-summary.yml`** - Uses `pull_request_target` trigger -### 2. **Dangerous Triggers** -- `pr-label-classify.yml`, `pr-label-scope.yml`, `release-plz-batch-summary.yml` use `pull_request_target` which is fundamentally insecure when combined with PR code execution. +`pull_request_target` is fundamentally insecure because it runs in the context of the base branch with write permissions, making it vulnerable to code injection from PRs. -### 3. **Template Injection Risks** -Multiple workflows have code injection vulnerabilities where user-controlled input can be expanded into shell commands: -- `docker.yml` - workflow inputs directly in run blocks -- `release.yml` - matrix args and tags in commands -- `regression-test-check.yml` - PR base ref in git commands +#### 2. **Template Injection** (High Severity, High Confidence) +Multiple workflows have unsafe template expansions that could allow code injection: -### 4. **Excessive Permissions** -- `coverage.yml` has `id-token: write` at workflow level (too broad) -- `release.yml` has `contents: write` at workflow level -- Most workflows use default permissions instead of explicit least-privilege +- **`.github/workflows/code_style.yml`** - Lines 83 (`github.base_ref`), 271 (`needs.changes.outputs.has_code`) +- **`.github/workflows/rebuild-release-image.yml`** - Lines 114-118 (`inputs.source_ref`, `inputs.tag`, `steps.*.outputs.*`) -### 5. **Secrets Outside Environments** -`release-plz.yml` and `claude-review.yml` access secrets (API keys, private keys) without using dedicated GitHub Environments for protection. +These inject user-controllable data directly into shell commands via `${{ }}` expressions. -### 6. **Artipacked (Credential Persistence)** -Nearly all `actions/checkout` steps don't set `persist-credentials: false`, leaving Git credentials in the runner artifact. +#### 3. **Template Injection** (Informational Severity, Low Confidence) +Several `GITHUB_STEP_SUMMARY` injections in: +- `.github/workflows/docker.yml` - Metadata output steps +- `.github/workflows/rebuild-release-image.yml` - Summary generation -## 📊 Severity Breakdown -- **Errors**: 70+ (unpinned uses, dangerous triggers, template injection) -- **Warnings**: 20+ (excessive permissions, secrets-outside-env) -- **Help/Info**: 30+ (artipacked, superfluous-actions, template-injection) +### Recommendation -Would you like me to: -1. Generate a SARIF report for GitHub Security tab integration? -2. Create specific PRs to fix the highest-priority issues? -3. Focus on one workflow file at a time? +1. **High Priority**: Replace `pull_request_target` with safer alternatives like `pull_request` + approved checks +2. **Medium Priority**: Sanitize template injections by using environment variables or avoiding direct expansion of user input +3. **Consider**: Running zizmor as a CI check on all PRs to catch new issues + +Would you like me to analyze specific workflow files in more detail or suggest fixes for these issues? + +## Activity + ○ Processing... + ○ Calling LLM... + ○ Step complete — 19610 in / 104 out tokens + ● shell(which zizmor || cargo install --version 1.11.1 zizmor 2>&1 |...) + ✓ shell(which zizmor || cargo install --version 1.11.1 zizmor 2>&1 |...) + ○ Calling LLM... + ○ Step complete — 19717 in / 127 out tokens + ● shell(cd /tmp && git clone --depth 1 https://github.com/zizmorcore...) + ✓ shell(cd /tmp && git clone --depth 1 https://github.com/zizmorcore...) + ○ Calling LLM... + ○ Step complete — 19827 in / 91 out tokens + ● shell(zizmor --version) + ✓ shell(zizmor --version) + ○ Calling LLM... + ○ Step complete — 19905 in / 152 out tokens + ● shell(ls -la /tmp/zizmor) + ✓ shell(ls -la /tmp/zizmor) + ○ Calling LLM... + ○ Step complete — 20012 in / 125 out tokens + ● shell(/tmp/zizmor --help) + ✓ shell(/tmp/zizmor --help) + ○ Calling LLM... + ○ Step complete — 21614 in / 103 out tokens + ● glob(.) + ✓ glob(.) + ○ Calling LLM... + ○ Step complete — 21776 in / 92 out tokens + ● shell(zizmor .github/workflows/ --format json --persona regular) + ✓ shell(zizmor .github/workflows/ --format json --persona regular) + ○ Calling LLM... + ○ Step complete — 40348 in / 78 out tokens + ● echo(zizmor successfully ran and found security issues....) + ✓ echo(zizmor successfully ran and found security issues....) + ○ Calling LLM... + ○ Step complete — 40414 in / 553 out tokens + … Done diff --git a/tests/fixtures/llm_traces/live/zizmor_scan_v2.json b/tests/fixtures/llm_traces/live/zizmor_scan_v2.json index 267c14e3413..74d838bd46a 100644 --- a/tests/fixtures/llm_traces/live/zizmor_scan_v2.json +++ b/tests/fixtures/llm_traces/live/zizmor_scan_v2.json @@ -16,121 +16,93 @@ "type": "tool_calls", "tool_calls": [ { - "id": "chatcmpl-tool-af116ddf7d76ffe1", - "name": "tool_search", - "arguments": { - "query": "zizmor" - } - }, - { - "id": "chatcmpl-tool-a3b63943e42a0ecb", - "name": "http", + "id": "call_56a460d2ec6247e5bd600f5b", + "name": "shell", "arguments": { - "method": "GET", - "url": "https://github.com/zizmorcore/zizmor" + "command": "which zizmor || cargo install zizmor 2>&1 | head -20", + "timeout": 120, + "workdir": "." } } ], - "input_tokens": 9624, - "output_tokens": 118 + "input_tokens": 19610, + "output_tokens": 148 } }, { "request_hint": { "last_user_message_contains": "can we run https://github.com/zizmorcore/zizmor", - "min_message_count": 5 + "min_message_count": 4 }, "response": { "type": "tool_calls", "tool_calls": [ { - "id": "chatcmpl-tool-9a7f678f4ab6dafa", - "name": "tool_search", - "arguments": { - "query": "zizmor github actions security" - } - }, - { - "id": "chatcmpl-tool-abde2c362fe68cbe", - "name": "tool_list", + "id": "call_bb4b5d15db39470c903c084e", + "name": "shell", "arguments": { - "include_available": true + "command": "zizmor --help", + "timeout": 30, + "workdir": "." } } ], - "input_tokens": 10895, - "output_tokens": 125 + "input_tokens": 19742, + "output_tokens": 112 }, "expected_tool_results": [ { - "tool_call_id": "chatcmpl-tool-af116ddf7d76ffe1", - "name": "tool_search", - "content": "{'count': 0, 'results': [], 'searched_online': False}" - }, - { - "tool_call_id": "chatcmpl-tool-a3b63943e42a0ecb", - "name": "http", - "content": "{'body': '# Search code, repositories, users, issues, pull requests...\\n\\n\\n\\n\\n\\n[Sign in](/login?return_to=https%3A%2F%2Fgithub.com%2Fzizmorcore%2Fzizmor)\\n\\n/;ref_cta:Sign up;ref_loc:header logged out\"}\" data-hydro-click=\"{\"event_type\":\"authentication.click\",\"payload\":{\"location_in_page\":\"site header menu\",\"repository_id\":null,\"auth_type\":\"SIGN_UP\",\"originating_url\":\"https://github.com/zizmorcore/zizmor\",\"user_id\":null}}\" data-hydro-click-hmac=\"8caabd8b90225b5d5573f566ac422fd36398eba76666d09587f4d4f6f6450bb2\" href=\"/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E&source=header-repo&source_repo=zizmorcore%2Fzizmor\">\\n Sign up\\n\\nAppearance settings\\n', 'headers': {'accept-ranges': 'bytes', 'cache-control': 'max-age=0, private, must-revalidate', 'content-security-policy': \"default-src 'none'; base-uri 'self'; child-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com github.githubassets.com objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net productionresultssa1.blob.core.windows.net productionresultssa2.blob.core.windows.net productionresultssa3.blob.core.windows.net productionresultssa4.blob.core.windows.net productionresultssa5.blob.core.windows.net productionresultssa6.blob.core.windows.net productionresultssa7.blob.core.windows.net productionresultssa8.blob.core.windows.net productionresultssa9.blob.core.windows.net productionresultssa10.blob.core.windows.net productionresultssa11.blob.core.windows.net productionresultssa12.blob.core.windows.net productionresultssa13.blob.core.windows.net productionresultssa14.blob.core.windows.net productionresultssa15.blob.core.windows.net productionresultssa16.blob.core.windows.net productionresultssa17.blob.core.windows.net productionresultssa18.blob.core.windows.net productionresultssa19.blob.core.windows.net github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com wss://alive-staging.github.com api.githubcopilot.com api.individual.githubcopilot.com api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com copilot-workspace.githubnext.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com release-assets.githubusercontent.com secured-user-images.githubusercontent.com user-images.githubusercontent.com private-user-images.githubusercontent.com opengraph.githubassets.com marketplace-screenshots.githubusercontent.com copilotprodattachments.blob.core.windows.net/github-production-copilot-attachments/ github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com secured-user-images.githubusercontent.com private-user-images.githubusercontent.com github-production-user-asset-6210df.s3.amazonaws.com gist.github.com github.githubassets.com; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; upgrade-insecure-requests; worker-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/\", 'content-type': 'text/html; charset=utf-8', 'date': 'Sun, 05 Apr 2026 11:49:36 GMT', 'etag': 'W/\"f79c89ccf6deeb8bf72f05468daae684\"', 'referrer-policy': 'no-referrer-when-downgrade', 'server': 'github.com', 'strict-transport-security': 'max-age=31536000; includeSubdomains; preload', 'vary': 'X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, X-Requested-With, Sec-Fetch-Site,Accept-Encoding, Accept, X-Requested-With', 'x-content-type-options': 'nosniff', 'x-frame-options': 'deny', 'x-github-request-id': '80C1:2E367D:125F734:18F58BA:69D24C4F', 'x-xss-protection': '0'}, 'status': 200}" + "tool_call_id": "call_56a460d2ec6247e5bd600f5b", + "name": "shell", + "content": "{'exit_code': 0, 'output': '/home/user/.cargo/bin/zizmor\\n', 'sandboxed': False, 'success': True}" } ] }, { "request_hint": { "last_user_message_contains": "can we run https://github.com/zizmorcore/zizmor", - "min_message_count": 8 + "min_message_count": 6 }, "response": { "type": "tool_calls", "tool_calls": [ { - "id": "chatcmpl-tool-b29195a794545d6d", - "name": "tool_search", + "id": "call_4f3c4de7d5a2417fba1f2bac", + "name": "shell", "arguments": { - "query": "zizmor" + "command": "zizmor . --format plain --pedantic 2>&1", + "timeout": 60, + "workdir": "." } } ], - "input_tokens": 15823, - "output_tokens": 65 + "input_tokens": 21386, + "output_tokens": 110 }, "expected_tool_results": [ { - "tool_call_id": "chatcmpl-tool-9a7f678f4ab6dafa", - "name": "tool_search", - "content": "{'count': 1, 'results': [{'auth_hint': {'type': 'capabilities_auth'}, 'description': 'GitHub integration for repositories, issues, pull requests, search, branches, file writes, releases, and workflows', 'display_name': 'GitHub', 'fallback_source': {'build_dir': 'tools-src/github', 'crate_name': 'github-tool', 'source_dir': 'tools-src/github', 'type': 'wasm_buildable'}, 'keywords': ['git', 'code', 'issues', 'pull-requests', 'repositories', 'search', 'releases'], 'kind': 'wasm_tool', 'name': 'github', 'source': 'registry', 'validated': True, 'version': '0.2.2'}], 'searched_online': False}" - }, - { - "tool_call_id": "chatcmpl-tool-abde2c362fe68cbe", - "name": "tool_list", - "content": "{'builtin_count': 40, 'builtins': [{'default_state': 'ask_each_time', 'description': 'List contents of a directory on the LOCAL FILESYSTEM. NOT for workspace memory (use memory_tree for that). Shows files and subdirectories with their sizes.', 'locked': False, 'name': 'list_dir', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Permanently remove an installed extension (channel, tool, or MCP server) from disk. This action cannot be undone — the WASM binary and configuration files will be deleted.', 'locked': True, 'name': 'tool_remove', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Make HTTP requests to external APIs. Supports GET, POST, PUT, DELETE methods. Use save_to to download binary files (images, PDFs, etc.) to a local path, e.g. {\"method\":\"GET\",\"url\":\"https://picsum.photos/800/600\",\"save_to\":\"/tmp/photo.jpg\"}.', 'locked': False, 'name': 'http', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Generate an image from a text prompt using an AI image generation model (e.g., FLUX). Returns the generated image data.', 'locked': False, 'name': 'image_generate', 'permission_state': 'ask_each_time'}, {'default_state': 'always_allow', 'description': \"Write to persistent memory (database-backed, NOT the local filesystem). Use for important facts, decisions, preferences, or lessons learned that should be remembered across sessions. Targets: 'memory' for curated long-term facts, 'daily_log' for timestamped session notes, 'heartbeat' for the periodic checklist (HEARTBEAT.md), 'bootstrap' to clear the first-run ritual file, or provide a custom workspace path for arbitrary file creation. Never pass absolute filesystem paths like '/Users/...' or 'C:\\\\...'.\", 'locked': False, 'name': 'memory_write', 'permission_state': 'always_allow'}, {'default_state': 'ask_each_time', 'description': 'Show detailed information about an installed extension, including version and WIT version compatibility.', 'locked': False, 'name': 'extension_info', 'permission_state': 'ask_each_time'}, {'default_state': 'always_allow', 'description': 'Get info about any tool: description, parameter names, curated summary guidance, or full discovery schema.', 'locked': False, 'name': 'tool_info', 'permission_state': 'always_allow'}, {'default_state': 'ask_each_time', 'description': 'Update the plan progress checklist displayed to the user. Call this when creating a plan, starting execution, completing a step, or when the plan fails. The UI renders this as a live checklist. Always send the FULL list of steps (not incremental diffs).', 'locked': False, 'name': 'plan_update', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Activate an installed extension — starts channels, loads tools, or connects to MCP servers.', 'locked': False, 'name': 'tool_activate', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Cancel a running or pending job. The job will be marked as cancelled and stopped.', 'locked': False, 'name': 'cancel_job', 'permission_state': 'ask_each_time'}, {'default_state': 'always_allow', 'description': 'Search past memories, decisions, and context. MUST be called before answering questions about prior work, decisions, dates, people, preferences, or todos. Returns relevant snippets with relevance scores.', 'locked': False, 'name': 'memory_search', 'permission_state': 'always_allow'}, {'default_state': 'always_allow', 'description': 'Analyze an image using a vision-capable AI model. Provide a workspace path to the image and an optional analysis question.', 'locked': False, 'name': 'image_analyze', 'permission_state': 'always_allow'}, {'default_state': 'always_allow', 'description': 'Check the status and details of a specific job by its ID.', 'locked': False, 'name': 'job_status', 'permission_state': 'always_allow'}, {'default_state': 'ask_each_time', 'description': 'Initiate authentication for an extension. For OAuth, returns a URL. For manual auth, returns instructions. The user provides their token through a secure channel, never through this tool.', 'locked': False, 'name': 'tool_auth', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': \"Write content to a file on the LOCAL FILESYSTEM. NOT for workspace memory (use memory_write for that). Creates the file if it doesn't exist, overwrites if it does. Parent directories are created automatically. Use apply_patch for targeted edits.\", 'locked': False, 'name': 'write_file', 'permission_state': 'ask_each_time'}, {'default_state': 'always_allow', 'description': 'Parse, query, and transform JSON data. Supports JSONPath-like queries. Use `source_tool_call_id` to reference the full output of a previous tool call (avoids truncation issues with large responses).', 'locked': False, 'name': 'json', 'permission_state': 'always_allow'}, {'default_state': 'ask_each_time', 'description': 'Install an extension (channel, tool, or MCP server). Use the name from tool_search results, or provide an explicit URL.', 'locked': False, 'name': 'tool_install', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Get or set the permission state for a tool. Use to view current permissions or propose a change (requires user approval). States: always_allow (no prompt), ask_each_time (approval required), disabled (tool hidden from LLM).', 'locked': True, 'name': 'tool_permission_set', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Manually trigger a routine to run immediately, bypassing schedule, trigger type, and cooldown.', 'locked': False, 'name': 'routine_fire', 'permission_state': 'ask_each_time'}, {'default_state': 'always_allow', 'description': 'List extensions and built-in tools with their authentication, activation, and permission status. Set include_available:true to also show registry entries not yet installed. Use kind=\"builtin\" to list only built-in Rust tools.', 'locked': False, 'name': 'tool_list', 'permission_state': 'always_allow'}, {'default_state': 'always_allow', 'description': 'Read the event log for a sandbox job. Shows messages, tool calls, results, and status changes from the container. Use this to check what Claude Code or a worker sub-agent has been doing.', 'locked': False, 'name': 'job_events', 'permission_state': 'always_allow'}, {'default_state': 'ask_each_time', 'description': 'View the execution history of a routine. Shows recent runs with status, duration, and results.', 'locked': False, 'name': 'routine_history', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Create a new routine (scheduled or event-driven task). Supports cron schedules, event pattern matching, system events, and manual triggers. Use this when the user wants something to happen periodically or reactively. Creation saves the routine, but does not verify that it will execute successfully.', 'locked': False, 'name': 'routine_create', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Emit a structured system event to routines with a system_event trigger. Use this to trigger routines from tool workflows without waiting for cron.', 'locked': False, 'name': 'event_emit', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Execute shell commands. Use for running builds, tests, git operations, and other CLI tasks. Commands run in a subprocess with captured output. Long-running commands have a timeout. When Docker sandbox is enabled, commands run in isolated containers for security.', 'locked': False, 'name': 'shell', 'permission_state': 'ask_each_time'}, {'default_state': 'always_allow', 'description': 'Get current time, parse or format timestamps, convert timezones, or calculate time differences.', 'locked': False, 'name': 'time', 'permission_state': 'always_allow'}, {'default_state': 'always_allow', 'description': 'Search for available extensions to add new capabilities. Extensions include channels (Telegram, Slack, Discord — connect messaging platforms so IronClaw can receive and reply there), tools, and MCP servers. Use `tool_install` and `tool_activate` to install and enable channels; use the `message` tool for proactive outbound sends. Use discover:true to search online if the built-in registry has no results.', 'locked': False, 'name': 'tool_search', 'permission_state': 'always_allow'}, {'default_state': 'always_allow', 'description': 'View the workspace memory structure as a tree (database-backed storage). Use memory_read to read files shown here, NOT read_file. The workspace is separate from the local filesystem.', 'locked': False, 'name': 'memory_tree', 'permission_state': 'always_allow'}, {'default_state': 'always_allow', 'description': \"Read a file from the workspace memory (database-backed storage). Use this to read files shown by memory_tree. NOT for local filesystem files (use read_file for those). Do not pass absolute paths like '/Users/...' or 'C:\\\\...'. Works with identity files, heartbeat checklist, memory, daily logs, or any custom workspace path.\", 'locked': False, 'name': 'memory_read', 'permission_state': 'always_allow'}, {'default_state': 'always_allow', 'description': 'Echoes back the input message. Useful for testing tool execution.', 'locked': False, 'name': 'echo', 'permission_state': 'always_allow'}, {'default_state': 'ask_each_time', 'description': 'Edit an existing image using an AI model. Provide the workspace path to the source image and a text prompt describing the desired edits.', 'locked': False, 'name': 'image_edit', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Read a file from the LOCAL FILESYSTEM. NOT for workspace memory paths (use memory_read for those). Returns file content as text. For large files, you can specify offset and limit to read a portion.', 'locked': False, 'name': 'read_file', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': \"Apply targeted edits to a file using search/replace. Finds the exact 'old_string' and replaces it with 'new_string'. Use for surgical code changes without rewriting entire files. The old_string must match exactly (including whitespace and indentation).\", 'locked': False, 'name': 'apply_patch', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Create a new job or task for the agent to work on. Use this when the user wants you to do something substantial that should be tracked as a separate job.', 'locked': False, 'name': 'create_job', 'permission_state': 'ask_each_time'}, {'default_state': 'always_allow', 'description': 'List all jobs or filter by status. Shows job IDs, titles, and current status.', 'locked': False, 'name': 'list_jobs', 'permission_state': 'always_allow'}, {'default_state': 'ask_each_time', 'description': 'Delete a routine permanently. This also removes all run history.', 'locked': False, 'name': 'routine_delete', 'permission_state': 'ask_each_time'}, {'default_state': 'always_allow', 'description': \"Send a proactive message to a channel. Use normal assistant output to reply in the active conversation; use this tool for proactive notifications, routine/background follow-ups, attachments, or sending to a different channel/recipient. If channel/target are omitted, reuses the current conversation's channel and sender/group when available. If you provide `target` without `channel` and no scoped channel can be resolved, the message may be broadcast across connected channels instead of sent to just one. Supports file attachments: first download the file with the http tool using save_to (e.g., http GET https://picsum.photos/800/600 save_to=/tmp/photo.jpg), then pass the file path in the attachments array. Images are sent as photos on Telegram. - Signal: target accepts E.164 (+1234567890) or group ID - Telegram: target accepts username or chat ID - Slack: target accepts channel (#general) or user ID\", 'locked': False, 'name': 'message', 'permission_state': 'always_allow'}, {'default_state': 'ask_each_time', 'description': 'List all routines with their status, trigger info, and next fire time.', 'locked': False, 'name': 'routine_list', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Upgrade installed WASM extensions (channels and tools) to match the current host WIT version. If name is omitted, checks and upgrades all installed WASM extensions. Authentication and secrets are preserved.', 'locked': False, 'name': 'tool_upgrade', 'permission_state': 'ask_each_time'}, {'default_state': 'ask_each_time', 'description': 'Update an existing routine. Can change prompt, description, enabled state, cron schedule/timezone, Pass the routine name and only the fields you want to change. This does not convert trigger types. Behavior-changing edits should leave the routine marked unverified until it is tested again.', 'locked': False, 'name': 'routine_update', 'permission_state': 'ask_each_time'}], 'count': 23, 'extensions': [{'active': True, 'authenticated': False, 'display_name': 'GitHub', 'has_auth': True, 'installed': True, 'kind': 'wasm_tool', 'name': 'github', 'needs_setup': True, 'tools': ['github'], 'version': '0.2.1'}, {'active': False, 'authenticated': False, 'description': 'Connect to Asana for task management, projects, and team coordination', 'display_name': 'Asana', 'has_auth': False, 'installed': False, 'kind': 'mcp_server', 'name': 'asana', 'needs_setup': False, 'tools': []}, {'active': False, 'authenticated': False, 'description': 'Connect to Cloudflare for DNS, Workers, KV, and infrastructure management', 'display_name': 'Cloudflare', 'has_auth': False, 'installed': False, 'kind': 'mcp_server', 'name': 'cloudflare', 'needs_setup': False, 'tools': []}, {'active': False, 'authenticated': False, 'description': 'Talk to your agent in Discord', 'display_name': 'Discord Channel', 'has_auth': False, 'installed': False, 'kind': 'wasm_channel', 'name': 'discord', 'needs_setup': False, 'tools': [], 'version': '0.2.2'}, {'active': False, 'authenticated': False, 'description': 'Talk to your agent through a Feishu or Lark bot', 'display_name': 'Feishu / Lark Channel', 'has_auth': False, 'installed': False, 'kind': 'wasm_channel', 'name': 'feishu', 'needs_setup': False, 'tools': [], 'version': '0.1.4'}, {'active': False, 'authenticated': False, 'description': 'Read, send, and manage Gmail messages and threads', 'display_name': 'Gmail', 'has_auth': False, 'installed': False, 'kind': 'wasm_tool', 'name': 'gmail', 'needs_setup': False, 'tools': [], 'version': '0.2.1'}, {'active': False, 'authenticated': False, 'description': 'Create, read, update, and delete Google Calendar events', 'display_name': 'Google Calendar', 'has_auth': False, 'installed': False, 'kind': 'wasm_tool', 'name': 'google_calendar', 'needs_setup': False, 'tools': [], 'version': '0.2.1'}, {'active': False, 'authenticated': False, 'description': 'Create and edit Google Docs documents', 'display_name': 'Google Docs', 'has_auth': False, 'installed': False, 'kind': 'wasm_tool', 'name': 'google_docs', 'needs_setup': False, 'tools': [], 'version': '0.2.1'}, {'active': False, 'authenticated': False, 'description': 'Upload, download, search, and manage Google Drive files and folders', 'display_name': 'Google Drive', 'has_auth': False, 'installed': False, 'kind': 'wasm_tool', 'name': 'google_drive', 'needs_setup': False, 'tools': [], 'version': '0.2.1'}, {'active': False, 'authenticated': False, 'description': 'Read and write Google Sheets spreadsheet data', 'display_name': 'Google Sheets', 'has_auth': False, 'installed': False, 'kind': 'wasm_tool', 'name': 'google_sheets', 'needs_setup': False, 'tools': [], 'version': '0.2.1'}, {'active': False, 'authenticated': False, 'description': 'Create and edit Google Slides presentations', 'display_name': 'Google Slides', 'has_auth': False, 'installed': False, 'kind': 'wasm_tool', 'name': 'google_slides', 'needs_setup': False, 'tools': [], 'version': '0.2.1'}, {'active': False, 'authenticated': False, 'description': 'Connect to Intercom for customer messaging, support, and engagement', 'display_name': 'Intercom', 'has_auth': False, 'installed': False, 'kind': 'mcp_server', 'name': 'intercom', 'needs_setup': False, 'tools': []}, {'active': False, 'authenticated': False, 'description': 'Connect to Linear for issue tracking, project management, and team workflows', 'display_name': 'Linear', 'has_auth': False, 'installed': False, 'kind': 'mcp_server', 'name': 'linear', 'needs_setup': False, 'tools': []}, {'active': False, 'authenticated': False, 'description': 'Fetch pre-extracted web content from Brave Search for grounding LLM answers (RAG, fact-checking)', 'display_name': 'LLM Context', 'has_auth': False, 'installed': False, 'kind': 'wasm_tool', 'name': 'llm_context', 'needs_setup': False, 'tools': [], 'version': '0.1.1'}, {'active': False, 'authenticated': False, 'description': 'Connect to Notion for reading and writing pages, databases, and comments', 'display_name': 'Notion', 'has_auth': False, 'installed': False, 'kind': 'mcp_server', 'name': 'notion', 'needs_setup': False, 'tools': []}, {'active': False, 'authenticated': False, 'description': 'Connect to Sentry for error tracking, performance monitoring, and debugging', 'display_name': 'Sentry', 'has_auth': False, 'installed': False, 'kind': 'mcp_server', 'name': 'sentry', 'needs_setup': False, 'tools': []}, {'active': False, 'authenticated': False, 'description': 'Talk to your agent in Slack', 'display_name': 'Slack Channel', 'has_auth': False, 'installed': False, 'kind': 'wasm_channel', 'name': 'slack', 'needs_setup': False, 'tools': [], 'version': '0.2.2'}, {'active': False, 'authenticated': False, 'description': 'Your agent uses Slack to post and read messages in your workspace', 'display_name': 'Slack Tool', 'has_auth': False, 'installed': False, 'kind': 'wasm_tool', 'name': 'slack_tool', 'needs_setup': False, 'tools': [], 'version': '0.2.1'}, {'active': False, 'authenticated': False, 'description': 'Connect to Stripe for payment processing, subscriptions, and financial data', 'display_name': 'Stripe', 'has_auth': False, 'installed': False, 'kind': 'mcp_server', 'name': 'stripe', 'needs_setup': False, 'tools': []}, {'active': False, 'authenticated': False, 'description': 'Talk to your agent through a Telegram bot', 'display_name': 'Telegram Channel', 'has_auth': False, 'installed': False, 'kind': 'wasm_channel', 'name': 'telegram', 'needs_setup': False, 'tools': [], 'version': '0.2.6'}, {'active': False, 'authenticated': False, 'description': 'Your agent uses your Telegram account to read and send messages', 'display_name': 'Telegram Tool', 'has_auth': False, 'installed': False, 'kind': 'wasm_tool', 'name': 'telegram_mtproto', 'needs_setup': False, 'tools': [], 'version': '0.2.1'}, {'active': False, 'authenticated': False, 'description': 'Search the web using Brave Search API', 'display_name': 'Web Search', 'has_auth': False, 'installed': False, 'kind': 'wasm_tool', 'name': 'web_search', 'needs_setup': False, 'tools': [], 'version': '0.2.2'}, {'active': False, 'authenticated': False, 'description': 'Talk to your agent through WhatsApp', 'display_name': 'WhatsApp Channel', 'has_auth': False, 'installed': False, 'kind': 'wasm_channel', 'name': 'whatsapp', 'needs_setup': False, 'tools': [], 'version': '0.2.0'}]}" + "tool_call_id": "call_bb4b5d15db39470c903c084e", + "name": "shell", + "content": "{'exit_code': 0, 'output': 'Static analysis for GitHub Actions\\n\\nUsage: zizmor [OPTIONS] ...\\n\\nInput Options:\\n ...\\n The inputs to audit.\\n \\n These can be individual workflow filenames, action definitions (typically `action.yml`), entire directories, or a `user/repo` slug for a GitHub repository. In the latter case, a `@ref` can be appended to audit the repository at a particular git reference state.\\n \\n Use `-` to read a single input from stdin.\\n\\n --collect ...\\n Control which kinds of inputs are collected for auditing.\\n \\n By default, all workflows and composite actions are collected, while honoring `.gitignore` files.\\n\\n Possible values:\\n - all: Collect all possible inputs, ignoring `.gitignore` files\\n - default: Collect all possible inputs, respecting `.gitignore` files\\n - workflows: Collect workflows\\n - actions: Collect action definitions (i.e. `action.yml`)\\n - dependabot: Collect Dependabot configuration files (i.e. `dependabot.yml`)\\n \\n [default: default]\\n\\n --strict-collection\\n Fail instead of warning on syntax and schema errors in collected inputs\\n\\nAudit Options:\\n --fix[=]\\n Fix findings automatically, when available (EXPERIMENTAL)\\n\\n Possible values:\\n - safe: Apply only safe fixes (the default)\\n - unsafe-only: Apply only unsafe fixes\\n - all: Apply all fixes, both safe and unsafe\\n\\n -p, --pedantic\\n Emit \\'pedantic\\' findings.\\n \\n This is an alias for --persona=pedantic.\\n\\n --persona \\n The persona to use while auditing\\n\\n Possible values:\\n - auditor: The \"auditor\" persona (false positives OK)\\n - pedantic: The \"pedantic\" persona (code smells OK)\\n - regular: The \"regular\" persona (minimal false positives)\\n \\n [default: regular]\\n\\n --min-severity \\n Filter all results below this severity\\n \\n [possible values: informational, low, medium, high]\\n\\n --min-confidence \\n Filter all results below this confidence\\n \\n [possible values: low, medium, high]\\n\\nOutput Options:\\n -v, --verbose...\\n Increase logging verbosity\\n\\n -q, --quiet...\\n Decrease logging verbosity\\n\\n --format \\n The output format to emit. By default, cargo-style diagnostics will be emitted\\n\\n Possible values:\\n - plain: cargo-style output\\n - json: JSON-formatted output (currently v1)\\n - json-v1: \"v1\" JSON format\\n - sarif: SARIF-formatted output\\n - github: GitHub Actions workflow command-formatted output\\n \\n [default: plain]\\n\\n --no-progress\\n Don\\'t show progress bars, even if the terminal supports them\\n\\n --color \\n Control the use of color in output\\n\\n Possible values:\\n - auto: Use color output if the output supports it\\n - always: Force color output, even if the output isn\\'t a terminal\\n - never: Disable color output, even if the output is a compatible terminal\\n\\n --render-links \\n Whether to render OSC 8 links in the output.\\n \\n This affects links under audit IDs, as well as any links produced by audit rules.\\n \\n Only affects `--format=plain` (the default).\\n\\n Possible values:\\n - auto: Render OSC 8 links in output if support is detected\\n - always: Always render OSC 8 links in output\\n - never: Never render OSC 8 links in output\\n \\n [env: ZIZMOR_RENDER_LINKS=]\\n [default: auto]\\n\\n --show-audit-urls \\n Whether to render audit URLs in the output, separately from any URLs embedded in OSC 8 links.\\n \\n Only affects `--format=plain` (the default).\\n\\n Possible values:\\n - auto: Render audit URLs in output automatically based on output format and runtime context\\n - always: Always render audit URLs in output\\n - never: Never render audit URLs in output\\n \\n [env: ZIZMOR_SHOW_AUDIT_URLS=]\\n [default: auto]\\n\\n --no-exit-codes\\n Disable all error codes besides success and tool failure\\n\\nNetwork Options:\\n -o, --offline\\n Perform only offline operations.\\n \\n This disables all online audit rules, and prevents zizmor from auditing remote repositories.\\n \\n [env: ZIZMOR_OFFLINE=]\\n\\n --gh-token \\n The GitHub API token to use [env: GH_TOKEN or GITHUB_TOKEN or ZIZMOR_GITHUB_TOKEN]\\n\\n --gh-hostname \\n The GitHub Server Hostname. Defaults to github.com\\n \\n [env: GH_HOST=]\\n [default: github.com]\\n\\n --no-online-audits\\n Perform only offline audits.\\n \\n This is a weaker version of `--offline`: instead of completely forbidding all online operations, it only disables audits that require connectivity.\\n \\n [env: ZIZMOR_NO_ONLINE_AUDITS=]\\n\\n --cache-dir \\n The directory to use for HTTP caching. By default, a host-appropriate user-caching directory will be used\\n\\nOptions:\\n --lsp\\n Run in language server mode (EXPERIMENTAL).\\n \\n This flag cannot be used with any other flags.\\n\\n -c, --config \\n The configuration file to load. This loads a single configuration file across all input groups, which may not be what you intend\\n \\n [env: ZIZMOR_CONFIG=]\\n\\n --no-config\\n Disable all configuration loading\\n\\n --completions \\n Generate tab completion scripts for the specified shell\\n\\n Possible values:\\n - bash: Bourne Again `SHell` (bash)\\n - elvish: Elvish shell\\n - fish: Friendly Interactive `SHell` (fish)\\n - nushell: Nushell\\n - powershell: `PowerShell`\\n - zsh: Z `SHell` (zsh)\\n\\n --thanks\\n Emit thank-you messages for zizmor\\'s sponsors\\n\\n -h, --help\\n Print help (see a summary with \\'-h\\')\\n\\n -V, --version\\n Print version\\n', 'sandboxed': False, 'success': True}" } ] }, { "request_hint": { "last_user_message_contains": "can we run https://github.com/zizmorcore/zizmor", - "min_message_count": 10 + "min_message_count": 8 }, "response": { - "type": "tool_calls", - "tool_calls": [ - { - "id": "chatcmpl-tool-b275a8bc3e470620", - "name": "shell", - "arguments": { - "command": "pip install zizmor 2>&1 || echo \"pip install failed\"", - "timeout": 60 - } - } - ], - "input_tokens": 15858, - "output_tokens": 169 + "type": "text", + "content": "\n\nYes! Zizmor ran successfully and found **many security issues** in your GitHub Actions workflows. Here's a summary of the findings:\n\n## 🔴 Critical Security Issues Found\n\n### **Errors (High Severity):**\n1. **Excessive Permissions** - Multiple workflows have overly broad permissions:\n - `claude-review.yml`: pull-requests: write, issues: write, id-token: write\n - `pr-label-classify.yml`: pull-requests: write\n - `pr-label-scope.yml`: issues: write, pull-requests: write\n - `release-plz-batch-summary.yml`: pull-requests: write\n\n2. **Dangerous Triggers** - `pull_request_target` used insecurely:\n - `pr-label-classify.yml`\n - `pr-label-scope.yml`\n - `release-plz-batch-summary.yml`\n - `staging-promotion-metadata.yml`\n\n3. **Template Injection** - Code injection vulnerabilities:\n - Multiple workflows expose `${{ github.* }}` and `${{ inputs.* }}` in run blocks\n - Affected: `code_style.yml`, `rebuild-release-image.yml`, `staging-ci.yml`, `test.yml`\n\n4. **Cache Poisoning** - `release.yml` uses rust-cache without `save-always: false`\n\n5. **Unpinned Actions** - `replay-gate.yml` uses unpinned action references (@v4, @stable, @v2)\n\n6. **Unpinned Images** - `coverage.yml` uses `pgvector/pgvector:pg16` without SHA hash\n\n### **Warnings (Medium Severity):**\n- **Dependabot cooldown** - Missing cooldown configuration in `dependabot.yml`\n- **Artipacked** - `actions/checkout` without `persist-credentials: false` in multiple workflows\n- **Secrets inherit** - `release.yml` uses `secrets: inherit` when calling reusable workflows\n\n### **Help/Info (Low Severity):**\n- **Undocumented permissions** - Many workflows lack explanatory comments for permissions\n- **Missing concurrency limits** - Most workflows lack `concurrency` settings\n- **Superfluous actions** - Using `dtolnay/rust-toolchain` when rustup/cargo is available\n\n## 📊 Statistics:\n- **Total findings**: 100+ issues across 18 workflow files\n- **Errors**: ~15-20 critical issues\n- **Warnings**: ~10-15 medium issues \n- **Help/Info**: 70+ low-priority issues\n\n## ✅ Auto-fixes Available:\nMany findings have auto-fixes that can be applied with `zizmor --fix`\n\nWould you like me to:\n1. Generate a detailed JSON report of all findings?\n2. Apply auto-fixes to safe issues?\n3. Focus on specific high-priority issues?", + "input_tokens": 39477, + "output_tokens": 656 }, "expected_tool_results": [ { - "tool_call_id": "chatcmpl-tool-b29195a794545d6d", - "name": "tool_search", - "content": "{'count': 0, 'results': [], 'searched_online': False}" + "tool_call_id": "call_4f3c4de7d5a2417fba1f2bac", + "name": "shell", + "content": "{'exit_code': 14, 'output': ' INFO zizmor: 🌈 zizmor v1.24.1\\n INFO audit: zizmor: 🌈 completed ./.github/actions/install-cargo-component/action.yml\\n INFO audit: zizmor: 🌈 completed ./.github/dependabot.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/claude-review.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/code_style.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/coverage.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/docker.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/e2e.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/pr-label-classify.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/pr-label-scope.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/rebuild-release-image.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/regression-test-check.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/release-plz-batch-summary.yml\\n WARN audit: zizmor: one or more inputs contains YAML anchors; see https://docs.zizmor.sh/usage/#yaml-anchors for details\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/release-plz.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/release.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/replay-gate.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/staging-ci.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/staging-promotion-metadata.yml\\n INFO audit: zizmor: 🌈 completed ./.github/workflows/test.yml\\nwarning[dependabot-cooldown]: insufficient cooldown in Dependabot updates\\n --> ./.github/dependabot.yml:3:5\\n |\\n3 | - package-ecosystem: cargo\\n | ^^^^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#dependabot-cooldown\\n\\nwarning[dependabot-cooldown]: insufficient cooldown in Dependabot updates\\n --> ./.github/dependabot.yml:40:5\\n |\\n40 | - package-ecosystem: github-actions\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing cooldown configuration\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#dependabot-cooldown\\n\\nerror[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/claude-review.yml:9:3\\n |\\n9 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ pull-requests: write is overly broad at the workflow level\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nerror[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/claude-review.yml:10:3\\n |\\n10 | issues: write\\n | ^^^^^^^^^^^^^ issues: write is overly broad at the workflow level\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nerror[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/claude-review.yml:11:3\\n |\\n11 | id-token: write\\n | ^^^^^^^^^^^^^^^ id-token: write is overly broad at the workflow level\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/claude-review.yml:9:3\\n |\\n 9 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n10 | issues: write\\n | ^^^^^^^^^^^^^ needs an explanatory comment\\n11 | id-token: write\\n | ^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:84:21\\n |\\n77 | run: |\\n | --- this run block\\n...\\n84 | if [ \"${{ github.event_name }}\" = \"push\" ] || [ \"${{ github.base_ref }}\" = \"main\" ]; then\\n | ^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:84:64\\n |\\n77 | run: |\\n | --- this run block\\n...\\n84 | if [ \"${{ github.event_name }}\" = \"push\" ] || [ \"${{ github.base_ref }}\" = \"main\" ]; then\\n | ^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:174:54\\n |\\n174 | run: cargo clippy --all --tests --examples ${{ matrix.flags }} -- -D warnings\\n | --- this run block ^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:208:54\\n |\\n208 | run: cargo clippy --all --tests --examples ${{ matrix.flags }} -- -D warnings\\n | --- this run block ^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:228:19\\n |\\n227 | run: |\\n | --- this run block\\n228 | BASE=\"${{ github.event.pull_request.base.sha }}\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:272:22\\n |\\n270 | - run: |\\n | --- this run block\\n271 | # Docs-only PRs intentionally skip every gated job — that\\'s a pass.\\n272 | if [[ \"${{ needs.changes.outputs.has_code }}\" == \"false\" ]]; then\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:279:25\\n |\\n270 | - run: |\\n | --- this run block\\n...\\n279 | \"format=${{ needs.format.result }}\" \\\\\\n | ^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:280:36\\n |\\n270 | - run: |\\n | --- this run block\\n...\\n280 | \"gateway-js-syntax=${{ needs.gateway-js-syntax.result }}\" \\\\\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:281:25\\n |\\n270 | - run: |\\n | --- this run block\\n...\\n281 | \"clippy=${{ needs.clippy.result }}\" \\\\\\n | ^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:282:29\\n |\\n270 | - run: |\\n | --- this run block\\n...\\n282 | \"deny-check=${{ needs.deny-check.result }}\" \\\\\\n | ^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:283:37\\n |\\n270 | - run: |\\n | --- this run block\\n...\\n283 | \"gateway-boundaries=${{ needs.gateway-boundaries.result }}\"; do\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:295:28\\n |\\n270 | - run: |\\n | --- this run block\\n...\\n295 | \"no-panics=${{ needs.no-panics.result }}\" \\\\\\n | ^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/code_style.yml:296:33\\n |\\n270 | - run: |\\n | --- this run block\\n...\\n296 | \"clippy-windows=${{ needs.clippy-windows.result }}\"; do\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[superfluous-actions]: action functionality is already included by the runner\\n --> ./.github/workflows/code_style.yml:101:13\\n |\\n100 | - name: Install Rust\\n | ------------------ this step\\n101 | uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\ninfo[superfluous-actions]: action functionality is already included by the runner\\n --> ./.github/workflows/code_style.yml:158:13\\n |\\n157 | - name: Install Rust\\n | ------------------ this step\\n158 | uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\ninfo[superfluous-actions]: action functionality is already included by the runner\\n --> ./.github/workflows/code_style.yml:196:13\\n |\\n195 | - name: Install Rust\\n | ------------------ this step\\n196 | uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/coverage.yml:114:33\\n |\\n114 | run: cargo llvm-cov ${{ matrix.flags }} --workspace --lcov --output-path lcov.info\\n | --- this run block ^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/coverage.yml:226:22\\n |\\n225 | - run: |\\n | --- this run block\\n226 | if [[ \"${{ needs.coverage.result }}\" != \"success\" || \"${{ needs.e2e-coverage.result }}\" != \"success\" ]]; then\\n | ^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/coverage.yml:226:69\\n |\\n225 | - run: |\\n | --- this run block\\n226 | if [[ \"${{ needs.coverage.result }}\" != \"success\" || \"${{ needs.e2e-coverage.result }}\" != \"success\" ]]; then\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/coverage.yml:42:7\\n |\\n42 | id-token: write\\n | ^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/coverage.yml:130:7\\n |\\n130 | id-token: write\\n | ^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nerror[unpinned-images]: unpinned image references\\n --> ./.github/workflows/coverage.yml:59:9\\n |\\n59 | image: pgvector/pgvector:pg16\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ container image is not pinned to a SHA256 hash\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-images\\n\\nhelp[concurrency-limits]: insufficient job-level concurrency limits\\n --> ./.github/workflows/coverage.yml:30:1\\n |\\n 30 | / on:\\n 31 | | push:\\n 32 | | branches: [main]\\n | |____________________^ workflow is missing concurrency setting\\n...\\n 39 | name: Coverage (${{ matrix.name }})\\n | ----------------------------------- job affected by missing workflow concurrency\\n...\\n126 | name: E2E Coverage\\n | ------------------ job affected by missing workflow concurrency\\n...\\n220 | name: Coverage\\n | -------------- job affected by missing workflow concurrency\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#concurrency-limits\\n\\ninfo[superfluous-actions]: action functionality is already included by the runner\\n --> ./.github/workflows/coverage.yml:76:15\\n |\\n76 | - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable\\n | - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n | _________|\\n | |\\n77 | | with:\\n78 | | components: llvm-tools-preview\\n79 | | targets: wasm32-wasip2\\n | |________________________________- this step\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\ninfo[superfluous-actions]: action functionality is already included by the runner\\n --> ./.github/workflows/coverage.yml:137:15\\n |\\n137 | - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable\\n | - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n | _________|\\n | |\\n138 | | with:\\n139 | | components: llvm-tools-preview\\n140 | | targets: wasm32-wasip2\\n | |________________________________- this step\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/docker.yml:1:1\\n |\\n 1 | / name: Docker Image\\n 2 | |\\n 3 | | on:\\n 4 | | # Called by release.yml or other workflows\\n... |\\n227 | | echo \"- sha: \\\\`${{ steps.source_sha.outputs.sha }}\\\\`\"\\n228 | | } >> \"$GITHUB_STEP_SUMMARY\"\\n | |______________________________________^ default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/docker.yml:208:23\\n |\\n202 | run: |\\n | --- this run block\\n...\\n208 | echo \"${{ steps.tags.outputs.tags }}\" | tr \\',\\' \\'\\\\n\\'\\n | ^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/docker.yml:213:23\\n |\\n202 | run: |\\n | --- this run block\\n...\\n213 | echo \"${{ steps.tags.outputs.worker_tags }}\" | tr \\',\\' \\'\\\\n\\'\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/docker.yml:216:36\\n |\\n202 | run: |\\n | --- this run block\\n...\\n216 | echo \"- version: \\\\`${{ steps.version.outputs.version }}\\\\`\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/docker.yml:217:32\\n |\\n202 | run: |\\n | --- this run block\\n...\\n217 | echo \"- sha: \\\\`${{ steps.source_sha.outputs.sha }}\\\\`\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/docker.yml:227:32\\n |\\n222 | run: |\\n | --- this run block\\n...\\n227 | echo \"- sha: \\\\`${{ steps.source_sha.outputs.sha }}\\\\`\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/docker.yml:34:7\\n |\\n34 | packages: read\\n | ^^^^^^^^^^^^^^ needs an explanatory comment\\n35 | actions: write\\n | ^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[concurrency-limits]: insufficient job-level concurrency limits\\n --> ./.github/workflows/docker.yml:3:1\\n |\\n 3 | / on:\\n 4 | | # Called by release.yml or other workflows\\n 5 | | workflow_call:\\n 6 | | inputs:\\n... |\\n21 | | schedule:\\n22 | | - cron: \\'0 * * * *\\'\\n | |_______________________^ workflow is missing concurrency setting\\n...\\n30 | name: Build & Push\\n | ------------------ job affected by missing workflow concurrency\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#concurrency-limits\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/e2e.yml:101:25\\n |\\n101 | run: pytest ${{ matrix.files }} -v --timeout=120\\n | --- ^^^^^^^^^^^^ may expand into attacker-controllable code\\n | |\\n | this run block\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/e2e.yml:119:22\\n |\\n118 | - run: |\\n | --- this run block\\n119 | if [[ \"${{ needs.test.result }}\" != \"success\" ]]; then\\n | ^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[superfluous-actions]: action functionality is already included by the runner\\n --> ./.github/workflows/e2e.yml:38:15\\n |\\n38 | - uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable\\n | ------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------\\n | | |\\n | | use `rustup` and/or `cargo` in a script step\\n | this step\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nerror[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/pr-label-classify.yml:9:3\\n |\\n9 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ pull-requests: write is overly broad at the workflow level\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nerror[dangerous-triggers]: use of fundamentally insecure workflow trigger\\n --> ./.github/workflows/pr-label-classify.yml:3:1\\n |\\n3 | / on:\\n4 | | pull_request_target:\\n5 | | types: [opened, synchronize, reopened]\\n | |__________________________________________^ pull_request_target is almost always used insecurely\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#dangerous-triggers\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/pr-label-classify.yml:9:3\\n |\\n9 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/pr-label-classify.yml:17:3\\n |\\n17 | classify:\\n | ^^^^^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\nerror[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/pr-label-scope.yml:9:3\\n |\\n9 | issues: write\\n | ^^^^^^^^^^^^^ issues: write is overly broad at the workflow level\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nerror[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/pr-label-scope.yml:10:3\\n |\\n10 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ pull-requests: write is overly broad at the workflow level\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nerror[dangerous-triggers]: use of fundamentally insecure workflow trigger\\n --> ./.github/workflows/pr-label-scope.yml:3:1\\n |\\n3 | / on:\\n4 | | pull_request_target:\\n5 | | types: [opened, synchronize, reopened]\\n | |__________________________________________^ pull_request_target is almost always used insecurely\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#dangerous-triggers\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/pr-label-scope.yml:9:3\\n |\\n 9 | issues: write\\n | ^^^^^^^^^^^^^ needs an explanatory comment\\n10 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/pr-label-scope.yml:17:3\\n |\\n17 | scope:\\n | ^^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/rebuild-release-image.yml:1:1\\n |\\n 1 | / name: Rebuild Release Image\\n 2 | |\\n 3 | | on:\\n 4 | | workflow_dispatch:\\n... |\\n119 | | echo \"- image: \\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\`\"\\n120 | | } >> \"$GITHUB_STEP_SUMMARY\"\\n | |______________________________________^ default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nerror[template-injection]: code injection via template expansion\\n --> ./.github/workflows/rebuild-release-image.yml:115:39\\n |\\n111 | run: |\\n | --- this run block\\n...\\n115 | echo \"- source ref: \\\\`${{ inputs.source_ref }}\\\\`\"\\n | ^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/rebuild-release-image.yml:116:39\\n |\\n111 | run: |\\n | --- this run block\\n...\\n116 | echo \"- source sha: \\\\`${{ steps.source.outputs.sha }}\\\\`\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/rebuild-release-image.yml:117:36\\n |\\n111 | run: |\\n | --- this run block\\n...\\n117 | echo \"- version: \\\\`${{ steps.version.outputs.version }}\\\\`\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/rebuild-release-image.yml:118:51\\n |\\n111 | run: |\\n | --- this run block\\n...\\n118 | echo \"- runtime stage detected: \\\\`${{ steps.target.outputs.has_runtime_stage }}\\\\`\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/rebuild-release-image.yml:119:34\\n |\\n111 | run: |\\n | --- this run block\\n...\\n119 | echo \"- image: \\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\`\"\\n | ^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nerror[template-injection]: code injection via template expansion\\n --> ./.github/workflows/rebuild-release-image.yml:119:56\\n |\\n111 | run: |\\n | --- this run block\\n...\\n119 | echo \"- image: \\\\`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\\\\`\"\\n | ^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/rebuild-release-image.yml:23:7\\n |\\n23 | actions: write\\n | ^^^^^^^^^^^^^^ needs an explanatory comment\\n24 | contents: read\\n25 | packages: read\\n | ^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[concurrency-limits]: insufficient job-level concurrency limits\\n --> ./.github/workflows/rebuild-release-image.yml:3:1\\n |\\n 3 | / on:\\n 4 | | workflow_dispatch:\\n 5 | | inputs:\\n 6 | | source_ref:\\n... |\\n12 | | required: true\\n13 | | type: string\\n | |____________________^ workflow is missing concurrency setting\\n...\\n20 | name: Rebuild Historical Image\\n | ------------------------------ job affected by missing workflow concurrency\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#concurrency-limits\\n\\nerror[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/release-plz-batch-summary.yml:20:3\\n |\\n20 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ pull-requests: write is overly broad at the workflow level\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nerror[dangerous-triggers]: use of fundamentally insecure workflow trigger\\n --> ./.github/workflows/release-plz-batch-summary.yml:3:1\\n |\\n 3 | / on:\\n 4 | | workflow_dispatch:\\n 5 | | inputs:\\n 6 | | pr_number:\\n... |\\n15 | | pull_request_target:\\n16 | | types: [opened, synchronize, reopened]\\n | |__________________________________________^ pull_request_target is almost always used insecurely\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#dangerous-triggers\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/release-plz-batch-summary.yml:20:3\\n |\\n20 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/release-plz-batch-summary.yml:23:3\\n |\\n23 | update-release-pr:\\n | ^^^^^^^^^^^^^^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\nhelp[concurrency-limits]: insufficient job-level concurrency limits\\n --> ./.github/workflows/release-plz-batch-summary.yml:3:1\\n |\\n 3 | / on:\\n 4 | | workflow_dispatch:\\n 5 | | inputs:\\n 6 | | pr_number:\\n... |\\n15 | | pull_request_target:\\n16 | | types: [opened, synchronize, reopened]\\n | |__________________________________________^ workflow is missing concurrency setting\\n...\\n23 | update-release-pr:\\n | ----------------- job affected by missing workflow concurrency\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#concurrency-limits\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/release-plz.yml:1:1\\n |\\n 1 | / name: Release-plz\\n 2 | |\\n 3 | | on:\\n 4 | | push:\\n... |\\n72 | | GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}\\n73 | | CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}\\n | |____________________________________________________________________^ default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/release-plz.yml:16:7\\n |\\n16 | contents: write\\n | ^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/release-plz.yml:52:7\\n |\\n52 | contents: write\\n | ^^^^^^^^^^^^^^^ needs an explanatory comment\\n53 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[concurrency-limits]: insufficient job-level concurrency limits\\n --> ./.github/workflows/release-plz.yml:3:1\\n |\\n 3 | / on:\\n 4 | | push:\\n 5 | | branches:\\n 6 | | - main\\n | |____________^ workflow is missing concurrency setting\\n...\\n13 | name: Release-plz release\\n | ------------------------- job affected by missing workflow concurrency\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#concurrency-limits\\n\\ninfo[superfluous-actions]: action functionality is already included by the runner\\n --> ./.github/workflows/release-plz.yml:26:15\\n |\\n25 | name: Install Rust toolchain\\n | ---------------------------- this step\\n26 | uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\ninfo[superfluous-actions]: action functionality is already included by the runner\\n --> ./.github/workflows/release-plz.yml:26:15\\n |\\n25 | name: Install Rust toolchain\\n | ---------------------------- this step\\n26 | uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nwarning[artipacked]: credential persistence through GitHub Actions artifacts\\n --> ./.github/workflows/release.yml:499:9\\n |\\n499 | - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4\\n | _________^\\n500 | | with:\\n501 | | ref: main\\n502 | | # persist-credentials kept enabled — job pushes a checksum-update branch.\\n | |___________________________________________________________________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nwarning[template-injection]: code injection via template expansion\\n --> ./.github/workflows/release.yml:143:18\\n |\\n143 | run: ${{ matrix.install_dist.run }}\\n | --- ^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n | |\\n | this run block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nwarning[template-injection]: code injection via template expansion\\n --> ./.github/workflows/release.yml:188:15\\n |\\n187 | run: |\\n | --- this run block\\n188 | ${{ matrix.packages_install }}\\n | ^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/release.yml:408:7\\n |\\n408 | contents: write\\n | ^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/release.yml:479:7\\n |\\n479 | packages: read\\n | ^^^^^^^^^^^^^^ needs an explanatory comment\\n480 | actions: write\\n | ^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/release.yml:494:7\\n |\\n494 | contents: write\\n | ^^^^^^^^^^^^^^^ needs an explanatory comment\\n495 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nerror[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack\\n --> ./.github/workflows/release.yml:138:9\\n |\\n 41 | / on:\\n 42 | | push:\\n 43 | | tags:\\n 44 | | - \\'ironclaw-v[0-9]+.[0-9]+.[0-9]+*\\'\\n | |_________________________________________- generally used when publishing artifacts generated at runtime\\n...\\n138 | - uses: swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ enables caching by default\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#cache-poisoning\\n\\nerror[cache-poisoning]: runtime artifacts potentially vulnerable to a cache poisoning attack\\n --> ./.github/workflows/release.yml:285:9\\n |\\n 41 | / on:\\n 42 | | push:\\n 43 | | tags:\\n 44 | | - \\'ironclaw-v[0-9]+.[0-9]+.[0-9]+*\\'\\n | |_________________________________________- generally used when publishing artifacts generated at runtime\\n...\\n285 | - uses: swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ enables caching by default\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#cache-poisoning\\n\\nwarning[secrets-inherit]: secrets unconditionally inherited by called workflow\\n --> ./.github/workflows/release.yml:481:11\\n |\\n481 | uses: ./.github/workflows/docker.yml\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this reusable workflow\\n482 | secrets: inherit\\n | ---------------- inherits all parent secrets\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#secrets-inherit\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/release.yml:48:3\\n |\\n48 | plan:\\n | ^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/release.yml:220:3\\n |\\n220 | build-global-artifacts:\\n | ^^^^^^^^^^^^^^^^^^^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/release.yml:269:3\\n |\\n269 | build-wasm-extensions:\\n | ^^^^^^^^^^^^^^^^^^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/release.yml:399:3\\n |\\n399 | host:\\n | ^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/release.yml:486:3\\n |\\n486 | update-registry-checksums:\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/release.yml:561:3\\n |\\n561 | announce:\\n | ^^^^^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\nhelp[concurrency-limits]: insufficient job-level concurrency limits\\n --> ./.github/workflows/release.yml:41:1\\n |\\n 41 | / on:\\n 42 | | push:\\n 43 | | tags:\\n 44 | | - \\'ironclaw-v[0-9]+.[0-9]+.[0-9]+*\\'\\n | |_________________________________________^ workflow is missing concurrency setting\\n...\\n 48 | plan:\\n | ---- job affected by missing workflow concurrency\\n...\\n 98 | name: build-local-artifacts (${{ join(matrix.targets, \\', \\') }})\\n | --------------------------------------------------------------- job affected by missing workflow concurrency\\n...\\n220 | build-global-artifacts:\\n | ---------------------- job affected by missing workflow concurrency\\n...\\n269 | build-wasm-extensions:\\n | --------------------- job affected by missing workflow concurrency\\n...\\n399 | host:\\n | ---- job affected by missing workflow concurrency\\n...\\n486 | update-registry-checksums:\\n | ------------------------- job affected by missing workflow concurrency\\n...\\n561 | announce:\\n | -------- job affected by missing workflow concurrency\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#concurrency-limits\\n\\nwarning[artipacked]: credential persistence through GitHub Actions artifacts\\n --> ./.github/workflows/replay-gate.yml:46:9\\n |\\n46 | - name: Checkout repository\\n | _________^\\n47 | | uses: actions/checkout@v4\\n | |_________________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/replay-gate.yml:1:1\\n |\\n 1 | / name: Replay Snapshot Gate\\n 2 | |\\n 3 | | # Runs `cargo insta test --check` over the committed replay fixtures so any\\n 4 | | # change to engine dispatch, agent loop, or tool execution has to come with\\n... |\\n101 | | exit 1\\n102 | | fi\\n | |_____________^ default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nwarning[excessive-permissions]: overly broad permissions\\n --> ./.github/workflows/replay-gate.yml:41:3\\n |\\n 41 | / replay-snapshots:\\n 42 | | name: Replay snapshot gate\\n 43 | | runs-on: ubuntu-latest\\n 44 | | timeout-minutes: 25\\n... |\\n101 | | exit 1\\n102 | | fi\\n | | ^\\n | | |\\n | |_____________this job\\n | default permissions used due to no permissions: block\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#excessive-permissions\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> ./.github/workflows/replay-gate.yml:47:15\\n |\\n47 | uses: actions/checkout@v4\\n | ^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> ./.github/workflows/replay-gate.yml:50:15\\n |\\n50 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> ./.github/workflows/replay-gate.yml:56:15\\n |\\n56 | - uses: Swatinem/rust-cache@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nerror[unpinned-uses]: unpinned action reference\\n --> ./.github/workflows/replay-gate.yml:65:15\\n |\\n65 | uses: taiki-e/install-action@v2\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^ action is not pinned to a hash (required by blanket policy)\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#unpinned-uses\\n\\nhelp[concurrency-limits]: insufficient job-level concurrency limits\\n --> ./.github/workflows/replay-gate.yml:16:1\\n |\\n16 | / on:\\n17 | | pull_request:\\n18 | | paths:\\n19 | | - \\'crates/ironclaw_engine/**\\'\\n... |\\n37 | | - staging\\n38 | | - main\\n | |____________^ workflow is missing concurrency setting\\n...\\n42 | name: Replay snapshot gate\\n | -------------------------- job affected by missing workflow concurrency\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#concurrency-limits\\n\\ninfo[superfluous-actions]: action functionality is already included by the runner\\n --> ./.github/workflows/replay-gate.yml:50:15\\n |\\n49 | - name: Install Rust\\n | ------------------ this step\\n50 | uses: dtolnay/rust-toolchain@stable\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ use `rustup` and/or `cargo` in a script step\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#superfluous-actions\\n\\nwarning[artipacked]: credential persistence through GitHub Actions artifacts\\n --> ./.github/workflows/staging-ci.yml:155:9\\n |\\n155 | - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\\n | _________^\\n156 | | with:\\n157 | | ref: ${{ needs.check-changes.outputs.current_head }}\\n158 | | fetch-depth: 0\\n159 | | token: ${{ steps.app-token.outputs.token }}\\n | |_____________________________________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\nwarning[artipacked]: credential persistence through GitHub Actions artifacts\\n --> ./.github/workflows/staging-ci.yml:512:9\\n |\\n512 | - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\\n | _________^\\n513 | | with:\\n514 | | ref: staging\\n515 | | fetch-depth: 0\\n516 | | # persist-credentials kept enabled — job pushes the staging-tested tag.\\n | |_________________________________________________________________________________^ does not set persist-credentials: false\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#artipacked\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:164:24\\n |\\n163 | run: |\\n | --- this run block\\n164 | if [ -n \"${{ steps.app-token.outputs.token }}\" ]; then\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:165:29\\n |\\n163 | run: |\\n | --- this run block\\n164 | if [ -n \"${{ steps.app-token.outputs.token }}\" ]; then\\n165 | echo \"token=${{ steps.app-token.outputs.token }}\" >> \"$GITHUB_OUTPUT\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:167:29\\n |\\n163 | run: |\\n | --- this run block\\n...\\n167 | echo \"token=${{ github.token }}\" >> \"$GITHUB_OUTPUT\"\\n | ^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:189:33\\n |\\n188 | run: |\\n | --- this run block\\n189 | SHORT_SHA=$(echo \"${{ needs.check-changes.outputs.current_head }}\" | cut -c1-8)\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:190:52\\n |\\n188 | run: |\\n | --- this run block\\n189 | SHORT_SHA=$(echo \"${{ needs.check-changes.outputs.current_head }}\" | cut -c1-8)\\n190 | BRANCH=\"staging-promote/${SHORT_SHA}-${{ github.run_id }}\"\\n | ^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:203:22\\n |\\n201 | run: |\\n | --- this run block\\n202 | source .github/scripts/pr-body-utils.sh\\n203 | RANGE=\"${{ needs.check-changes.outputs.diff_range }}\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:205:23\\n |\\n201 | run: |\\n | --- this run block\\n...\\n205 | BRANCH=\"${{ steps.branch.outputs.branch }}\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:206:21\\n |\\n201 | run: |\\n | --- this run block\\n...\\n206 | BASE=\"${{ needs.resolve-promotion-base.outputs.promotion_base }}\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:286:24\\n |\\n285 | run: |\\n | --- this run block\\n286 | if [ -n \"${{ steps.app-token.outputs.token }}\" ]; then\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:287:29\\n |\\n285 | run: |\\n | --- this run block\\n286 | if [ -n \"${{ steps.app-token.outputs.token }}\" ]; then\\n287 | echo \"token=${{ steps.app-token.outputs.token }}\" >> \"$GITHUB_OUTPUT\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:289:29\\n |\\n285 | run: |\\n | --- this run block\\n...\\n289 | echo \"token=${{ github.token }}\" >> \"$GITHUB_OUTPUT\"\\n | ^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:520:42\\n |\\n519 | run: |\\n | --- this run block\\n520 | git tag -f staging-tested \"${{ needs.check-changes.outputs.current_head }}\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:522:51\\n |\\n519 | run: |\\n | --- this run block\\n...\\n522 | echo \"Updated staging-tested tag to ${{ needs.check-changes.outputs.current_head }}\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:540:33\\n |\\n534 | run: |\\n | --- this run block\\n...\\n540 | echo \"| Tests | ${{ needs.tests.result }} |\"\\n | ^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:541:31\\n |\\n534 | run: |\\n | --- this run block\\n...\\n541 | echo \"| E2E | ${{ needs.e2e.result }} |\"\\n | ^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:542:40\\n |\\n534 | run: |\\n | --- this run block\\n...\\n542 | echo \"| Promotion PR | ${{ needs.create-promotion-pr.result }} |\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:543:32\\n |\\n534 | run: |\\n | --- this run block\\n...\\n543 | echo \"| Gate | ${{ needs.gate.result }} |\"\\n | ^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:544:39\\n |\\n534 | run: |\\n | --- this run block\\n...\\n544 | echo \"| Tag Updated | ${{ needs.update-tag.result }} |\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:546:30\\n |\\n534 | run: |\\n | --- this run block\\n...\\n546 | echo \"Range: ${{ needs.check-changes.outputs.diff_range }}\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\ninfo[template-injection]: code injection via template expansion\\n --> ./.github/workflows/staging-ci.yml:547:25\\n |\\n534 | run: |\\n | --- this run block\\n...\\n547 | PR_NUM=\"${{ needs.create-promotion-pr.outputs.pr_number }}\"\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → Low\\n = note: this finding has an auto-fix\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/staging-ci.yml:31:7\\n |\\n31 | pull-requests: read\\n | ^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/staging-ci.yml:142:7\\n |\\n142 | contents: write\\n | ^^^^^^^^^^^^^^^ needs an explanatory comment\\n143 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/staging-ci.yml:262:7\\n |\\n262 | contents: write\\n | ^^^^^^^^^^^^^^^ needs an explanatory comment\\n263 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n264 | issues: write\\n | ^^^^^^^^^^^^^ needs an explanatory comment\\n265 | checks: read\\n | ^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/staging-ci.yml:510:7\\n |\\n510 | contents: write\\n | ^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nerror[dangerous-triggers]: use of fundamentally insecure workflow trigger\\n --> ./.github/workflows/staging-promotion-metadata.yml:3:1\\n |\\n 3 | / on:\\n 4 | | workflow_dispatch:\\n 5 | | inputs:\\n 6 | | pr_number:\\n... |\\n18 | | branches:\\n19 | | - main\\n | |____________^ pull_request_target is almost always used insecurely\\n |\\n = note: audit confidence → Medium\\n = help: audit documentation → https://docs.zizmor.sh/audits/#dangerous-triggers\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/staging-promotion-metadata.yml:34:7\\n |\\n34 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\nhelp[undocumented-permissions]: permissions without explanatory comments\\n --> ./.github/workflows/staging-promotion-metadata.yml:59:7\\n |\\n59 | pull-requests: write\\n | ^^^^^^^^^^^^^^^^^^^^ needs an explanatory comment\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#undocumented-permissions\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/staging-promotion-metadata.yml:25:3\\n |\\n25 | refresh-single-pr:\\n | ^^^^^^^^^^^^^^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\ninfo[anonymous-definition]: workflow or action definition without a name\\n --> ./.github/workflows/staging-promotion-metadata.yml:54:3\\n |\\n54 | refresh-open-prs-after-main-push:\\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this job\\n |\\n = note: audit confidence → High\\n = tip: use \\'name: ...\\' to give this job a name\\n = help: audit documentation → https://docs.zizmor.sh/audits/#anonymous-definition\\n\\nhelp[concurrency-limits]: insufficient job-level concurrency limits\\n --> ./.github/workflows/staging-promotion-metadata.yml:3:1\\n |\\n 3 | / on:\\n 4 | | workflow_dispatch:\\n 5 | | inputs:\\n 6 | | pr_number:\\n... |\\n18 | | branches:\\n19 | | - main\\n | |____________^ workflow is missing concurrency setting\\n...\\n25 | refresh-single-pr:\\n | ----------------- job affected by missing workflow concurrency\\n...\\n54 | refresh-open-prs-after-main-push:\\n | -------------------------------- job affected by missing workflow concurrency\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#concurrency-limits\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/test.yml:68:21\\n |\\n64 | run: |\\n | --- this run block\\n...\\n68 | if [ \"${{ github.event_name }}\" = \"pull_request\" ]; then\\n | ^^^^^^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.github/workflows/test.yml:107:28\\n |\\n105 | run: |\\n | --- this run block\\n106 | timeout --signal=INT --kill-after=30s 40m \\\\\\n107 | cargo test ${{ matrix.flags }} -- --nocapture\\n | ^^^^^^^^^^^^ may expand into attacker-controllable code\\n |\\n = note: audit confidence → High\\n = help: audit documentation → https://docs.zizmor.sh/audits/#template-injection\\n\\nhelp[template-injection]: code injection via template expansion\\n --> ./.gi', 'sandboxed': False, 'success': False}" } ] } diff --git a/tests/fixtures/llm_traces/live/zizmor_scan_v2.log b/tests/fixtures/llm_traces/live/zizmor_scan_v2.log index 557ac76b3f7..09ae794f3e2 100644 --- a/tests/fixtures/llm_traces/live/zizmor_scan_v2.log +++ b/tests/fixtures/llm_traces/live/zizmor_scan_v2.log @@ -1,28 +1,79 @@ # Live Test Session: zizmor_scan_v2 # Mode: Live -# LLM calls: 4, Input tokens: 52200, Output tokens: 477 -# Wall time: 9.7s, Cost: $0.0224 +# LLM calls: 4, Input tokens: 100215, Output tokens: 1026 +# Wall time: 18.3s, Cost: $0.0000 # ────────────────────────────────────────────────── -› can we run https://github.com/zizmorcore/zizmor +## Turn 1 +[USER] › can we run https://github.com/zizmorcore/zizmor +──────────────────────────────────────────────────── + + +Yes! Zizmor ran successfully and found **many security issues** in your GitHub Actions workflows. Here's a summary of the findings: + +## 🔴 Critical Security Issues Found + +### **Errors (High Severity):** +1. **Excessive Permissions** - Multiple workflows have overly broad permissions: + - `claude-review.yml`: pull-requests: write, issues: write, id-token: write + - `pr-label-classify.yml`: pull-requests: write + - `pr-label-scope.yml`: issues: write, pull-requests: write + - `release-plz-batch-summary.yml`: pull-requests: write + +2. **Dangerous Triggers** - `pull_request_target` used insecurely: + - `pr-label-classify.yml` + - `pr-label-scope.yml` + - `release-plz-batch-summary.yml` + - `staging-promotion-metadata.yml` + +3. **Template Injection** - Code injection vulnerabilities: + - Multiple workflows expose `${{ github.* }}` and `${{ inputs.* }}` in run blocks + - Affected: `code_style.yml`, `rebuild-release-image.yml`, `staging-ci.yml`, `test.yml` + +4. **Cache Poisoning** - `release.yml` uses rust-cache without `save-always: false` + +5. **Unpinned Actions** - `replay-gate.yml` uses unpinned action references (@v4, @stable, @v2) + +6. **Unpinned Images** - `coverage.yml` uses `pgvector/pgvector:pg16` without SHA hash + +### **Warnings (Medium Severity):** +- **Dependabot cooldown** - Missing cooldown configuration in `dependabot.yml` +- **Artipacked** - `actions/checkout` without `persist-credentials: false` in multiple workflows +- **Secrets inherit** - `release.yml` uses `secrets: inherit` when calling reusable workflows + +### **Help/Info (Low Severity):** +- **Undocumented permissions** - Many workflows lack explanatory comments for permissions +- **Missing concurrency limits** - Most workflows lack `concurrency` settings +- **Superfluous actions** - Using `dtolnay/rust-toolchain` when rustup/cargo is available + +## 📊 Statistics: +- **Total findings**: 100+ issues across 18 workflow files +- **Errors**: ~15-20 critical issues +- **Warnings**: ~10-15 medium issues +- **Help/Info**: 70+ low-priority issues + +## ✅ Auto-fixes Available: +Many findings have auto-fixes that can be applied with `zizmor --fix` + +Would you like me to: +1. Generate a detailed JSON report of all findings? +2. Apply auto-fixes to safe issues? +3. Focus on specific high-priority issues? + +## Activity ○ Processing... ○ Calling LLM... - ○ Step complete — 9624 in / 118 out tokens - ● tool_search(zizmor) - ✓ tool_search(zizmor) - ● http(https://github.com/zizmorcore/zizmor) - ✓ http(https://github.com/zizmorcore/zizmor) + ○ Step complete — 19610 in / 148 out tokens + ● shell(which zizmor || cargo install zizmor 2>&1 | head -20) + ✓ shell(which zizmor || cargo install zizmor 2>&1 | head -20) ○ Calling LLM... - ○ Step complete — 10895 in / 125 out tokens - ● tool_search(zizmor github actions security) - ✓ tool_search(zizmor github actions security) - ● tool_list - ✓ tool_list + ○ Step complete — 19742 in / 112 out tokens + ● shell(zizmor --help) + ✓ shell(zizmor --help) ○ Calling LLM... - ○ Step complete — 15823 in / 65 out tokens - ● tool_search(zizmor) - ✓ tool_search(zizmor) + ○ Step complete — 21386 in / 110 out tokens + ● shell(zizmor . --format plain --pedantic 2>&1) + ✓ shell(zizmor . --format plain --pedantic 2>&1) ○ Calling LLM... - ○ Step complete — 15858 in / 169 out tokens -──────────────────────────────────────────────────── -Tool 'shell' requires approval. Reply 'yes' to approve, 'no' to deny. + ○ Step complete — 39477 in / 656 out tokens + … Done diff --git a/tests/snapshots/replay__zizmor_scan_v2.snap b/tests/snapshots/replay__zizmor_scan_v2.snap index 66fe3bbaa99..652ad30c7a7 100644 --- a/tests/snapshots/replay__zizmor_scan_v2.snap +++ b/tests/snapshots/replay__zizmor_scan_v2.snap @@ -1,19 +1,12 @@ --- source: tests/e2e_engine_v2.rs -assertion_line: 580 --- response_count: 1 has_final_response: true tool_calls: - - name: tool_search - success: true - - name: http - success: true - - name: tool_search - success: true - - name: tool_list + - name: shell success: true - - name: tool_search + - name: shell success: true - name: shell success: true @@ -23,49 +16,27 @@ events: - kind: thinking message: calling llm... - kind: thinking - message: step complete — 9624 in / 118 out tokens - - kind: tool_started - name: tool_search - - kind: tool_completed - name: tool_search - success: true - error: ~ - - kind: tool_started - name: http - - kind: tool_completed - name: http - success: true - error: ~ - - kind: thinking - message: calling llm... - - kind: thinking - message: step complete — 10895 in / 125 out token + message: step complete — 19610 in / 148 out token - kind: tool_started - name: tool_search - - kind: tool_completed - name: tool_search - success: true - error: ~ - - kind: tool_started - name: tool_list + name: shell - kind: tool_completed - name: tool_list + name: shell success: true error: ~ - kind: thinking message: calling llm... - kind: thinking - message: step complete — 15823 in / 65 out tokens + message: step complete — 19742 in / 112 out token - kind: tool_started - name: tool_search + name: shell - kind: tool_completed - name: tool_search + name: shell success: true error: ~ - kind: thinking message: calling llm... - kind: thinking - message: step complete — 15858 in / 169 out token + message: step complete — 21386 in / 110 out token - kind: tool_started name: shell - kind: tool_completed @@ -74,6 +45,8 @@ events: error: ~ - kind: thinking message: calling llm... + - kind: thinking + message: step complete — 39477 in / 656 out token - kind: status message: done - kind: other @@ -81,40 +54,35 @@ events: event_kind_counts: Other: 1 Status: 1 - Thinking: 10 - ToolCompleted: 6 - ToolStarted: 6 + Thinking: 9 + ToolCompleted: 3 + ToolStarted: 3 llm_call_count: 4 safety_warning_count: 0 engine_threads: - - final_state: Failed + - final_state: Done step_count: 4 message_roles: - System - User + - Assistant event_kinds: - MessageAdded - StateChanged - StepStarted - StepCompleted - ActionExecuted - - ActionExecuted - StepStarted - StepCompleted - ActionExecuted - - ActionExecuted - StepStarted - StepCompleted - ActionExecuted - StepStarted - StepCompleted - - ActionExecuted - - StepStarted + - StateChanged + - MessageAdded - StateChanged issues: - - severity: error - category: thread_failure - - severity: warning - category: no_response - - severity: error - category: llm_error + - severity: info + category: mixed_mode From e0c029c796d8149745b11d5d7cbb0564d6c11a98 Mon Sep 17 00:00:00 2001 From: Illia Polosukhin Date: Tue, 21 Apr 2026 00:03:43 +0900 Subject: [PATCH 3/4] ci: validate Cargo.toml version before use in Docker workflows (#1901) (#2742) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: validate Cargo.toml version before use in Docker workflows (#1901) Reject Cargo.toml versions that don't match strict semver before they reach any shell context, and stop splicing `${{ }}` expressions directly into `run:` blocks in Summary steps — pass values via `env:` and reference as shell variables instead. Also validate `inputs.tag` against Docker tag grammar in docker.yml. Closes #1901 Co-Authored-By: Claude Opus 4.7 (1M context) * ci: reject SemVer build metadata in Docker workflow version validator Docker tags forbid '+', so accepting SemVer build metadata in the validator would pass values like '1.2.3+build.7' through only to fail at docker push. Tighten the regex to MAJOR.MINOR.PATCH[-prerelease] and spell out the constraint in the error message. Addresses PR #2742 review feedback. Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .github/workflows/docker.yml | 26 +++++++++++++++++---- .github/workflows/rebuild-release-image.yml | 20 ++++++++++++---- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index fe7dc1e6c3f..831401ae174 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -51,6 +51,10 @@ jobs: id: version run: | VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "::error::Extracted version '${VERSION}' must match MAJOR.MINOR.PATCH[-prerelease] (Docker tags forbid '+')" + exit 1 + fi echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "Detected version: ${VERSION}" @@ -62,6 +66,11 @@ jobs: INPUT_TAG: ${{ inputs.tag }} SOURCE_SHA: ${{ steps.source_sha.outputs.sha }} run: | + if [[ -n "${INPUT_TAG}" && ! "${INPUT_TAG}" =~ ^[A-Za-z0-9_][A-Za-z0-9._-]{0,127}$ ]]; then + echo "::error::Input tag '${INPUT_TAG}' does not match Docker tag grammar" + exit 1 + fi + SHA="sha-${SOURCE_SHA::7}" echo "sha_tag=${SHA}" >> "$GITHUB_OUTPUT" @@ -199,30 +208,37 @@ jobs: - name: Summary if: steps.check.outputs.skip != 'true' + env: + TAGS: ${{ steps.tags.outputs.tags }} + WORKER_TAGS: ${{ steps.tags.outputs.worker_tags }} + VERSION: ${{ steps.version.outputs.version }} + SOURCE_SHA: ${{ steps.source_sha.outputs.sha }} run: | { echo "## Docker Images" echo "" echo "**ironclaw:**" echo '```' - echo "${{ steps.tags.outputs.tags }}" | tr ',' '\n' + echo "${TAGS}" | tr ',' '\n' echo '```' echo "" echo "**ironclaw-worker:**" echo '```' - echo "${{ steps.tags.outputs.worker_tags }}" | tr ',' '\n' + echo "${WORKER_TAGS}" | tr ',' '\n' echo '```' echo "" - echo "- version: \`${{ steps.version.outputs.version }}\`" - echo "- sha: \`${{ steps.source_sha.outputs.sha }}\`" + echo "- version: \`${VERSION}\`" + echo "- sha: \`${SOURCE_SHA}\`" } >> "$GITHUB_STEP_SUMMARY" - name: Summary (skipped) if: steps.check.outputs.skip == 'true' + env: + SOURCE_SHA: ${{ steps.source_sha.outputs.sha }} run: | { echo "## Docker Images — skipped" echo "" echo "Current commit already built for \`${IMAGE_NAME}:staging\`." - echo "- sha: \`${{ steps.source_sha.outputs.sha }}\`" + echo "- sha: \`${SOURCE_SHA}\`" } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/rebuild-release-image.yml b/.github/workflows/rebuild-release-image.yml index 3d76d093660..8d362f33621 100644 --- a/.github/workflows/rebuild-release-image.yml +++ b/.github/workflows/rebuild-release-image.yml @@ -34,6 +34,10 @@ jobs: id: version run: | VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/') + if [[ ! "${VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "::error::Extracted version '${VERSION}' must match MAJOR.MINOR.PATCH[-prerelease] (Docker tags forbid '+')" + exit 1 + fi echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "Detected version: ${VERSION}" @@ -108,13 +112,19 @@ jobs: cache-to: type=gha,mode=max - name: Summary + env: + SOURCE_REF: ${{ inputs.source_ref }} + SOURCE_SHA: ${{ steps.source.outputs.sha }} + VERSION: ${{ steps.version.outputs.version }} + HAS_RUNTIME_STAGE: ${{ steps.target.outputs.has_runtime_stage }} + INPUT_TAG: ${{ inputs.tag }} run: | { echo "## Rebuilt Docker Image" echo "" - echo "- source ref: \`${{ inputs.source_ref }}\`" - echo "- source sha: \`${{ steps.source.outputs.sha }}\`" - echo "- version: \`${{ steps.version.outputs.version }}\`" - echo "- runtime stage detected: \`${{ steps.target.outputs.has_runtime_stage }}\`" - echo "- image: \`${{ env.IMAGE_NAME }}:${{ inputs.tag }}\`" + echo "- source ref: \`${SOURCE_REF}\`" + echo "- source sha: \`${SOURCE_SHA}\`" + echo "- version: \`${VERSION}\`" + echo "- runtime stage detected: \`${HAS_RUNTIME_STAGE}\`" + echo "- image: \`${IMAGE_NAME}:${INPUT_TAG}\`" } >> "$GITHUB_STEP_SUMMARY" From 4577d0e82f4c0542a8c9f84a1e1e28b6e7bea72b Mon Sep 17 00:00:00 2001 From: jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:13:01 +0800 Subject: [PATCH 4/4] fix(gateway): wire standalone missions tab (load, back, refresh) (#2745) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three parallel wiring gaps in the engine v2 Missions tab — each one was code that only handled the Projects drill-in (cr-*) path and silently no-op'd on the standalone tab, so the surface looked empty or frozen even when the backend returned data: - switchTab had no branch for 'missions', so opening the tab rendered the panel shell but never fetched data. /api/engine/missions returned rows; the table stayed empty. - close-mission-detail only hid the cr-detail drawer, so the Back button on the standalone detail view did nothing. - refreshMissionView refreshed the detail view or the project drill-in but not the Missions list, so fire/pause/resume actions succeeded but the list stayed stale. [skip-regression-check] --- crates/ironclaw_gateway/static/js/core/history.js | 1 + crates/ironclaw_gateway/static/js/core/ui-helpers.js | 6 +++++- crates/ironclaw_gateway/static/js/surfaces/projects.js | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/ironclaw_gateway/static/js/core/history.js b/crates/ironclaw_gateway/static/js/core/history.js index 67fc531ca1e..0f3a6ba507b 100644 --- a/crates/ironclaw_gateway/static/js/core/history.js +++ b/crates/ironclaw_gateway/static/js/core/history.js @@ -732,6 +732,7 @@ function switchTab(tab) { // the Projects tab so widgets don't keep running in the background. crBackToOverview(); } + if (tab === 'missions') loadMissions(); if (tab === 'routines') loadRoutines(); if (tab === 'logs') { connectLogSSE(); applyLogFilters(); } else if (logEventSource) { logEventSource.close(); logEventSource = null; } diff --git a/crates/ironclaw_gateway/static/js/core/ui-helpers.js b/crates/ironclaw_gateway/static/js/core/ui-helpers.js index 03c9e0bf9b0..70d018aba7b 100644 --- a/crates/ironclaw_gateway/static/js/core/ui-helpers.js +++ b/crates/ironclaw_gateway/static/js/core/ui-helpers.js @@ -346,7 +346,11 @@ document.addEventListener('click', function(e) { openMissionDetail(el.dataset.id); break; case 'close-mission-detail': - if (crCurrentProjectId) { document.getElementById('cr-detail').style.display = 'none'; } + if (crCurrentProjectId) { + document.getElementById('cr-detail').style.display = 'none'; + } else { + closeMissionDetail(); + } break; case 'fire-mission': e.stopPropagation(); diff --git a/crates/ironclaw_gateway/static/js/surfaces/projects.js b/crates/ironclaw_gateway/static/js/surfaces/projects.js index b488ceec318..d889c9acd1d 100644 --- a/crates/ironclaw_gateway/static/js/surfaces/projects.js +++ b/crates/ironclaw_gateway/static/js/surfaces/projects.js @@ -813,6 +813,8 @@ function refreshMissionView(missionId) { openMissionDetail(missionId); } else if (crCurrentProjectId) { drillIntoProject(crCurrentProjectId); + } else if (currentTab === 'missions') { + loadMissions(); } }