fix(deployments): stop interpolating .env values into a sed script

The set_env/env_set helper escaped its value with
`esc=${val//\/\\}; esc=${esc//|/\|}; esc=${esc//&/\&}` and then
interpolated it into `sed -i -e "s|^KEY=.*|KEY=${esc}|"`. The escaping does
not do what it looks like. Tested on bash 5.2:

  set_env K 'a&b'   ->  K=aK=seedb        (sed expanded & to the whole match)
  set_env K 'a|b'   ->  sed: unknown option to `s'   (rc!=0, aborts under set -e)

So any value containing & is silently corrupted and any value containing the
s||| delimiter kills the run. That is reachable: headscale writes
OIDC_CLIENT_SECRET through this, pocket-id writes REDIRECT_URL, copyparty
writes DATA_DIR. A generated secret or a URL query string hits both cases.

The copies in headscale and pocket-id were additionally mangled when they
were introduced -- `${val//\/\}` (pattern `\/`, a literal SLASH) and a raw
newline inside `printf '%s=%s\n'`. The mangled form is a no-op rather than a
corrupter, so the practical failure mode was the same as the original.

Replace all of them with an awk rewrite that passes the key and value through
the ENVIRONMENT, so the value is never parsed as part of a script and needs no
escaping at all. ENVIRON and index() are POSIX, so busybox awk handles them.
Output goes to a temp file and is copied back with `cat >`, which preserves the
original mode and owner -- a .env holding secrets stays 0600. If awk fails,
set -e aborts before .env is touched, which `sed -i` could not promise.

Verified against plain, a&b, a|b, a\b, p@ss&w|rd\x, R&D, a URL with a query
string, s/foo/bar/, a trailing space and the empty string; plus the
append-when-key-absent path, the file-does-not-exist path, non-target lines
left intact, no line-count drift, and mode preservation.

copyparty/update.sh and the rebuilt copyparty payload are included because
update.sh is embedded; regenerated with build.sh.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 14:21:07 -05:00
co-authored by Claude Opus 5
parent 0f2273545a
commit 947c89931d
5 changed files with 266 additions and 204 deletions
+197 -181
View File
@@ -210,13 +210,25 @@ chown "$PUID:$PGID" "$DATA_DIR"
ENV_FILE="$STACK_DIR/.env" ENV_FILE="$STACK_DIR/.env"
set_env() { # <KEY> <value>: update KEY in .env, or append if absent set_env() { # <KEY> <value>: update KEY in .env, or append if absent
local key="$1" val="$2" esc # The value goes through the ENVIRONMENT, never interpolated into a sed
esc=${val//\\/\\\\}; esc=${esc//|/\\|}; esc=${esc//&/\\&} # script. Interpolating it corrupts any value containing & (sed expands it to
if grep -qE "^${key}=" "$ENV_FILE"; then # the whole match) and aborts the run on one containing the s||| delimiter --
sed -i -e "s|^${key}=.*|${key}=${esc}|" "$ENV_FILE" # which is reachable for an OIDC secret, a password or a URL query string.
else local key="$1" val="$2" tmp
printf '%s=%s\n' "$key" "$val" >> "$ENV_FILE" if [[ ! -f "$ENV_FILE" ]]; then
printf '%s=%s
' "$key" "$val" >> "$ENV_FILE"
return 0
fi fi
tmp="$(mktemp)"
_SE_KEY="$key" _SE_VAL="$val" awk '
BEGIN { k = ENVIRON["_SE_KEY"]; v = ENVIRON["_SE_VAL"]; seen = 0 }
!seen && index($0, k "=") == 1 { print k "=" v; seen = 1; next }
{ print }
END { if (!seen) print k "=" v }
' "$ENV_FILE" > "$tmp"
cat "$tmp" > "$ENV_FILE" # rewrite in place: keeps the original mode/owner
rm -f "$tmp"
} }
# Pin COPYPARTY_TAG to the newest release so the running version is explicit. # Pin COPYPARTY_TAG to the newest release so the running version is explicit.
@@ -445,178 +457,182 @@ EOF
# build.sh. # build.sh.
exit 0 exit 0
__ARCHIVE_BELOW__ __ARCHIVE_BELOW__
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
VKYylalMZSrT/036L64TOhwAoAAA tNe+OUjPktVLav9SttRx44c2btf65ibh8eSxHFlTE5NZnI17RBJpPxsg2y5rd+bU0VuJWy3cLjBn
5p9zMd9LjYpcI6Dm9PAAarRGCcg5gz/D4CRh5cG8ANdIFWygQFwFKZXj3FsECUL5cxj2xvwwYv09
W3TWoH3KeUiIgZgI2NXRue/UFrOGHiy3q+st3ikTd7bofeMo1vZhiYA2cqSDFeXJvhDQkb7QHajv
lFgieWdPKNtw4F77lGVfk4bMZPQ259vCi/tmjnhcdzSEO3l4py3vHIxjpl2m27/6/py61KUudalL
XepSl7rUpS51qUtd6lKXutSlLnWpS13qUpe61KUudalLXepSl7rUpS51+avK/wFMvaeYAKAAAA==
+19 -7
View File
@@ -83,14 +83,26 @@ osfam() {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
ENV_FILE="$STACK_DIR/.env" ENV_FILE="$STACK_DIR/.env"
env_get() { [[ -f "$ENV_FILE" ]] && grep -E "^$1=" "$ENV_FILE" | head -n1 | cut -d= -f2- || true; } env_get() { [[ -f "$ENV_FILE" ]] && grep -E "^$1=" "$ENV_FILE" | head -n1 | cut -d= -f2- || true; }
env_set() { # <KEY> <value> env_set() { # <KEY> <value>: update KEY in .env, or append if absent
local key="$1" val="$2" esc # The value goes through the ENVIRONMENT, never interpolated into a sed
esc=${val//\\/\\\\}; esc=${esc//|/\\|}; esc=${esc//&/\\&} # script. Interpolating it corrupts any value containing & (sed expands it to
if grep -qE "^${key}=" "$ENV_FILE"; then # the whole match) and aborts the run on one containing the s||| delimiter --
sed -i -e "s|^${key}=.*|${key}=${esc}|" "$ENV_FILE" # which is reachable for an OIDC secret, a password or a URL query string.
else local key="$1" val="$2" tmp
printf '%s=%s\n' "$key" "$val" >> "$ENV_FILE" if [[ ! -f "$ENV_FILE" ]]; then
printf '%s=%s
' "$key" "$val" >> "$ENV_FILE"
return 0
fi fi
tmp="$(mktemp)"
_SE_KEY="$key" _SE_VAL="$val" awk '
BEGIN { k = ENVIRON["_SE_KEY"]; v = ENVIRON["_SE_VAL"]; seen = 0 }
!seen && index($0, k "=") == 1 { print k "=" v; seen = 1; next }
{ print }
END { if (!seen) print k "=" v }
' "$ENV_FILE" > "$tmp"
cat "$tmp" > "$ENV_FILE" # rewrite in place: keeps the original mode/owner
rm -f "$tmp"
} }
# strip a leading v and anything after the X.Y.Z core # strip a leading v and anything after the X.Y.Z core
+16 -5
View File
@@ -258,14 +258,25 @@ install -m 0640 "$SCRIPT_DIR/Caddyfile" "$STACK_DIR/Caddyfile"
# .env # .env
set_env() { # <KEY> <value>: update KEY in .env, or append if absent set_env() { # <KEY> <value>: update KEY in .env, or append if absent
local key="$1" val="$2" esc # The value goes through the ENVIRONMENT, never interpolated into a sed
esc=${val//\/\\}; esc=${esc//|/\|}; esc=${esc//&/\&} # script. Interpolating it corrupts any value containing & (sed expands it to
if grep -qE "^${key}=" "$ENV_FILE"; then # the whole match) and aborts the run on one containing the s||| delimiter --
sed -i -e "s|^${key}=.*|${key}=${esc}|" "$ENV_FILE" # which is reachable for an OIDC secret, a password or a URL query string.
else local key="$1" val="$2" tmp
if [[ ! -f "$ENV_FILE" ]]; then
printf '%s=%s printf '%s=%s
' "$key" "$val" >> "$ENV_FILE" ' "$key" "$val" >> "$ENV_FILE"
return 0
fi fi
tmp="$(mktemp)"
_SE_KEY="$key" _SE_VAL="$val" awk '
BEGIN { k = ENVIRON["_SE_KEY"]; v = ENVIRON["_SE_VAL"]; seen = 0 }
!seen && index($0, k "=") == 1 { print k "=" v; seen = 1; next }
{ print }
END { if (!seen) print k "=" v }
' "$ENV_FILE" > "$tmp"
cat "$tmp" > "$ENV_FILE" # rewrite in place: keeps the original mode/owner
rm -f "$tmp"
} }
if [[ ! -f "$ENV_FILE" ]]; then if [[ ! -f "$ENV_FILE" ]]; then
+16 -5
View File
@@ -181,14 +181,25 @@ install -m 0640 "$SCRIPT_DIR/docker-compose.yml" "$STACK_DIR/docker-compose.yml"
ENV_FILE="$STACK_DIR/.env" ENV_FILE="$STACK_DIR/.env"
set_env() { # <KEY> <value>: update KEY in .env, or append if absent set_env() { # <KEY> <value>: update KEY in .env, or append if absent
local key="$1" val="$2" esc # The value goes through the ENVIRONMENT, never interpolated into a sed
esc=${val//\/\\}; esc=${esc//|/\|}; esc=${esc//&/\&} # script. Interpolating it corrupts any value containing & (sed expands it to
if grep -qE "^${key}=" "$ENV_FILE"; then # the whole match) and aborts the run on one containing the s||| delimiter --
sed -i -e "s|^${key}=.*|${key}=${esc}|" "$ENV_FILE" # which is reachable for an OIDC secret, a password or a URL query string.
else local key="$1" val="$2" tmp
if [[ ! -f "$ENV_FILE" ]]; then
printf '%s=%s printf '%s=%s
' "$key" "$val" >> "$ENV_FILE" ' "$key" "$val" >> "$ENV_FILE"
return 0
fi fi
tmp="$(mktemp)"
_SE_KEY="$key" _SE_VAL="$val" awk '
BEGIN { k = ENVIRON["_SE_KEY"]; v = ENVIRON["_SE_VAL"]; seen = 0 }
!seen && index($0, k "=") == 1 { print k "=" v; seen = 1; next }
{ print }
END { if (!seen) print k "=" v }
' "$ENV_FILE" > "$tmp"
cat "$tmp" > "$ENV_FILE" # rewrite in place: keeps the original mode/owner
rm -f "$tmp"
} }
if [[ ! -f "$ENV_FILE" ]]; then if [[ ! -f "$ENV_FILE" ]]; then
+18 -6
View File
@@ -192,13 +192,25 @@ install -m 0644 "$SCRIPT_DIR/cache-domains.regex" "$STACK_DIR/cache-domains.rege
ENV_FILE="$STACK_DIR/.env" ENV_FILE="$STACK_DIR/.env"
set_env() { # <KEY> <value>: update KEY in .env, or append if absent set_env() { # <KEY> <value>: update KEY in .env, or append if absent
local key="$1" val="$2" esc # The value goes through the ENVIRONMENT, never interpolated into a sed
esc=${val//\\/\\\\}; esc=${esc//|/\\|}; esc=${esc//&/\\&} # script. Interpolating it corrupts any value containing & (sed expands it to
if grep -qE "^${key}=" "$ENV_FILE"; then # the whole match) and aborts the run on one containing the s||| delimiter --
sed -i -e "s|^${key}=.*|${key}=${esc}|" "$ENV_FILE" # which is reachable for an OIDC secret, a password or a URL query string.
else local key="$1" val="$2" tmp
printf '%s=%s\n' "$key" "$val" >> "$ENV_FILE" if [[ ! -f "$ENV_FILE" ]]; then
printf '%s=%s
' "$key" "$val" >> "$ENV_FILE"
return 0
fi fi
tmp="$(mktemp)"
_SE_KEY="$key" _SE_VAL="$val" awk '
BEGIN { k = ENVIRON["_SE_KEY"]; v = ENVIRON["_SE_VAL"]; seen = 0 }
!seen && index($0, k "=") == 1 { print k "=" v; seen = 1; next }
{ print }
END { if (!seen) print k "=" v }
' "$ENV_FILE" > "$tmp"
cat "$tmp" > "$ENV_FILE" # rewrite in place: keeps the original mode/owner
rm -f "$tmp"
} }
if [[ ! -f "$ENV_FILE" ]]; then if [[ ! -f "$ENV_FILE" ]]; then