trustless — Credential Broker CLI for AI Agents
Go License Website CI Go Report Card
Overview
trustless is a credential broker CLI that decouples AI agents from the secrets they use. Instead of agents holding plaintext credentials in their context window (where prompt injection or leakage can expose them), trustless acts as an intermediary: agents reference credentials by name, and the broker resolves them at the transport or process layer — the agent never holds plaintext values.
The name reflects the architecture: you don't need to trust the agent with secrets because the agent structurally cannot access them.
Why trustless?
Traditional AI agent setups give the agent direct access to credentials — either as environment variables, config files, or inline in prompts. This means a single prompt injection or overly-verbose debug output can leak secrets to an attacker or an untrusted third-party API.
trustless inverts the model: the agent says "use GITHUB_TOKEN", the broker resolves the value, and the agent only ever sees the API response — never the key itself.
Traditional: agent → sees key → uses key → key is in context → prompt injection leaks it
↑ agent is a trusted principal
trustless: agent → says "use GITHUB_TOKEN" → broker resolves → agent gets API response
↑ agent is an untrusted caller, broker is the authority
How it compares
The "keep secrets away from AI agents" space has several tools. trustless is the only one that combines subprocess injection with output sanitization, integrated DLP (outbound request redaction), and existing password-manager backends (pass / Bitwarden) in a single static binary — one vetted Go module, no runtime dependencies.
| trustless | tene | vaulty | agent-secrets | secretless-ai | enject | |
|---|---|---|---|---|---|---|
| Injection method | subprocess env + HTTP proxy | subprocess env | HTTP proxy + MCP | subprocess env (lease) | env + shell hook | subprocess env |
| Existing backend (pass/Bitwarden) | ✅ | ❌ own vault | ❌ own vault | ❌ own vault | ❌ keychain/1Password | ❌ own vault |
| Output sanitization | ✅ run + proxy | ❌ | ✅ | ❌ | ❌ | ❌ |
| DLP (outbound redaction) | ✅ integrated | ❌ | partial (request) | ❌ | ❌ | ❌ |
| OAuth token management | ✅ (google/lark, refresh) | ❌ | ❌ | ❌ | ❌ | ❌ |
| Audit log (structured) | ✅ | ❌ | ✅ file | ✅ append-only | ❌ | ❌ |
| Agent skills bundled | ✅ 4 | ✅ context files | MCP only | ✅ skill | ✅ rules | ❌ |
| Dependencies | 1 (go-toml/v2, pure Go) | Go static | Go static | Go static | npm/npx | Go static |
| License | MIT | MIT | MIT | MIT | Apache-2.0 | MIT |
tene / vaulty / agent-secrets / secretless-ai / enject are compared as of Aug 2026.
The practical difference: tools like tene or enject solve "agent shouldn't read
.env". trustless also solves "agent shouldn't see the key when it runs a
command" (output sanitization) and "secrets shouldn't leak out of the machine
when the agent calls an API" (DLP). If you already use pass or Bitwarden, there
is no migration — trustless reads your existing store.
Installation
One-liner (Linux / macOS)
curl -fsSL https://raw.githubusercontent.com/ikkun1222/trustless/main/scripts/install.sh | sh
To install without the setup prompt (for CI/Docker):
curl -fsSL https://raw.githubusercontent.com/ikkun1222/trustless/main/scripts/install.sh | sh -s -- --minimal
To upgrade an existing installation:
curl -fsSL https://raw.githubusercontent.com/ikkun1222/trustless/main/scripts/install.sh | sh -s -- --update
From source (Go 1.26+)
git clone https://github.com/ikkun1222/trustless
cd trustless && go build -o trustless .
Or install directly:
go install github.com/ikkun1222/trustless@latest
Note:
go buildwithout ldflags reportstrustless dev. Release binaries embed the version via-ldflags "-X main.version=vX.Y.Z"(the release pipeline andmake build VERSION=vX.Y.Zdo this automatically).
Verifying release artifacts
Starting with v0.5.1, every release artifact (binaries, SHA256SUMS, SBOM) is signed with cosign keyless (OIDC, no key management). Verify before use:
# 1. Download the artifact + its signature and certificate
gh release download v0.5.1 -p 'trustless-linux-amd64*' -p 'SHA256SUMS*'
# 2. Verify the binary matches the published checksum
sha256sum -c <(grep 'trustless-linux-amd64' SHA256SUMS)
# 3. Verify the cosign signature (keyless: identity = GitHub Actions of ikkun1222/trustless)
cosign verify-blob --certificate trustless-linux-amd64.pem \
--signature trustless-linux-amd64.sig \
--certificate-identity-regexp '^https://github.com/ikkun1222/trustless/.github/workflows/release.yml@refs/tags/v' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
trustless-linux-amd64
Each release also ships an SPDX SBOM (trustless.sbom.spdx.json) generated
with syft for supply-chain transparency.
Prerequisites
- Go 1.26+ for building from source
pass(the standard Unix password manager) +gpg— the default credential backend- Environment variables only (no
passneeded) when usingbackend = "env" bw(Bitwarden CLI) required only when usingbackend = "bitwarden"(cloud store)
Backends are swappable via trustless config set backend <name>: pass (default), env, bitwarden. See docs/bitwarden-backend-design.md for the Bitwarden backend design.
Quick Start
# First-time setup (GPG key, pass store, .env migration, agent config)
trustless setup
# Check system health
trustless doctor
# List credentials from the pass store
trustless secret list
# Run a command with a credential injected (output is sanitized)
trustless run -s iria/api/xai -- curl -s https://api.x.ai/v1/models
# Start the credential proxy
trustless proxy start --port 8080
Agent Plugins 1.0.0
This repository is also packaged as an Agent Plugins 1.0.0 plugin — the open, vendor-neutral standard for packaging Agent Skills into portable plugins. A single plugin.json manifest lets compatible clients discover the trustless-usage skill from the fixed location:
trustless/
├── plugin.json # Agent Plugins 1.0.0 manifest ($schema + name required)
├── skills/
│ └── trustless-usage/
│ └── SKILL.md # Agent Skills spec-compliant (agentskills: true)
├── schemas/
│ └── plugin.schema.json # Vendored official 1.0.0 plugin JSON Schema
└── scripts/validate-plugin.py # Packaging validation (wired into make check)
The plugin ships the trustless-usage skill, which teaches agents the credential conventions (trustless run for injection, trustless secret set for registration, no plaintext persistence).
Agent Plugins–compatible clients at launch: ChatGPT / Codex, Cursor, GitHub Copilot, Kiro, VS Code. Install by cloning or vendoring the repo and pointing the client at the plugin root (the directory containing plugin.json).
Validation: make validate-plugin (or make check) verifies the manifest against the closed schema and the skills/ discovery layout using the vendored official schema.
Commands Reference
trustless secret — Credential Store Operations
| Subcommand | Description | Example |
|---|---|---|
list | List all available credential keys | trustless secret list |
get <key> | Retrieve a credential value (JSON output) | trustless secret get github_token |
set <key> [value] | Store a new credential (wraps pass insert) | trustless secret set openai_key sk-... |
get outputs JSON by default:
{"key": "github_token", "value": "ghp_..."}
trustless oauth — OAuth Credential Management
Manage OAuth credentials (RFC 8628 device flow + refresh grant) for providers like Google and Lark. trustless oauth login runs the device authorization flow and stores the resulting tokens as a compact single-line JSON entry (type=oauth) in the credential backend. The entry is then resolved like any other credential — trustless run -s <key> / trustless proxy return a fresh access token, with automatic refresh on expiry.
| Subcommand | Description | Example |
|---|---|---|
login <provider> <key> | Device flow login; stores the OAuth entry | trustless oauth login google api/google |
refresh <key> | Force refresh the OAuth entry (ignore cache) | trustless oauth refresh api/google |
status <key> | Show entry status (valid / expired / reauth_required) | trustless oauth status api/google |
providers | List configured providers | trustless oauth providers |
login prints the verification URL to stdout, then polls until the user approves:
$ trustless oauth login google api/google
https://oauth2.googleapis.com/device/code?user_code=ABCD-1234 # open this in a browser
{"key":"api/google","provider":"google","expires_at":"2026-08-13T12:00:00Z"}
refresh force-refreshes the access token without waiting for expiry; the access token value is never printed. status reports valid when the token is still fresh, and reauth_required when the refresh token is revoked (invalid_grant):
$ trustless oauth status api/google
{"key":"api/google","provider":"google","expires_at":"...","status":"valid"}
Configuration ([oauth.providers]): define a provider with its token/device endpoints and credentials. The built-in google and lark definitions ship with the endpoints below — you only need to fill in client_id / client_secret (register the app in the provider's developer console) and any additional scopes:
[oauth.providers.google]
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
scopes = ["https://www.googleapis.com/auth/gmail.readonly"]
[oauth.providers.lark]
client_id = "YOUR_CLIENT_ID"
client_secret = "YOUR_CLIENT_SECRET"
# scopes 未設定時は既定の offline_access が使われる(refresh token 取得に必須)
| Provider | Device authorization endpoint | Token endpoint | Device auth | Token request |
|---|---|---|---|---|
google | https://oauth2.googleapis.com/device/code | https://oauth2.googleapis.com/token | body (client_secret in form body) | form |
lark | https://accounts.larksuite.com/oauth/v1/device_authorization | https://open.larksuite.com/open-apis/authen/v2/oauth/token | basic (Authorization header) | json (Lark code-style response) |
client_id / client_secret are registered in the provider's developer console (Google Cloud Console / Lark Open Platform) — never commit them. OAuth entries in the backend store only the tokens, never the client credentials.
trustless audit — Structured Audit Log
All events (proxy injection/deny, run spawn, DLP redaction, OAuth refresh/failure/reauth) are recorded as JSONL. No token or secret values ever appear in events — only key names, hosts, verdicts, and small details.
| Sink | Where | Default |
|---|---|---|
journald | serve (stdout JSONL → systemd journald) | serve |
file | append-only ~/.local/state/trustless/audit.jsonl (0600, SIGHUP-reopen for logrotate) | run / proxy / oauth |
off | discard | — |
[audit]
sink = "file" # "journald" | "file" | "off"(未設定はコマンド別デフォルト)
file = "~/.local/state/trustless/audit.jsonl"
buffer = 1024
$ journalctl --user -u trustless | grep '"event"'
{"ts":"...","event":"proxy.inject","key":"edinet","host":"api.edinet-fsa.go.jp","verdict":"inject","detail":"header=Ocp-Apim-Subscription-Key"}
{"ts":"...","event":"oauth.refresh","key":"iria/api/lark-oauth","verdict":"refresh","detail":"provider=lark"}
Events: proxy.inject / proxy.deny / run.spawn / dlp.redact / oauth.refresh / oauth.fail / oauth.reauth_required.
Notes:
- Access tokens are cached in memory (validity minus a 60s safety margin); refresh happens automatically on
Resolvewhen expired. - When a provider rotates the refresh token (Lark), the updated entry is written back with a CAS guard so a concurrent writer is never overwritten.
invalid_grant(revoked refresh token) is not retried — re-runtrustless oauth loginto re-authenticate.
trustless run — Subprocess Credential Injection (Core Command)
Run a command with one or more credentials injected as environment variables. The injected values are never returned to the caller — only the subprocess stdout/stderr, and any matching credential patterns are redacted.
trustless run -s iria/api/xai -- curl -s https://api.x.ai/v1/models
trustless run -s GITHUB_TOKEN -s OPENAI_KEY -- gh pr list
How it works:
- trustless resolves each
-skey from the backend - Spawns the subprocess with the credential value set as an environment variable
- The environment variable name is derived from the last path segment of the key, converted to
UPPER_SNAKE_CASE(e.g.iria/api/xai→XAI) - Forwards stdin to the subprocess and streams stdout/stderr
- Scans output (line by line) for credential patterns and redacts matches with
[REDACTED] - Returns sanitized output to the caller
Security features:
--scan-args(default:true): Before spawning the subprocess, all command arguments are scanned for credential patterns and injected values. If detected, execution is blocked with exit code 3 (fail closed). This prevents the agent from accidentally exposing credential values in CLI arguments likecurl -H "Authorization: Bearer ***".--sanitize(default:true): Scans and redacts credential patterns from subprocess output.- Policy engine: Command-level access control (see configuration section).
stdio protocols (ACP / MCP / LSP): stdin is always forwarded to the child (fixed 2026-07-31), and output is sanitized line-by-line in real time so long-running processes (ACP servers, gateways) flush output instead of buffering until exit. For interactive JSON-RPC stdio servers, sanitizing the stream can corrupt protocol messages — pass --sanitize=false for those (e.g. hermes acp).
| Flag | Description |
|---|---|
-s, --secret <key> | Credential key to inject (repeatable, format: KEY or KEY:ENVNAME) |
--sanitize | Enable output scanning/redaction (default: on) |
--sanitize-policy <file> | Custom redaction patterns file |
--scan-args | Scan command arguments for credential patterns before spawning (default: on) |
--json | Output as JSON {"exit_code": N, "stdout": "...", "stderr": "..."} |
--timeout <duration> | Subprocess timeout (default: 5m) |
trustless proxy — HTTP Forward Proxy with Credential Injection
Start a local HTTP forward proxy that injects credentials into requests based on the destination host. The agent sends plain requests — no placeholder syntax, no knowledge of the key.
trustless proxy start --port 8080
trustless proxy start --port 8080 --mitm # HTTPS interception mode
Configure your agent to use the proxy:
export HTTPS_PROXY=http://127.0.0.1:8080
Injection rules (config [proxy.rules]): map a host to a credential injected as a header or query parameter. The header/parameter is injected only when absent; unresolved keys fail open (no injection).
[proxy.rules]
# Header injection (e.g. LLM APIs, EDINET)
"api.x.ai" = { header = "Authorization", key = "xai", prefix = "Bearer " }
"api.edinet-fsa.go.jp" = { header = "Ocp-Apim-Subscription-Key", key = "edinet" }
# Query parameter injection (e.g. e-Stat, Alpha Vantage)
"statdb.nstac.go.jp" = { query = "appid", key = "estat" }
"www.alphavantage.co" = { query = "apikey", key = "alphavantage/mcp-key" }
header/query: injection target (exactly one per rule)key: credential key (resolution: lowercase → pass, fallbackiria/api/<key>)prefix/suffix: header value wrapping (e.g.Bearerprefix)
Egress allowlist (config proxy.allowlist): when set, only listed hosts are permitted through the proxy; all other requests are rejected with 403 Forbidden. Empty/absent = all hosts allowed.
[proxy]
allowlist = ["api.x.ai", "api.edinet-fsa.go.jp"]
Hot reload (SIGHUP): rule/allowlist changes and credential rotations take effect without a restart.
systemctl --user reload trustless-proxy # systemd: sends SIGHUP
# or manually:
# kill -HUP $(pgrep -f "trustless proxy start")
Reload re-reads config.toml (rules/allowlist) and refreshes the backend cache (bitwarden), so newly rotated keys are visible immediately instead of waiting for the 24h cache TTL.
MITM mode (--mitm):
-
Enables HTTPS interception for credential injection into encrypted requests
-
Auto-generates a root CA certificate at
~/.config/trustless/trustless-ca.{crt,key}on first use -
Leaf certificates are generated per-hostname (24h validity, ECDSA P-256)
-
Install the CA certificate system-wide for seamless HTTPS interception:
sudo cp ~/.config/trustless/trustless-ca.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates
| Flag | Description |
|---|---|
--port <n> | Listen port (default: 8080) |
--unix-socket <path> | Listen on Unix socket (file permission control) |
--mitm | Enable MITM mode (intercept HTTPS for credential injection) |
HTTPS CONNECT tunneling is supported. Without --mitm, CONNECT requests pass through without modification. With --mitm, the connection is intercepted and host-based credential injection applies.
trustless dlp — Outbound DLP Reverse Proxy (former dlp-proxy)
trustless dlp is the successor subcommand for the former github.com/ikkun1222/dlp-proxy: an outbound DLP reverse proxy that masks known secrets in LLM API request bodies with <redacted> before they leave the host.
trustless dlp start -config ~/.config/dlp-proxy/config.json # start DLP reverse proxy (default 127.0.0.1:8787)
trustless dlp scrub-db <db-path> [--apply] [--backup] # scan / scrub secrets in a SQLite DB
trustless dlp scrub-text <path> [--apply] # scan / scrub secrets in text files / dirs
- Config schema is the same JSON as dlp-proxy:
listen/min_secret_len/secrets_source(pass|bitwarden, default pass) /secrets_refresh_interval(required, e.g."10m") /routes(prefix → upstream URL) - Secrets load through the shared backend (
backend.Values); the former bitwardenloader/passstore are gone - fail-closed: startup aborts if secrets cannot be loaded; a failed reload keeps the previous set and logs a warning (fail-safe)
- Hot reload: periodic refresh per
secrets_refresh_interval+ immediate reload on SIGHUP - Two-layer redaction (2026-08-14): Layer 1 = known-value substring scan (zero false positives); Layer 2 = gitleaks-compatible pattern rules (API key formats, JWT, private keys, etc.) with keyword pre-filter → RE2 regex → Shannon entropy threshold (default 3.5, per-rule override). Pattern rules are bundled in
internal/dlp/redact/rules.toml(40 rules,//go:embed), derived from gitleaks (MIT, Copyright (c) 2019 Zachary Rice — seeLICENSE.gitleaks/NOTICE) - New config fields:
rules_file(path to an external gitleaks-compatible rules TOML; empty = bundled rules) /pattern_mode("mask"= redact pattern matches,"log"= detect only, body unchanged, audit event withdetail="patterns=hit&mode=log") /pattern_disabled(list of rule IDs to disable, e.g.["generic-api-key"]to silence a false-positive rule) - Hot reload (serve):
trustless servere-appliespattern_mode/pattern_disabled/rules_fileon every reload (SIGHUP viakill -HUP $(pgrep -f 'trustless serve')or the 10-minute periodic refresh) — config is re-read, the pattern set is atomically swapped (PatternSet.Replace), failures keep the previous state (fail-safe). Standalonetrustless dlp startreads them at startup only - The former dlp-proxy repository is frozen (2026-08-13);
trustless dlpis its replacement
Scrub commands clean up secrets that already persisted on disk — agent session DBs, logs, dumps — using the same two-layer redaction as the live proxy:
trustless dlp scrub-db ~/.local/state/hermes/sessions.db # dry-run: scan only
trustless dlp scrub-db ~/.local/state/hermes/sessions.db --apply # write changes
trustless dlp scrub-db ~/.local/state/hermes/sessions.db --apply --backup # keep a .bak copy first
trustless dlp scrub-text ~/.hermes/sessions --apply # scan/scrub text files & dirs
- Default is dry-run: both commands scan and print per-table/per-file hit counts without writing. Add
--applyto actually scrub;scrub-dbadditionally accepts--backup(copy DB to<db>.bakbefore writing) and--min-len(minimum secret length, default 8). scrub-dboperates on SQLite databases: Layer 1 known-value replacement + Layer 2 pattern masking, then rebuilds FTS virtual tables and runsVACUUMso no physical remnants survive in the file (verified by tests).scrub-textwalks a file or directory tree (agentsessions/, logs, dumps) with the same two-layer redaction.- Both load secrets through the DLP config's
secrets_source(pass / bitwarden) and honorpattern_mode—"log"counts hits without masking,"mask"redacts in place.
trustless setup — First-Time Setup Wizard
Interactive wizard that automates the full first-time setup:
trustless setup
4-step flow:
| Step | Action | Auto-detection |
|---|---|---|
| [1/4] GPG Key | Detect existing key or batch-create RSA 3072 (no passphrase, 5y expiry) | Scans gpg --list-secret-keys |
| [2/4] pass Store | Initialize pass store, git init | Checks pass availability |
| [3/4] .env Import | Scan directories for .env files, parse KEY=VALUE, import to pass, backup originals | Walks --import-dir paths (default: .) |
| [4/4] Agent Integration | Detect AI coding agents and install trustless-usage SKILL.md into their skill directory (upon confirmation) | Config file existence + grep for trustless references |
Skill installation paths per agent:
| Agent | Skill directory |
|---|---|
| OpenCode | ~/.config/opencode/skills/trustless-usage/ |
| Claude Code | ~/.claude/skills/trustless-usage/ |
| Codex | ~/.codex/skills/trustless-usage/ |
| Hermes | ~/.hermes/skills/credential-management/trustless-usage/ |
The installed skill teaches the AI agent the credential conventions: use trustless run for injection, trustless secret set for registration, and never store plaintext credentials.
Options:
| Flag | Description |
|---|---|
--non-interactive | Run in non-interactive mode (safe defaults, no prompts, no file removal) |
--import-dir <dir> | Directory to scan for .env files (repeatable, default: .) |
Agent detection currently supports: OpenCode, Claude Code, Codex, Hermes.
trustless doctor — System Health Check
Diagnostic tool that validates the entire trustless setup:
trustless doctor # Human-readable output
trustless doctor --json # Structured JSON for cron/SIEM
trustless doctor --fix # Auto-resolve detected issues (stub)
Health checks performed: GPG key validity, pass store health, gpg-agent status, .env file security scan, agent integration status, MITM CA certificate installation.
trustless config — Tool Configuration
| Subcommand | Description |
|---|---|
init | Create default config at ~/.config/trustless/config.toml |
show | Print current configuration |
set <key> <value> | Update a configuration value |
Config keys:
| Key | Description | Default |
|---|---|---|
backend | Credential backend (pass, env, bitwarden) | pass |
output | Default output mode | json |
run_defaults.sanitize | Enable sanitization by default | true |
run_defaults.timeout | Default subprocess timeout | 5m |
proxy.port | Default proxy port | 8080 |
policy.default.denied_commands | Commands blocked globally (e.g., sh,bash) | (empty) |
Config file location: ~/.config/trustless/config.toml (overridable via TRUSTLESS_CONFIG env var)
backend = "pass"
output = "json"
run_defaults = { sanitize = true, timeout = "5m" }
[proxy]
port = 8080
[sanitize]
patterns = [
"(sk_live|sk_test)_[A-Za-z0-9]+",
"(ghp|gho|ghu|ghs)_[A-Za-z0-9_]+",
"Bearer [A-Za-z0-9._-]+",
]
[policy.default]
denied_commands = ["sh", "bash", "zsh"]
[[policy.overrides]]
secret_key = "iria/api/xai"
denied_commands = ["curl"]
trustless completion — Shell Completion
Generate shell completion scripts for bash, zsh, or fish:
trustless completion bash > /etc/bash_completion.d/trustless
trustless completion zsh > /usr/local/share/zsh/site-functions/_trustless
trustless completion fish > ~/.config/fish/completions/trustless.fish
trustless version — Version Information
trustless version
Architecture
┌─────────────────────────────────────────────────────────┐
│ AI Agent (Hermes / LLM) │
│ "run psql with DATABASE_URL" │
└────────────────────┬────────────────────────────────────┘
│ CLI / HTTP
▼
┌─────────────────────────────────────────────────────────┐
│ trustless CLI │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ secret │ │ run │ │ proxy │ │
│ │ (list/ │ │ (subproc │ │ (HTTP │ │
│ │ get/set)│ │ inject) │ │ proxy + │ │
│ └────┬─────┘ └────┬─────┘ │ MITM) │ │
│ │ │ └─────┬─────┘ │
│ │ │ │ │
│ ┌────┴─────────────┴──────────────┴─────┐ │
│ │ Backend Interface │ │
│ │ (pass / env / bitwarden — swappable) │ │
│ └─────────────────────┬──────────────────┘ │
└────────────────────────┼────────────────────────────────┘
│
┌─────────────┴─────────────┐
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ pass store │ │ Target API │
│ (GPG + pass) │ │ / Service │
└──────────────┘ └──────────────┘
Backend Abstraction
The credential resolver is abstracted behind a simple interface:
type Backend interface {
Resolve(ctx context.Context, key string) (string, error)
List(ctx context.Context) ([]Entry, error)
}
Implemented backends:
pass(default) — wrapspass show <key>, reads first line as secretenv— reads from environment variables viaos.Getenv()(for CI/CD, containers)bitwarden— wraps thebwCLI (bw list items), resolves secureNotefields[value]/ login passwords / notes first line. Session key is passed viaBW_SESSIONenv (never argv). Unlock viatrustless bw-unlock(session key saved to~/.config/trustless/bw-session, 0600). Fails closed on invalid session. Details: docs/bitwarden-backend-design.md
Configure via trustless config set backend <name>.
Security Model
What trustless guarantees
-
Credential values never enter the LLM context window
run: credentials are set on the subprocess environment, output is scanned and redacted before being returnedproxy: credentials are substituted inside the proxy process; the agent sees only the API response- Direct
getoutputs the value but requires explicit invocation (not available to the agent in normal workflows)
-
Command argument scanning (
--scan-args)- Before spawning a subprocess, all command arguments are scanned for credential patterns and injected values
- If detected, execution is blocked with exit code 3 (fail closed)
- Prevents the agent from accidentally exposing credential values in CLI arguments
-
Policy engine — command-level access control
policy.default.denied_commands: block dangerous commands globally (e.g.,sh,bash)policy.<key>.denied_commands: block specific commands per credential- Fail-closed: policy violation blocks execution with exit code 3
-
Subprocess output sanitization
- Default patterns match common credential formats: GitHub tokens, OpenAI keys, xAI keys, AWS keys, Bearer tokens, and generic patterns
- Injected values are themselves pattern-scanned: if the subprocess echoes the credential, that value is redacted
- Custom patterns via config file or
--sanitize-policy
-
Minimal attack surface
- Proxy listens on
127.0.0.1by default (not exposed to the network) - Unix socket mode available for file permission control
- MITM proxy generates per-hostname ephemeral certificates (24h validity)
- Single static binary, no runtime dependencies (reads your existing
pass+gpgstore; thebitwardenbackend additionally requires thebwCLI)
- Proxy listens on
-
No credential persistence in the broker process
- Credentials are resolved on-demand and released after the subprocess exits
- HTTP proxy holds credentials in memory only during active request processing
What trustless does NOT solve (v1 scope)
- Dynamic/rotating credentials — the pass store is static; rotation is handled externally
- Full audit trail — basic logging only; SIEM export is future work
- Hardware-backed key storage — relies on GPG keyring security
- HTTPS MITM CA trust management — the MITM proxy generates a CA cert; the user must install it in the OS trust store
Configuration
Configuration is stored at ~/.config/trustless/config.toml. Use trustless config init to create the default file, or create it manually.
The config file path can be overridden with the TRUSTLESS_CONFIG environment variable.
For full configuration options, see the design document.
Development
Prerequisites
- Go 1.26+
passCLI +gpg(for testing with a real backend)
Building
git clone https://github.com/ikkun1222/trustless
cd trustless
go build -o trustless .
Testing
go test ./...
Project Structure
├── main.go # CLI entry point & subcommand dispatch
├── plugin.json # Agent Plugins 1.0.0 manifest
├── skills/
│ └── trustless-usage/ # Agent Skills spec-compliant SKILL.md
├── schemas/
│ └── plugin.schema.json # Vendored official Agent Plugins schema
├── internal/
│ ├── backend/
│ │ ├── backend.go # Backend interface + types
│ │ ├── env.go # Environment variable backend
│ │ └── pass.go # Pass CLI backend implementation
│ ├── config/
│ │ └── config.go # TOML config loading/saving (+ policy types)
│ ├── proxy/
│ │ ├── ca.go # MITM CA certificate generation
│ │ ├── command.go # HTTP forward proxy with credential substitution
│ │ └── mitm.go # MITM CONNECT handler (HTTPS interception)
│ ├── run/
│ │ └── command.go # Subprocess credential injection (+ policy check)
│ ├── scanner/
│ │ ├── scanner.go # Pattern-based credential redaction
│ │ └── scanner_test.go # Scanner tests
│ └── secret/
│ └── command.go # Credential store operations
├── scripts/
│ └── validate-plugin.py # Agent Plugins packaging validation
└── docs/
└── design.md # Architecture & design document
Dependencies
trustless builds as a single static binary with no runtime dependencies. The
Go standard library covers nearly everything; the one exception is TOML
parsing, which the standard library doesn't provide. Rather than hand-rolling
a parser (unvetted code is a bigger supply-chain risk than a vetted module),
trustless uses github.com/pelletier/go-toml/v2
— the project's only external module. It's pure Go (no cgo, links statically
with the rest of the binary), actively maintained, and fuzz-tested. The
version is pinned via go.sum, with Dependabot and govulncheck monitoring
it.
github.com/pelletier/go-toml/v2— TOML config file parsing (the only external module)
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Credential not found / invalid args |
| 3 | Subprocess error / policy violation / credential in args |
| 4 | Config error |
License
MIT