Credential-Safe Action Enforcement
This page describes PaloNexus’s egress-gateway enforcement mode — one of the three enforcement modes (governed tool · token exchange · egress gateway), and the one that ships as today’s primary enforcement. The agent makes its outbound request through PaloNexus, which authorizes it and injects credentials after the untrusted boundary — the agent boundary never holds standing credentials. This implements the credential-injecting outbound-proxy pattern that LangChain’s sandbox documentation recommends for keeping secrets outside the sandbox.
The hard part of governing agents is not the inbound request — it is egress:
making every outbound action an agent takes (a model call, a tool call, an
agent→agent hop, an external webhook) pass through the same /authz decision,
regardless of framework (create_agent, a hand-rolled StateGraph, raw
httpx/curl), enforced at the network layer rather than by cooperative
middleware, and with a human egress-approval path for risky calls.
Status: shipped and verified live on a managed Kubernetes cluster (DigitalOcean Kubernetes — DOKS — example). This page summarizes the canonical design (
docs/egress-enforcement.mdin the platform repo).
Defense in depth: the layers
Section titled “Defense in depth: the layers” agent pod ──HTTP(S)_PROXY──▶ egress forward-proxy ──▶ /authz ──▶ target (or Envoy egress gateway, transparent data plane) admission webhook guarantees the wiring + provisioning egress identity sidecar carries a fresh, revocable VP1. Control-plane egress forward-proxy — the enforcement floor
Section titled “1. Control-plane egress forward-proxy — the enforcement floor”A standard HTTP forward proxy built into the control plane
(internal/egressproxy), listening on EGRESS_PROXY_ADDR
(default :9092), fronted by the egress-proxy.palonexus.svc:80 Service. It is the
quick, real enforcement floor. Agent pods get HTTPS_PROXY/HTTP_PROXY pointed at
it and are NetworkPolicy-confined to reach only it (plus DNS + agent-idp), so
every outbound call flows through /authz. Started only when AGENT_IDP_URL is set.
It handles two request shapes:
- a plaintext absolute-URI forward request (in-cluster, e.g.
http://model-broker.palonexus.svc:8080/...), and CONNECT host:port(a TLS tunnel for externalhttps://...).
Identity. It reads Proxy-Authorization: Bearer <Membership-VP> — a Verifiable
Presentation (VP) proving the agent’s identity — and verifies it
via agent-idp:
| Condition | Result |
|---|---|
missing Proxy-Authorization | 407 Proxy Authentication Required — this is what blocks raw curl |
| invalid / spoofed / revoked VP | 403 |
| valid VP | proceeds to the decision |
Target resolution. The request host:port is reverse-mapped to a registry Service
(by upstream host, with a name-prefix fallback, e.g. model-broker.* → a model-*
service). A host the registry doesn’t know is treated as external → requires approval.
Decision + outcomes (the same egress logic as /authz, audited as egress.proxy):
| Outcome | Behavior |
|---|---|
| allow | forward to the target, stream the response |
| deny | 403 with X-Palonexus-Deny-Reason |
| needs-approval / external | create a pending egress request and hold (poll its status up to ApprovalTimeout, default 120s): approved → forward; denied/expired → 403 |
The hold queue is the /v1/egress/requests API and the
portal’s Credential-Safe Enforcement tab — where an operator releases or blocks each held call:

The Credential-Safe Enforcement console: where operators release or block outbound agent calls held at the proxy — shown here with a clear queue.
2. Envoy egress gateway — the transparent data plane
Section titled “2. Envoy egress gateway — the transparent data plane”The proper, transparent alternative: an Envoy egress gateway with
SecurityPolicy.extAuth pointed at /authz, so agent traffic is decided without the
pod needing proxy env at all. Shipped as the components/egress-gateway Kustomize
component.
3. Mutating/validating admission webhook — the registration-time guarantee
Section titled “3. Mutating/validating admission webhook — the registration-time guarantee”A webhook (admission/) on pods labeled
palonexus.io/agent=true:
- mutate — inject the
HTTPS_PROXY/HTTP_PROXY/NO_PROXYenv (if absent), so the wiring can’t be forgotten. - validate — look the agent name up at agent-idp; if it is not registered and provisioned, reject the pod.
Shipped as components/agent-admission.
4. Egress identity sidecar — for clients that strip proxy env
Section titled “4. Egress identity sidecar — for clients that strip proxy env”The framework-agnostic HTTP(S)_PROXY floor covers any cooperating Python HTTP lib,
but langchain_openai’s transport injection drops the proxy env. The fix is a
localhost sidecar container in each agent pod (components/egress-sidecar):
- the agent points its broker
base_urlat the sidecar (a setting LangChain honors and cannot strip); - the agent writes its identity (
did,privateKeyB64,membershipVc) to a sharedemptyDir; the sidecar reads it each call; - per request the sidecar mints a fresh, long-TTL (12h), revocable Membership VP
and forwards to the real broker through the egress proxy, with the VP as
Proxy-Authorization.
A long TTL is safe because the proxy re-checks the Membership Verifiable Credential (VC) against the revocation StatusList on every call — revocation still cuts egress immediately.
The egress decision, step by step
Section titled “The egress decision, step by step”Whichever layer above carries the call, the decision itself is the same /authz egress
logic. The flowchart below walks one egress call through it. It is deny-by-default: every
diamond can only move forward on an explicit yes, and any “no” falls straight to a 403
deny. After identity (the VP) is verified and the caller and target are resolved from the
registry, the call must clear the allowlist + budget gate. Then the target’s dataClass
forks the path: a regulated target with no valid, human-approved delegation becomes a
needs-approval 401 that is held for a human (the task-based access control — TBAC — branch), while an internal
target or one already covered by a valid delegation proceeds to the inline allow. The last
gate is the deny-overrides Open Policy Agent (OPA) veto — an inline allow plus an OPA deny still denies, so
OPA can veto but never rubber-stamp.
flowchart TD
start([Egress call reaches /authz]) --> idv{VP / identity valid?}
idv -->|no| deny[Deny 403]
idv -->|yes| known{Caller + target known in registry?}
known -->|no| deny
known -->|yes| budget{On allowlist and under budget?}
budget -->|no| deny
budget -->|yes| dc{Target dataClass?}
dc -->|regulated, no valid delegation| na[needs-approval 401 - hold for human]
dc -->|internal or valid delegation| inline[Inline allow]
inline --> opa{OPA veto?}
opa -->|deny| deny
opa -->|allow| allow[Allow - forward to target]
na -->|human approves| allow
na -->|denied / expired| deny
One egress decision: identity → registry → allowlist/budget → delegation (the needs-approval/TBAC branch) → OPA deny-overrides veto, ending in allow, deny, or a held needs-approval.
Dry-run this exact decision path before shipping a policy change. The portal’s Policy simulator runs design-time authority previews and runtime-faithful live decisions against the real paths:

The Policy simulator: what-if over the real decision paths — a design-time authority preview versus a runtime-faithful live dry-run of the flow above.
Coarse vs fine: the layering decision
Section titled “Coarse vs fine: the layering decision”The egress proxy is the coarse gate: it proves identity, enforces the allowlist,
and meters the budget. It sees only raw HTTP, so it cannot match a fine-grained
delegation like runbook:read on a specific resource.
Fine-grained, human-approved Decentralized Identifier (DID) / VC enforcement is therefore done server-side by
the protected resource itself. This applies to any resource type — runbooks,
payments, ticketing, a data API — not a specific one; the example below uses the
runbooks demo resource, but a payments-api enforcing payments:write works the same
way. The consequence for the registry’s dataClass:
dataClass | Where enforced | Behavior at the proxy |
|---|---|---|
internal | server-side DID/VC challenge (the resource’s own server, e.g. payments-api or the runbooks demo) | allowlist-gated only; the server runs the fine-grained delegation/challenge |
regulated | the proxy itself (no server-side gate, e.g. scale_deployment) | the proxy holds for human approval |
So a resource that runs its own DID/VC challenge is registered internal (the
runbooks demo is one example), while a target with no server-side gate stays
regulated so the proxy holds it. See
Agent identity & credentials for the
cryptographic identity that underpins all of this.
The end-to-end governed flow
Section titled “The end-to-end governed flow”End to end through the demo’s real LangGraph agents: incident-triage summarizes (model call
via its sidecar → /authz) → reads a runbook → DENIED → escalates agent-to-agent (A2A) to
access-broker (carrying a VP) → the broker requests a delegation for the actor and
block-polls the identity provider (IdP) → a human approves in the portal → the Delegation VC is
granted → triage reads the runbook via the DID/VC challenge through the proxy → a
grounded plan. Every hop is decided at /authz.
Related
Section titled “Related”- HTTP API — the egress-request API
- Headers — Proxy-Authorization VP
- Consoles — Credential-Safe Enforcement
- Authority delegation (how-to) — the needs-approval branch, end to end.
- Credential-Safe Action Enforcement (Ops) — wiring the proxy, gateway, and admission webhook.