Skip to content
PaloNexus
Request access Request

Security model

This is the authoritative page for PaloNexus’s security posture — both the invariants the platform is built on and the scannable overview a security reviewer needs (trust boundaries, what is verified, data handling, disclosure, compliance posture). It states the invariants once and links out to the pages that show each in depth — it does not re-explain the egress plumbing (Credential-safe action enforcement), the credential crypto (Agent identity & credentials), or the deny-reason catalog (Troubleshooting).

The whole system reduces to one question, asked of every agent action:

May this agent make this outbound call, on behalf of this human, for this task, right now?

The same question gates ordinary inbound calls too (may this caller reach this service?) — that north-south capability is the foundation egress is built on, not the headline.

/authz answers it. Identity, registry, policy, audit, and metrics converge there (internal/authz/authz.go); everything else is a dependency of that one decision. PaloNexus is built deny-by-default and audit-by-construction: recording the decision is the audit step in the same code path that makes it. And PaloNexus sits beside the workforce identity provider (IdP), not in front of it: it never holds employee credentials, and it acts on agent egress only when a human has delegated authority for a specific, time-boxed task.

Four trust zones meet at the control plane, and PaloNexus is explicit about what crosses each boundary. The workforce IdP owns human identity and keeps the credentials. The PaloNexus control plane makes the /authz decision and writes the tamper-evident audit. The agent-idp service is the did:web issuer and the authority for delegations and revocations. The agents themselves are confined by NetworkPolicy so their only route out is the egress proxy — there is no un-governed path to a model, tool, or peer agent.

flowchart LR
  subgraph IdP["Workforce IdP — humans"]
    human["Employee<br/>holds the credentials"]
  end
  subgraph CP["PaloNexus control plane"]
    authz["/authz decision<br/>+ hash-chained audit"]
  end
  subgraph AIDP["agent-idp"]
    issuer["did:web issuer<br/>delegations + StatusList"]
  end
  subgraph Agents["Agent workloads — NetworkPolicy egress-only via proxy"]
    agent["Agent<br/>did:key holder"]
    proxy["Egress proxy"]
  end
  out["Model broker / tool / peer agent"]

  human -->|OIDC token in| authz
  agent -->|VP + delegation on egress| proxy
  proxy -->|same /authz decision| authz
  authz -->|verify VP and not-revoked| issuer
  authz -->|stamped subject + actor out| out

Trust boundaries: OpenID Connect (OIDC) tokens enter from the IdP, agent egress presents a Verifiable Presentation (VP) plus a delegation through the proxy, the control plane verifies identity against agent-idp, and only a trusted, stamped subject and actor cross to the target. Credentials never leave the IdP, and agents have no path out except the proxy.

Every decision is the conjunction of these checks. Any one failing is a deny — and when a dependency can’t return a trustworthy yes, the decision fails closed rather than assuming allow.

SurfaceWhat is checkedFailure mode
Human tokenOIDC JSON Web Token (JWT) validated against the IdP’s JSON Web Key Set (JWKS) (OIDC_ISSUER / OIDC_AUDIENCE / OIDC_JWKS_URL), required scope present verbatimdeny — missing/invalid token or scope
Agent identityIn vc mode, a fresh holder-signed Membership VP over audience + nonce, proven did:key mapped to the registered agent namedeny — verified agent credential required; actor-header mismatch denies
Delegation (TBAC)A human-approved, time-boxed Delegation Verifiable Credential (VC) — task-based access control (TBAC) — scoped to (actor, task, action, resource) for regulated targetsdeny / needs-approval — missing or expired delegation
RevocationMembership and Delegation vcJti re-checked against the StatusList on every calldeny — CredentialRevoked, in under a second after revoke
PolicyInline registry rules (public? scope? allowlisted? under budget?) then an Open Policy Agent (OPA) deny-overrides veto over org Regodeny — inline or OPA deny; unreachable OPA fails closed
BudgetPer-agent rolling ceilings (calls/hour, tokens/hour, USD/day) from broker usage callbacksdeny — over budget
Audit chainEach record hash-chains to its predecessor; /v1/audit/verify recomputes the chaintamper detected — names the first broken sequence

The default answer is no. Access is granted only by an explicit, current allow; the absence of a grant is a deny. Concretely, all of these deny:

  • unknown service / unknown agent / unknown target (not in the registry);
  • anonymous caller to a non-public service, or a token missing the verbatim requireScope;
  • a target not on the calling agent’s egress allowlist;
  • a regulated target with no valid, human-approved, task-scoped delegation;
  • an invalid, mismatched, expired, or revoked agent credential.

Every one of these emits a specific X-Palonexus-Deny-Reason; in the SDK it surfaces as a typed exception, never a silent failure.

When the decision point cannot get a trustworthy yes, it denies — it never assumes allow:

Dependency unreachableBehavior
OPA (OPA_URL set but down)deny — opa unavailable: …
agent-idp delegation checkdeny — delegation authority unreachable: …
egress approval not decided in timedeny — egress approval expired (default 120s)
a durable DB backend misconfigured at startupthe process exits rather than silently falling back to in-memory (would lose registrations/delegations/revocations)
the control plane itself, from the SDKraises ControlPlaneUnavailable — never a silent allow

