CI License: Apache 2.0 1Password — supported Bitwarden — supported
Your AI agents call authenticated APIs without ever holding the credentials.
Postern is a credential-brokering HTTPS proxy. Agents send requests with no API keys (or with harmless placeholders); postern matches the destination host against your rules, fetches the real secret from your 1Password or Bitwarden vault at request time, and injects it on the way out. The agent only ever sees placeholders.
Works with 1Password (Service Accounts) and Bitwarden Secrets Manager — credential providers are pluggable.
Why it matters: an agent that can read a credential is a credential an attacker can exfiltrate through prompt injection or a compromised dependency. Brokering moves the secret out of the agent's reach entirely — the blast radius of a compromised agent no longer includes your API keys.
Your agent makes a normal request through the proxy, with no Authorization
header:
curl -x http://localhost:1701 \ https://api.anthropic.com/v1/messages \ -d '{ "model": "claude-sonnet-4-6", "messages": [ ... ] }' # ↑ no API key anywhere in the agent's environment or request
Postern matches api.anthropic.com, resolves bw://.../op://... from your vault,
injects the key, and forwards the now-authenticated request. The upstream sees a
valid call; the agent never touched the secret. On any resolver error postern
fails closed with a 502 and never contacts the upstream.
The agent points HTTPS_PROXY at postern and trusts its local CA. For each
request, postern matches the destination host against YAML-declared rules,
resolves the matched rule's secret reference from a credential provider, injects
the credential, and forwards the request. The full request lifecycle and trust
boundary are in docs/architecture.md.
The matched rule's secret reference (op://... or bw://...) resolves from the
configured provider; adding a new provider is a single package. See
docs/providers.md.
Status: early development. The proxy works end-to-end, and the release pipeline (checksum-verified binaries, SBOMs, and a signed multi-arch container image) publishes from the first tagged release (
v0.1.0) onward. Linux amd64/arm64 only — macOS and Windows are deferred until there is demand.
Prebuilt binaries and the container image publish from
v0.1.0onward. Until then, build from source.
curl -fsSL https://raw.githubusercontent.com/mmartinez/postern/main/install.sh | shThe script detects your architecture, downloads the release tarball and
checksums.txt, verifies the SHA-256, and installs to /usr/local/bin/postern.
Use sudo for that default location, set POSTERN_INSTALL_DIR to install
elsewhere, or POSTERN_VERSION to pin a release.
The SHA-256 check guards against a corrupted or truncated download, not
against a tampered release: checksums.txt comes from the same source as the
tarball, so an attacker who can rewrite the release rewrites both. For
supply-chain assurance, verify the keyless cosign
signature before trusting a download (or verify the signed container image by
digest):
cosign verify-blob \
--bundle checksums.txt.sigstore.json \
--certificate-identity-regexp '^https://github\.com/mmartinez/postern/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
checksums.txtThe image is ghcr.io/mmartinez/postern (multi-arch linux/amd64 + arm64),
distroless and non-root (uid 65532). The vendor token is delivered as a mounted
secret, never baked into the image or its environment (the CI build asserts this
on every snapshot). See Docker Compose for a runnable
example.
From an installed binary (or ./dist/postern when building from source):
postern ca install # generate a local CA and add it to your trust store postern config init # write a starter ~/.postern/config.yaml postern token set --stdin # store your vault service-account / machine token # edit ~/.postern/config.yaml to add rules for the APIs your agent calls, then: postern server # run the proxy # in the agent's shell, wire HTTPS_PROXY + CA trust in one step: eval "$(postern bootstrap)"
postern config validate checks a config with line-numbered errors, and
postern rules list shows the loaded rules (never the resolved credentials).
Every field is documented in docs/configuration.md.
A minimal config:
credstores: - name: vault provider: 1password # or: bitwarden token: source: env env_var: OP_SERVICE_ACCOUNT_TOKEN proxy: listen: 127.0.0.1:1701 cache_ttl: 5m rules: - host: api.anthropic.com secret_ref: op://Agents/Anthropic/api_key # or: bw://<secret-uuid> inject: type: header name: x-api-key template: "{{ CREDENTIAL }}"
docker-compose.yml in the repo root runs the proxy with the token as a Docker
secret and the CA mounted read-only. It expects two files alongside it:
-
op_token— a0600file containing your vault token. Never commit it. -
config.yaml— point the token source at the mounted secret and bind to all interfaces:token: source: file file: /run/secrets/op_token proxy: listen: 0.0.0.0:1701 cache_ttl: 5m rules: - host: api.anthropic.com secret_ref: op://Agents/Anthropic/api_key inject: type: header name: x-api-key template: "{{ CREDENTIAL }}"
postern's CA is generated once, then read-only for its ~10-year life (the proxy only reads it to sign per-host leaf certs in memory). Bootstrap it once, then start the proxy:
mkdir -p postern-ca export POSTERN_UID="$(id -u)" POSTERN_GID="$(id -g)" # run as your uid so it can read the CA you generate docker compose run --rm bootstrap # one-time: generate the CA into ./postern-ca docker compose up -d # run the proxy
Distribute ./postern-ca/.postern/ca.pem to your agents, point them at the
proxy, and have them trust the CA:
export HTTPS_PROXY=http://localhost:1701 export SSL_CERT_FILE=/path/to/ca.pem # NODE_EXTRA_CA_CERTS for Node-based agents
Rotating the CA (at its ~10-year expiry, or on key compromise) is just
re-running docker compose run --rm bootstrap and redistributing ca.pem.
On a non-container Linux host, deliver the token with systemd's credential store so it never lands in the unit's environment or on disk unencrypted:
[Service] ExecStart=/usr/local/bin/postern server --config /etc/postern/config.yaml LoadCredential=op_token:/etc/postern/op_token
Set token.source: file and token.file: /run/credentials/postern.service/op_token in config.yaml; systemd mounts the
token read-only into the unit's private credentials directory for the lifetime of
the process.
- Architecture — request lifecycle, trust boundary, components.
- Security model — fail-closed semantics, logging, threat model, key handling.
- Configuration — the full YAML reference.
- Providers — the credential-vendor plugin contract (1Password, Bitwarden).
Postern is developed container-first. The host needs only Docker, git, and the devcontainer CLI — no Go toolchain.
git clone https://github.com/mmartinez/postern.git cd postern devcontainer up --workspace-folder . # build the container (once) make shell # drop into it make build # produces dist/postern make ci # lint + test + vuln + license check (what CI runs)
See CONTRIBUTING.md for the full workflow, conventions, and how the git hooks work.
To report a vulnerability, follow SECURITY.md — please do not open a public issue. The security model (what postern defends, what it does not, and how it handles keys and tokens) is documented in docs/security.md.
1Password is a registered trademark of AgileBits Inc. Bitwarden is a trademark of Bitwarden, Inc. Postern is not affiliated with, endorsed by, or sponsored by AgileBits, 1Password, or Bitwarden.
Postern is licensed under the Apache License 2.0. See LICENSE for details. Bundled third-party dependencies and their licenses are listed in THIRD_PARTY_NOTICES.md (generated in CI).