Skip to main content

Control mapping

Each row names one item of a framework, a control or component that PolicyArc ships, what it does, and the test that shows it. A row says what the code does about the item. It does not say the item is met. Whether a row is enough for your risk is your call.

A control is one of the policy controls under src/main/resources/controls/. A component is a part of the server, such as the managed gateway. Paths under Code start at src/main/java/com/identos/pbac/ unless they name another directory. Tests ending in .rego run with opa test opa/policies. The others run against an instance with ./pbac test <instance> -- <file> -k <name>.

NIST SP 800-207, section 2.1 tenets​

ItemShipped controlWhat it doesCodeEvidence
Tenet 3. Access to individual enterprise resources is granted on a per-session basis.Managed gatewayA token whose grant names one endpoint by resource indicator is refused on another endpoint of the same type, and that upstream is not called. Type-wide grants are not limited this way. A token of another type is accepted when its scope names match the scopes the call needs. In our 0.65.0 run a token for urn:example:x with scope orders:read read an order through a resource of type urn:example:orders.gateway/service/GatewayFlowService.java
  • integration-tests/test_explicit_client_grants.py::test_a_token_anchored_to_endpoint_a_is_refused_on_endpoint_b
Tenet 3. Access to individual enterprise resources is granted on a per-session basis.jit_single_useWhen the operator turns it on, marks a token single-use for a SPIFFE workload, or for any client in a segment the operator binds it to. The baseline data has no row for it, so it is off until then. Unit-tested only. No integration test turns this control on.opa/policies/pbac/controls/jit_single_use.rego
  • opa/policies/pbac/controls/jit_single_use_test.rego::test_a_binding_whose_segment_holds_turns_it_on
  • opa/policies/pbac/controls/jit_single_use_test.rego::test_an_unbound_non_spiffe_client_is_still_not_governed
Tenet 4. Access to resources is determined by dynamic policy, including the observable state of client identity, application/service, and the requesting asset, and may include other behavioral and environmental attributes.resource_read_onlyAt the managed gateway only. Marking a resource read-only applies to later calls with the same token once the policy engine loads the change. The test polls for up to 120 seconds. A write that succeeded is then refused with 403, and reads still pass. When the server cannot publish the resource's write scopes, it logs a warning and the control does not act for that resource.opa/policies/pbac/controls/resource_read_only.rego
  • integration-tests/test_control_resource_read_only.py::test_read_only_denies_a_write_and_permits_it_when_off
Tenet 6. All resource authentication and authorization are dynamic and strictly enforced before access is allowed.Managed gatewayChecks each call to a managed resource at /introspect before it forwards it. A call with no token gets 401 with a WWW-Authenticate header. A token without the route's scope gets 403. The gateway's two built-in tools, policyarc__list_my_upstream_connections and policyarc__reconnect_upstream_credential, are not checked this way.gateway/service/GatewayFlowService.java
  • integration-tests/test_managed_gateway_auth.py::test_missing_token_returns_401_with_www_authenticate
  • integration-tests/test_managed_gateway_auth.py::test_client_with_wrong_scope_returns_403
Tenet 7. The enterprise collects as much information as possible about the current state of assets, network infrastructure and communications and uses it to improve its security posture.Audit logRecords a row for each gateway call checked against policy, allowed or denied. When the audit write fails, the server logs a warning, no row is written, and the gateway call is refused with HTTP 403 (0.65.0 run with a trigger that rejected every insert). A denial by a control names that control on the gateway row. Using the record to improve security posture is the operator's part.gateway/service/GatewayFlowService.java
  • integration-tests/test_managed_gateway_audit.py::test_allow_creates_audit_entry
  • integration-tests/test_managed_gateway_audit.py::test_deny_creates_audit_entry
  • integration-tests/test_managed_gateway_audit.py::test_a_control_deny_names_the_control_on_the_gateway_row

OWASP Top 10 for Agentic Applications 2026​

ItemShipped controlWhat it doesCodeEvidence
ASI02 Tool Misuse and ExploitationManaged gatewayChecks each MCP tool call at /introspect against the route scope for REST-backed tools, or the resource's default_scope for MCP upstreams. A token without that scope gets an error result that names 403 instead of the tool's output. The gateway's built-in tools are not checked. The test does not show that the upstream went uncalled.gateway/service/GatewayFlowService.java
  • integration-tests/test_managed_gateway_mcp.py::test_tools_call_denied_scope_returns_isError
