feat(proxy): context-aware policy primitives (#13)#65
Merged
Conversation
Three policy primitives for hook-based (host-enforced) adapters, building on the sideband governance API: - match.metadata.*: match rules on adapter-supplied context (channel_id, sender_id, sender_name, conversation_id) plus a virtual agent_id key. Threaded through the shared decision pipeline; inert on the MCP path by construction. Reserved agent_id is rejected inside metadata as a service-layer invariant. - scope by sender_id: rate/spend limits keyed per adapter sender, backed by a GovernanceService reservation registry that caps distinct sender keys (fail-closed, sender:* only) so sideband traffic cannot starve the shared MCP-path limiter. Rejected at config when the sideband is disabled. - deny_install: install-time policy (policies.install) evaluated by /install-scan; blocked installs persist policy_decision=deny + block_reason=install_denied and render as a deny in the dashboard. Docs (policies.md, adapter-api.md, audit.md) updated alongside. MCP-path behavior is unchanged; the full existing proxy suite passes.
A reviewer reading governance-api.ts can't tell why the metadata schema has no agent_id guard. Record in-code that reserved-key rejection is intentionally service-layer only (covers direct embedders too), so the point isn't reopened. Comment only; no behavior change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds three context-aware policy primitives for hook-based (host-enforced) adapters, building on the #12 sideband governance API. Third step of the OpenClaw adapter runway (#12 → #13 → #16 → #11).
match.metadata.*— rules can match on adapter-supplied context (channel_id,sender_id,sender_name,conversation_id) plus a virtualagent_idkey (read from the request column). Bare-string =eq, or an operator object (eq/neq/contains/regex, regex through the existing ReDoS analyzer). Threaded through the shared decision pipeline; inert on the MCP path by construction (no metadata there). Reservedagent_idinsidemetadatais rejected as a service-layer invariant (400 reserved_metadata_key).scope: by: sender_id— rate/spend limits keyed per adapter sender, backed by aGovernanceServicereservation registry that caps distinct sender keys (fail-closed,sender:*-scoped only, default 50k) so caller-minted sender ids cannot starve the shared MCP-path limiter. Rejected at config whensdk.enabledis false; falls back totool:with a warning on the MCP path.deny_install— install-time policy (policies.install) evaluated byPOST /install-scan. Blocked installs persistpolicy_decision: deny+block_reason: install_deniedand render as a deny in the dashboard.MCP-path behavior is unchanged (the full existing proxy suite passes); docs (
policies.md,adapter-api.md,audit.md) updated alongside the code.Closes #13
Type of Change
Packages Affected
packages/proxypackages/dashboardpackages/python-sdkdocs/examples/Checklist
anytypes or@ts-ignorewithout justificationpnpm secrets:scan,pnpm docs:check:ci,pnpm audit --audit-level=high,pnpm build,pnpm lint,pnpm format:check,pnpm typecheck,pnpm test)feat:,fix:,docs:)How to Test
match.metadata— add a rulematch: { metadata: { channel_id: "C1" } }, action: deny, enable the SDK sideband (sdk.enabled: true), andPOST /evaluatewithmetadata: { channel_id: "C1" }→decision: deny; withchannel_id: "C2"or no metadata →allow. Confirm the same rule never fires on the MCP path.sender_idlimits — arate_limitrule withlimits: { key: sender_id, … }: two/evaluate→/auditloops for the samesender_idconsume one bucket; a different sender is independent. Setsdk.enabled: falseand confirmhelio validaterejectskey: sender_id.deny_install— addpolicies.install.ruleswith adeny_installrule (match: { name: "evil-*", source: npm }),POST /install-scana matching package →decision: deny, and confirm the audit row showsrecord_kind: install_scan+block_reason: install_denied.POST /evaluatewithmetadata: { agent_id: "x" }→400 reserved_metadata_key.pnpm --filter @gethelio/proxy test(1565) andpnpm --filter @gethelio/dashboard test(281) — full suites pass.Additional Context