Skip to content

Commit fc4a6ad

Browse files
ivanprytulaclaude
andcommitted
fix(security): write generated secrets to a 0600 file instead of stdout
CodeQL flagged py/clear-text-logging-sensitive-data on generate-secrets.py: _print_secret printed each freshly-generated secret value straight to stdout, which can land in terminal scrollback, screen-recording tools, or shared sessions. Values now go to .generated.secrets.env (mode 0600, truncated at the start of each run so stale secrets from a prior invocation never linger), gitignored so it can never be committed. Only the env var name and a "wrote to <path>" confirmation print to stdout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 86db5e3 commit fc4a6ad

2 files changed

Lines changed: 25 additions & 2 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ celerybeat.pid
135135
# For per-environment overrides (e.g. .env.development.local, .env.production.local, etc.) that should not be shared
136136
.env.*.local
137137

138+
# Output of scripts/tools/generate-secrets.py — NEVER commit this
139+
.generated.secrets.env
140+
138141
.venv
139142
env/
140143
venv/

scripts/tools/generate-secrets.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import argparse
1717
import secrets
1818
import string
19+
from pathlib import Path
1920

2021

2122
# ── alphabet definitions ──────────────────────────────────────────────────────
@@ -78,6 +79,23 @@ def _bits_for(length: int) -> int:
7879
return length * 6 # rough: ~6 bits/char for mixed alpha
7980

8081

82+
# ── output file ──────────────────────────────────────────────────────────────
83+
84+
# Overwritten (not appended) on every run so it never accumulates secrets from
85+
# prior invocations. Must stay out of version control — see .gitignore.
86+
_OUTPUT_ENV_PATH = Path(".generated.secrets.env")
87+
_output_file_started = False
88+
89+
90+
def _write_secret_line(env_var: str, value: str) -> None:
91+
global _output_file_started
92+
mode = "w" if not _output_file_started else "a"
93+
with _OUTPUT_ENV_PATH.open(mode, encoding="utf-8") as f:
94+
f.write(f"{env_var}={value}\n")
95+
_output_file_started = True
96+
_OUTPUT_ENV_PATH.chmod(0o600)
97+
98+
8199
# ── display ───────────────────────────────────────────────────────────────────
82100

83101

@@ -142,15 +160,17 @@ def _bits_for(length: int) -> int:
142160
def _print_secret(env_var: str, value: str, label: str, desc: str) -> None:
143161
print(f"# ── {label} ──")
144162
print(f"# {desc}")
145-
print(f"{env_var}={value}")
163+
_write_secret_line(env_var, value)
164+
print(f"# Wrote {env_var} to {_OUTPUT_ENV_PATH}")
146165
print()
147166

148167

149168
def _print_all() -> None:
150169
for env_var, value, label, desc in _SECRET_SPECS:
151170
_print_secret(env_var, value, label, desc)
152171

153-
print("# Copy the lines above into your .env and .env.example")
172+
print(f"# Secrets were written to {_OUTPUT_ENV_PATH}")
173+
print("# Copy values from that file into your .env and .env.example")
154174
print("# Regenerate any time before deploying to a new environment.")
155175
print("# Store a backup in your password manager / vault.")
156176

0 commit comments

Comments
 (0)