ASI02 Tool Misuse and Exploitationcapability_optinRefuses a connector operation that the connector names as sensitive, such as creating a public sharing link, until an operator opts in. Opting in one capability leaves another on the same scope refused.opa/policies/pbac/controls/capability_optin.rego
  • integration-tests/test_control_capability_optin.py::test_a_named_capability_is_denied_then_permitted_once_enabled
ASI03 Identity and Privilege Abusetoken_derivationThe token rules that the token_derivation control describes refuse a token exchange that asks for more than the subject token holds, but on 0.65.0 only for a resource the exchange did not derive itself. When the subject token names a resource, the exchange inherits it as an explicit resource, and that resource is compared. Otherwise the server derives the resource from the requested scope, marks it derived, and does not compare it. In our run a client_credentials token for mcp:tools, requested with or without resource_types=urn:mcp:server, was exchanged for mcp:resources and for introspection, and a token for introspection requested with resource_types=urn:as:introspect was exchanged for mcp:tools. A token for introspection requested without resource_types names the resource urn:as:introspect, and its exchange for mcp:tools was refused with access_denied, reason token_exchange_actions_exceeded. The integration test asserts only a 400 with an error. The rego test asserts the reason.opa/policies/oauth/token.regooauth/grant/TokenExchangeGrantHandler.javasrc/main/resources/controls/token_derivation.control.v1.json
  • integration-tests/test_rfc8693_token_exchange.py::test_exchange_requestedScopeExceedsSubject_denied
  • opa/policies/oauth/token_test.rego::test_reason_token_exchange_actions_exceeded
  • Run on 2026-10-14. POST /token grant_type=urn:ietf:params:oauth:grant-type:token-exchange, subject tokens from one client_credentials client entitled to mcp:tools, mcp:resources and introspection. mcp:tools (resource_types=urn:mcp:server or none) to mcp:resources: 200. introspection (resource_types=urn:as:introspect) to mcp:tools: 200. introspection (no resource_types) to mcp:tools: 400 access_denied, audit reason token_exchange_actions_exceeded. mcp:tools (either) to introspection: 200.
ASI03 Identity and Privilege AbusedpopRequires a DPoP proof at /token from every client once the operator sets require_dpop, and the gateway refuses a bound token sent with a proof from another key. The control also takes a per-client list, require_dpop_for_clients. Its test, test_per_client_floor_hits_only_the_listed_client, fails on a database that has no dpop row yet, because the test waits for a require_dpop value it never writes, so this row does not cite it.src/main/resources/controls/dpop.control.v1.json
  • integration-tests/test_dpop.py::test_undeclared_client_is_allowed_when_the_floor_is_off
  • integration-tests/test_dpop.py::test_deployment_floor_denies_a_client_that_never_declared_dpop
  • integration-tests/test_dpop_gateway.py::test_dpop_bound_token_wrong_proof_key_rejected
ASI04 Agentic Supply Chain VulnerabilitiestrustRefuses registration with a software statement whose issuer is not on the operator's trusted list, before the server fetches the issuer's keys. It governs only registrations that carry a software statement. Plain registration and client ID metadata documents never meet the list.src/main/resources/controls/trust.control.v1.json
  • integration-tests/test_software_statement_verification.py::test_untrusted_issuer_is_denied_by_policy
  • integration-tests/test_software_statement_verification.py::test_untrusted_issuer_gate_skips_the_fetch
  • integration-tests/test_software_statement_verification.py::test_plain_dcr_is_unaffected
ASI07 Insecure Inter-Agent CommunicationToken exchangeA token exchange that sends an actor_token gives the new token an act claim. Its sub is the actor token's subject, or its client when it has none, and introspection returns it. A chained exchange nests the claims. An exchange without an actor_token gets no act claim.oauth/service/IntrospectService.javaoauth/grant/TokenExchangeGrantHandler.java
  • integration-tests/test_rfc8693_token_exchange.py::test_delegation_act_claim_has_actor_sub
  • integration-tests/test_rfc8693_token_exchange.py::test_chained_delegation_nests_act_claims
