Docs · Governance
Control it. Then prove it.
Two things sit on the broker's egress path: a policy that decides what a pod may reach, and a tamper-evident audit log that records what it did. Both are opt-in and both are secret-free.
Egress policy
A policy is a named set of rules - ~/.config/poddle/policies/<name>.toml - that the broker consults on every request a pod makes. With no policy, a pod may reach anything; add one to lock it down.
| Field | Rule |
|---|---|
allow_upstreams | Hosts the pod may reach. When set, everything else is denied (default-deny). A leading dot - ".example.com" - matches that domain and any subdomain. |
deny_upstreams | Hosts that are always denied. The deny-list wins over the allow-list. |
methods | Per-host allowed HTTP methods, e.g. "api.github.com" = ["GET", "POST"]. A ".suffix" key covers subdomains. (CONNECT tunnels are governed by the host rules alone - their real method is encrypted.) |
egress | What the broker does with secrets in outbound bodies: redact (default) / block / off. |
The broker decides in a fixed order: deny-list wins, then the allow-list (default-deny if it is set), then per-host methods - otherwise the request is allowed. Every decision is written to the audit log.
Write one
A CI policy that can only reach GitHub and the npm registry, blocks the cloud metadata endpoint, and refuses to leak secrets:
# only GitHub + the npm registry, read-mostly
allow_upstreams = ["api.github.com", ".githubusercontent.com", "registry.npmjs.org"]
deny_upstreams = ["169.254.169.254"] # block cloud metadata (SSRF)
egress = "block"
[methods]
"api.github.com" = ["GET", "POST"]Attach it to a pod
Pass --policy, or set policy in a template so every pod built from it inherits the rules:
poddle up api --policy ci --identity work
# or, in .poddle/api.toml:
policy = "ci"Policies resolve like templates: a repo's poddle/policies/ shadows your global ~/.config/poddle/policies/, so a project can pin its own version.
Opt-in, and enforced at the broker
A pod with no policy reaches anything - policy is opt-in. When one is set, the poddled broker enforces it on the wire, so a compromised or prompt-injected agent still can't reach a host the policy denies.
The audit log
The broker is the single writer of a hash-chained log: each event carries the hash of the one before it, so any tampering breaks the chain and is detectable. Events aresecret-free by construction - no request bodies, no resolved credentials, no query-string URLs (which can carry tokens). What gets recorded:
| Event | What it records |
|---|---|
request | A proxied request to a brokered upstream. |
redact / block | A stray secret was scrubbed from egress, or egress was blocked. |
policy.allow / policy.deny | A policy allowed or denied an action. |
handle.issue / handle.revoke | A pod-scoped credential handle was issued or revoked. |
l4.connect | A pod reached a datastore through the L4 broker. |
pod.up / pod.task / pod.move / pod.down | Pod lifecycle. |
mount.refuse | A credential-carrying mount was refused. |
autoscale.grow / autoscale.warn | The autoscaler grew or warned about a pod. |
View & verify
poddle dashboard serves a local, read-only view of the log with a live feed and one-click hash-chain verification. It binds 127.0.0.1 only:
poddle dashboard --open
→ audit dashboard on http://127.0.0.1:7333 · live feed · chain verified ✓Governance is the same everywhere
Policies and audit sit behind one interface, so the file-backed self-hosted setup and the multi-tenant cloud tier share the exact enforcement path - the rules you write locally are the rules that run in production.