Repository navigation
security: guessable default credentials ship in templates, ansible defaults and the compose file #16299
Description
Activity
- addedbugSomething isn't workingSomething isn't working
on Sep 11, 2026 Acceptance criterion added, 2026-09-11 (from #16300's closure review):
- Every allowlisted
.secrets.baselineentry has a tracked reason. The baseline format has no reason field, so the reasons need a companion record. The template's weak default passwords are marked as pending this issue, never silently allowed. A guard fails any entry that has no reason.
- Every allowlisted
Facts and decisions (2026-09-11), before any code
These are from a read-only sweep. Every claim below was re-checked against the tree.
Correction to the issue's table.
AUTOBOT_SECRETS_ROOT_KEYdoes reach its consumers:docker/with-secrets.sh:38-39exports it for backend, worker and slm; it is their entrypoint (docker-compose.yml:214).autobot_shared/secrets_envelope.py:125(load_root_key) requires it.repo_tests/secrets_root_key_provisioned_test.pyguards it.
The compose file never names it, because the wrapper loads it from the secrets volume. So AC3 is met by existing wiring, with no code needed. It gets ticked with this evidence against merged base.
AC5.
.secrets.baselineis a detect-secrets v1.5.0 baseline with 0 entries, and nothing enforces it. The only reader,stranded_recorder_entries_test.py, just parses it. So AC5's reason record applies to the audited baseline #16275 introduces, and it will be built from that baseline's actual contents.Engineering, no decision needed:
- Compose. 9 port literals (lines 50, 87, 291, 306, 697, 737, 749, 776 and 787) fail the hardcoded-values hook whenever the file is staged. They can't be baselined in the same change (
check_baseline_no_growth.sh:330-337), so they get fixed at the source. - Ansible. The defaults for
backend_secret_key,backend_jwt_secretandgrafana_admin_passwordmove to generate-or-reuse, the same way the DB password already works (roles/postgresql/tasks/databases.yml:27-49). Anassertrefuses a known default.
Decision 1: AUTOBOT_DB_PASSWORD on existing compose installs
POSTGRES_PASSWORD=${AUTOBOT_DB_PASSWORD:-autobot}(docker-compose.yml:93) applies only when the volume is first initialised. Neither generator creates it; ansible installs already generate or reuse it. No rotation mechanism exists. The builtin updater's only generic hook is the per-rolepost_sync_cmd(services/sync_orchestrator.py:203-229).- A: A builtin-updater step re-keys the existing role. If the password is still the default, it generates one, re-keys through the updater's own step, persists it to the secrets volume and restarts the consumers. Never a manual
ALTER ROLE. This fixes every install. - B: Existing installs keep their current password, and only new installs generate one. Existing installs stay on a publicly known password.
- C: Refuse to start while the default is in use, and the operator re-keys. Existing installs break until someone acts.
Recommendation: A.
Decision 2: the
.env*edit block.claude/hooks/protect-files.sh:108-109denies every.env,.env.*,*/.envand*/.env.*path. It blocks tracked templates (.env.docker,.env.example,docker/.env.dockerand so on) exactly like real secret files, and it protects itself (:112-113). Only the owner can change it.- A: The owner allows edits to an explicit list of tracked templates only. Real
.envfiles stay blocked. - B: Keep the hook as it is. The owner applies the template edits by hand from a patch in the PR.
- C: Don't edit the templates. Startup refuses the known template defaults instead.
Recommendation: A, as the narrowest change.
Decision 3: VNC credentials
VITE_{DESKTOP,TERMINAL,PLAYWRIGHT}_VNC_PASSWORD(.env.example:462-468) are compiled into the frontend bundle and sent as a URL query parameter (AppConfig.js:211,219;useChatStore.ts:743). The path is marked DORMANT (#1130; re-integration is #5136). The backend/vnc-proxyis authenticated, but it passes the page through with no password. The SLM has an encrypted VNC credential store with a one-time token exchange (services/vnc_credentials.py,api/vnc.py).- A: The backend proxy injects the VNC password server-side, from the secrets manager. The frontend never holds it, and the
VITE_*password variables go. - B: Route VNC through the SLM's existing token exchange.
- C: Strip the credentials from the dormant path now, and let feat(browser): region-marking + AI-assisted scraping templates on InteractiveScreenshot #5136 build the auth when it re-integrates VNC.
Recommendation: A.
These are being put to the owner now, and the rulings will be recorded here.
- addedneeds-decisionBlocked on an owner decision; options and a recommendation are on the issueBlocked on an owner decision; options and a recommendation are on the issue
on Sep 11, 2026 Owner rulings (2026-09-11), given in the implementing session
- DB password: option A. A builtin-updater step re-keys the existing role when it is still the default. It generates a password, re-keys through the updater's own step, persists the result to the secrets volume and restarts the consumers. Never a manual
ALTER ROLE. .env*hook: option A. Edits are allowed to an explicit list of tracked template files only; real.envfiles stay blocked. The hook protects its own file (protect-files.sh:112-113), so the PR carries the exact hook change for the owner to apply by hand. No session edits the hook.- VNC: option A. The authenticated backend
/vnc-proxyinjects the VNC password server-side from the secrets manager. The frontend never holds it, and theVITE_*VNC password variables go.
AC3 is already met by existing wiring (see the comment above). Implementation starts after #16298, #16259 and #16266 land.
- DB password: option A. A builtin-updater step re-keys the existing role when it is still the default. It generates a password, re-keys through the updater's own step, persists the result to the secrets volume and restarts the consumers. Never a manual
- removedneeds-decisionBlocked on an owner decision; options and a recommendation are on the issueBlocked on an owner decision; options and a recommendation are on the issue
on Sep 11, 2026 - added a commit that references this issue
on Sep 11, 2026 The implementation plan found three places where a recorded ruling conflicts with the current code. Each needs an owner decision before its PR starts. Nothing is implemented yet; work begins after #16298 and #16259 land, per the earlier comment.
1. DB re-key (ruling: "the updater re-keys it"). The builtin updater has no path into a compose install.
Self-update runsupdate-all-nodes.ymlagainst the SLM host, andpost_sync_cmdreaches nodes over SSH. Neither can reach the compose install:docker-compose.ymlmounts no docker socket.- The secrets volume is read-only in backend and SLM; only
autobot-secrets-initwrites it. - The "docker" infra role points at a deprecated playbook.
No builtin step can persist a new password or restart the consumers. Per the updater rule, this is a gap to fix, not route around.
- (a) Recommended: file a precursor issue, "the builtin updater has no compose path". Block only the DB PR on it; the other three PRs proceed.
- (b) Re-key at container start:
secrets-initgenerates the password, and a one-shotALTER ROLEruns while the default still authenticates. This is a boot-time migration rather than an updater step. I have not verified that it's feasible.
2. VNC (ruling: "the backend proxy injects it"). Injecting into the page gives the browser the password.
get_vnc_clientonly passesvnc.htmlthrough. Putting the password into that page or the query string delivers it to the client. The only fully server-side point is the websocket relay: there the proxy answers the VNC server's password challenge itself and offers the browser no-auth.- (a) Recommended: handshake in the relay, in a new module so
vnc_proxy.pystays under 600 lines. The challenge needs DES. I have not verified that the pinnedcryptographystill ships TripleDES. - (b) Inject into the page. The password reaches the browser, which defeats the ruling's intent.
3. VNC password source. Nothing stores it in the canonical secrets manager today.
It lives in a node-local env file written by thevncandbrowserroles, and is registered only in the SLM's own credential store.- (a) Recommended: Ansible writes it to the backend's SYSTEM vault through the service secret-create path, and the proxy reads it through
SecretsCoordinator. Two copies exist until the SLM store reads from the same vault; I'd file that follow-up. - (b) The proxy reads it from the SLM store over its API. That is a cross-service call on every VNC connect.
Other findings, no decision needed:
- AC5:
.secrets.baselineholds 1,331 entries, allis_secret:false. They include the five weak defaults this issue names, so they are silently allowed today. The plan is a companion reasons file keyed by(file, type, hashed_secret), plus a guard that fails on an entry with no reason. Removing the five entries leaves 1,326, still above the rescan floor of 1,300. - AC3 is already met on base:
docker/with-secrets.shexports the root key, and the backend, SLM and worker entrypoints use it.
Proposed split:
- The AC5 guard.
- Deploy config: port literals, Ansible generate-or-reuse, the
.env*hook patch for the owner to apply, Grafana and SLM-admin defaults. - VNC.
- DB re-key, blocked on conflict 1.
- addedneeds-decisionBlocked on an owner decision; options and a recommendation are on the issueBlocked on an owner decision; options and a recommendation are on the issue
on Sep 12, 2026 Owner decision, 2026-09-17: generate on first run.
Defaults are replaced with a real generated secret at install time, in the manner
docker/generate-secrets.shalready does for the credentials it covers. No refuse-to-start gate, and no grace-period deadline.What this means for each listed default: it must be generated at install rather than shipped with a guessable value — the Grafana admin password, the database password, the compose
SLM_ADMIN_PASSWORDand:-autobotfallbacks, and the ansiblechange-mebackend secret key and JWT secret.The consequence to state plainly, because the decision accepts it: a deployment already running on a shipped default keeps running on it. Generation applies at install time, so it fixes new installs and does not reach existing ones. Since whether real deployments override these has not been measured, the size of that residue is unknown. This is a deliberate trade — no existing install is broken — not an oversight, and it should not be quietly re-litigated later as though it were.
Two things are unaffected by the decision and still need doing:
AUTOBOT_SECRETS_ROOT_KEYis generated but never reaches any service. That is a wiring gap, not a default-value question. Either route it to the services that need it, or remove its generation with the reason recorded.- The
VITE_*VNC passwords must not carry a credential at all.VITE_*values are compiled into the frontend bundle, so generating a strong one only means shipping a strong credential to every browser. Generation is the wrong fix here; the auth has to move server-side or behind the SLM. Generating these would make the issue look closed while shipping the secret exactly as before.
The two hook conflicts that blocked #16275 still block this and need resolving first, since they are why the tracked template defaults are unchanged:
- a project hook blocks every
.env*edit, which is where two of the defaults live; - the compose file cannot be edited because the hardcoded-values hook fails on 9 pre-existing port literals, and its baseline forbids adding entries for a file the change touches.
Neither is a reason to defer the work — they are the first part of it.
- removedneeds-decisionBlocked on an owner decision; options and a recommendation are on the issueBlocked on an owner decision; options and a recommendation are on the issue
on Sep 17, 2026 15 remaining items
- added 9 commits that reference this issue
on Sep 19, 2026 Chunking triage — a proposal, not an assignment
- Proposed priority:
priority: critical— not applied. Setting it is the milestone owner's call. - triage(v0.9.0): release criticality of the 123 open issues — 26 blocking, 36 small, 18 umbrellas, 41 misfiled, 2 undetermined #17639 criterion met: a security exposure in shipped code — guessable default credentials ship in the public repo's compose/env/role defaults
- triage(v0.9.0): release criticality of the 123 open issues — 26 blocking, 36 small, 18 umbrellas, 41 misfiled, 2 undetermined #17639 bucket: 1 — genuinely blocking a v0.9.0 release
- Scope:
security (secondary deploy) - Primary files named by the issue:
autobot-slm-backend/ansible/roles/backend/defaults/main.yml,autobot-slm-backend/ansible/roles/monitoring/defaults/main.yml,docker-compose.yml,docker/generate-secrets.sh - Umbrella / container: YES — open children security(vnc): both VNC launch paths bind the RFB port on every interface although only loopback websockify uses it #17055 security(vnc): docker-compose deployments have no VNC password provisioning at all #17059 (both OPEN). A container is never "finished today", so it must not sit in a numbered chunk; the chunk would never close.
- Pre-filter: ⚠ a merged commit references this issue —
b085fa4b3b fix(ci): record the frontend-source glob dependency of the #16299 VNC-pass…. A reference is not a delivery: verify AC coverage before putting it in a chunk, and consider a closure pass first. - Basis: triage(v0.9.0): release criticality of the 123 open issues — 26 blocking, 36 small, 18 umbrellas, 41 misfiled, 2 undetermined #17639's per-issue row, reused rather than re-derived — "Guessable default credentials still ship in the public repo's compose/env/role defaults (admin password, dashboard password, backend secret). Owner ruled generate-on-first-run."
Nothing was relabelled, moved or closed by this pass.
- Proposed priority:
- added a commit that references this issue
on Oct 5, 2026
Found by the #16275 audit (11 Sep 2026). This is a public repository, so any credential with a guessable default is a known credential on every install that never overrides it.
.env.docker:26GRAFANA_ADMIN_PASSWORDdefaults to the app's namedocker/generate-secrets.shgenerate it. The tracked template default is unchanged, because a project hook blocks every.env*edit.docker/.env.docker:102AUTOBOT_DB_PASSWORDdefaults to the app's namedocker-compose.ymlSLM_ADMIN_PASSWORD:-admin, plus the:-autobotfallbacksautobot-slm-backend/ansible/roles/backend/defaults/main.yml:108-109change-meautobot-slm-backend/ansible/roles/monitoring/defaults/main.yml:26grafana_admin_password: admin.env.exampleVITE_*) are guessableVITE_*values they are compiled into the frontend bundle, so a real value would ship to every browser.docker/generate-secrets.sh/docker-compose.ymlAUTOBOT_SECRETS_ROOT_KEYis generated but never passed to any serviceWhether real deployments override these has not been measured.
Acceptance criteria
VITE_*variable carries a credential; VNC auth moves server-side or behind the SLMAUTOBOT_SECRETS_ROOT_KEYreaches the services that need it, or its generation is removed with a reason.env*edits for templates, and the compose file's pre-existing port literals