Skip to content

Releases: stephnangue/warden

Warden v0.21.0

Choose a tag to compare

@github-actions github-actions released this 05 Oct 17:03
c9f510c

Warden v0.21.0

v0.21.0 is the first-experience release. Trying Warden used to mean standing up an identity provider, finding an upstream key to hand it, and wiring a Compose stack before the first request — the better part of an hour before anything was governed. Now it is one command. warden server -dev-playground starts Warden with its own identity provider and a bank to protect, and a nine-scenario tour walks your own agent — Claude Code, Codex, Cursor, Gemini CLI, opencode, VS Code, or any MCP client — through what Warden does: the agent holds no secret, policy picks its tools and then its arguments, it acts for a person, a prompt-injected memo cannot move the money, every call is audited, it finds its own roles, and the tour ends on your own GitHub. Start at Getting started.

Discovery grows up alongside it. A role now says what it is in structured fields — the skill to read and the provider_path it fronts — and list_roles hands the agent the URL to call and a skill:// URI, read with read_skill or through the MCP Skills extension. What an agent can read is bound to who it is.

Keyless keeps widening: Anthropic and OpenAI federate, Cloudflare gets a keyless source, Azure Key Vault joins the chaining producers, and the default assertion becomes an RFC 8693 delegation token that names the user on top and the agent in act. A new keyless_enforcement_level warns — or refuses — when a write would leave a secret stored in Warden. And when Warden itself refuses a request, it now answers in the upstream's own error shape, so an AWS SDK, an OpenAI client or an Anthropic client reads the failure as it reads any other.

Nine breaking changes — read Upgrading before you bump.

Breaking Changes

  • The default assertion profile is an RFC 8693 delegation token. With a user disclosed, the top level is the user and the agent moves into act. warden_sub, warden_auth_mount, warden_user and the agent-level warden_namespace are gone; the agent's composite sub is unchanged, so agent-only trusts keep matching.
  • get_skill is removed for read_skill(uri), and the (skill: …, url: …) description convention gives way to the role fields skill and provider_path. Backfill provider_path, or a role is listed with no URL.
  • Skill reads on the discovery server are identity-bound.
  • Skill names drop the underscore: every stored skill with one is renamed at unseal — mcp_aws becomes mcp-aws, ansible_tower becomes ansible-tower.
  • MCP refusals are JSON-RPC errors (-32090); the former error / error_description move under error.data.
  • Azure key_vault_secret specs fail every mint — move them to secret_read.
  • GitHub's singular repository is refused, and a stored permissions value is now enforced.
  • New AWS federated specs default to the aws profile (session tags, needs sts:TagSession); new Azure federated specs default to minimal.
  • AWS and Alibaba Cloud config writes merge — a key left out keeps its value.

Security

  • Azure Key Vault URL injection closed, and rotation no longer deletes live secrets.
  • MCP policy is enforced on every POST to an MCP mount, whatever its Content-Type.

New Features

The playground

  • warden server -dev-playground, its identity provider, a bank with MCP and REST faces, and GitHub's MCP server waiting for your PAT.
  • warden dev jwt | scenarios | audit, and the tour written out for seven agents — on the page, or with warden dev scenarios -client.

Discovery and skills

  • Structured role fields, list_roles with a resolved provider, skill URI and URL, the MCP Skills extension, and read_skill returning the skill's markdown in its structured output.

Keyless credentials

  • Anthropic and OpenAI workload identity federation, a keyless Cloudflare source, and Azure Key Vault as a chaining producer.
  • assertion_profile — default, minimal or aws — and a per-spec assertion_ttl.
  • keyless_enforcement_level, stored_secrets on every source and spec, and warden cred source|spec keyless-plan to describe the keyless replacement for one.
  • Public token_exchange clients, and GitHub App tokens scoped by repositories and permissions.

The gateway

  • Native error shapes for AWS, OpenAI and Anthropic, and a 504 — not an empty 200 — when a mount's timeout beats the upstream.
  • Policy reads request bodies on rest mounts, up to the mount's max_body_size.
  • Anthropic workspaces, version and beta headers governed from mount config, and user-profile attribution.
  • The audit log records the user's namespace, role and act chain.

Fixed

  • A mint the upstream refuses answers 403, not 500.
  • A config write takes effect only once it is saved; a refused write changes nothing.
  • Rotation activations no longer revert operator edits or orphan keys, and source drivers are rebuilt after a node steps down.
  • Each credential client owns its connection pool, so deleting one source no longer drops another's connections.

Upgrading

Nine changes need action. Work through Upgrading from v0.20.0 before rolling out the binary.

  • Find every verifier bound to the old default claims — a Vault JWT role, a GCP attribute condition, a CEL rule reading warden_sub, warden_user or warden_auth_mount — and rewrite it.
  • Backfill provider_path on roles that relied on the description convention, and move agents from get_skill to read_skill.
  • Move Azure key_vault_secret specs to secret_read, and GitHub specs from repository to repositories.
  • Behind a load balancer, list it in trusted_proxies; and add internal hosts to NO_PROXY, which sources with a custom CA now honour.

A local-source github_token spec stored with repositories or permissions before this release keeps minting the unscoped token it holds; a static token cannot be narrowed. New writes are refused.

Warden v0.20.0

Choose a tag to compare

@github-actions github-actions released this 12 Sep 23:23
332f5b1

Warden v0.20.0

v0.20.0 is the agent-and-user authorization release. Warden has carried two principals per request since v0.19.0, but only one of them could be reasoned about: the agent authorized the request and the user was along for attribution. That changes here. The user principal is now visible to the policy layer, so a condition can require a human behind the agent — and, more importantly, require that this agent is the one the human authorized. The CEL token namespace is renamed agent to say plainly which principal a rule is talking about, and a new user namespace sits beside it.

