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) <noreply@anthropic.com>
This commit is contained in:
Paul O'Reilly
2026-03-28 21:29:08 +13:00
parent 8bd4c7253b
commit c470867039
3 changed files with 12 additions and 12 deletions

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -uo pipefail set -uo pipefail
CHARSET='A-Za-z0-9._+\-:@^~' CHARSET='A-Za-z0-9._\-'
DEFAULT_LENGTH=32 DEFAULT_LENGTH=32
usage() { usage() {
cat <<'EOF' cat <<'EOF'
Usage: gen-secret [OPTIONS] [LENGTH] 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: Arguments:
LENGTH Number of characters (default: 32) LENGTH Number of characters (default: 32)

View File

@@ -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): 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 ## Behaviour
@@ -48,7 +48,7 @@ Note: `/` is excluded because YAML plain scalars starting with `//` or containin
When `--dryrun` or `-n` is passed: 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.) (Substituting the actual length if provided.)
@@ -70,13 +70,13 @@ No random output is produced.
```bash ```bash
# Default 32-character secret # Default 32-character secret
$ gen-secret $ gen-secret
xQ9.kT3+mR7:nW2@pF5^bY8~cH4_dL6a xQ9.kT3-mR7_nW2.pF5-bY8_cH4.dL6a
# Custom length # Custom length
$ gen-secret 64 $ 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 # Dryrun
$ gen-secret -n 16 $ 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._\-]
``` ```

View File

@@ -57,12 +57,12 @@ echo
out=$("$GEN_SECRET" --dryrun 2>&1) out=$("$GEN_SECRET" --dryrun 2>&1)
assert_eq "dryrun default length" \ 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"
out=$("$GEN_SECRET" -n 16 2>&1) out=$("$GEN_SECRET" -n 16 2>&1)
assert_eq "dryrun custom length" \ 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" "$out"
# --- Help --- # --- Help ---
@@ -78,7 +78,7 @@ out=$("$GEN_SECRET" 2>&1)
rc=$? rc=$?
assert_exit "default exits 0" 0 "$rc" assert_exit "default exits 0" 0 "$rc"
assert_eq "default length is 32" 32 "${#out}" 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 --- # --- Custom length ---