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.
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
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.
curl -fsSL https://raw.githubusercontent.com/ikkun1222/trustless/main/scripts/install.sh | shTo install without the setup prompt (for CI/Docker):
curl -fsSL https://raw.githubusercontent.com/ikkun1222/trustless/main/scripts/install.sh | sh -s -- --minimalTo upgrade an existing installation:
curl -fsSL https://raw.githubusercontent.com/ikkun1222/trustless/main/scripts/install.sh | sh -s -- --updategit clone https://github.com/ikkun1222/trustless
cd trustless && go build -o trustless .Or install directly:
go install github.com/ikkun1222/trustless@latestNote:
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).
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-amd64Each release also ships an SPDX SBOM (trustless.sbom.spdx.json) generated
with syft for supply-chain transparency.
- 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.
# 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 8080This 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.
| 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_..."}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.
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.
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 listHow 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) |
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 modeConfigure your agent to use the proxy:
export HTTPS_PROXY=http://127.0.0.1:8080Injection 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 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.
Interactive wizard that automates the full first-time setup:
trustless setup4-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.
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.
| 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"]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.fishtrustless version┌─────────────────────────────────────────────────────────┐
│ 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 │
└──────────────┘ └──────────────┘
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>.
-
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
- 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 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.
- Go 1.26+
passCLI +gpg(for testing with a real backend)
git clone https://github.com/ikkun1222/trustless
cd trustless
go build -o trustless .go test ./...├── 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
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)
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error |
| 2 | Credential not found / invalid args |
| 3 | Subprocess error / policy violation / credential in args |
| 4 | Config error |
MIT