MINARA

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:

  1. Visible. Every gate decision (reject / confirm / allow) writes one audit row. Always. Operators can look back.
  2. 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."
  3. Always audited. Even when you set DISABLE_SCRIPT_RISK_GATE=1 during an incident, every bypassed call writes a row marked bypassed_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 --json

The 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 body
  • user_decision (execute / cancel / timeout) when the path went through the confirmation widget
  • bypassed_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 sha256 answers "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 swap

When 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 swap is 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 .env is chmod 600. The gate stops Layer 4 from reading unauthorized files at runtime, but it can't stop you from cat .env in 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=1 or DISABLE_SCRIPT_RISK_GATE=1 into .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_decisions SQLite table is part of your forensic story. Treat it like a log.

On this page