Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions diagnostic/build-45f49127.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"generated_at": "2026-06-24T02:30:03.682131+00:00",
"commit": "45f49127",
"diagnostic_logd": "diagnostic/build-45f49127.logd",
"diagnostic_logd_error": null,
"message_blocker": null,
"chunked": false,
"chunk_size_bytes": null,
"password": "bc8ff9d39d3268fbcc63",
"decrypt_command": "encryptly unpack diagnostic/build-45f49127.logd <outdir> --password bc8ff9d39d3268fbcc63",
"total_modules": 1,
"passed": 1,
"failed": 0,
"modules": [
{
"name": "python-config-tests",
"status": "PASS",
"elapsed_seconds": 0.0,
"artifact": null,
"output": "5 unittest cases passed; development output passed; production missing secrets failed; production with secrets passed with redacted output"
}
],
"pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic/build-45f49127.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging."
}
Binary file added diagnostic/build-45f49127.logd
Binary file not shown.
28 changes: 28 additions & 0 deletions docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,34 @@ was dissolved in the 2023 reorg. The current model uses a simple linear
regression based on the last 6 months of data, which doesn't account
for seasonality or business cycles.

## Production Configuration Secret Validation

`tools/config_generator.py --env production` validates required production
secrets before writing or printing generated configuration. The generator reads
these values from environment variables:

| Config key | Environment variable |
|------------|----------------------|
| `database.password` | `DATABASE_PASSWORD` |
| `redis.password` | `REDIS_PASSWORD` |
| `auth.jwt_secret` | `AUTH_JWT_SECRET` |

Empty and placeholder-like values such as `change-me`, `placeholder`,
`replace-*`, and `your-secret-here` fail validation. Error messages identify the
missing config key but never print secret values.

Example:

```bash
DATABASE_PASSWORD="$DATABASE_PASSWORD" \
REDIS_PASSWORD="$REDIS_PASSWORD" \
AUTH_JWT_SECRET="$AUTH_JWT_SECRET" \
python3 tools/config_generator.py --env production --format yaml --output config.yaml
```

Development and staging generation remain compatible and do not require these
production secret variables.

## Security

### Access Control
Expand Down
84 changes: 84 additions & 0 deletions tests/test_config_generator_production_secrets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import importlib.util
import os
from pathlib import Path
import unittest
from unittest.mock import patch


MODULE_PATH = Path(__file__).resolve().parents[1] / "tools" / "config_generator.py"
spec = importlib.util.spec_from_file_location("config_generator", MODULE_PATH)
config_generator = importlib.util.module_from_spec(spec)
spec.loader.exec_module(config_generator)


class ProductionSecretValidationTest(unittest.TestCase):
def test_production_config_requires_all_secret_keys(self):
with patch.dict(os.environ, {}, clear=True):
config = config_generator.generate_config("production")

valid, errors = config_generator.validate_production_secrets(config)

self.assertFalse(valid)
self.assertEqual(
errors,
[
"database.password is required for production",
"redis.password is required for production",
"auth.jwt_secret is required for production",
],
)

def test_production_config_accepts_environment_secrets(self):
env = {
"DATABASE_PASSWORD": "database-secret-value",
"REDIS_PASSWORD": "redis-secret-value",
"AUTH_JWT_SECRET": "jwt-secret-value",
}

with patch.dict(os.environ, env, clear=True):
config = config_generator.generate_config("production")

valid, errors = config_generator.validate_production_secrets(config)

self.assertTrue(valid)
self.assertEqual(errors, [])

def test_placeholder_secret_values_are_rejected_without_leaking_value(self):
overrides = {
"database": {"password": "change-me"},
"redis": {"password": "replace-this"},
"auth": {"jwt_secret": "your-secret-here"},
}

with patch.dict(os.environ, {}, clear=True):
config = config_generator.generate_config("production", overrides)

valid, errors = config_generator.validate_production_secrets(config)
joined_errors = "\n".join(errors)

self.assertFalse(valid)
self.assertIn("database.password", joined_errors)
self.assertIn("redis.password", joined_errors)
self.assertIn("auth.jwt_secret", joined_errors)
self.assertNotIn("change-me", joined_errors)
self.assertNotIn("replace-this", joined_errors)
self.assertNotIn("your-secret-here", joined_errors)