Two consequences follow. A denial that a user could remedy no longer looks like a wall: Warden answers 401 with a WWW-Authenticate challenge and publishes RFC 9728 protected resource metadata, so a client discovers where to authenticate the person and retries. And MCP authorization becomes its own policy type — capability policies govern paths, MCP policies govern calls, and access is the intersection of both. MCP traffic with no MCP policy in scope is now denied rather than passing unrestricted.

On the credential side, keyless keeps widening: Alibaba Cloud and Kubernetes federate, AWS Secrets Manager and GCP Secret Manager join OpenBao/Vault as chaining producers, and a client assertion can now be signed inside a KMS that Warden never sees the key for.

Eight breaking changes — read Upgrading before you bump. Two of them can take a working deployment down if you roll the binary out first and migrate afterwards.

Breaking Changes

  • The CEL token namespace is now agent. No alias. A stored policy referencing token fails to compile at load, and because policies are re-parsed on read, it cannot be read back afterwards — export your policy texts first. Audit salt_fields selectors move with it, and a stale one does not error: it stops matching, and the value it protected begins logging in clear.
  • Dual-token extraction. On a user_auth_path mount the legs swap: Authorization carries the user, the agent moves to X-Warden-Agent-Token or a client certificate. user_token_header is retired and user_auth_path is mount-only.
  • MCP rules move to sys/policies/mcp/<name>. A nested mcp { } block is rejected at parse, the grammar becomes family blocks, and allowed_params / denied_params give way to a CEL condition over call.args.
  • MCP traffic with no MCP policy in scope is denied (no_mcp_policy). Mounts meant to stay open need an explicit wildcard policy.
  • mcp / mcp_aws timeout defaults to 60 seconds and now caps a single call; long-lived streams answer to the new listen_timeout.
  • IBM iam_with_cos, OVH dynamic_s3 and oauth2_token_and_s3 are removed, replaced by access_keys sourced through secret_spec. Drain OVH leased pairs before upgrading.
  • apikey sources rename optional_metadata to credential_fields — and the mechanism now works, where before the declared fields silently never reached the provider.

New Features

Agent and user in policy

  • The user principal reaches CEL. A user namespace beside agent, carrying the same fields minus policies — the user never authorizes, so it cannot widen what an agent may do, only be made required.
  • Binding an agent to the user it acts for. It is not enough that both are present; the user must be paired with this agent. The pairing is attested on the user's token by an RFC 8693 claim — may_act where the IdP pre-authorizes an agent, act where the credential is itself a delegation token — and either maps through a JSON Pointer.
  • 401 instead of 403 when a user would fix it, with a resource_metadata parameter, plus RFC 9728 protected resource metadata and a warden protected-resource command to configure it. A retry that still fails gets a terminal 403; the exchange cannot loop.

MCP

  • A first-class MCP policy type with its own grammar, storage and -type flag, composing with capability policies as an intersection.
  • Protocol hardening: per-URI gating for subscriptions/listen, transport headers validated against the body, modern-era batches refused, per-principal responses marked uncacheable.

Credentials

  • Keyless federation for Alibaba Cloud and Kubernetes — the latter presents its assertion directly as the bearer token, with no exchange hop.
  • AWS and GCP secret stores can produce chained secrets (mint_method=secret_read), and can be federated themselves, so a chain is keyless at both hops.
  • KMS-signed client assertions (client_auth=kms_private_key_jwt) fed by transit_signer, which mints a scoped signing capability rather than key material — the private key is read by nobody, including Warden.
  • Nine more drivers can chain their standing secret, and claim templating now reaches AWS secret_id and GCP secret_name alongside Vault's secret_path, so one spec can resolve a different secret per caller.
  • The honeycomb credential driver is removed — its keys could be neither revoked nor expired. Hold the key in a vault and chain it. The honeycomb provider is unaffected.

Fixed

  • cas_required is enforced on the policy write path, which never applied it — a policy that set it was unguarded. Refusals answer 4xx, not 500.
  • Responses longer than 10 seconds survive. A hardcoded listener write timeout severed every response over ~10s, so no LLM mount could reach its 120-second budget; raising the mount timeout did nothing. The listener's HTTP timeouts are configurable now, and streaming sheds them.
  • Denied requests are audited once, not twice.

Upgrading

Eight changes need action. Work through Upgrading from v0.19.0 before rolling out the binary — the first two steps are irreversible once it is running.

  • Export every policy that uses a token.* condition — after the upgrade it cannot be read back:
    for name in $(warden policy list -o text); do
      warden policy read -o table "$name" > "policy-backup-$name.hcl"
    done
  • Snapshot your audit configuration and rewrite salt_fields selectors to the agent namespace, or the values they protect begin logging in clear.
  • Drain OVH leased credential pairs, which are not cleaned up after the upgrade.
  • Then: rewrite token.* conditions, re-express mcp { } blocks as -type=mcp policies and attach them to the roles that need them, add wildcard MCP policies to mounts meant to stay open, set timeout explicitly on MCP mounts with long calls, move agents to X-Warden-Agent-Token, and rename optional_metadata to credential_fields.

Warden v0.19.0

Choose a tag to compare

@github-actions github-actions released this 15 Aug 09:15
5413baa

Warden v0.19.0

v0.19.0 is the keyless federation release. Warden can now act as its own OIDC issuer — minting short-lived, signed identity assertions that describe the caller and publishing the JWKS / OIDC discovery to verify them — so an upstream trusts who the caller is instead of a secret Warden holds. On top of that, keyless credential sources (auth_method=oidc_federation) across AWS, Azure, GCP, and OpenBao/Vault store no cloud secret at all — each request federates an assertion for a short-lived credential — and credential chaining (secret_spec) sources any remaining standing secret from an external keyless-federated vault per request, so there is no key-trove to breach. The issuer's signing key can live in an external KMS (the private key never enters Warden), and a public publisher exposes the verification keys to internet-facing clouds without exposing Warden itself. Finally, a second user principal lets an agent act on behalf of a verified human — natively (RFC 8693 / RFC 7523 / ID-JAG) or, for upstreams with no on-behalf-of flow, via a per-user OAuth token from OpenBao/Vault at fleet scale. Three breaking changes — read Upgrading before bumping.

