Skip to content

Security

Loopback, redaction, no private keys.

Tokens never sit in the TUI, logs, or MCP output. Listeners bind 127.0.0.1. Service-account keys are never created.

Guarantees · Bind rules · Secrets · Identity · On disk


What we guarantee

RuleWhat you see
No tokens on screenTUI, devctl status, and MCP tool results never print access tokens
Redacted envNames matching PASSWORD, SECRET, TOKEN, PRIVATE_KEY, CLIENT_SECRET, API_KEY, CREDENTIAL, ACCESS_KEY, AUTH_KEY → ********
Loopback onlyProxy, token endpoint, and MCP refuse 0.0.0.0, ::, and other non-loopback binds
Argv by defaultShell metacharacters fail validation unless shell: true
No SA keysImpersonation uses IAM Credentials APIs, never a downloaded JSON key
Config is not a secret storeWorking dirs join the repo root. Put secrets in overlays, keychain, or Secret Manager

Extra redaction: secrets.extra_markers and secrets.extra_patterns in .devctl.


Bind rules

Three listeners, same rule:

ListenerAuth at the door
ProxyRoute identity (user ADC or impersonated SA). Logs never include Authorization
Token endpointX-Devctl-Internal-Token + loopback peer only. Returns access_token to that caller
MCPOff by default. Mutating tools need Authorization: Bearer (session token). Copied snippets include it; get_status does not

Host child processes always get DEVCTL_INTERNAL_TOKEN. They only get DEVCTL_TOKEN_URL when proxy.token_endpoint.enabled is turned on (off by default) — never a raw Google token in the environment. Containers get neither value: the loopback token endpoint is not reachable as container loopback, and embedding the internal token in inspectable container metadata would add exposure without providing access. With the token endpoint off, a service that needs its own Google credential (rather than relying on the proxy to inject one on inbound requests) must get it another way, e.g. its own ADC discovery.


Secrets and /reveal

/reveal lasts for this TUI session only. The header says secrets shown so it cannot stay silent.

Proxy log lines include method, path, route, identity, status, duration — never the bearer header.


Identity

User identity and service identity are never swapped. A route or service must declare which one to use. A user ADC token is not substituted for service_account.

Developers need roles/iam.serviceAccountTokenCreator on each target SA (group binding preferred). Doctor reports AVAILABLE / UNAVAILABLE. See Impersonation and Admin setup.


Commands

String commands that contain |, ||, &&, ;, >, >>, <, or & fail config validate unless the service sets shell: true. Prefer argv lists:

yaml
command: [python3, main.py]
shell: false

On disk

Two checkouts do not share a lock. repoID is sha256(canonical repo root) (16 hex chars).

PathMode / note
~/.devctl/state/<repoID>/state.json, devctl.lock, and on Unix devctl.sock. Windows attach uses \\.\pipe\devctl-<repoID>
leftover ~/.devctl/sessions/Migrated once
Stale lock from a dead PIDReplaced
~/.devctl/credentials/Directory 0700, files 0600 (Unix mode bits; Windows uses ACLs). OS keychain holds tokens; the file fallback stores metadata only (no access token). Cache keys are sanitized so they are valid filenames on Windows. Restart remints via ADC
.devctl/config.local.yamlGitignore-friendly overlay — still do not commit secrets

On Unix, the owner-only state directory restricts access to the supervisor RPC socket. Bun's networking API does not currently expose named-pipe DACL configuration on Windows, so devctl cannot promise equivalent current-user-only access control for \\.\pipe\devctl-<repoID>; this is a documented platform limitation rather than enforced parity.

Override the home directory with DEVCTL_HOME.


Threat model (local)

devctl is a localhost orchestrator. It is not a multi-tenant server.

  • Anyone who can reach your user account can reach 127.0.0.1 listeners.
  • MCP is off until you flip it. Treat the copied bearer token like a session secret.
  • /reveal and log export write what you can already see on that machine.
  • Doctor never enables Google APIs or grants IAM.

PageWhy
AuthenticationADC, project source, devctl auth
ImpersonationSA tokens without keys
IAPAudience + identity on each route
ProxyRequest flow and token endpoint
MCPLoopback Streamable HTTP
EnvironmentSource order, keychain, Secret Manager
Security policyHow to report a vulnerability

Released under the MIT License.