This is a deliberate design choice: an identity-and-access-management (IAM) product that “allows on error” is worse than none.

Two layers decide, in order: fast inline rules from the registry entry (public? required scope? allowlisted? under budget?), then an optional OPA veto over org-wide Rego. An inline allow plus an OPA deny equals deny. OPA can veto; it can never rubber-stamp. This lets platform teams ship org policy (geo, time-of-day, data-class) without redeploying the control plane, and guarantees the stricter answer always wins.

Every decision — allow or deny, ingress or egress — is recorded to a hash chain: each record’s prev_hash equals the previous record’s hash. Editing or deleting any entry breaks the chain, and pn.audit.verify_chain() (control-plane /v1/audit/verify) detects it. Audit is by construction, not an afterthought: recording the decision is the audit step in the same code path that makes it.

The diagram shows why tampering can’t hide. Each record hashes its own contents plus the previous record’s hash into its prev_hash, so the records form an append-only chain. If anyone edits or removes record n, its recomputed hash no longer matches the prev_hash stored in record n+1; verify walks the chain recomputing each hash and reports the exact sequence number where the first mismatch occurs.

flowchart LR
  r0["record n-1<br/>hash = H0"]
  r1["record n<br/>prev_hash = H0<br/>hash = H1"]
  r2["record n+1<br/>prev_hash = H1<br/>hash = H2"]
  r0 -->|prev_hash| r1 -->|prev_hash| r2
  verify[["/v1/audit/verify<br/>recompute every hash"]]
  verify -.->|first mismatch = tamper| r1

Each record links to its predecessor by hash; verify recomputes the chain and names the sequence where it first breaks — so any edit or deletion is detectable.

In the portal, the Audit explorer exposes exactly this: the hash-chained log with a Verify chain button, per-event Tempo trace deep-links, and task/agent/scenario filters:

Hash-chained audit log with a Verify-chain button and task, agent and scenario filters, above a table of decisions showing sequence, action, actor-to-subject, outcome, reason, truncated hash and per-row Tempo trace links

The Audit explorer: the tamper-evident, hash-chained decision log — filter it, deep-link any event to its Tempo trace, and verify the chain.

The same /authz covers both directions, so there is no per-service auth code — just one place to reason about access:

  • Egress (the headline, and the hard part): every outbound action an agent takes — model call, tool call, agent-to-agent hop — passes the decision, carrying agent and on-behalf-of identity, answering may this agent make this outbound call, on behalf of this human, for this task, right now? Enforced at the network layer (forward proxy + NetworkPolicy + admission webhook), so it holds for any framework, not just cooperating SDK code. See Credential-safe action enforcement.
  • Ingress (north-south): client → gateway → /authz → upstream. Envoy’s external-authorization (ext_authz) filter routes every request through the same /authz before it reaches a service (the keystone, SecurityPolicy.extAuth). This is the foundation egress is built on, not the MVP headline.

A request is an agent egress call iff it carries an X-Palonexus-Actor header; otherwise it takes the ingress path.

Identity propagation, not token forwarding

Section titled “Identity propagation, not token forwarding”

On an allow, the control plane does not forward the caller’s raw token upstream. It verifies the credential at the edge and stamps trusted headers — X-Palonexus-Subject (the human), X-Palonexus-Actor (the agent), X-Palonexus-Agent-DID (the proven did:key), X-Palonexus-Upstream — and upstreams trust the edge and never re-parse tokens. The target service is named by X-Palonexus-Service (set by the HTTPRoute), falling back to Host.

For regulated work an agent never acts as itself: it acts on-behalf-of a human subject, and the audit records both actor and subject. Authorization is task-based (TBAC): a delegation is granted for one task (e.g. a single incident), time-boxed, and the agent does not retain the privilege afterward.

Header-asserted actor identity is spoofable, so the production posture binds the actor to a Verifiable Credential:

  • each agent holds a did:key private key + an issuer-signed Membership VC (from agent.provision());
  • on every egress call it presents a fresh, holder-signed VP over an audience + nonce;
  • the control plane verifies it via agent-idp, maps the proven did:key to the registered agent name, and treats that as authoritative — the X-Palonexus-Actor header, if present, must match or the call is denied;
  • AGENT_IDENTITY_MODE=vc makes a verified VP mandatory.

Revocation is enforced on the decision path, not advisory: verification re-checks the StatusList on every call, so revoking a credential denies the next decision in under a second — even mid-run. See the revocation race recipe. Full design in Agent identity & credentials.

The SDK makes the posture a typed contract — deny-by-default surfaces as exceptions to handle, not status codes that are easy to forget to check:

Platform behaviorSDK surface
hard deny (403)PolicyDenied (carries the reason)
needs-approval (401 + needs-approval)ApprovalRequired → drives request_delegation / interrupt()
delegation timed outDelegationExpired
credential revoked mid-runCredentialRevoked
decision point unreachableControlPlaneUnavailable (raised, never swallowed)
missing owner/sponsor at registrationGovernanceError (client-side, before any network call)

