fix: accept literal database secret punctuation
This commit is contained in:
@@ -53,9 +53,33 @@ environment_value() {
|
|||||||
|
|
||||||
is_safe_literal() {
|
is_safe_literal() {
|
||||||
local value="$1"
|
local value="$1"
|
||||||
# Required values are generated as single-line hex-safe literals. This also
|
local inner
|
||||||
# permits percent-encoded schema URLs without evaluating dotenv syntax.
|
|
||||||
[[ "$value" =~ ^[A-Za-z0-9._~%:@/+,-]+$ ]]
|
# Required values are literal single-line values: use an unquoted token or
|
||||||
|
# matching non-empty quotes. Dotenv interpolation, comments, and malformed
|
||||||
|
# quoting are rejected rather than evaluated, so generate secrets without $.
|
||||||
|
if [ -z "$value" ] || [[ "$value" == *'$'* ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$value" in
|
||||||
|
\"*\")
|
||||||
|
inner="${value:1}"
|
||||||
|
inner="${inner%?}"
|
||||||
|
[ -n "$inner" ] && [[ "$inner" != *'"'* ]]
|
||||||
|
;;
|
||||||
|
\'*\')
|
||||||
|
inner="${value:1}"
|
||||||
|
inner="${inner%?}"
|
||||||
|
[ -n "$inner" ] && [[ "$inner" != *"'"* ]]
|
||||||
|
;;
|
||||||
|
*\"*|*\'*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
*[[:space:]]*|*\#*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once_non_empty() {
|
require_once_non_empty() {
|
||||||
@@ -65,7 +89,7 @@ require_once_non_empty() {
|
|||||||
count="$(definition_count "$key")"
|
count="$(definition_count "$key")"
|
||||||
value="$(environment_value "$key")"
|
value="$(environment_value "$key")"
|
||||||
if [ "$count" -ne 1 ] || ! is_safe_literal "$value"; then
|
if [ "$count" -ne 1 ] || ! is_safe_literal "$value"; then
|
||||||
echo "required staging database environment variable is missing or duplicated: $key" >&2
|
echo "required staging database literal is missing, duplicated, or ambiguous: $key" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,35 @@ test("database env validator rejects quoted and interpolated required secrets",
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("database env validator accepts punctuated literal required secrets", () => {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), "jyotisha-database-env-"));
|
||||||
|
const envFile = join(root, ".env.staging.database");
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const password of ["c2VjcmV0IT0=", '"secret!=value"']) {
|
||||||
|
writeFileSync(
|
||||||
|
envFile,
|
||||||
|
`${validEnvironment
|
||||||
|
.map((line) =>
|
||||||
|
line.startsWith("POSTGRES_PASSWORD=")
|
||||||
|
? `POSTGRES_PASSWORD=${password}`
|
||||||
|
: line,
|
||||||
|
)
|
||||||
|
.join("\n")}\n`,
|
||||||
|
{ mode: 0o600 },
|
||||||
|
);
|
||||||
|
chmodSync(envFile, 0o600);
|
||||||
|
|
||||||
|
const result = spawnSync("bash", [validator, envFile], { encoding: "utf8" });
|
||||||
|
assert.equal(result.status, 0, result.stderr);
|
||||||
|
assert.equal(result.stdout, "staging database environment validated\n");
|
||||||
|
assert.doesNotMatch(`${result.stdout}${result.stderr}`, /secret!=value|c2VjcmV0IT0=/);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
rmSync(root, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("database env validator rejects symlinks and unsafe modes", () => {
|
test("database env validator rejects symlinks and unsafe modes", () => {
|
||||||
const root = mkdtempSync(join(tmpdir(), "jyotisha-database-env-"));
|
const root = mkdtempSync(join(tmpdir(), "jyotisha-database-env-"));
|
||||||
const envFile = join(root, ".env.staging.database");
|
const envFile = join(root, ".env.staging.database");
|
||||||
|
|||||||
Reference in New Issue
Block a user