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 useLeaf 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