| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import shutil |
| import textwrap |
| from pathlib import Path |
|
|
|
|
| ROOT = Path(__file__).resolve().parents[1] |
| TASKS_ROOT = ROOT / "tasks" |
|
|
|
|
| def dedent(value: str) -> str: |
| return textwrap.dedent(value).lstrip("\n") |
|
|
|
|
| TASKS: list[dict] = [ |
| { |
| "category": "easy", |
| "task_id": 1, |
| "title": "Cloud Provisioning", |
| "description": "Hardcoded AWS Access Key in config.py.", |
| "scan_mode": "dir", |
| "exclude_paths": [".env"], |
| "secrets": [ |
| {"id": "s1", "type": "aws_access_key", "path": "config.py", "line": 3, "visibility": "surface"}, |
| {"id": "s2", "type": "aws_access_key", "path": "config.py", "line": 3, "visibility": "surface"}, |
| {"id": "s3", "type": "assignment_secret", "path": "config.py", "line": 3, "visibility": "shallow"}, |
| ], |
| "files": { |
| "config.py": """ |
| import os |
| |
| AWS_ACCESS_KEY_ID = "AKIA1234567890ABCDEF" |
| REGION = "us-east-1" |
| |
| |
| def load_config() -> dict[str, str]: |
| return { |
| "aws_access_key_id": os.getenv("AWS_ACCESS_KEY_ID", AWS_ACCESS_KEY_ID), |
| "region": REGION, |
| } |
| """, |
| "tests/test_config.py": """ |
| import importlib |
| |
| |
| def test_load_config_uses_env_override(monkeypatch): |
| monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAZZZZZZZZZZZZZZZZ") |
| config = importlib.import_module("config") |
| assert config.load_config()["aws_access_key_id"] == "AKIAZZZZZZZZZZZZZZZZ" |
| |
| |
| def test_region_is_stable(): |
| config = importlib.import_module("config") |
| assert config.load_config()["region"] == "us-east-1" |
| """, |
| }, |
| }, |
| { |
| "category": "easy", |
| "task_id": 2, |
| "title": "Database Layer", |
| "description": "Password embedded in a raw SQL connection string.", |
| "scan_mode": "dir", |
| "exclude_paths": [".env"], |
| "secrets": [ |
| {"id": "s1", "type": "connection_string", "path": "db.py", "line": 3, "visibility": "surface"}, |
| {"id": "s2", "type": "connection_string", "path": "db.py", "line": 3, "visibility": "surface"}, |
| {"id": "s3", "type": "assignment_secret", "path": "db.py", "line": 3, "visibility": "shallow"}, |
| ], |
| "files": { |
| "db.py": """ |
| import os |
| |
| DATABASE_URL = "postgres://appuser:Sup3rS3cretP4ss@db.internal:5432/app" |
| |
| |
| def connection_string() -> str: |
| return os.getenv("DATABASE_URL", DATABASE_URL) |
| |
| |
| def connection_summary() -> str: |
| return connection_string().split("@", maxsplit=1)[-1] |
| """, |
| "tests/test_db.py": """ |
| import importlib |
| |
| |
| def test_connection_summary_is_host_only(monkeypatch): |
| monkeypatch.setenv("DATABASE_URL", "postgres://demo:pw@db.test:5432/demo") |
| db = importlib.import_module("db") |
| assert db.connection_summary() == "db.test:5432/demo" |
| """, |
| }, |
| }, |
| { |
| "category": "easy", |
| "task_id": 3, |
| "title": "Frontend Config", |
| "description": "Firebase API key exposed in a client-side config file.", |
| "scan_mode": "dir", |
| "exclude_paths": [".env"], |
| "secrets": [ |
| {"id": "s1", "type": "firebase_api_key", "path": "settings.js", "line": 2, "visibility": "surface"}, |
| {"id": "s2", "type": "firebase_api_key", "path": "settings.js", "line": 2, "visibility": "surface"}, |
| {"id": "s3", "type": "assignment_secret", "path": "settings.js", "line": 2, "visibility": "shallow"}, |
| ], |
| "files": { |
| "settings.js": """ |
| const firebaseConfig = { |
| apiKey: "AIzaSyA12345678901234567890123456789012", |
| authDomain: "demo.firebaseapp.com", |
| projectId: "demo-project", |
| }; |
| |
| export function getFirebaseConfig() { |
| return firebaseConfig; |
| } |
| """, |
| "tests/test_settings.py": """ |
| from pathlib import Path |
| |
| |
| def test_settings_exports_a_config_accessor(): |
| settings = Path("settings.js").read_text(encoding="utf-8") |
| assert "getFirebaseConfig" in settings |
| assert "projectId" in settings |
| """, |
| }, |
| }, |
| { |
| "category": "easy", |
| "task_id": 4, |
| "title": "System Logging", |
| "description": "Debug logging leaks a user token.", |
| "scan_mode": "dir", |
| "exclude_paths": [], |
| "secrets": [ |
| {"id": "s1", "type": "github_token", "path": "logger.py", "line": 4, "visibility": "surface"}, |
| {"id": "s2", "type": "github_token", "path": "logger.py", "line": 4, "visibility": "surface"}, |
| {"id": "s3", "type": "assignment_secret","path": "logger.py", "line": 4, "visibility": "shallow"}, |
| ], |
| "files": { |
| "logger.py": """ |
| import logging |
| |
| logger = logging.getLogger("audit") |
| USER_TOKEN = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
| |
| |
| def emit_debug_trace(user_id: str) -> bool: |
| logger.debug("user=%s token=%s", user_id, USER_TOKEN) |
| return True |
| """, |
| "tests/test_logger.py": """ |
| import importlib |
| |
| |
| def test_emit_debug_trace_returns_true(): |
| logger_mod = importlib.import_module("logger") |
| assert logger_mod.emit_debug_trace("alice") is True |
| """, |
| }, |
| }, |
| { |
| "category": "easy", |
| "task_id": 5, |
| "title": "Git Basics", |
| "description": "A tracked .env file leaks credentials in the working tree.", |
| "scan_mode": "dir", |
| "exclude_paths": [], |
| "secrets": [ |
| {"id": "s1", "type": "service_token", "path": ".env", "line": 1, "visibility": "surface"}, |
| {"id": "s2", "type": "service_token", "path": ".env", "line": 1, "visibility": "surface"}, |
| {"id": "s3", "type": "assignment_secret","path": ".env", "line": 1, "visibility": "shallow"}, |
| ], |
| "files": { |
| ".env": """ |
| SERVICE_TOKEN=ghp_1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ |
| """, |
| "app.py": """ |
| import os |
| |
| |
| def service_token() -> str: |
| return os.environ["SERVICE_TOKEN"] |
| """, |
| "tests/test_app.py": """ |
| import importlib |
| |
| |
| def test_service_token_is_loaded_from_environment(): |
| app = importlib.import_module("app") |
| assert app.service_token().startswith("ghp_") |
| """, |
| }, |
| }, |
| { |
| "category": "medium", |
| "task_id": 6, |
| "title": "Utility Module", |
| "description": "A base64-encoded auth token hides in utils.py.", |
| "scan_mode": "dir", |
| "exclude_paths": [".env"], |
| "secrets": [ |
| {"id": "s1", "type": "base64_github_token", "path": "utils.py", "line": 4, "visibility": "surface"}, |
| {"id": "s2", "type": "assignment_secret", "path": "utils.py", "line": 4, "visibility": "shallow"}, |
| {"id": "s3", "type": "github_token", "path": "utils.py", "line": 4, "visibility": "deep", |
| "requires_action": "inspect_encoded"}, |
| ], |
| "files": { |
| "utils.py": """ |
| import base64 |
| import os |
| |
| ENCODED_AUTH_TOKEN = "Z2hwXzEyMzQ1Njc4OTBBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjEy" |
| |
| |
| def auth_token() -> str: |
| encoded = os.getenv("AUTH_TOKEN_B64", ENCODED_AUTH_TOKEN) |
| return base64.b64decode(encoded).decode("utf-8") |
| """, |
| "tests/test_utils.py": """ |
| import base64 |
| import importlib |
| |
| |
| def test_auth_token_uses_env_override(monkeypatch): |
| monkeypatch.setenv("AUTH_TOKEN_B64", base64.b64encode(b"override-token").decode("utf-8")) |
| utils = importlib.import_module("utils") |
| assert utils.auth_token() == "override-token" |
| """, |
| }, |
| }, |
| { |
| "category": "medium", |
| "task_id": 7, |
| "title": "CI/CD Pipeline", |
| "description": "A deployment workflow prints a secret directly to logs.", |
| "scan_mode": "dir", |
| "exclude_paths": [], |
| "secrets": [ |
| {"id": "s1", "type": "github_token", "path": "deploy.yml", "line": 8, "visibility": "surface"}, |
| {"id": "s2", "type": "assignment_secret","path": "deploy.yml", "line": 8, "visibility": "shallow"}, |
| {"id": "s3", "type": "github_token", "path": "deploy.yml", "line": 8, "visibility": "deep", |
| "requires_action": "inspect_git_history"}, |
| ], |
| "files": { |
| "deploy.yml": """ |
| name: deploy |
| on: [push] |
| jobs: |
| deploy: |
| runs-on: ubuntu-latest |
| steps: |
| - uses: actions/checkout@v4 |
| - run: echo "deploying with ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
| """, |
| "tests/test_deploy.py": """ |
| from pathlib import Path |
| import yaml |
| |
| |
| def test_deploy_workflow_parses_as_yaml(): |
| data = yaml.safe_load(Path("deploy.yml").read_text(encoding="utf-8")) |
| assert "jobs" in data |
| assert "deploy" in data["jobs"] |
| """, |
| }, |
| }, |
| { |
| "category": "medium", |
| "task_id": 8, |
| "title": "Noise Filtering", |
| "description": "High-entropy dummy values are mixed with one real secret in TOML.", |
| "scan_mode": "dir", |
| "exclude_paths": [], |
| "secrets": [ |
| {"id": "s1", "type": "github_token", "path": "app.toml", "line": 6, "visibility": "surface"}, |
| {"id": "s2", "type": "assignment_secret","path": "app.toml", "line": 6, "visibility": "shallow"}, |
| {"id": "s3", "type": "github_token", "path": "app.toml", "line": 6, "visibility": "deep", |
| "requires_action": "inspect_git_history"}, |
| ], |
| "files": { |
| "app.toml": """ |
| [noise] |
| dummy_token = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB" |
| sample_key = "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDD" |
| |
| [production] |
| access_key = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
| """, |
| "tests/test_toml.py": """ |
| from pathlib import Path |
| import tomllib |
| |
| |
| def test_toml_config_is_parseable(): |
| data = tomllib.loads(Path("app.toml").read_text(encoding="utf-8")) |
| assert "noise" in data |
| assert "production" in data |
| """, |
| }, |
| }, |
| { |
| "category": "medium", |
| "task_id": 9, |
| "title": "DB Migration", |
| "description": "A legacy migration embeds administrator credentials.", |
| "scan_mode": "dir", |
| "exclude_paths": [], |
| "secrets": [ |
| {"id": "s1", "type": "sql_password", "path": "migrate.sql", "line": 1, "visibility": "surface"}, |
| {"id": "s2", "type": "assignment_secret","path": "migrate.sql", "line": 1, "visibility": "shallow"}, |
| {"id": "s3", "type": "sql_password", "path": "migrate.sql", "line": 1, "visibility": "deep", |
| "requires_action": "inspect_git_history"}, |
| ], |
| "files": { |
| "migrate.sql": """ |
| CREATE ROLE admin WITH LOGIN PASSWORD 'Sup3rS3cretPass!'; |
| CREATE TABLE widgets ( |
| id SERIAL PRIMARY KEY, |
| name TEXT NOT NULL |
| ); |
| """, |
| "tests/test_migrate.py": """ |
| from pathlib import Path |
| |
| |
| def test_migration_keeps_table_creation(): |
| sql = Path("migrate.sql").read_text(encoding="utf-8") |
| assert "CREATE TABLE widgets" in sql |
| assert sql.strip().endswith(";") |
| """, |
| }, |
| }, |
| { |
| "category": "medium", |
| "task_id": 10, |
| "title": "Deployment", |
| "description": "A multiline RSA private key is embedded in a shell script.", |
| "scan_mode": "dir", |
| "exclude_paths": [".env"], |
| "secrets": [ |
| {"id": "s1", "type": "private_key", "path": "deploy.sh", "line": 4, "visibility": "surface"}, |
| {"id": "s2", "type": "assignment_secret","path": "deploy.sh", "line": 4, "visibility": "shallow"}, |
| {"id": "s3", "type": "private_key", "path": "deploy.sh", "line": 4, "visibility": "deep", |
| "requires_action": "inspect_encoded"}, |
| ], |
| "files": { |
| "deploy.sh": """ |
| #!/usr/bin/env bash |
| set -euo pipefail |
| |
| PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\\nMIIEpAIBAAKCAQEAu6d5k5S8yM2ExampleLineOne\\nEXAMPLELineTwoAndStillSensitive\\n-----END RSA PRIVATE KEY-----" |
| |
| write_key() { |
| printf "%b" "${PRIVATE_KEY}" > ./deploy.key |
| printf "%s" "wrote-key" |
| } |
| |
| if [[ "${1:-}" == "write" ]]; then |
| write_key |
| fi |
| """, |
| "tests/test_deploy_sh.py": """ |
| import subprocess |
| |
| |
| def test_deploy_script_runs(): |
| completed = subprocess.run( |
| ["bash", "deploy.sh", "write"], |
| check=True, |
| capture_output=True, |
| text=True, |
| ) |
| assert completed.stdout == "wrote-key" |
| """, |
| }, |
| }, |
| { |
| "category": "hard", |
| "task_id": 11, |
| "title": "Microservices", |
| "description": "The same API key is duplicated across five services.", |
| "scan_mode": "dir", |
| "exclude_paths": [".env"], |
| "secrets": [ |
| {"id": "s1", "type": "github_token", "path": "service_a.py", "line": 3, "visibility": "surface"}, |
| {"id": "s2", "type": "github_token", "path": "service_b.py", "line": 3, "visibility": "deep", |
| "requires_action": "inspect_file"}, |
| {"id": "s3", "type": "github_token", "path": "service_c.py", "line": 3, "visibility": "cascading", |
| "trigger_secret_id": "s1"}, |
| ], |
| "conflict_map": { |
| "s1": { |
| "reveals": ["s3"], |
| "blocks": [], |
| "note": "Remediating service_a exposes the cascading leak in service_c which uses the same fallback key" |
| }, |
| "s2": { |
| "reveals": [], |
| "blocks": ["s3"], |
| "note": "service_b key must be rotated before service_c rotation is safe" |
| } |
| }, |
| "files": { |
| "service_a.py": """ |
| import os |
| |
| API_KEY = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
| |
| |
| def outbound_header() -> dict[str, str]: |
| return {"Authorization": f"Bearer {os.getenv('SERVICE_API_KEY', API_KEY)}"} |
| """, |
| "service_b.py": """ |
| import os |
| |
| API_KEY = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
| |
| |
| def outbound_header() -> dict[str, str]: |
| return {"Authorization": f"Bearer {os.getenv('SERVICE_API_KEY', API_KEY)}"} |
| """, |
| "service_c.py": """ |
| import os |
| |
| API_KEY = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
| |
| |
| def outbound_header() -> dict[str, str]: |
| return {"Authorization": f"Bearer {os.getenv('SERVICE_API_KEY', API_KEY)}"} |
| """, |
| "service_d.py": """ |
| import os |
| |
| API_KEY = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
| |
| |
| def outbound_header() -> dict[str, str]: |
| return {"Authorization": f"Bearer {os.getenv('SERVICE_API_KEY', API_KEY)}"} |
| """, |
| "service_e.py": """ |
| import os |
| |
| API_KEY = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
| |
| |
| def outbound_header() -> dict[str, str]: |
| return {"Authorization": f"Bearer {os.getenv('SERVICE_API_KEY', API_KEY)}"} |
| """, |
| "tests/test_services.py": """ |
| import importlib |
| |
| |
| def test_all_services_use_the_same_override(monkeypatch): |
| monkeypatch.setenv("SERVICE_API_KEY", "shared-token") |
| for name in ["service_a", "service_b", "service_c", "service_d", "service_e"]: |
| module = importlib.import_module(name) |
| assert module.outbound_header()["Authorization"] == "Bearer shared-token" |
| """, |
| }, |
| }, |
| { |
| "category": "hard", |
| "task_id": 12, |
| "title": "Deep Logic", |
| "description": "A secret is embedded as a local variable inside a function.", |
| "scan_mode": "dir", |
| "exclude_paths": [".env"], |
| "secrets": [ |
| {"id": "s1", "type": "github_token", "path": "crypto.py", "line": 4, "visibility": "surface"}, |
| {"id": "s2", "type": "assignment_secret","path": "crypto.py", "line": 4, "visibility": "deep", |
| "requires_action": "inspect_git_history"}, |
| {"id": "s3", "type": "github_token", "path": "crypto.py", "line": 4, "visibility": "cascading", |
| "trigger_secret_id": "s1"}, |
| ], |
| "conflict_map": { |
| "s1": { |
| "reveals": ["s3"], |
| "blocks": [], |
| "note": "Removing the inline secret reveals that the function still falls back to env var without validation" |
| }, |
| "s2": { |
| "reveals": [], |
| "blocks": ["s3"], |
| "note": "Git history secret must be purged before the cascading env-var leak can be addressed" |
| } |
| }, |
| "files": { |
| "crypto.py": """ |
| import os |
| |
| |
| def derive_signature(payload: str) -> str: |
| secret = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
| active = os.getenv("CRYPTO_SECRET", secret) |
| return f"{payload}:{active[-8:]}" |
| """, |
| "tests/test_crypto.py": """ |
| import importlib |
| |
| |
| def test_signature_uses_runtime_secret(monkeypatch): |
| monkeypatch.setenv("CRYPTO_SECRET", "12345678override") |
| crypto = importlib.import_module("crypto") |
| assert crypto.derive_signature("payload") == "payload:override" |
| """, |
| }, |
| }, |
| { |
| "category": "hard", |
| "task_id": 13, |
| "title": "Legacy Audit", |
| "description": "A secret was committed in v1.0 and still exists in Git history.", |
| "scan_mode": "git", |
| "exclude_paths": [".env"], |
| "build_script": "build_repo.py", |
| "secrets": [ |
| {"id": "s1", "type": "github_token", "path": "config.py", "line": 1, "visibility": "surface"}, |
| {"id": "s2", "type": "github_token", "path": "config.py", "line": 1, "visibility": "deep", |
| "requires_action": "inspect_git_history"}, |
| {"id": "s3", "type": "assignment_secret","path": "config.py", "line": 1, "visibility": "cascading", |
| "trigger_secret_id": "s1"}, |
| ], |
| "conflict_map": { |
| "s1": { |
| "reveals": ["s3"], |
| "blocks": [], |
| "note": "Removing the live token exposes the fact the git history still leaks the original v1.0 value" |
| }, |
| "s2": { |
| "reveals": [], |
| "blocks": ["s3"], |
| "note": "Git history must be rewritten (filter-repo) before the cascading env-var secret can be resolved" |
| } |
| }, |
| "files": { |
| "build_repo.py": """ |
| from __future__ import annotations |
| |
| import argparse |
| import shutil |
| import subprocess |
| import textwrap |
| from pathlib import Path |
| |
| |
| def write(path: Path, content: str) -> None: |
| path.parent.mkdir(parents=True, exist_ok=True) |
| path.write_text(textwrap.dedent(content).lstrip("\\n"), encoding="utf-8") |
| |
| |
| def run(command: list[str], cwd: Path) -> None: |
| subprocess.run(command, cwd=cwd, check=True, capture_output=True, text=True) |
| |
| |
| def main() -> None: |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--output", required=True) |
| args = parser.parse_args() |
| |
| output = Path(args.output).resolve() |
| if output.exists(): |
| shutil.rmtree(output) |
| output.mkdir(parents=True, exist_ok=True) |
| |
| run(["git", "init"], output) |
| run(["git", "config", "user.name", "SecretsAuditEnv"], output) |
| run(["git", "config", "user.email", "benchmark@example.com"], output) |
| |
| write( |
| output / "config.py", |
| ''' |
| API_TOKEN = "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" |
| |
| |
| def get_api_token() -> str: |
| return API_TOKEN |
| ''', |
| ) |
| run(["git", "add", "config.py"], output) |
| run(["git", "commit", "-m", "v1.0 token"], output) |
| |
| write( |
| output / "README.md", |
| ''' |
| # Legacy service |
| |
| This repository has a long audit trail. |
| ''', |
| ) |
| run(["git", "add", "README.md"], output) |
| run(["git", "commit", "-m", "docs"], output) |
| |
| write( |
| output / "app.py", |
| ''' |
| import os |
| |
| from config import get_api_token |
| |
| |
| def active_token() -> str: |
| return os.getenv("LEGACY_TOKEN", get_api_token()) |
| ''', |
| ) |
| write( |
| output / "tests/test_app.py", |
| ''' |
| import importlib |
| |
| |
| def test_active_token_uses_runtime_override(monkeypatch): |
| monkeypatch.setenv("LEGACY_TOKEN", "runtime-token") |
| app = importlib.import_module("app") |
| assert app.active_token() == "runtime-token" |
| ''', |
| ) |
| run(["git", "add", "app.py", "tests/test_app.py"], output) |
| run(["git", "commit", "-m", "add runtime app"], output) |
| |
| |
| if __name__ == "__main__": |
| main() |
| """, |
| }, |
| }, |
| ] |
|
|
|
|
| def write_task(task: dict) -> None: |
| task_dir = TASKS_ROOT / task["category"] / f"task_{task['task_id']:02d}" |
| if task_dir.exists(): |
| shutil.rmtree(task_dir) |
| task_dir.mkdir(parents=True, exist_ok=True) |
|
|
| metadata: dict = { |
| "id": task["task_id"], |
| "category": task["category"], |
| "title": task["title"], |
| "description": task["description"], |
| "scan_mode": task["scan_mode"], |
| "exclude_paths": task["exclude_paths"], |
| "secrets": task.get("secrets", []), |
| } |
| if "build_script" in task: |
| metadata["build_script"] = task["build_script"] |
| if "conflict_map" in task: |
| metadata["conflict_map"] = task["conflict_map"] |
|
|
| (task_dir / "task.json").write_text(json.dumps(metadata, indent=2) + "\n", encoding="utf-8") |
| for relative_path, content in task["files"].items(): |
| target = task_dir / relative_path |
| target.parent.mkdir(parents=True, exist_ok=True) |
| target.write_text(dedent(content), encoding="utf-8") |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser(description="Generate the SecretsAuditEnv task corpus.") |
| parser.parse_args() |
| for category_dir in [TASKS_ROOT / "easy", TASKS_ROOT / "medium", TASKS_ROOT / "hard"]: |
| category_dir.mkdir(parents=True, exist_ok=True) |
| for task in TASKS: |
| write_task(task) |
| print(f"Generated {len(TASKS)} tasks.") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|