Stashbase

Credential rules

Scope credential injection by destination host, HTTP method, and URL path

Use rules to turn a secret binding into a scoped HTTP credential capability. Each rule defines an effect (allow or deny), hosts, methods, and paths.

Rules also decide which requests may reach the rule's hosts. A host named in an allow rule does not need to be listed in egress_hosts: the proxy admits requests that carry the secret and match a rule, and refuses other requests to that host. Rules never widen ordinary egress; requests without a credential still need egress_hosts.

Rule evaluation

Rules are unordered. Matching allow rules are additive, while a matching deny always wins. If a binding has any rules, it is default-deny until an allow rule matches.

For a request carrying a secret placeholder, Stashbase evaluates:

  1. deny_hosts; a match denies the destination.
  2. The binding's legacy hosts list when it has no rules, or its rules when it does.
  3. Credential injection only after all applicable checks allow the request.

The request does not need its host in egress_hosts. A request to the same host without a placeholder is ordinary egress and needs egress_hosts.

Methods are normalized to uppercase. Query strings are ignored, paths are normalized before matching, and * matches any sequence of path characters. Redirects are independently evaluated, so a credential is never forwarded to a redirect destination without a fresh policy check.

Example: GitHub issue access

.stashbase/agents/agent.toml
egress_hosts = ["api.anthropic.com"]

[secrets.GITHUB_TOKEN]
from = "GITHUB_TOKEN"

[[secrets.GITHUB_TOKEN.rules]]
effect = "allow"
hosts = ["api.github.com"]
methods = ["GET"]
paths = ["/user", "/repos/*/*"]

[[secrets.GITHUB_TOKEN.rules]]
effect = "allow"
hosts = ["api.github.com"]
methods = ["POST", "PATCH"]
paths = ["/repos/*/*/issues", "/repos/*/*/pulls"]

[[secrets.GITHUB_TOKEN.rules]]
effect = "deny"
hosts = ["api.github.com"]
methods = ["DELETE"]
paths = ["*"]

The two allows form a union. For this binding, a GET /user or PATCH /repos/acme/widget/issues request is eligible for injection, while a DELETE request is denied even if another rule could allow it.

Body conditions

A rule can also check the JSON request body. body sets conditions on fields, and allow_fields lists the only other fields the body may contain:

[[secrets.MAIL_KEY.rules]]
effect = "allow"
hosts = ["api.mail.example"]
methods = ["POST"]
paths = ["/v1/messages"]
body = { send = false, to = ["support@acme.com"] }
allow_fields = ["subject", "text"]

This injects the credential only into drafts (send is false) to the listed recipient, with nothing besides a subject and text. Any other body is refused with a 403 that names the rule and field. Rules without body or allow_fields work as above, and the proxy never reads their bodies.

See Request conditions for every condition, how the body is read, and more examples.

Explain a decision

Inspect a proposed request without loading a secret, opening a connection, or starting a proxy:

stashbase agent explain --verbose --profile github \
  --host api.github.com --method GET --path /repos/acme/widget/../widget/issues

--verbose reports the normalized path and matching-rule diagnostics. Use --json for automation.

Legacy host bindings

Existing bindings that set hosts and no rules retain their host-only behavior:

[secrets.GITHUB_TOKEN]
hosts = ["api.github.com"]

Add rules when the credential needs method- or path-level restrictions. See profile configuration for secret sources, headers, and ordinary egress.

On this page