Security Model
The binary contains no secrets and grants no authority. Real protection comes from the API server. This page documents the boundary so you can reason about what the CLI does and doesn't defend against.
The boundary
Every request the CLI makes flows through three server-side gates:
- Token validation (Laravel Passport) — rejects any unknown / expired / revoked token.
- Tenant resolution (Stancl Tenancy) — ensures the request runs in the requesting tenant's database context.
- Permission check (Spatie Permissions) — enforces RBAC on every endpoint, same as the web app.
If any gate rejects, the request is over before any business logic runs. The CLI receives the rejection and translates to a typed exit code.
OAuth flow security properties
The default autocom auth login uses OAuth 2.0 authorization-code with PKCE (RFC 7636). Why each piece matters:
| Property | What it defends against |
|---|---|
| Public client (no secret) | Public clients can't keep secrets — anything baked into the binary leaks. PKCE replaces client-secret as the proof-of-identity. |
| PKCE S256 | A network attacker who intercepts the authorization code can't redeem it without the verifier (which only ever lives in the CLI process memory). |
| CSRF state token | A malicious page that tricks the browser into hitting the callback can't poison the flow — the state is a fresh random per login. |
Loopback redirect (127.0.0.1) |
Per RFC 8252 §7.3, loopback IP literals can't be DNS-spoofed. We use 127.0.0.1, never localhost. |
| Single-use callback | The local listener accepts ONE request then shuts down. Stale browser tabs can't replay the code. |
| 5-minute timeout | Idle authorize sessions get cleaned up; the local port becomes available again. |
What's stored where
| Data | Default storage | What protects it |
|---|---|---|
| Access token | OS keychain (autocom-cli service) |
Encrypted by OS — Keychain (macOS), Secret Service (Linux), Credential Manager (Windows) |
| Refresh token | Same | Same |
| Tokens (fallback) | ~/.autocom/credentials (mode 0600) |
Filesystem permissions only — used only when no keychain reachable |
| User name + email | Same as tokens | Same |
| API URL + tenant | ~/.autocom/config.toml (mode 0600) |
Filesystem permissions only |
| PKCE verifier | Process memory only | Never written to disk |
| OAuth client ID | Built into binary at link time | Public information — not a secret |
Build-time hardening
| Layer | What we do |
|---|---|
| Symbol stripping | -ldflags="-s -w" on every release — removes Go's debug metadata, cuts ~30% of binary size, makes string-grep reverse engineering harder |
| Reproducible paths | -trimpath strips local filesystem paths from the binary so two builds of the same source produce identical bytes |
| Identifier obfuscation | make build-garbled opt-in (uses garble) — mangles identifier names. Off by default because it breaks reflection and panics show garbled stacks |
| Distribution integrity | SHA256 sums published with every release; signed Sigstore Cosign keyless attestation planned |
What we don't do
Things people sometimes ask about that we deliberately don't ship:
- "Encrypted binary at rest" — the decryption key has to ship with the binary. Anyone who can read the binary can extract the key. It's cosmetic. We skip the snake oil.
- License keys baked into the binary — same problem, same answer.
- Telemetry that includes content — the planned version-check ping is opt-out via
no_telemetry = trueand only sends(version, os, arch). Never any tenant data, command names, or arguments. - Local-only authority — the CLI never bypasses the server. There's no
autocom orders force-cancel --offlineor similar.
Threat model
What the CLI defends against, and what it explicitly doesn't:
| Threat | Defense |
|---|---|
| Network MitM intercepting tokens | TLS to the API; OAuth code can't be redeemed without PKCE verifier |
| Stolen binary | Binary is public — nothing to steal except your time reading the code |
| Stolen laptop with tokens in keychain | Keychain is unlocked at user-login; if the laptop is unlocked, the attacker is you. Use FileVault / LUKS / BitLocker. |
| Compromised release artifact | Verify SHA256SUMS against the GitLab Release entry before installing |
| Compromised npm/PyPI dependency (n/a here) | We have zero JS/Python deps. Go modules are vendored into go.sum; Renovate would alert on changes |
| Phishing / fake authorize page | The OAuth flow is bound to api_url you typed — if that URL is real, the page you see is real. Confirm the cert chain. |
| Logged shell history with PAT | Use --token-stdin or auth login (OAuth) instead of --with-token <PAT> |
ls /tmp/cli-state finds your token |
Don't put AUTOCOM_CREDENTIALS_FILE in a world-readable location. The default ~/.autocom/credentials is mode 0600 |
Auditability
Every mutation routed through the GraphQL gateway lands in module_bus_audit_log (see GraphQL Gateway → Mutation audit). The CLI doesn't add an extra log layer — what the server records is what happened. Operators investigating "what did the CLI do" pull from the same audit trail as web-app actions.
Reporting issues
If you find a security issue with the CLI, the API, or the OAuth flow, please email security@autocom.wexron.io rather than opening a public issue. We backport fixes to the previous minor release.