Breaking Changes

  • RFC 8693 token exchange reshaped around the user. The header token source and its subject-trust keys (subject_issuer / subject_audience / subject_jwks_url / subject_oidc_discovery_url) are removed; the subject_token_source / actor_token_source value auth_token is renamed to agent_identity; and delegation now forwards the end user's own validated credential via subject_token_source=user_identity (with actor_token_source=warden_identity for the on-behalf-of-user shape). A create/update with a retired value is rejected, and a persisted spec carrying one fails closed at mint.

  • GitHub credential specs select the credential with mint_method, not auth_method. The github driver now reads a required spec-level mint_method (app | pat); the old auth_method key is rejected. GitLab is unaffected.

  • X-Warden-On-Behalf-Of retired; audit/policy actors are verified-only. The self-reported header is gone — the actor chain now has a single source, the cryptographically-verified RFC 8693 act claim. The verified field is dropped everywhere, so a CEL condition referencing a.verified (e.g. token.actors.all(a, a.verified)) must become size(token.actors) > 0.

New Features

Federation & keyless identity

  • Warden is now an OIDC issuer. It mints short-lived, signed JWT identity assertions (sub = wid:<namespaceID>:<mountAccessor>:<principalID>, plus warden_* claims) and publishes the matching JWKS + OIDC discovery, so any upstream that trusts an OIDC issuer — a cloud STS, an IdP, a secrets vault — can verify a caller's identity directly. The issuer fails closed until a signing key is installed, and key rotation retires prior keys into the JWKS so in-flight assertions keep verifying.
  • The signing key can live in an external KMS. A signer { } stanza binds the assertion-signing key to an external KMS (type = "transit" today) so the private key never enters Warden; the interface is built for more backends.
  • Publishers expose the keys to internet-facing upstreams. A publisher pushes the discovery + JWKS to a bucket/CDN (s3, gcs, azure_blob, http_put, local_file) that becomes the issuer_url upstreams fetch from — so Warden itself need not be internet-facing — and self-rotates its own write credential.
  • Keyless credential sources — auth_method=oidc_federation. A source can hold no stored cloud secret: on each request Warden federates an identity assertion for a short-lived credential. Ships for AWS (AssumeRoleWithWebIdentity, keyless Secrets Manager), Azure (Entra client_assertion), GCP (Workload Identity Federation), and OpenBao/Vault (per-request JWT-auth login).
  • Assertion shaping. warden_identity assertions can pin a single downstream resource (warden_resource) and carry allowlisted login metadata (assertion_metadata_claims → nested warden_metadata) for verifier-side least privilege.

Credential chaining

  • Chain a spec's secret from another cred spec (secret_spec). A secret-backed provider sources its standing secret from another spec at request time instead of storing it — keyless end-to-end, single-hop, nothing persisted. Includes a generic kv2_read mint method + key_value credential type, an opt-in source-scoped cache (secret_cache_ttl), and support for sourcing the token_exchange client secret this way.

User principal & acting on behalf of a human

  • Per-user credential chaining via secondary transparent authentication. A request can carry a second, per-request user credential (default header X-Warden-User-Token) that Warden resolves into a first-class user principal — identity-only today. A warden_identity spec can disclose the user under a nested warden_user claim, and a per-user secret_path / credential_name template ({{user.<claim>}}) selects a secret or OAuth token scoped to the verified user — so an agent acts as the human, even against upstreams with no native on-behalf-of flow.

Upgrading

Three changes need action; each is a small, mechanical migration. See the Upgrading from v0.18.0 guide for the full detail.

  • CEL actor conditions: rewrite token.actors.all(a, a.verified) → size(token.actors) > 0 (note the empty-chain semantics flip from allow to deny — drop the condition entirely if you did not mean to require an actor).
  • GitHub specs: rename -config=auth_method=<app|pat> → -config=mint_method=<app|pat>.
  • Token-exchange specs: rename subject_token_source=auth_token → agent_identity; re-express a header-sourced delegation as subject_token_source=user_identity (+ actor_token_source=warden_identity) with a user_auth_path configured; drop the removed header source and subject_* trust keys.

Warden v0.18.0

Choose a tag to compare

@github-actions github-actions released this 23 Jul 21:27
49676a2

Warden v0.18.0

v0.18.0 is the on-behalf-of release. A new token_exchange credential source lets a Warden role broker an upstream call as the agent acting for the end user rather than as a static service identity: it exchanges a caller-presented token at an STS/IdP endpoint following RFC 8693, with support for the id_jag cross-app-access flow, RFC 8707 resource indicators, and private_key_jwt client authentication. The exchange is verified-origin — the subject and actor tokens are drawn from the agent's own verified inbound Warden JWT (or explicitly-supplied headers), and the caller-supplied exchange headers are stripped before the request reaches the upstream. Alongside it, an always-on MCP discovery server at sys/mcp lets an agent discover its roles and skills over MCP itself, and MCP tools/list responses are pruned to the callable set so discovery matches enforcement. One breaking change — MCP mcp { } authorization is now deny-by-default; read Upgrading before bumping.

