From c470867039befc341c24ffa03feaafc361507b7e Mon Sep 17 00:00:00 2001 From: Paul O'Reilly Date: Sat, 28 Mar 2026 21:29:08 +1300 Subject: [PATCH] Remove URL-unsafe characters from gen-secret charset Remove ^, +, ~, :, @ from the allowed charset. The ^ character breaks SQLAlchemy DATABASE_URL parsing, + becomes space in URL query strings, : and @ are URL delimiters. The remaining charset (A-Za-z0-9._-) is safe in URLs, database connection strings, YAML, JSON, and shell without any encoding. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/gen-secret | 4 ++-- specs/gen-secret.spec.md | 14 +++++++------- tests/test-gen-secret.sh | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/gen-secret b/scripts/gen-secret index 3128104..43ca4c3 100755 --- a/scripts/gen-secret +++ b/scripts/gen-secret @@ -1,14 +1,14 @@ #!/usr/bin/env bash set -uo pipefail -CHARSET='A-Za-z0-9._+\-:@^~' +CHARSET='A-Za-z0-9._\-' DEFAULT_LENGTH=32 usage() { cat <<'EOF' Usage: gen-secret [OPTIONS] [LENGTH] -Generate a cryptographically random string safe for bash, YAML, and JSON. +Generate a cryptographically random string safe for bash, YAML, JSON, and URLs. Arguments: LENGTH Number of characters (default: 32) diff --git a/specs/gen-secret.spec.md b/specs/gen-secret.spec.md index 02cf4e4..a0a64fa 100644 --- a/specs/gen-secret.spec.md +++ b/specs/gen-secret.spec.md @@ -28,12 +28,12 @@ gen-secret [OPTIONS] [LENGTH] The output uses only characters that need no escaping in bash (unquoted assignment), YAML (plain scalar), and JSON (string value): ``` -A-Z a-z 0-9 . _ + - : @ ^ ~ +A-Z a-z 0-9 . _ - ``` -**Excluded** (unsafe in at least one context): `"`, `'`, `\`, `` ` ``, `$`, `!`, `{`, `}`, `(`, `)`, `[`, `]`, `#`, `%`, `&`, `|`, `<`, `>`, `*`, `?`, `;`, `,`, `=`, space, tab, newline, `/` +**Excluded** (unsafe in at least one context): `"`, `'`, `\`, `` ` ``, `$`, `!`, `{`, `}`, `(`, `)`, `[`, `]`, `#`, `%`, `&`, `|`, `<`, `>`, `*`, `?`, `;`, `,`, `=`, `+`, `^`, `~`, `:`, `@`, space, tab, newline, `/` -Note: `/` is excluded because YAML plain scalars starting with `//` or containing `#` after a space can cause issues, and removing `/` keeps the set simpler without meaningful entropy loss. +Note: `^` breaks URL parsing (e.g., SQLAlchemy DATABASE_URL). `+` becomes space in URL query strings. `:` and `@` are URL delimiters (userinfo/host separators). `~` can be shell-expanded. These are excluded to ensure secrets are safe in database connection strings, URLs, and shell contexts without encoding. ## Behaviour @@ -48,7 +48,7 @@ Note: `/` is excluded because YAML plain scalars starting with `//` or containin When `--dryrun` or `-n` is passed: ``` -[dryrun] Would generate a 32-character secret from charset: [A-Za-z0-9._+\-:@^~] +[dryrun] Would generate a 32-character secret from charset: [A-Za-z0-9._\-] ``` (Substituting the actual length if provided.) @@ -70,13 +70,13 @@ No random output is produced. ```bash # Default 32-character secret $ gen-secret -xQ9.kT3+mR7:nW2@pF5^bY8~cH4_dL6a +xQ9.kT3-mR7_nW2.pF5-bY8_cH4.dL6a # Custom length $ gen-secret 64 -xQ9.kT3+mR7:nW2@pF5^bY8~cH4_dL6axQ9.kT3+mR7:nW2@pF5^bY8~cH4_dL6a +xQ9.kT3-mR7_nW2.pF5-bY8_cH4.dL6axQ9.kT3-mR7_nW2.pF5-bY8_cH4.dL6a # Dryrun $ gen-secret -n 16 -[dryrun] Would generate a 16-character secret from charset: [A-Za-z0-9._+\-:@^~] +[dryrun] Would generate a 16-character secret from charset: [A-Za-z0-9._\-] ``` diff --git a/tests/test-gen-secret.sh b/tests/test-gen-secret.sh index f422434..0c4ff1e 100755 --- a/tests/test-gen-secret.sh +++ b/tests/test-gen-secret.sh @@ -57,12 +57,12 @@ echo out=$("$GEN_SECRET" --dryrun 2>&1) assert_eq "dryrun default length" \ - '[dryrun] Would generate a 32-character secret from charset: [A-Za-z0-9._+\-:@^~]' \ + '[dryrun] Would generate a 32-character secret from charset: [A-Za-z0-9._\-]' \ "$out" out=$("$GEN_SECRET" -n 16 2>&1) assert_eq "dryrun custom length" \ - '[dryrun] Would generate a 16-character secret from charset: [A-Za-z0-9._+\-:@^~]' \ + '[dryrun] Would generate a 16-character secret from charset: [A-Za-z0-9._\-]' \ "$out" # --- Help --- @@ -78,7 +78,7 @@ out=$("$GEN_SECRET" 2>&1) rc=$? assert_exit "default exits 0" 0 "$rc" assert_eq "default length is 32" 32 "${#out}" -assert_match "default uses safe charset" '^[A-Za-z0-9._+:@^~-]+$' "$out" +assert_match "default uses safe charset" '^[A-Za-z0-9._-]+$' "$out" # --- Custom length ---