Offline (PaloNexus.offline()), a FakeControlPlane mirrors the same deny-by-default semantics so tests prove the contract with no cluster.

PaloNexus stores the minimum needed to make and prove decisions. It does not store workforce passwords or credentials — those stay with the IdP. Secrets are never baked into images (see Secrets); they arrive at runtime from a secret manager.

DataWhereSensitivityRetention
Registry (services, agents, models, tools, allowlists, budgets, ownership)control-plane store (REGISTRY_DB_URL)Config / metadata — no end-user credentialsOperational; re-creatable from declarative source
agent-idp store (provisioned agents, delegations, revocations / StatusList)agent-idp store (IDP_DB_URL)Identity metadata; losing revocation state could resurrect a revoked credentialOperational; back up so revocation survives
Audit hash-chaincontrol-plane audit store → Loki / durable object storageDecision system-of-record (metadata: actor, subject, target, outcome, reason, hash) — not payloadsSet to the applicable regulatory window; the long-term artifact
LangGraph checkpointerPALONEXUS_AGENT_DB_URLIn-flight human-in-the-loop (HITL) thread state (paused approvals)Operational; needed to resume paused runs
Issuer keyagent-idp Secret, from a secret managerHigh — signs every VC and Security Token Service (STS) tokenMust be stable; never rotated as part of a code upgrade
Workforce passwords / credentialsNot stored — held by the IdPn/an/a

The audit trail records decision metadata (who, on behalf of whom, against what target, the outcome and reason, and the chain hash) — it is not a content/payload store. See Persistence (operations) for what each store holds and Backups & restore for proving the audit chain survives intact.

Production posture is opt-in and documented as a checklist. Turn the dev/demo overlay’s open defaults into the strict settings with the Production hardening checklist, and keep components within the supported set in the Upgrades compatibility matrix (control-plane / agent-idp / portal :h13, remediation :h12, model-broker :dev).

Production hardening at a glance:

  • OIDC on — real human identity (OIDC_ISSUER / OIDC_AUDIENCE / OIDC_JWKS_URL).
  • AGENT_IDENTITY_MODE=vc — a verified Membership VP is mandatory; header-only egress denied.
  • OPA org vetoOPA_URL set, deny-overrides, fails closed when unreachable.
  • NetworkPolicy egress-only-to-proxy — agents reach only DNS, agent-idp, and the proxy.
  • Postgres-backed durable registry + agent-idp store (CloudNativePG — CNPG), so revocation survives restarts.
  • Audit retention — ship the hash-chained audit to durable storage with a retention window.
  • External Secrets — no secret in any image; deliver via External Secrets / sealed-secrets.

Report a suspected security vulnerability in PaloNexus privately:

  • Email: security@palonexus.example (placeholder — confirm the real security contact before publishing this page externally).
  • Please do not open a public GitHub issue, discussion, or PR for a suspected vulnerability, and avoid posting proof-of-concept details publicly until a fix is available.
  • Include the affected component and version (e.g. control-plane :h13), reproduction steps, and impact. We aim to acknowledge reports and coordinate a fix and disclosure timeline with the reporter.

The full policy — supported components, scope (what’s in/out), expected handling, and a safe-harbor statement — lives in the repository’s SECURITY.md at the repo root, and a machine-readable contact is published as /.well-known/security.txt (RFC 9116). Under this site’s /docs base that file serves at /docs/.well-known/security.txt; the canonical site should also serve it at the domain root.

PaloNexus is designed to support common control objectives that enterprise audits care about — least-privilege / just-in-time access, a complete and tamper-evident audit trail, separation of duties, and deny-by-default. It does not currently hold formal attestations (SOC 2, ISO 27001); those are roadmap, not current state. Where a control is designed-for rather than shipped, the table says so — it maps control objectives to how the platform supports them and the honest status.

Control objectiveHow PaloNexus supports itStatus
Least privilege / just-in-time (JIT) accessTask-based access (TBAC): delegations are human-approved, time-boxed to one task, and not retained afterShipped
Complete, tamper-evident audit trailEvery decision is a hash-chained record; /v1/audit/verify proves integrityShipped
Separation of dutiesAgent ownership governance requires an accountable owner; delegation approval requires a human with real authority (owner ≠ approver, or logged break-glass)Shipped
Deny-by-default / fail-closedThe default answer is deny; unreachable dependencies deny rather than allowShipped
Identity lifecycle (joiner/mover/leaver)System for Cross-domain Identity Management (SCIM) directory sync + revocation cascade auto-suspends agents and invalidates delegations on a leaverShipped
Key management (issuer key)Issuer key delivered out-of-band via a secret manager; KMS/HSM-backed key + automated rotation is on the roadmapPartial
WORM / retention-locked audit sinkAudit ships to durable storage today; a retention-locked object store for WORM durability is on the roadmapPartial
Formal attestation (SOC 2 / ISO 27001)The control objectives above are designed-for; no certification is claimedRoadmap — not attained

Status terms match the Feature matrix: Shipped is built and verified live; Partial is shipped with a production-grade hardening upgrade still open; the formal attestations are explicitly not claimed.