ASI09 Human-Agent Trust Exploitationciba_approvalHolds a client_credentials token request that matches an operator rule until the approver the rule names decides. The tests cover client_credentials only. A /bc-authorize request that names another person is refused, and a client that names its own approval service does not change where the approval goes. The message the approver sees is the rule's template, filled from the request's resource properties.opa/policies/pbac/controls/ciba_approval.rego
  • opa/policies/pbac/controls/ciba_approval_test.rego::test_approval_required_from_its_own_row
  • integration-tests/test_ciba.py::test_login_hint_must_name_the_policy_approver
  • integration-tests/test_ciba.py::test_client_cannot_pick_the_approval_idp
ASI10 Rogue AgentsdenylistRefuses registration when the software id, or the domain of the client URI or a redirect URI, is on the operator's list. The row covers registration only. The domain test asserts a 400 with any error, not which rule refused it.src/main/resources/controls/denylist.control.v1.json
  • integration-tests/test_register.py::test_register_denied_denylist
  • integration-tests/test_register.py::test_register_denied_denylist_domain

OWASP Top 10 for LLM Applications 2025​

ItemShipped controlWhat it doesCodeEvidence
LLM06:2025 Excessive Agencyresource_read_onlyRefuses, at the managed gateway, each call whose scope the resource's projection classifies as write, on a resource the operator marks read-only, whatever the client was granted. The test checks one write scope.opa/policies/pbac/controls/resource_read_only.rego
  • integration-tests/test_control_resource_read_only.py::test_read_only_denies_a_write_and_permits_it_when_off
LLM06:2025 Excessive Agencyciba_approvalA token request that matches an operator rule gets an approval_required obligation and no token until a person approves.opa/policies/pbac/controls/ciba_approval.rego
  • integration-tests/test_ciba.py::test_full_flow_issues_a_usable_token_for_the_approver
LLM02:2025 Sensitive Information Disclosurefile_accessOn the Google Drive connector, removes files with a restricted label or in a blocked folder from a list result, and returns an error result naming 403 for a get of such a file. The tests use a mock Drive upstream.opa/policies/pbac/controls/file_access.rego
  • integration-tests/test_connector_google_drive_label_filter.py::test_list_files_filters_label_and_location
  • integration-tests/test_connector_google_drive_label_filter.py::test_get_restricted_label_file_denied
  • integration-tests/test_connector_google_drive_label_filter.py::test_get_blocked_folder_file_denied

Items with no row​

An item has no row when no shipped control acts on it, or when one may but no test shows it. These items have none.

  • NIST SP 800-207 tenet 1 (all data sources and services are resources), tenet 2 (all communication is secured) and tenet 5 (the enterprise monitors the security posture of its assets).
  • OWASP agentic ASI01 Agent Goal Hijack, ASI05 Unexpected Code Execution (RCE), ASI06 Memory & Context Poisoning and ASI08 Cascading Failures.
  • OWASP LLM01:2025 Prompt Injection, LLM03:2025 Supply Chain, LLM04:2025 Data and Model Poisoning, LLM05:2025 Improper Output Handling, LLM07:2025 System Prompt Leakage, LLM08:2025 Vector and Embedding Weaknesses, LLM09:2025 Misinformation and LLM10:2025 Unbounded Consumption.

PolicyArc sits between an agent and its tools. It sees each tool call and its arguments, not the prompts a model receives or the text it writes to a person, so it ships no control for prompt injection or for model output. The controls under ASI02 and LLM06 act on token requests and on calls through the managed gateway, whatever instructions the agent followed. They do not see calls that go around the gateway, and the gateway's two built-in tools are not checked.

Where the rows come from​

The rows live in services/pbac-documentation/src/data/control-mapping.json. python3 scripts/docs_claims_check.py fails when a row names a control with no manifest, has no code path, or has neither a test nor a run. It also fails when a cited file no longer contains the text the row cites, or a cited test is no longer defined. It refuses words such as "satisfies", "compliant" or "legal" anywhere in a row's own text, leaving out the file paths, anchors and test names it quotes from the code. It does not run the tests.