feat(agent): HTTP body conditions and allow_fields by radim10 · Pull Request #79 · stashbase/cli · GitHub
Skip to content

feat(agent): HTTP body conditions and allow_fields - #79

Merged
radim10 merged 16 commits into
masterfrom
feat/http-body-conditions
Oct 19, 2026
Merged

radim10 merged 16 commits into
masterfrom
feat/http-body-conditions

Conversation

@radim10

@radim10 radim10 commented Oct 19, 2026

Copy link
Copy Markdown
Member

Summary

Credential rules can now check what a request sends, not only where it goes. The design is the same as the MCP argument conditions from #78.

[[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"]
  • body on [[secrets.*.rules]] and [[personal_credentials.*.rules]]. It uses the same conditions as MCP arguments: a single value, a list (one of), { min, max }, and { absent = true }. When a matching rule has conditions, the local proxy buffers the body (at most 1 MiB) and checks it strictly (JSON object, no encoding, no duplicate keys) before injecting the credential. It then forwards the exact bytes. An unreadable body fails closed. Requests whose rules have no conditions still stream unchanged.
  • allow_fields on HTTP rules and MCP tool_rules. It is strict mode: no fields other than those named are allowed. Allow rules only.
  • Denials name the rule and field, never values. The 403 reads like (rule 1: unlisted body.bcc). Audit reasons are allow_condition_failed, unlisted_field, deny_condition_matched and body_unreadable, and work with agent logs --reason.
  • [[policy_tests]] accept a JSON body. agent policy test output is now colored.
  • Other commands:
    • agent validate rejects malformed conditions and allow_fields on deny rules, and warns about moot or best-effort rules.
    • agent explain reports body-dependent decisions as conditional.
    • agent mcp tools shows allow_fields and warns about undeclared fields.
  • --remote sends body and allow_fields to the control plane.
  • AgentHttpRule now rejects unknown fields.
  • Policy fingerprints now include body and argument conditions. The fingerprint changes once for profiles with HTTP rules or MCP tool rules.
  • Docs: docs/agent-profiles/request-conditions.md, plus runnable examples for httpbin and the GitHub REST API.

Depends on

radim10/backend-monorepo#314 must be deployed before this is released. An older backend drops body and allow_fields, so with --remote a conditioned allow would apply to every request.

Release note

Profiles using body or allow_fields require CLI 0.22.0 or newer. Older CLIs ignore these keys and would treat the rules as unconditional allows.

Tests

cargo test: 664 + 25 + 33 pass. Verified live:

  • the local proxy against httpbin and the GitHub REST API;
  • --remote on staging against GitHub. Create with label, comment and rename were allowed. Extra label, assignees and close were refused with the right field.

An optional body on [[secrets.*.rules]] and [[personal_credentials.*.rules]]
uses the MCP argument conditions on a JSON request body. When a matching
rule has body conditions, the local proxy buffers the body (at most
1 MiB), checks it strictly before injecting the credential, and forwards
the exact bytes. Unreadable bodies fail closed. Denials name the rule
and field, never values. AgentHttpRule now rejects unknown fields, and
--remote refuses body conditions until the control plane enforces them.
CLIs before body conditions silently ignore 'body' on credential rules,
which would turn a conditioned allow into an unconditional one. They
reject unknown top-level keys, so validation now requires
min_cli_version >= 0.22.0 when any rule has 'body', and refuses
profiles that need a newer CLI than the one running.
allow_fields on MCP tool rules and HTTP credential rules lists the only
fields a call or body may contain besides those with conditions; any
other field fails the rule (unlisted_field:<prefix>.<field>). Same key
and semantics for both, allow rules only. MCP rules send allow_fields
to the control plane.

[[policy_tests]] cases accept a body (and content_type), evaluated like
the proxy evaluates a request body.
Body conditions only read JSON, so a test body is always JSON.
Match agent mcp tools: green/red markers and allow/deny, dimmed reasons
and labels, plain text when color is off. A test case without a body
now reports body unreadable: no_body.
The control plane now enforces body and allow_fields on credential
rules, so --remote no longer refuses profiles that use them.
HTTP rule body/allow_fields and MCP rule allow_fields were missing from
ProxyPolicy::fingerprint, so profiles differing only in conditions got
the same fingerprint in audit records.
Profiles no longer need a min_cli_version key to use body or
allow_fields.
With allow_fields = ["project.id"] and no condition on it, an array or
scalar at project skipped the nested check, so {"project": [{...}]}
passed. Only null is accepted where just descendant paths are listed.
In remote mode the relay skipped reading the body but still authorized
locally as if no body was sent, so conditioned allow rules could never
match and valid requests got a local 403. The relay now refuses only
what no body could allow and leaves body conditions to the remote
proxy, which enforces the same rules.
@radim10 radim10 self-assigned this Oct 19, 2026
@radim10
radim10 merged commit a848271 into master Oct 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant