Playbook: lap1 (dev-laptop) role credential access
Background — GAP-7
The 2026-07-06 secrets-sync audit found that role credential files (role-sys-admin,
role-secret-manager, role-dev-coder, role-dev-reviewer, role-dev-tester) sync to
vps-i1, vps-h1, and bms-4 via secrets-sync.yml but not to lap1 (Acer laptop, also referred
to as dev-laptop in older docs — see docs/environments/lap1.md). Tracked as issue #2945.
The issue’s design comment (2026-07-26) evaluated three options and recommended Option 3:
lap1 decrypts role-*.env.sops files itself, locally, using its own age key — the same way a
developer workstation already works. No CI push needed.
⚠️ Option 3 is now architecturally invalid — read this before doing anything else
Three days after that design was posted, plan #4556 Phase 3 (merged 2026-07-29) narrowed the
.sops.yaml recipients for all five secrets/role-*.env.sops files down to developer + CI
(AGE_KEY_GHA) only, deliberately removing the four universal per-host keys (bms-4, vps-i1,
dev-laptop, vps-h1) that could previously decrypt them directly. See .sops.yaml lines ~71-114
and docs/plans/plan-4556-sops-role-repo-scoped-access.md §4.3/§6.1:
“Role credentials are delivered exclusively via the CI-mediated pre-decrypt-and-deploy path. No live
sops -dof arole-*.env.sopsfile ever happens on a host.”
This was an intentional, independently-shipped security hardening step — closing exactly the
kind of host-side bypass that GAP-7’s “Option 3” would have relied on. Do not re-add lap1’s
age key as a recipient of any role-*.env.sops file. That would reopen the hole Phase 3 was
built to close, for the benefit of the least-controlled, least-monitored host in the fleet — the
opposite of what Phase 3 intended.
Net effect: lap1 currently has no way to receive role credentials, by neither the push model
(no CI job targets it) nor the pull model (its age key is no longer a valid recipient). GAP-7
is still open; the fix this playbook was meant to document turned out to be blocked by a
later, independent decision. This is not a regression introduced by this playbook — it is the
current, correct state of .sops.yaml, documented here so the next audit pass doesn’t
re-recommend Option 3.
Current state (verify without printing any secret value)
# Confirm role-*.env.sops recipients are developer + CI only (no per-host keys)
Select-String -Path .sops.yaml -Pattern "role-(sys-admin|secret-manager|dev-coder|dev-reviewer|dev-tester)" -Context 0,4Expected: each of the five blocks lists exactly two age1... recipients (the developer key and
the CI key), not four.
# A canary decrypt attempt from lap1's own key must FAIL for role-* files (this is correct,
# not a bug) — run this ON lap1, never on the CI runner:
sops --decrypt --input-type dotenv --output-type dotenv secrets/role-dev-coder.env.sops
# Expected: non-zero exit, "no matching recipient found" (or similar) — do not treat as an
# incident; it is the intended post-#4556-Phase-3 behavior.Why this hasn’t been prioritized as a live blocker
lap1-que-w1,lap1-que-w2, andlap1-gh-w1are all still pending activation / installation perdocs/infrastructure-overview.md§lap1 — no live worker on lap1 is currently claiming jobs that need role credentials.- lap1’s reachability itself is unverified: the CF Tunnel path
(
lap1-ssh.tunnel.zintegrowana.online,docs/environments/lap1.md§Remote Access) is the currently-documented access method, but the machine has been offline / unverified since 2026-07-08 per prior session notes on the older autossh-based path. A CI job that pushes to an unreachable host on everysecrets/*.env.sopschange would either fail noisily or need a skip-on-unreachable guard from day one.
The only remaining viable path — CI-mediated push over the CF Tunnel (future work)
Once lap1 is confirmed reachable and at least one of its workers is live, the correct fix is the
same CI-mediated push pattern already used for vps-i1/vps-h1/bms-4 (decrypt server-side with
AGE_KEY_GHA, scp the plaintext to the host, install with restrictive permissions) — not a
pull/local-decrypt model. Concretely, a future sync-lap1 job in .github/workflows/secrets-sync.yml
would need to:
- Decrypt the five
role-*.env.sopsfiles server-side usingsecrets.AGE_KEY_GHA(already a valid recipient — no.sops.yamlchange required for this part). - Reach lap1 via
cloudflared access ssh --hostname lap1-ssh.tunnel.zintegrowana.online(seedocs/environments/lap1.md§Remote Access for the exactProxyCommandinvocation). - Install each decrypted file to a lap1-local path (e.g.
C:\p24-infra\lap1\role-<name>.env) with the tightest permission model Windows supports for a single-user workstation. - Skip with a warning, never fail the job, if the tunnel is unreachable — mirroring the
existing self-targeting guard already in
sync-vps-i1(.github/workflows/secrets-sync.yml, “Guard — refuse self-targeting deploy on ionos-2” step) as the precedent for a non-fatal skip branch in this workflow. - Scrub the plaintext files from the CI runner’s
/tmpin anif: always()step, same as every othersync-*job.
This is deliberately not implemented yet — it should be scoped as its own follow-up issue once lap1’s reachability and at least one active worker are confirmed, rather than building and maintaining a CI target that is unreachable by default.
Interim guidance for any bootstrap / job-claim logic on lap1
Until the above ships, any worker bootstrap running on lap1 must not assume a
role-*.env file is present. A worker that needs role credentials and can’t find the file
should refuse to claim the job rather than fail mid-task. This is the same failure mode GAP-7
originally described (“if workers there need role credentials, they currently do not receive
them via CI”) — it remains true today, just for a different underlying reason than the issue
first assumed.
Escalation
- Need a role credential on lap1 right now, before the CI job above exists? Do not manually
decrypt a
role-*.env.sopsfile with the developer age key and copy it to lap1 by hand — that recreates the exact bypass Phase 3 removed, just done by a human instead of automation. Use thesecret-manager-request.mdqueue flow instead and treat it as a one-off, logged operation, not a standing credential. - lap1 reachability status unclear? Check
docs/environments/lap1.md§Remote Access and the worker capacity docs before assuming the tunnel is up.
Related
- Issue #2945 — GAP-7, this playbook’s origin
docs/plans/plan-4556-sops-role-repo-scoped-access.md— the Phase 3 narrowing that invalidated the issue’s original recommended resolution.sops.yaml— current recipient list forsecrets/role-*.env.sopsdocs/environments/lap1.md— lap1 hardware, connectivity, and remote-access detailsdocs/infrastructure-overview.md§lap1 — inventory entry, updated alongside this playbook