Time: 3 minutes Prerequisites: Node 20+, a storage backend configured (Bring Your Own Vault)
The broker is a local daemon that mediates credential access for one or more agents. Policy rules gate every resolve: agent ID, credential name, time window, rate limit, optional AIM trust score and capabilities.
For most workflows you don't — vault exec (Tier 2) is enough. Reach for the broker when:
- Multiple long-lived agents need credentials in the same session, and you want one policy to govern all of them
- You need per-agent rate limits (e.g. "scan-bot gets at most 10 resolves per minute on GITHUB_*")
- You want an audit log of every credential access, not just the run
- You plan to wire AIM trust-score policy later
If you're running one short-lived script, skip the daemon — secretless-ai run --only KEY -- cmd is simpler.
Important architectural note before you start:
- Identity Vault (
~/.aim/vault) — whatvault registerandvault execuse. Ed25519 identity-bound, XSalsa20-Poly1305. - SecretStore (your configured backend: local, keychain, 1password, vault, gcp-sm) — what
secret set,run --only, and the broker's/resolveendpoint use.
The broker does not read from the Identity Vault. vault exec does. They're two independent storage paths. Pick the one that fits your use case; most broker users will put credentials in SecretStore.
npx secretless-ai broker start Secretless Broker
Starting credential broker daemon...
Broker is running.
PID: 12345
HTTP port: 19421
Socket: /Users/you/.secretless-ai/broker.sock
AIM: not configured
Policy file: (default)
Press Ctrl+C to stop.
The daemon is foreground by default. In a second terminal:
npx secretless-ai broker status Secretless Broker Status
Status: running
PID: 12345
Uptime: 5s
HTTP port: 19421
Socket: /Users/you/.secretless-ai/broker.sock
AIM: not configured
Policies: 0
Requests: 0
Stop the daemon:
npx secretless-ai broker stopThe broker generates a random bearer token on start and writes it to ~/.secretless-ai/broker.token (mode 0600). Any client resolving a credential must pass it:
TOK=$(cat ~/.secretless-ai/broker.token)
curl -H "Authorization: Bearer $TOK" http://127.0.0.1:19421/statusHTTP routes:
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Health check |
GET |
/status |
Uptime, request count, policy count, AIM state |
POST |
/resolve |
{agentId, credentialName} -> {value} or 403 |
Both the HTTP port and a Unix socket at ~/.secretless-ai/broker.sock accept the same routes. The socket is 0600, same user only.
Default deny. Without a policy, every /resolve returns 403.
Create ~/.secretless-ai/broker-policies.json:
{
"version": 1,
"rules": [
{
"id": "scanner-github-readonly",
"agentSelector": "scan-*",
"credentialSelector": "GITHUB_TOKEN",
"constraints": {
"rateLimit": { "maxPerMinute": 30 },
"timeWindow": { "start": "09:00", "end": "18:00" }
},
"effect": "allow"
}
]
}Supported constraints:
timeWindow— 24h, supports overnight (e.g.22:00–06:00)rateLimit.maxPerMinute— per agent + credential pairminTrustScore— AIM-only, see belowrequireCapability— AIM-only, see belowscopeCheck— deny if credential permissions expanded since last baseline
Reload by restarting the daemon. Or pass --policy-file <path> on start to use a non-default location.
If you run an AIM server and want trust-score / capability policy constraints to work:
export SECRETLESS_AIM_TOKEN=<bearer-token>
npx secretless-ai broker start --aim-url https://aim.oa2a.orgOr on the flag:
npx secretless-ai broker start --aim-url https://aim.oa2a.org --aim-token <bearer-token>Prefer the env var — CLI tokens are visible in ps aux.
Check AIM wiring:
npx secretless-ai broker status AIM: configured (reachable)
Three possible AIM states:
| Status | Meaning |
|---|---|
not configured |
--aim-url was not passed |
configured (reachable) |
AIM responded to /health at startup |
configured (unreachable) |
--aim-url set but AIM did not respond — trust-score / capability constraints will never be satisfied until AIM comes back |
If AIM returns 4xx on agent identity lookups (typically 401 from a missing or bad token), an aim_auth_error event lands in ~/.secretless-ai/broker-audit.log with the status and URL. Check the audit log if your trust-score rules appear to never match.
Every resolve, every auth failure, every startup and shutdown — ~/.secretless-ai/broker-audit.log, one JSON event per line.
tail -f ~/.secretless-ai/broker-audit.log{"timestamp":"...","eventType":"resolve","agentId":"scan-01","credentialName":"GITHUB_TOKEN","policyId":"scanner-github-readonly","result":"allowed","reason":"Allowed by rule ...","latencyMs":3}
{"timestamp":"...","eventType":"auth","agentId":"unknown","credentialName":"","policyId":"","result":"denied","reason":"Unauthorized access attempt from 127.0.0.1","latencyMs":0}An agent resolving a credential from the broker makes a POST with its agent ID and the credential name. Example in shell:
TOK=$(cat ~/.secretless-ai/broker.token)
curl -s -X POST \
-H "Authorization: Bearer $TOK" \
-H "Content-Type: application/json" \
-d '{"agentId":"scan-01","credentialName":"GITHUB_TOKEN"}' \
http://127.0.0.1:19421/resolve{"value":"ghp_..."}Denied requests return HTTP 403 with a reason:
{"error":"Access denied","reason":"No matching allow rule (default deny)"}npx secretless-ai broker stopThe daemon removes the socket, pid file, and auth token on shutdown.
broker statusshowsPolicies: 0but I have a policy file — confirm the file is at~/.secretless-ai/broker-policies.jsonor you passed--policy-file. Restart the daemon after edits.broker statusnow pulls live values from the running server, so a zero means the daemon really loaded zero rules.- All
/resolvecalls return 403 "default deny" — write anallowrule that matches youragentIdandcredentialSelector. Check the audit log for the exact input. - AIM shows
configured (unreachable)— probe the URL yourself:curl -s -o /dev/null -w "%{http_code}\n" <aim-url>/health. Common causes: wrong URL, network segmentation, AIM not deployed to that host.
- Bring Your Own Vault -- Connect the broker to HashiCorp Vault or 1Password
- Team Setup -- Shared backend + CI/CD
- Protect My Credentials -- The simpler flow when you don't need the daemon