raxit
security.yaml reference

Anatomy & philosophy

The anatomy and philosophy of the single security.yaml config file.

.raxit/security.yaml is the only file you author. Everything raxit does — which tool calls are allowed, which need a human, which are denied outright — comes from this one document. raxit init generates a first draft from a real scan of your repo; you edit it, raxit plan shows you the diff, raxit apply makes it live.

What raxit init writes

Running raxit init against a 3-tool LangChain agent produces:

✓ Framework detected: unknown
✓ Tools discovered: 3 — calc, post_to_webhook, read_customer_db
✓ .raxit/security.yaml written
✓ Agent id: default-agent (use --agent default-agent, or edit agent: in security.yaml)
✓ Policy compiler ready (Cedar stays internal)
Next steps:
  edit .raxit/security.yaml   tune the generated rules
  raxit plan                  preview the decision changes
  raxit apply                 make them live (enforce mode)

"Framework detected: unknown" is normal even for a real LangChain script — tool discovery (AST-walking @tool-decorated functions) and framework labeling are separate passes with independent confidence. A correctly-discovered tool list doesn't require a correctly-labeled framework.

and the .raxit/security.yaml it writes:

apiVersion: raxit.security/v1
agent: default-agent
default: block
rules:
    - effect: allow
      resource: calc
    - effect: allow
      resource: post_to_webhook
    - effect: allow
      resource: read_customer_db
tests:
    - name: calc is permitted
      action: calc
      expect: permit
    - name: unknown_tool is denied (default-deny)
      action: unknown_tool
      expect: deny

Every tool the scan finds becomes a rules[] entry: sensitive tools (as flagged by the scan) get effect: review, everything else gets effect: allow, and anything the scan explicitly marks forbidden gets effect: block. raxit init --demo payments-bot shows all three effects plus the implicit default-deny in one file:

apiVersion: raxit.security/v1
agent: payments-bot
default: block
rules:
    - effect: allow
      resource: read_file
    - effect: allow
      resource: search_docs
    - effect: allow
      resource: send_email
    - effect: review
      resource: stripe/refund
    - effect: block
      resource: stripe/payouts
tests:
    - name: read_file is permitted
      action: read_file
      expect: permit
    - name: stripe/refund defers (sensitive tool)
      action: stripe/refund
      expect: defer
    - name: stripe/payouts is denied (forbidden)
      action: stripe/payouts
      expect: deny
    - name: unknown_tool is denied (default-deny)
      action: unknown_tool
      expect: deny

raxit init always appends tests: entries for you: the first allow rule expects permit, the first review rule expects defer, the first block rule expects deny, and a final unknown_tool case always expects deny — asserting the default-deny floor from day one. See The tests: block for the full shape.

Deny-by-default, always

default: accepts observe, audit, enforce (and the legacy alias block, which means the same thing as enforce). allow is explicitly rejected — there is no "default: allow" escape hatch:

security.yaml: default allow is not supported; use enforce

Two deny-by-default behaviors are worth internalizing:

  • Unmatched calls deny. If no rules[] entry covers a resource, the call is denied (deny.default_or_forbid) — there is no implicit fallthrough-to-allow.
  • Empty discovery is an explicit deny-all, not an empty file. If a repo scan finds zero tools, raxit init does not write an empty or invalid rules: block — it writes a single explicit {effect: block, resource: "*"} wildcard-deny rule instead, so the generated file "reads as deny-all rather than being an accidentally-empty file."

That second point is raxit's stance on the fail-open surprise: a config that looks like it does nothing should never behave like it does nothing. An agent with no discovered tools gets a policy that visibly and loudly denies everything, not a policy that silently permits everything because no rule happened to match.

The agent: field must match how you run

agent: is a required, freeform string — it becomes the Cedar principal (Agent::"<value>") and the identity every rule is matched against. It has no format constraint beyond non-blank, but it must match the identity the agent actually presents at runtime: either --agent <id> on raxit run, or RAXIT_AGENT_ID in the SDK/environment.

If the attested principal doesn't match agent: in any rule, every call structurally denies — not because a rule matched and blocked it, but because no rule could ever reference that principal. This is deny.principal_mismatch, and it's common enough on a first run that it has its own troubleshooting entry: see Common issues → wrong agent id.

What's next

On this page