deploy-vps should quote app-env values itself #54

Closed
opened 2026-09-07 11:31:14 +00:00 by ahmad · 1 comment
Owner

Context

deploy-vps writes the app-env lines verbatim into the host .env and the remote step shell-sources that file (set -a; . ./.env). A consumer that writes KEY="value" gets $, backticks, \ and " interpreted by the host shell; a generated password with any of them is silently altered or, for a backtick, executed on the host. Every consumer currently has to know this and quote correctly; expiro (ahmad/expiro#54) works around it with single quotes. The action's own DEPLOY_DOMAIN="…" lines have the same shape.

Scope

  • In: the action accepts KEY=value lines and writes them so that any value is preserved byte-for-byte after sourcing and after compose's .env parsing; the action's own lines use the same escaping; documentation of what a consumer may pass.
  • Out: changing how consumers supply secrets to the action.

Acceptance criteria

  • Given an app-env value containing $, a backtick, \, " or ', when the deploy runs, then the container sees the value unchanged.
  • Given a consumer that still writes its own quotes, when the deploy runs, then the behaviour is defined and documented (either honoured or rejected, not doubly quoted).
  • Given the action's own DEPLOY_DOMAIN line, when the domain contains a shell-special character, then it is preserved.
### Context `deploy-vps` writes the `app-env` lines verbatim into the host `.env` and the remote step shell-sources that file (`set -a; . ./.env`). A consumer that writes `KEY="value"` gets `$`, backticks, `\` and `"` interpreted by the host shell; a generated password with any of them is silently altered or, for a backtick, executed on the host. Every consumer currently has to know this and quote correctly; expiro (ahmad/expiro#54) works around it with single quotes. The action's own `DEPLOY_DOMAIN="…"` lines have the same shape. ### Scope - In: the action accepts `KEY=value` lines and writes them so that any value is preserved byte-for-byte after sourcing and after compose's `.env` parsing; the action's own lines use the same escaping; documentation of what a consumer may pass. - Out: changing how consumers supply secrets to the action. ### Acceptance criteria - [ ] Given an `app-env` value containing `$`, a backtick, `\`, `"` or `'`, when the deploy runs, then the container sees the value unchanged. - [ ] Given a consumer that still writes its own quotes, when the deploy runs, then the behaviour is defined and documented (either honoured or rejected, not doubly quoted). - [ ] Given the action's own `DEPLOY_DOMAIN` line, when the domain contains a shell-special character, then it is preserved.
ahmad self-assigned this 2026-09-07 18:14:59 +00:00
Author
Owner

Plan.

Affected: deploy-vps/action.yml (the "Write the runtime environment file" step and the app-env input description), README (input table, example, and the "exactly as given" paragraph).

Approach: a sq() helper single-quotes every value (embedded ' as '\''), applied to the action's own lines (DEPLOY_DOMAIN, IMAGE_TAG, COMPOSE_PROFILES, GRAFANA_PROM_*) and to each app-env value. Single quotes because the sourcing shell is the effective parser — compose reads the same file, but the sourced process environment takes precedence in its interpolation, so the shell's reading is the one that counts.

Criterion 2 (consumer-supplied quotes) — honoured, not rejected: a value already wrapped in matching quotes (single or double) is written as given; the consumer owns that quoting. Rejecting would break expiro's live workaround (ahmad/expiro#54 passes single-quoted values) on its very next deploy; honouring keeps it working unchanged, and expiro can drop the quotes at leisure. Documented on the input and in the README, whose example stops teaching the KEY="…" shape.

Guard: a line whose key is not a valid variable name (or has no =) fails the deploy with a message naming only the key, never the value — previously it was sourced as whatever the shell made of it.

Verification: local matrix extracting the exact run: block from the YAML and driving it through set -a; . ./file — hostile bytes ($, backtick, \, ", ', spaces, empty) in app-env and in the action's own values, pre-quoted values honoured verbatim, malformed keys rejected without leaking values, plus a negative control proving the old double-quote shape corrupts. 17/17 pass.

Plan. **Affected**: `deploy-vps/action.yml` (the "Write the runtime environment file" step and the `app-env` input description), README (input table, example, and the "exactly as given" paragraph). **Approach**: a `sq()` helper single-quotes every value (embedded `'` as `'\''`), applied to the action's own lines (`DEPLOY_DOMAIN`, `IMAGE_TAG`, `COMPOSE_PROFILES`, `GRAFANA_PROM_*`) and to each app-env value. Single quotes because the sourcing shell is the effective parser — compose reads the same file, but the sourced process environment takes precedence in its interpolation, so the shell's reading is the one that counts. **Criterion 2 (consumer-supplied quotes) — honoured, not rejected**: a value already wrapped in matching quotes (single or double) is written as given; the consumer owns that quoting. Rejecting would break expiro's live workaround (ahmad/expiro#54 passes single-quoted values) on its very next deploy; honouring keeps it working unchanged, and expiro can drop the quotes at leisure. Documented on the input and in the README, whose example stops teaching the `KEY="…"` shape. **Guard**: a line whose key is not a valid variable name (or has no `=`) fails the deploy with a message naming only the key, never the value — previously it was sourced as whatever the shell made of it. **Verification**: local matrix extracting the exact `run:` block from the YAML and driving it through `set -a; . ./file` — hostile bytes (`$`, backtick, `\`, `"`, `'`, spaces, empty) in app-env and in the action's own values, pre-quoted values honoured verbatim, malformed keys rejected without leaking values, plus a negative control proving the old double-quote shape corrupts. 17/17 pass.
ahmad 2026-09-07 18:23:55 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
amtronics/platform-actions#54
No description provided.