Updates ship-readiness.md, AGENTS.md, and 7 convoy files to reflect the as-shipped state of the 2026-05-26 7-convoy multitask wave: - PR #26 tighten-visual-diff-path-filter (P3) - PR #27 purge-weak-creds-from-helpers (P2, closes the umbrella) - PR #28 cleanup-mobile-nav-dead-props (P3) - PR #29 lint-against-cjs-in-esm-scripts (P3, surfaced by PR #25) - PR #30 single-sql-client (P1 #8 RESOLVED) - PR #31 single-auth-provider (P1 #9 RESOLVED) - PR #32 migration-tool (P1 #11 RESOLVED) Milestone: 5 of 6 P1 quality items RESOLVED. Only fix-lint-baseline (P1 #11.5) remains in the P1 lane. Newly queued follow-ups: - purge-quick-login-from-loginpage (surfaced by PR #27) - purge-neondatabase-serverless-fully (surfaced by PR #30, unblocked by PR #32's migration tool adoption) Co-authored-by: Cursor <cursoragent@cursor.com>
15 KiB
purge-weak-creds-from-helpers (P2 hygiene — final scope close)
Status: IN-FLIGHT 2026-05-26
Priority: P2 hygiene (not a security blocker; the test-fixture
script is dev-only and the documented passwords were never reachable
from a production code path — but the bug pattern is the same as the
P0-grade weak-creds shape that drop-public-setup removed from
setup-neon-db.js, so closing it brings the helper-script surface to
zero weak literals)
Convoy owner: parent (no architect — proven-pattern mirror;
single-script + single-doc fix following two already-shipped
applications of the same pattern)
Opened: 2026-05-26
Classification: hygiene
Background — the multi-convoy history that led here
The original purge-weak-creds-from-helpers convoy was queued in
.convoys/ship-readiness.md as the umbrella for sweeping every
helper-script + manual-QA-doc reference to the legacy admin123 /
alice123 / bob123 literals and the legacy @tcgvault.com email
domain. Its scope has been progressively whittled down by three
already-shipped convoys:
drop-public-setup(squashff80753Brief 1 +b63b509Brief 2): replaced the hardcodedadmin123inscripts/setup-neon-db.jswith the fail-loudADMIN_INITIAL_PASSWORDenv-var gate; converted the script from CJS to ESM sonpm run setup-dbactually runs on Node 22.x. Set the precedent for the env-var + fail-loud + no-echo pattern that the next two convoys (and this one) mirror verbatim.pick-a-nameBrief 2 (squash9abbab6, 2026-05-24): swept every@tcgvault.comliteral in scripts + docs to@deckhearth.comtogether with the one-shot migration script. Email half done.fix-reset-db-script(squash3ab9bf8, PR #25, 2026-05-26): second application of the post-drop-public-setuppattern, this time toscripts/reset-db.js. Removed the secondadmin123literal from the codebase, removed the only remainingAdmin Password:echo, converted the third CJS-in-ESM script.
After those three convoys, the remaining weak-credential surface is exactly two files — the alice/bob test-user fixture script and the manual-QA doc that pairs with it. Both are addressed here.
Remaining scope (this convoy)
scripts/create-test-users.js— alice + bob fixtures still hardcodebcrypt.hash('alice123', 12)+bcrypt.hash('bob123', 12)and echo the literal passwords to stdout (console.log('✅ Created Alice (alice@deckhearth.com / alice123)')).TESTING_GUIDE.md— Test Accounts table still documents the literal passwords for admin + alice + bob.
The fix shape — single env var, no echo, ESM-already
The fix is a verbatim mirror of the post-drop-public-setup
scripts/setup-neon-db.js pattern and the post-fix-reset-db-script
scripts/reset-db.js pattern, with one deliberate simplification:
- Single env var:
TEST_USERS_PASSWORD. Both alice and bob get the same hashed value. Per-user env vars (ALICE_PASSWORD,BOB_PASSWORD) would be unnecessary sprawl for what is a test fixture surface — these aren't independent identities, they're a collaborator-flow demo pair. Risk R2 below argues this explicitly. - Fail-loud at the top of
createTestUsers(). Readsprocess.env.TEST_USERS_PASSWORD; if unset or whitespace-only, prints an actionable error (names the var, points at.env.local, suggestsopenssl rand -base64 24, references README's "First-time admin setup" section) andprocess.exit(1)BEFORE opening any DB connection. Same wording template assetup-neon-db.jslines 20-26 andreset-db.jslines 29-35. - No password echo to stdout. The previous file logged the
literal
alice123/bob123strings in both the per-user creation line and the final summary block. All four echo lines are deleted; the new summary line documents where the password comes from ((passwords from TEST_USERS_PASSWORD)) without ever printing the value. - ESM already. Unlike
setup-neon-db.jsandreset-db.jsat the start of their respective convoys,create-test-users.jswas already top-level ESM (it imports{ config } from 'dotenv',{ sql } from '@vercel/postgres',bcrypt from 'bcryptjs'at the top of the file). No CJS→ESM conversion needed. This convoy is the first of the three to skip that half of the pattern. ON CONFLICT (email) DO NOTHINGis preserved (already in the original file at lines 19, 28) — defensive against double-run.
TESTING_GUIDE.md's Test Accounts table is rewritten to (a) remove
the literal passwords from the table, (b) document the env-var source
for each user, and (c) point at README's "First-time admin setup"
section for the openssl rand -base64 24 generation tip. The two
inline Password: alice123 / Password: bob123 snippets later in the
workflow are replaced with Password: <value of TEST_USERS_PASSWORD from .env.local>.
Verification plan (static-grep only — script is destructive)
This convoy does NOT live-test create-test-users.js. The script
opens a DB connection and inserts rows; running it against a Neon
branch in CI or in the boot-the-brief loop would be a side-effect
cost we don't need to incur. The verification surface is entirely
static:
node --check scripts/create-test-users.js→ exit 0npm run lint→ 128 problems (baseline preserved; no regression)npm run test:run→ 21/21 pass- Grep
scripts/ TESTING_GUIDE.mdforadmin123|password123|test123| alice123|bob123→ 0 hits on literal passwords - Grep
scripts/create-test-users.jsforrequire(→ 0 hits (mirror-the-pattern preserves ESM-only; trivially satisfied here because the file was already ESM) - Grep
scripts/ TESTING_GUIDE.mdforTEST_USERS_PASSWORD→ expect 10 hits (5 in script: docstring + const + error message body; 5 in TESTING_GUIDE.md: table + two inline workflow snippets + explanatory paragraph)
Live verification deferred to operator. Optional post-merge
action: set TEST_USERS_PASSWORD in .env.local, run
node scripts/create-test-users.js against a non-prod Neon branch,
verify alice + bob rows insert; then unset the env var and re-run,
verify the script exits 1 with the helpful error message before
opening the DB connection.
Risks
- R1 — A CI step or doc dep on the literal passwords. If
.github/workflows/**or any other doc (docs/**,TESTING_GUIDE.mdsibling files, the agent-context-pipeline docs) referencesalice123/bob123/admin123as part of an automated test flow, removing the literal would break it. Mitigation: the grep hunt coveredscripts/+TESTING_GUIDE.md. The broader hits in.convoys/**andAGENTS.mdare historical convoy narrative and must NOT be edited (rewriting history). The only live reference outside this convoy's scope ispages/login.js— see "Surfaced out-of-scope follow-up" below. - R2 — Env-var sprawl. Using a single
TEST_USERS_PASSWORDfor both alice and bob is intentional. These are test-fixture users for the collaboration demo flow in TESTING_GUIDE.md; they aren't modeled as independent identities anywhere in the auth surface, and giving them per-user passwords would (a) double the env-var contract for zero security benefit (anyone running this script already has full DB access) and (b) drift from the ADMIN_INITIAL_PASSWORD shape that the operator is already trained on. If future test-user additions need distinct passwords for realistic concurrency testing, that's a separate concern and a separate convoy.
Surfaced out-of-scope follow-up
pages/login.js"Quick Login" buttons still hardcode the legacy literals. Lines 172 + 184 invokehandleQuickLogin('alice@deckhearth.com', 'alice123')andhandleQuickLogin('bob@deckhearth.com', 'bob123'). These are client-side dev convenience buttons that ship to production HTML and reveal the legacy passwords directly to anyone viewing the login page source. NOT in scope for this convoy (the convoy spec is "scripts + docs only; do NOT touchpages/**"). Queue a follow-up convoy:purge-quick-login-from-loginpage(P2 hygiene) to either (a) delete the Quick Login section entirely or (b) gate it behindprocess.env.NODE_ENV === 'development'. The latter still requires a credential source that doesn't ship to prod HTML — likely a.env.local-onlyNEXT_PUBLIC_DEV_*convention or a dev-only proxy endpoint. Architect-worth.
Operator action required
- Pre-merge: none. No schema change. No new infra.
- Post-merge: anyone running
node scripts/create-test-users.js(ornpm run create-test-usersif such a script exists) must addTEST_USERS_PASSWORD=<value>to their.env.localfirst. Existing alice + bob rows in already-seeded environments are not rotated by re-running this script —ON CONFLICT (email) DO NOTHINGpreserves the old hashes. Any environment that rancreate-test-users.jsbefore this convoy still has the weakalice123/bob123hashes in its DB; operators must rotate manually via the app (or drop those rows and re-seed). Same caveat that applies to thedrop-public-setupadmin row —setup-neon-db.js's andcreate-test-users.js's idempotency means they do NOT rotate; they only seed.
Owns
Parent (single-script + single-doc proven-pattern fix; no architect or implementer subagent required).
As-shipped
Single squash commit 5f2b234 (PR #27, merged 2026-05-27T03:53:25Z
UTC / local 2026-05-26). Parent-owned end-to-end per the convoy spec
— no architect, no implementer subagent dispatched. Mirror-the-pattern
fix exactly as planned; no mid-execution surprises that would have
forced an architect bounce. The umbrella purge-weak-creds-from-helpers
is now fully closed — both remaining files swept, weak-cred surface
of the helper-script + manual-QA-doc lane is at zero.
Diff: 3 files, +249 / -22. scripts/create-test-users.js (the
alice/bob fixture script) + TESTING_GUIDE.md (the manual-QA doc
that pairs with it) + .convoys/purge-weak-creds-from-helpers.md
(the planning document, committed atomically with the fix).
The two files atomically resolved:
scripts/create-test-users.js— alice + bob fixtures previously hardcodedbcrypt.hash('alice123', 12)+bcrypt.hash('bob123', 12)and echoed both literals to stdout (✅ Created Alice (alice@deckhearth.com / alice123)+ matching bob line + a final summary block listing both passwords). All four echo lines are gone; the new shape reads a singleTEST_USERS_PASSWORDenv var at the top ofcreateTestUsers()with the same fail-loud + actionable error message template assetup-neon-db.js's post-DPS shape (names the var, points at.env.local, suggestsopenssl rand -base64 24, references README's "First-time admin setup" section,process.exit(1)BEFORE opening any DB connection). Both alice and bob get the same hashed value per Risk R2 (these are collaboration-flow demo fixtures, not independent identities — per-user env vars would be sprawl).ON CONFLICT (email) DO NOTHINGpreserved (already in the original file). The two per-user creation lines are rewritten to not echo the password:✅ Created Alice (alice@deckhearth.com)✅ Created Bob (bob@deckhearth.com); the final summary block includes(passwords from TEST_USERS_PASSWORD)to document where the password came from without printing it.
TESTING_GUIDE.md— Test Accounts table rewritten to (a) remove the literal passwords from the table, (b) document the env-var source for each user (admin←ADMIN_INITIAL_PASSWORD;alice/bob←TEST_USERS_PASSWORD), and (c) point at README's "First-time admin setup" section for theopenssl rand -base64 24generation tip. The two inlinePassword: alice123/Password: bob123snippets later in the workflow are replaced withPassword: <value of TEST_USERS_PASSWORD from .env.local>.
ESM-already. Unlike setup-neon-db.js and reset-db.js at the
start of their respective convoys, create-test-users.js was already
top-level ESM (it imports { config } from 'dotenv', { sql } from '@vercel/postgres', bcrypt from 'bcryptjs' at the top of the
file). This was the first of the three weak-creds-shape convoys to
skip the CJS→ESM conversion half of the pattern.
Verification (all gates green at merge):
node --check scripts/create-test-users.js→ exit 0npm run lint→ 125 problems (post-PR-#31 baseline preserved; no regression introduced)npm run test:run→ 21/21 pass- Grep
scripts/ TESTING_GUIDE.mdforadmin123|password123|test123|alice123|bob123→ 0 hits on literal passwords (the umbrella weak-cred surface is at zero across the helper-script + manual-QA-doc lane) - Grep
scripts/create-test-users.jsforrequire(→ 0 hits (ESM-already; trivially satisfied) - Grep
scripts/ TESTING_GUIDE.mdforTEST_USERS_PASSWORD→ expected ~10 hits (docstring + const + error message body in script; table + two inline workflow snippets + explanatory paragraph in doc) - CI on PR #27: Lint ✓ | Vitest 21/21 ✓ | Playwright smoke 3/3 ✓ |
forbidden-endpoints✓ |forbidden-cors-headers✓ | Vercel preview deploy ✓ | Aggregate gate ✓ Screenshot diff: not triggered (script + docs only —paths:filter excludesscripts/**andTESTING_GUIDE.md; the post-PR-#26!pages/api/**exclusion is not even relevant here)
Live verification deferred per convoy spec. Optional post-merge
operator action: set TEST_USERS_PASSWORD in .env.local, run node scripts/create-test-users.js against a non-prod Neon branch, verify
alice + bob rows insert; then unset the env var and re-run, verify
the script exits 1 with the helpful error message before opening the
DB connection.
Operator caveat (going forward). Existing alice + bob rows in
already-seeded environments are not rotated by re-running this
script — ON CONFLICT (email) DO NOTHING preserves the old hashes.
Any environment that ran create-test-users.js before this convoy
still has the weak alice123 / bob123 hashes in its DB; operators
must rotate manually via the app (or drop those rows and re-seed).
Same caveat that applies to the drop-public-setup admin row.
Surfaced follow-up (newly queued in .convoys/ship-readiness.md):
purge-quick-login-from-loginpage (P2 hygiene / security). Out-of-scope
sibling bug: pages/login.js lines ~172 + ~184 still hardcode
alice123 / bob123 in client-side "Quick Login" button handlers
that ship to production HTML. The convoy spec scope was "scripts +
docs only; do NOT touch pages/**", so this was deliberately left
for a follow-up. Architect-worth (the right shape — delete entirely,
gate behind process.env.NODE_ENV === 'development', or proxy through
a dev-only endpoint — is a design decision).
Spec deviation: none.
Cross-validation finding. The Playwright smoke 3/3 PASS on a
script + docs-only PR is the seventh consecutive convoy where the
same 3-test smoke spec defends the auth surface through a sweeping
change (PR #15 → #19 → #20 → #21 → #25 → #32 → this PR). The lineage
continues.