Breaking Changes

  • MCP mcp { } authorization is now deny-by-default. An mcp { } block previously allowed anything it did not explicitly deny; it now denies anything it does not explicitly allow — an empty or absent allowed_methods / allowed_tools / allowed_resources / allowed_prompts denies every method/tool/resource/prompt in that family. This silently tightens deployed policies: mcp { denied_tools = ["delete_*"] } (or a block carrying only a condition) goes from "all tools except delete_*" to "deny every tool". The session-lifecycle methods initialize, ping, and notifications/* are exempt — they pass without being allow-listed (a denied_methods entry can still block them). Audit every mcp { } block before upgrading; see Upgrading.

New Features

  • New token_exchange credential source — RFC 8693 on-behalf-of token exchange. A source of type token_exchange mints an upstream access token by exchanging a caller-presented token at a token_url. subject_token_source / actor_token_source choose where the subject and actor tokens come from — auth_token reuses the agent's verified inbound Warden JWT (verified origin), header reads X-Warden-Subject-Token / X-Warden-Actor-Token (unverified), none omits it (the two sources can't both be auth_token). Selectable grants (grant): rfc8693, jwt_bearer (RFC 7523), and id_jag (two-leg ID-JAG, needs resource_token_url). Client auth is client_secret_basic, client_secret_post, or private_key_jwt. Requested audience, scope, and RFC 8707 resources live on the spec; a header-sourced subject token is verified against a configured issuer/JWKS. The source issues an oauth_bearer_token credential, so any bearer-token provider (including the generic mcp provider) can carry it, and the caller-supplied exchange headers are stripped before forwarding upstream.

  • MCP discovery server at sys/mcp. An always-on MCP endpoint exposes two tools: list_roles returns the roles the caller can assume (each naming the skill that drives it), and get_skill (argument skill) returns that skill as markdown. Namespace is selected with the X-Warden-Namespace header. This replaces the retired CLI-loop discovery skills.

  • MCP list responses are filtered to the callable set. When a tools/list, resources/list, or prompts/list request is allowed, Warden prunes the response to only the items the caller could actually use — an item survives iff the corresponding tools/call / resources/read / prompts/get would pass policy (per-call conditions are deferred to call time). A batched request containing a list method is denied, and a list response that can't be buffered within max_body_size fails closed rather than streaming unfiltered.

Upgrading

The MCP deny-by-default flip changes how existing mcp { } blocks behave. Audit every block and restore intended openness explicitly:

  • Open everything, then subtract: mcp { allowed_methods = ["*"], allowed_tools = ["*"], allowed_resources = ["*"], allowed_prompts = ["*"], denied_tools = ["delete_*"] }
  • A block that carried only a condition (or only denied_* lists) now denies its whole family — add the matching allowed_* = ["*"] to keep it open.
  • You no longer need to allow-list the MCP handshake: initialize, ping, and notifications/* pass without being listed (block them explicitly via denied_methods if you must).

The change is enforced at request time, not policy-write time, so review blocks before upgrading rather than relying on a parse error to catch them.

Warden v0.17.0

Choose a tag to compare

@github-actions github-actions released this 04 Jul 10:10
1b30e93

Warden v0.17.0

v0.17.0 is the CEL release. Capability-Based Policy gains one expressive predicate layer: a path rule can carry a condition (evaluated once per request) and an mcp { } block a per-call condition (evaluated once per tool call) — a CEL expression over a fixed request.* / token.* / now / call.* namespace, with helpers for CIDR matching (cidrContains), optional arguments (call.args.?x.orValue(d)), and timezone-aware time (now.getHours(tz), now.getDayOfWeek(tz)). Evaluation is fail-closed and cost-bounded, conditions are compiled once and identity-independent, and every decision is explainable in the audit log (the expression and the exact input values it read). CEL replaces — and this release removes — the three structured mechanisms it subsumes: the conditions {} block, MCP allowed_params/denied_params, and CBP request-body *_parameters. Alongside it, login-derived token metadata lands across all four auth methods (feeding token.metadata.* in conditions), a new generic rest provider proxies arbitrary JSON/REST upstreams, and the documentation is substantially restructured and expanded (including a 20-example CEL cookbook). Three breaking changes — read Upgrading before bumping.

Breaking Changes

  • The structured conditions {} block is removed — express it as a CEL condition. Its four predicates map directly: source_ip → cidrContains("10.0.0.0/8", request.client_ip), time_window → now.getHours(tz) / now.getMinutes(tz), day_of_week → now.getDayOfWeek("UTC") (0 = Sunday), token_metadata → token.metadata.<key>. The parser rejects the block with a directed error.

  • MCP allowed_params / denied_params are removed — express them as a per-call condition over call.args. e.g. condition = "call.args.amount <= 1500 && call.args.currency in ['USD','EUR']". Semantics change deliberately: a condition is fail-closed (absent argument → deny), whereas the old lists let an absent argument pass — use call.args.?x.orValue(d) where "absent is OK".

  • CBP request-body required_parameters / allowed_parameters / denied_parameters are removed — express them as a condition over request.data. has(request.data.owner) requires a field, request.data.tier in ['gold','silver'] constrains a value, !has(request.data.internal) forbids one. Pagination (pagination_limit) and list-response filtering are unchanged. The parser rejects the removed keys.

New Features

  • CEL policy conditions. Path-level and per-call mcp { } conditions over request.* (incl. the new request.namespace), token.*, now, and call.*; helpers cidrContains, optional types, and the timezone-aware now.get* built-ins. Fail-closed and cost-bounded (type-checked and cost-limited at write time; a runtime cost limit backstops size-dependent expressions). A path-level condition that references call.* is a compile-time error. See the CEL condition cookbook.

  • Login-derived token metadata across jwt, cert, kubernetes, and spiffe. Per-role metadata_claims (claims, incl. nested via JSON Pointer) or metadata_mappings (certificate fields, TokenReview attributes, SPIFFE-ID components) map verified identity attributes onto the token; token.metadata.<key> is then usable in conditions and attributed in audit.

  • Explainable audit for CEL decisions. auth.policy_results.condition records the expression, decision, a sanitized error category, and the referenced inputs — logged in clear by default and HMAC-salt-able per key via salt_fields.

  • Generic rest provider. A REST/HTTP reverse-proxy backend for JSON/REST APIs without a dedicated provider, with a Warden-injected credential.

Upgrading

Rewrite any policy that used the removed structured mechanisms as a CEL condition:

  • conditions { source_ip = ["10.0.0.0/8"] } → condition = "cidrContains('10.0.0.0/8', request.client_ip)"
  • conditions { token_metadata = { env = ["prod"] } } → condition = "token.metadata.env == 'prod'"
  • MCP allowed_params { amount = ["<=1500"] } → mcp { condition = "call.args.amount <= 1500" } (add call.args.?amount.orValue(0) <= 1500 if an absent argument should still pass)
  • CBP required_parameters = ["owner"] → condition = "has(request.data.owner)"

The parser rejects each removed construct at policy-write time with an error naming its CEL equivalent, so a stale policy fails fast on warden policy write rather than changing behaviour silently.

Warden v0.16.0

Choose a tag to compare

@github-actions github-actions released this 15 Jun 18:00
5a9a9c9

Warden v0.16.0

v0.16.0 is the SPIFFE release. A new first-class spiffe auth method accepts a SPIFFE X.509-SVID (over mTLS or a trusted forwarding header) and a SPIFFE JWT-SVID (bearer) on the same mount, verifying each against the trust domain's bundle — which carries both the X.509 and JWT key sets — and issuing one token type. Roles bind on trust_domain, allowed_spiffe_ids, and bound_audiences; bundles are managed on the mount and can be federated from upstream endpoints with periodic refresh. The method leans on short-lived SVIDs and bundle rotation rather than CRL/OCSP. The control plane itself can now run on SPIFFE: the API listener can serve its TLS certificate straight from the SPIFFE Workload API, rotating automatically as the SVID renews. This release also consolidates the MCP providers — a single generic mcp provider replaces the typed mcp_github and mcp_gcp — and threads non-secret subject metadata into the audit log across the AWS, GCP, Azure, Kubernetes, and Vault sources, so a brokered call records which upstream identity acted. Two breaking changes — read the Upgrading section before bumping.

Breaking Changes

  • The mcp_github and mcp_gcp provider types are removed in favour of a single generic mcp provider. The new mcp provider fronts any bearer-authenticated MCP server (GitHub, Google Cloud, Slack, Cloudflare, …) and injects Authorization: Bearer for oauth_bearer_token, api_key, github_token, gcp_access_token, and azure_bearer_token — a superset of the typed providers it replaces. Re-mount as type mcp and set mcp_url (https://api.githubcopilot.com/mcp for GitHub; the operator's URL for Google Cloud). mcp_aws is unaffected — it SigV4-signs requests rather than injecting a bearer token.

  • jwt auth method: the bound_uri_patterns and uri_claim role fields are removed. They provided segment-aware string matching against a JWT claim as a stand-in for real SPIFFE JWT-SVID validation. SPIFFE identities now belong to the new spiffe auth method, which verifies a JWT-SVID against a trust-domain bundle and enforces its audience. Stored roles carrying these keys decode cleanly and ignore them — a role that relied on URI-pattern matching becomes more permissive, so migrate it to the spiffe method.

New Features

  • New spiffe auth method: first-class SPIFFE support for both SVID types on one mount. Accepts a SPIFFE X.509-SVID (presented over mTLS or a trusted forwarding header) and a SPIFFE JWT-SVID (bearer) on the same mount, verifying each against the trust domain's bundle and issuing a single spiffe_role token type. When a request presents both, the explicitly-presented JWT-SVID wins over an ambient forwarded certificate. Roles bind on trust_domain, allowed_spiffe_ids, and bound_audiences; an audience is required for JWT-SVID logins, as SPIFFE mandates. Trust-domain bundles are managed on the mount and can be federated from upstream endpoints with periodic refresh. There is no X.509-SVID revocation surface — the method relies on short-lived SVIDs and bundle rotation, and an issued token's TTL is capped by the SVID's expiry.

  • The API listener can serve its TLS certificate from the SPIFFE Workload API. Instead of a static certificate and key on disk, the listener can obtain an X.509-SVID from a SPIFFE Workload API endpoint and rotate it automatically as the SVID is renewed, keeping the control-plane TLS identity short-lived and self-renewing.

  • cert auth method consolidated to pure PKI. The never-shipped experimental SPIFFE mode (mode=spiffe, trust-domain config, bundle federation, and the spiffe_id principal claim) is removed in favour of the dedicated spiffe method. The cert method keeps its X.509 chain-of-trust validation and URI-SAN matching (uri_san principal claim plus allowed_uri_sans), so a SPIFFE X.509-SVID can still be accepted as an ordinary client certificate bound on its URI SAN.

  • Audit-log subject metadata across the cloud and secrets sources. The AWS, GCP, Azure, Kubernetes, and Vault drivers now populate the non-secret Credential.Metadata introduced in v0.15.0 when they mint — the assume-role identity on an AWS STS mint, and the token/access-token subject on the others — so an audit record shows which upstream identity a brokered call acted as, not merely that a call occurred.

Bug Fixes

  • httpproxy: the gateway suffix is forwarded verbatim. Gateway-suffix path extraction now preserves the upstream path exactly — including trailing slashes — so the SigV4-signed mcp_aws gateway's canonical request stays valid.

Documentation

  • Operator README for the new spiffe auth method, with SPIFFE content retargeted across the cert, jwt, and Vault-provider docs to point SPIFFE workloads at the dedicated method.
  • mcp_aws README: a Claude Code CLI registration step (claude mcp add) for pointing Claude Code at an mcp_aws mount.
  • Root README: a dedicated spiffe row in the authentication-methods table.

Upgrading

helm upgrade warden oci://ghcr.io/stephnangue/charts/warden \
  --version 0.3.2 \
  -n warden --reset-then-reuse-values

Chart 0.3.1 → 0.3.2 is an appVersion bump to track the v0.16.0 binary — there are no chart template or values changes, so existing values files apply as-is.

Two operator-facing migrations before bumping:

  • Re-mount any mcp_github or mcp_gcp provider as type mcp and set mcp_url (https://api.githubcopilot.com/mcp for GitHub; the operator's URL for Google Cloud). The generic provider reuses the same credential types, so existing credential specs apply unchanged. mcp_aws is unaffected.
  • Migrate any jwt role that used bound_uri_patterns or uri_claim to the spiffe method. Those fields are gone; a role that relied on them now ignores them and becomes more permissive until migrated.

Resources

License

MPL-2.0

Warden v0.15.0

Choose a tag to compare

@github-actions github-actions released this 06 Jun 20:37

Warden v0.15.0

v0.15.0 is the MCP release. A new mcp { } policy block brings body-authoritative governance to MCP (JSON-RPC) traffic — gate by method, tool, resource, prompt, and tool-argument values, with the gateway strict-parsing the request body rather than trusting client-supplied headers — and three new providers carry it: mcp_github (transparent proxy to GitHub's hosted MCP server), mcp_aws (SigV4-signed gateway to AWS's hosted MCP servers and Bedrock AgentCore), and mcp_gcp (bearer-token gateway to a Google Cloud MCP server). The OAuth2 credential source gains an authorization-code flow, so a Warden role can broker calls as a consenting user — consent captured once through warden cred spec connect, refresh tokens rotated and written back automatically. github and gitlab now proxy Git smart-HTTP (clone / fetch / push) on the same mount as their REST API, and a new kubernetes auth method validates workload identities via TokenReview. Three breaking changes — read the Upgrading section before bumping.

Breaking Changes

  • Transparent-auth role resolution: the provider role-extractor now outranks default_role. The chain is X-Warden-Role header > role embedded in the path > the provider's role-extractor hook > the mount's default_role. Mounts that combine an extractor with default_role change behaviour — github Git smart-HTTP, AWS-with-SigV4, and Alicloud-with-ACS3/SigV4 now resolve to the extractor-derived role instead of default_role. Mounts without an extractor (or AWS/Alicloud requests that aren't SigV4-signed) are unchanged.

  • github provider: the git_url config field is removed. It was declared but never read at runtime. Operators who had it set now get a clear "unknown field" error on config write instead of a silent no-op — drop it; the Git host is derived from github_url as before.

  • SDK signature changes for out-of-tree backends. StreamBodyParser.ShouldParseStreamBody and TransparentModeProvider.IsUnauthenticatedPath now take the *http.Request, and TransparentAuthRoleExtractor returns string (was (string, bool)). In-tree backends are unaffected; only code implementing these interfaces out of tree needs updating.

New Features

  • OAuth2 authorization-code flow — a Warden role can broker calls as a consenting user. The oauth2 source gains an authorization_code auth method alongside client_credentials, so a credential spec can hold a user's consent rather than a service identity. warden cred spec connect <name> runs the one-time browser consent over a loopback callback with PKCE and state, and the token exchange happens server-side — the client secret never reaches the operator's machine. Refresh tokens (and their expiry) are rotated and written back automatically under a per-spec lock that serializes concurrent mints. The audit log records which upstream identity acted, per call. mcp_github accepts these credentials, so one role can act as a GitHub App, a PAT, or a consenting GitHub user.

  • mcp { } policy block: body-authoritative governance of MCP (JSON-RPC) traffic. A new mcp { } block inside a control-by-policy path rule gates requests by JSON-RPC method, by the name in tools/call / resources/read / prompts/get, and by tool-call argument values — each with allow/deny symmetry and case-insensitive trailing-* globs. The gateway strict-parses the JSON-RPC body and fails closed on any malformed 2.0 envelope, then evaluates against the parsed body rather than client headers. Opt-in per provider and method-aware (only JSON-RPC POSTs are evaluated, so Streamable HTTP's GET/DELETE pass through). Denials return an OAuth-shaped HTTP 403, and the audit log carries a structured mcp_decision.

  • Three new MCP providers. mcp_github transparently proxies GitHub's hosted MCP server (reusing the GitHub credential, so one spec grants both REST and MCP reach). mcp_aws SigV4-signs traffic to AWS's hosted MCP servers and Bedrock AgentCore, inferring service and region from the endpoint. mcp_gcp mints a short-lived GCP access token per request for a Google Cloud MCP server. All three opt into mcp { } enforcement.

  • github and gitlab Git smart-HTTP. Both providers now proxy git clone / fetch / push on the same mount that proxies their REST API. Clone URLs carry the Warden role as the Basic Auth username and the Warden JWT as the password, so Git's credential helpers cache cleanly per role. A new git_max_body_size config field caps Git request bodies separately from the REST max_body_size.

  • kubernetes auth method: validate workload tokens via TokenReview. The first cloud-native member of the transparent-auth family validates a workload service-account token by calling TokenReview on the issuing kube-apiserver — no JWKS endpoint required on the spoke, awkward on hardened distros — matching the auth/kubernetes shape Vault and OpenBao operators know. The SA UID, namespace, and name land in the audit log for attribution.

  • Provider tune endpoint. warden provider tune <path> -description=... updates a mount's description in place; previously this required an unmount and remount.

  • X-Warden-Provider requests may omit the /v1/ prefix. Header-routed calls (the documented git clone form via http.extraheader) no longer require the /v1/ prefix on the path; the middleware prepends it when the header is present. The canonical /v1/-prefixed form is unchanged, and sys/-targeted requests with the header are rejected with 400.

Bug Fixes

  • SigV4: client cancellation logs at debug, not error. A client that disconnects mid-request on a SigV4 provider no longer produces an error-level log line.

Documentation

  • Operator READMEs for the cert, jwt, and kubernetes auth methods.
  • mcp_github provider docs: authorization-code flow setup around a GitHub App (with a Claude Code claude mcp add worked example) and a body-authoritative mcp { } policy section.
  • mcp_aws skill and operator README; httpproxy SDK hook reference; root README coverage of the MCP-server providers, on-behalf-of attribution, Kubernetes auth, and credential rotation.

Upgrading

helm upgrade warden oci://ghcr.io/stephnangue/charts/warden \
  --version 0.3.1 \
  -n warden --reset-then-reuse-values

Chart 0.3.0 → 0.3.1 is an appVersion bump to track the v0.15.0 binary — there are no chart template or values changes, so existing values files apply as-is.

The binary breaking changes are mostly transparent to a Helm install, but be aware of three things before bumping:

  • Mounts that set default_role together with a role-extractor (github Git smart-HTTP, AWS/Alicloud with SigV4) now resolve to the extractor-derived role instead of default_role. Review those mounts if you relied on default_role winning.
  • The github git_url config field is gone — drop it from any provider config (the Git host is derived from github_url).
  • Out-of-tree providers that implement StreamBodyParser.ShouldParseStreamBody, TransparentModeProvider.IsUnauthenticatedPath, or TransparentAuthRoleExtractor must update those signatures. In-tree providers and a stock Helm install need no changes.

Resources

License

MPL-2.0

Warden v0.14.0

Choose a tag to compare

@github-actions github-actions released this 26 May 14:16
b4a73fd

Warden v0.14.0

Warden is the secure gateway connecting AI agents to the enterprise systems they need to do real work. Agents discover what they're allowed to access, Warden brokers every connection, and operators get one control plane for identity, policy, and audit — across every cloud, code-host, observability stack, database, and SaaS the agent reaches. No upstream credentials ever reach the agent: Warden authenticates the caller (JWT or TLS certificate), evaluates fine-grained policy at request time, and injects short-lived credentials before forwarding.

v0.14.0 lands per-call agent attribution in the audit log — both unverified (X-Warden-On-Behalf-Of header) and verified (RFC 8693 act claim) — so an MCP-style concentrator that authenticates to Warden once and forwards calls for many upstream agents is no longer a black box at audit time. The audit-config story moves from "auto-magic on first unseal" to "declared in HCL, registered before the listener accepts traffic." A new X-Warden-Provider header lets non-agent clients (legacy SDKs, hand-rolled curl) reach a provider mount without rewriting URLs. The CLI gains Vault-style single-dash long flags, a -path flag on every path-taking command, a top-level warden status, and a :debug Docker image variant published alongside :nonroot. Seven small breaking changes — read the Upgrading section before bumping.

Breaking Changes

  • auth enable, provider enable, audit enable take TYPE as a positional, like Vault. The -type flag is gone. Migration: -type=jwt jwt-prod → -path=jwt-prod jwt; -type=aws → aws. The mount path still defaults to TYPE; override with -path=<mount>. In -json mode the payload's type field, if present, must match the TYPE positional or the CLI rejects with a usage error.

  • JWT auth: mode field removed from auth/{mount}/config. mode=jwt|oidc was redundant with the three mutually-exclusive key-source fields (oidc_discovery_url, jwks_url, jwt_validation_pubkeys) — the backend can derive everything it needs from which one is set. Exactly one of those three must now be provided. Existing scripts passing mode=... emit a framework warning but continue to succeed.

  • X-Warden-Role header now overrides any role embedded in the URL path. Previously a role in the URL took priority over the header; now the header wins unconditionally in both routing modes, matching the precedence of the new X-Warden-Provider. The in-tree skills, the Helm chart, and the CLI never produce the URL-role + header-role combination, so the affected surface is likely zero — but any caller relying on URL-role winning over a stray X-Warden-Role must drop the header or align it with the URL.

  • /v1/sys/health and /v1/sys/leader: is_self is renamed to is_leader. Symmetric across both endpoints. Update any external script that grepped for "is_self": against these endpoints.

  • /v1/sys/health: server_time_utc (int64 Unix seconds) is replaced with server_time (RFC3339 string). Human-readable and sortable as text. The old integer field is gone, not aliased.

  • Listener HCL: tls_enabled is replaced by tls_disable (TLS on by default). Drop tls_enabled = true; replace tls_enabled = false with tls_disable = true. Validation now rejects a listener that neither disables TLS nor supplies both tls_cert_file and tls_key_file. Dev mode sets tls_disable = true automatically; the shipped sample HCL, the e2e configs, and the Helm chart's listener template were all updated.

  • Audit auto-default device on first unseal is removed. The Helm chart ships a working audit "file" block by default (see Upgrading); dev mode intentionally ships no audit; bare-binary operators add a one-line HCL block, or bootstrap via API (the broker now fail-opens at zero registered devices so the API path is reachable from a freshly-installed cluster).

New Features

  • Audit log: per-call actor attribution for delegated chains. A new actors array on audit.Auth carries {subject, verified} entries from two sources: unverified subjects via the X-Warden-On-Behalf-Of request header (regex-validated, gated on an authenticated principal so an unauthenticated caller cannot plant an actor), and verified subjects via the standard RFC 8693 §4.1 act claim on JWTs from IdPs that mint act-tokens for delegation. The header is added to every provider strip-list so it never leaks upstream. MCP-style concentrators that fan out per-agent calls under one Warden identity now produce an audit log a security team can actually correlate.

  • X-Warden-Provider header for mount routing. Non-agent clients (legacy SDKs that take a single-string base_url, hand-rolled curl, scripted tools) can now reach a provider mount without rewriting the URL: send the request to /v1/<literal-upstream-api-path> with X-Warden-Provider naming the mount and X-Warden-Role naming the role, and the server synthesises the canonical <mount>/role/<role>/gateway/<api> shape before mount lookup. Multi-segment mount paths (gitlab/prod) are accepted; empty path segments and bare .. are rejected as 400; unknown mounts fall through to the existing 404. Combined with a SigV4 Authorization header the call is refused with 400 (rather than silently invalidating the client signature and surfacing as 401). New api.Client.SetProvider / WithProvider helpers mirror the existing role/namespace pattern.

  • warden status subcommand, plus cluster-aware /v1/sys/health. A new top-level CLI command that wraps /v1/sys/health and reports init/seal/standby/leader/active-since/version in one round-trip — exit code 0 on a healthy node (active or standby), 7 on a transport error, 10 when sealed-or-uninit, scriptable for operator playbooks. The endpoint grew ha_enabled, is_leader, leader_address, active_time (RFC3339, only set on the active leader), and version (server build) so external tooling no longer needs three round-trips to assemble a status view. Leader lookup is gated on !sealed && ha_enabled, so a sealed HA node no longer logs an error on every k8s probe.

  • JWT static public keys (jwt_validation_pubkeys) now work. The field has lived in the config schema for several releases but the backend returned "not yet implemented" at runtime. PEM-encoded RSA and ECDSA keys now validate JWT signatures against a static keyset — no JWKS endpoint required, suitable for air-gapped clusters, CI/CD pipelines, and fixed-issuer workloads where neither OIDC discovery nor a reachable JWKS endpoint is available. jwks_url and jwt_validation_pubkeys are mutually exclusive within a single mount.

  • Audit devices declared in the HCL server config. A new audit "TYPE" "PATH" { description = "..." options = { ... } } block registers devices before the API listener accepts traffic — a misconfigured sink (unwritable path, missing parent directory, permission denied) is a hard startup error, not a half-initialized cluster that takes requests and drops audit. Declarative (HCL) and imperative (API-enabled) devices coexist at different paths; an HCL block colliding with an API-enabled path refuses to start, and declarative devices cannot be modified or deleted via sys/audit/{path} (operators edit HCL and restart instead). The reconciler on every unseal preserves each entry's accessor and HMAC salt across restarts so audit-log HMACs stay stable. GET /v1/sys/audit responses now carry a declarative boolean so operators can tell the two origins apart.

  • :debug Docker image variant published alongside :nonroot. Same multi-arch binary on gcr.io/distroless/static-debian12:debug-nonroot instead of the production distroless base — so operators can kubectl exec -- sh into a pod and inspect /config, /app, and the filesystem when something goes wrong. Swap image.tag=v0.14.0-debug for the day and back when done; UID, GID, entrypoint, exposed port, and the /config mount contract are identical to the production image.

  • CLI accepts Vault-style single-dash long flags. Cobra/pflag is POSIX-strict, so -format=json would otherwise parse as -f taking value ormat=json and fail. A new normalizer rewrites single-dash long-flag tokens to double-dash before flag parsing; short flags, --, and genuine typos pass through unchanged so error paths still fire. All operator-facing docs (READMEs, CHANGELOG, tutorial scripts, provider docs, cobra help text) switched to the single-dash convention to match Vault muscle memory.

  • -path long flag on every path-taking CLI command. audit/auth/provider enable|disable|read and the basic read|write|delete|list commands all accept the mount path as -path=<mount> as an alternative to the trailing positional, so vault secrets enable -path=foo aws ports verbatim. Existing positional usage is unchanged.

  • Server config: warn-and-ignore unknown HCL keys. A typo or stale field in warden.hcl (think cluster_name, ui, default_lease_ttl) used to crash startup with an opaque gohcl error. The loader now walks the body against the schemas derived from Config{} and its nested blocks, deletes any unrecognized attribute or block, and emits one [WARN] config: ignoring unknown attribute "X" at <file:line:col> line on stderr per removal so the typo stays visible. Known fields still parse and validate exactly as before — this is a one-way relaxation, not a schema loosening.

Bug Fixes

  • Audit broker is fail-open at zero registered devices. Previously auditManager.LogRequest/LogResponse returned (continue=false, nil) when no devices were registered, causing the request handler to block every non-streaming request with HTTP 500 — including sys/audit/{path}, so a freshly-installed cluster with no audit had no way to bootstrap one via the API. The broker now short-circuits to (true, nil) when zero devices are registered. Once any device...
Read more

Warden v0.13.2

Choose a tag to compare

@github-actions github-actions released this 21 May 11:47
52adf81

Changelog

  • 8ebc17b ci(deps): bump azure/setup-helm from 4 to 5 (#212)
  • 644194a deps(deps): bump github.com/aws/aws-sdk-go-v2/service/redshift (#213)
  • 02820c9 docs(k8s): add Cleanup section to the Kubernetes deployment guide (#211)
  • 8907574 docs(skills): vault/bao CLI choice, Slack canvas worked example, discovery error recovery (#217)
  • 16ff274 docs(tutorial): rewrite vault-policy-hygiene around discover-and-connect (#218)
  • 62c1ec1 feat(ansible_tower): add agent-facing skill (#219)
  • 299d0d9 feat(atlassian): add agent-facing skill (#220)
  • 47daf8f feat(helm): opt-in cert-manager integration for the TLS listener (#214)
  • 9f32fe0 feat(slack): add agent-facing skill (#215)
  • 749cac6 fix(ci): run all checks on release-tag pushes (#210)
  • 79a7502 fix(cli): send JWT only via Authorization: Bearer, not X-Warden-Token (#216)

Warden v0.13.1

Choose a tag to compare

@github-actions github-actions released this 18 May 12:19
75a627c

Changelog