def test_non_production_generation_remains_compatible(self):
with patch.dict(os.environ, {}, clear=True):
development = config_generator.generate_config("development")
staging = config_generator.generate_config("staging")

self.assertEqual(development["app"]["environment"], "development")
self.assertEqual(staging["app"]["environment"], "staging")
self.assertEqual(development["database"]["password"], "")
self.assertEqual(staging["redis"]["password"], "")

def test_sensitive_key_list_has_no_duplicates(self):
keys = config_generator.SENSITIVE_KEYS

self.assertEqual(sorted(keys), sorted(set(keys)))


if __name__ == "__main__":
unittest.main()
85 changes: 81 additions & 4 deletions tools/config_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
"""

import argparse
import copy
import json
import os
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Tuple

try:
import yaml
Expand Down Expand Up @@ -168,9 +169,30 @@

SENSITIVE_KEYS = [
"database.password", "redis.password", "auth.jwt_secret",
"auth.jwt_secret", "auth.jwt_secret",
]

PRODUCTION_SECRET_ENV_VARS = {
"database.password": "DATABASE_PASSWORD",
"redis.password": "REDIS_PASSWORD",
"auth.jwt_secret": "AUTH_JWT_SECRET",
}

PLACEHOLDER_SECRET_VALUES = {
"",
"changeme",
"change-me",
"change_me",
"default",
"example",
"password",
"placeholder",
"secret",
"todo",
"your-secret",
"your-secret-here",
"***redacted***",
}


def merge_config(base: Dict, override: Dict) -> Dict:
result = dict(base)
Expand All @@ -183,14 +205,61 @@ def merge_config(base: Dict, override: Dict) -> Dict:


def generate_config(env: str, overrides: Optional[Dict] = None) -> Dict:
config = dict(DEFAULT_CONFIG)
config = copy.deepcopy(DEFAULT_CONFIG)
if env in ENV_OVERRIDES:
config = merge_config(config, ENV_OVERRIDES[env])
if overrides:
config = merge_config(config, overrides)
if env == "production":
config = apply_environment_secrets(config)
return config


def get_nested(config: Dict, dotted_key: str) -> Any:
value: Any = config
for part in dotted_key.split("."):
if not isinstance(value, dict) or part not in value:
return None
value = value[part]
return value


def set_nested(config: Dict, dotted_key: str, value: Any) -> None:
current = config
parts = dotted_key.split(".")
for part in parts[:-1]:
current = current.setdefault(part, {})
current[parts[-1]] = value


def is_placeholder_secret(value: Any) -> bool:
if value is None:
return True
if not isinstance(value, str):
return False
normalized = value.strip().lower()
if normalized in PLACEHOLDER_SECRET_VALUES:
return True
return normalized.startswith("replace-") or normalized.startswith("replace_")


def apply_environment_secrets(config: Dict) -> Dict:
result = copy.deepcopy(config)
for dotted_key, env_var in PRODUCTION_SECRET_ENV_VARS.items():
env_value = os.environ.get(env_var)
if env_value is not None:
set_nested(result, dotted_key, env_value)
return result


def validate_production_secrets(config: Dict) -> Tuple[bool, List[str]]:
errors = []
for dotted_key in PRODUCTION_SECRET_ENV_VARS:
if is_placeholder_secret(get_nested(config, dotted_key)):
errors.append(f"{dotted_key} is required for production")
return not errors, errors


def mask_sensitive(config: Dict, prefix: str = "") -> Dict:
masked = {}
for key, value in config.items():
Expand Down Expand Up @@ -320,6 +389,14 @@ def main():
args = parse_args()
config = generate_config(args.env)

if args.env == "production":
valid, errors = validate_production_secrets(config)
if not valid:
print("Production configuration validation failed:", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
return 1

if not args.show_sensitive:
display_config = mask_sensitive(config)
else:
Expand Down Expand Up @@ -350,4 +427,4 @@ def main():


if __name__ == "__main__":
main()
sys.exit(main())