Background
Follow-up agreed in #526 (review thread). The seed targets pass the password as a command-line argument; #526 deliberately kept its existing interface rather than change it, because reworking it touches the Docker path and the documented commands too. Quoting the thread:
The password exposure concern remains valid, but this PR does not need to make that cross-workflow interface decision. A follow-up should define the protected-input interface and update the scripts, Make targets, DEVELOPMENT.md, and the quickstart together.
Problem
PASS reaches the seed scripts as an argv value, which exposes it three ways:
-
Make echoes the recipe. No seed recipe is @-prefixed and there is no .SILENT, so the full command — password included — is printed to the terminal and into any CI log:
$ make seed-users USER=demo PASS=hunter2
docker compose exec cq-server /app/.venv/bin/python /app/scripts/seed-users.py --username "demo" --password "hunter2"
-
Process arguments are world-readable. While the script runs, the password is visible in ps output to any user on the host.
-
Shell expansion can alter it before the script sees it. The recipes interpolate $(PASS) into a double-quoted shell word, so a password containing `, $(, or ${ is expanded rather than passed through — the user silently gets an account with a different password than they typed.
Scope
Both interfaces take it as a required argument:
| Script |
Argument |
server/scripts/seed-users.py |
--password (required=True) |
server/scripts/seed-kus.py |
--pass → dest="password" (required=True) |
Six Make targets pass it — the Docker trio and the local trio added in #526:
| Target |
Line |
seed-users |
Makefile:127 |
seed-kus |
Makefile:137 |
seed-all |
Makefile:147-148 |
dev-seed-users |
Makefile:173 |
dev-seed-kus |
Makefile:189 |
dev-seed-all |
Makefile:202-203 |
Note seed-all and dev-seed-all re-pass PASS to their sub-makes, so the value appears more than once per run.
Docs to update in the same change:
README.md:94 (quickstart)
DEVELOPMENT.md:63, :73-75, and the Docker Compose table at :137-139
Suggested approach
Define one protected-input interface and apply it across both scripts and all six targets, rather than fixing them piecemeal:
- Read the password from an env var (e.g.
CQ_SEED_PASSWORD) and/or from stdin, keeping --password/--pass only if there's a reason to (if kept, it should probably warn).
- Fall back to
getpass.getpass() when the input is a TTY and nothing was supplied, so the interactive path needs no flag at all.
@-prefix the seed recipes so the command stops being echoed regardless.
- Pass the value through the environment rather than interpolating it into a shell word, which fixes the expansion problem in (3) as a side effect.
This is a breaking change to a documented command interface, so it wants a deliberate decision on whether PASS= keeps working (deprecated but accepted) or is removed outright.
Also worth folding in
seed-users / seed-kus / seed-all guard USER with ifndef USER, which never fires because every POSIX shell exports USER — they silently seed an account named after whoever ran the command. #526 fixed this for the dev- trio only (via $(origin USER) plus an empty-value check) and left the Docker trio alone to stay scoped. Already tracked separately in #530, but the two overlap in the same recipes, so they may be worth doing together.
Background
Follow-up agreed in #526 (review thread). The seed targets pass the password as a command-line argument; #526 deliberately kept its existing interface rather than change it, because reworking it touches the Docker path and the documented commands too. Quoting the thread:
Problem
PASSreaches the seed scripts as an argv value, which exposes it three ways:Make echoes the recipe. No seed recipe is
@-prefixed and there is no.SILENT, so the full command — password included — is printed to the terminal and into any CI log:Process arguments are world-readable. While the script runs, the password is visible in
psoutput to any user on the host.Shell expansion can alter it before the script sees it. The recipes interpolate
$(PASS)into a double-quoted shell word, so a password containing`,$(, or${is expanded rather than passed through — the user silently gets an account with a different password than they typed.Scope
Both interfaces take it as a required argument:
server/scripts/seed-users.py--password(required=True)server/scripts/seed-kus.py--pass→dest="password"(required=True)Six Make targets pass it — the Docker trio and the local trio added in #526:
seed-usersMakefile:127seed-kusMakefile:137seed-allMakefile:147-148dev-seed-usersMakefile:173dev-seed-kusMakefile:189dev-seed-allMakefile:202-203Note
seed-allanddev-seed-allre-passPASSto their sub-makes, so the value appears more than once per run.Docs to update in the same change:
README.md:94(quickstart)DEVELOPMENT.md:63,:73-75, and the Docker Compose table at:137-139Suggested approach
Define one protected-input interface and apply it across both scripts and all six targets, rather than fixing them piecemeal:
CQ_SEED_PASSWORD) and/or from stdin, keeping--password/--passonly if there's a reason to (if kept, it should probably warn).getpass.getpass()when the input is a TTY and nothing was supplied, so the interactive path needs no flag at all.@-prefix the seed recipes so the command stops being echoed regardless.This is a breaking change to a documented command interface, so it wants a deliberate decision on whether
PASS=keeps working (deprecated but accepted) or is removed outright.Also worth folding in
seed-users/seed-kus/seed-allguardUSERwithifndef USER, which never fires because every POSIX shell exportsUSER— they silently seed an account named after whoever ran the command. #526 fixed this for thedev-trio only (via$(origin USER)plus an empty-value check) and left the Docker trio alone to stay scoped. Already tracked separately in #530, but the two overlap in the same recipes, so they may be worth doing together.