Audit and overrides
Every gate decision is logged. Trusted workflows can be exempted at policy level. A master off switch exists for incident response — and always leaves a trail.
Last Monday a script you tried to run got blocked. You vaguely remember clicking "Cancel" — but which script exactly? Or you've got an automation that's been reviewed end-to-end, but every run asks the same confirmation question. Can the gate just let it through?
This page is about three operator-side levers: looking at past decisions, narrowing per-workflow exemptions, and the emergency off switch.
Design philosophy — visible, intercept-able, always audited
Three properties hold across every code path through the gate:
- Visible. Every gate decision (reject / confirm / allow) writes one audit row. Always. Operators can look back.
- Intercept-able. Trusted workflows can pre-approve specific
script bodies via
script_risk_policy. But the exemption itself writes an audit row — you can tell "this was approved because of policy" apart from "this script was genuinely clean." - Always audited. Even when you set
DISABLE_SCRIPT_RISK_GATE=1during an incident, every bypassed call writes a row markedbypassed_by="env_global". No usage pattern leaves the gate silent.
Why this shape? Trust comes from transparency, not from being maximally strict. A gate that's strict-only gets routed around — people set the env var and forget about it. A gate that offers a clear bypass path and records every use of it stays both flexible (real workflows can ship) and accountable (after an incident you can reconstruct what happened).
Looking at decisions: minara script-risk list / show
Start with "what just happened today":
# Most recent 50 decisions across all tools
minara script-risk list
# Just the rejections in the last 24 hours
minara script-risk list --verdict=reject --since=2026-05-14
# All decisions for a specific session
minara script-risk list --session=abc-123
# All decisions inside a specific workflow definition
minara script-risk list --workflow=wf_daily_rebalance
# Filter by tool
minara script-risk list --tool=execute_code
# Full detail (every finding, every evidence line) for one decision
minara script-risk show <decision-id>
# Machine-readable
minara script-risk list --verdict=confirm --jsonThe flags compose. The intent is forensics: when you want to
understand what got blocked yesterday, you start broad
(--verdict=reject) and narrow down (--session, --workflow,
--tool, --since).
The HTTP equivalent is the same data shape via:
GET /v1/admin/script-risk/decisions?verdict=reject&tool=terminal&since=…&limit=…
GET /v1/admin/script-risk/decisions/<id>Wrap that endpoint in your reverse proxy if you expose the gateway to anyone besides yourself.
What's in (and not in) an audit row
Recorded for every decision:
- Decision timestamp + tool name + tool call id (when known) + session id
- Verdict (reject / confirm / allow) and findings array — each finding has a category, a one-line message, redacted evidence text, and a line number in the body
body_sha256— the hash of the analyzed bodyuser_decision(execute / cancel / timeout) when the path went through the confirmation widgetbypassed_by(env_global / workflow_policy / null) when the path was let through by an explicit policy
Explicitly not recorded:
- The script body itself
- Raw private keys, Authorization headers, Cookie values, or Bearer tokens that matched a redaction rule
- The script's stdout / stderr
Why no full body? Because a script that contains a private key turns the audit log into a second leak channel the moment it's persisted. Storing the
sha256answers "have we seen this same script before?" (yes/no), and the redacted evidence answers "which rule fired and what did the match look like?" — without copying the secret into a long-lived table.The tradeoff: you can't replay the exact body byte-for-byte after the fact. In exchange you get an audit log that is safe to back up, share, and grep.
Workflow exemptions — script_risk_policy
The case that motivates this: you have a DCA workflow that calls
minara swap USDC ETH 100 every day. You wrote the script, you
audited it, the body doesn't change. Every run asking for
confirmation is just friction.
Inside the workflow definition:
script_risk_policy:
approved_body_sha256:
- "abc123…" # sha256 of the script you audited
allowed_categories:
- fund_moving_cli # this workflow is explicitly allowed
# to call minara swapWhen the workflow is activated (via workflow_activate, which
is itself a fund-moving two-step confirm), the policy is
registered. From then on, when this workflow runs the matching
script body, the YELLOW finding is downgraded silently — and
the audit row writes bypassed_by="workflow_policy" so the
fact that the gate didn't block is visible after the fact.
Use this for:
- Pre-audited scripts with stable bodies (the hash check pins you to a specific revision)
- Specific business workflows where a known category of action is the whole point (a DCA workflow that swaps; a rebalance workflow that calls approve)
Do not use this for:
- Generic catch-all workflows. The policy should be tight enough that you can answer "what is this exemption for?" in one sentence.
- Whitelisting every category. That's just turning the gate off for a workflow.
- Scripts whose bodies are dynamically generated. If the body changes per run, the sha256 won't match, and the policy silently fails closed (which is fine, but you've added complexity for no benefit).
Why does policy bypass YELLOW but not RED? YELLOW means "legitimate-but-stakes" —
minara swapis a real operation someone might want to automate. Policy is the right place to say "yes, this specific workflow is allowed to do that."RED means "no legitimate business reason." Mass deletion, IMDS exfiltration, inline private keys — these don't have a legitimate workflow scenario. If your workflow hits RED, the answer is to fix the workflow's script, not to widen the policy. RED is intentionally non-policy-bypassable.
The global off switch — DISABLE_SCRIPT_RISK_GATE
Setting the env var DISABLE_SCRIPT_RISK_GATE=1 skips the gate
entirely. Both RED and YELLOW are passed through. Each bypass
still writes an audit row, marked bypassed_by="env_global".
Use a handful of times per year:
- Production incident response. A genuine false-positive is blocking real business and you need ten minutes to ship a fix. Set the env, run the recovery, unset it, then file an issue to tighten the offending rule.
- Fully isolated CI. Backtests, end-to-end tests, regression suites where every caller is provably non-interactive and the test environment is sandboxed.
Never use for:
- "The confirmation widget is annoying" — that's the safety working.
- "I'm running the agent for a team and don't want to audit policies" — that exposes the whole team to whatever an attacker can sneak past Layer 1.
- Setting it in your shell profile and forgetting. The next unrelated session inherits it.
Audit-wise, you can spot misuse:
minara script-risk list --json \
| jq '.decisions[] | select(.bypassed_by == "env_global")'If you see env_global bypasses outside the windows you remember, the env var was set somewhere you didn't intend.
Full env-var reference at Environment variables → DISABLE_SCRIPT_RISK_GATE.
Operator responsibilities
Layer 4 (the gate) and Layer 3 (fund-moving confirm) and Layer 2 (the OS jail) do a lot, but they don't substitute for some unavoidable operator hygiene:
- One operator per Minara instance. Minara is designed for single-user trading, not multi-tenant SaaS. A team-shared web UI is not a supported configuration; each operator should run their own instance.
- Private keys live in
.env, not in the repo. And.envischmod 600. The gate stops Layer 4 from reading unauthorized files at runtime, but it can't stop you fromcat .envin a shell. - When you see RED, don't try to bypass it. Stop and think about why a script you ran hit a high-confidence malicious pattern. 95%+ of the time it's not a false positive — it's the gate doing its job.
- When you see YELLOW, read the evidence. Two seconds spent reading the matched line is the entire point of the widget. Muscle-clicking "Execute" trains you to be the weakest link.
- Never put
MINARA_SKIP_FUND_CONFIRM=1orDISABLE_SCRIPT_RISK_GATE=1into.zshrc,.envrc, or your systemd unit. If you genuinely need it for a script, set it inline for that one command and unset afterward. - Backups of the audit log matter. The
script_risk_decisionsSQLite table is part of your forensic story. Treat it like a log.