@@ -36,8 +36,44 @@ exhaustion) rather than data disclosure. Full rationale and the guarded-SQL thre
3636 themselves, not sensitive data — the cap is about log-line hygiene (one pathological query
3737 can't inflate a line), not confidentiality. Client IPs are not logged at the application level
3838 (Cloud Run's own request log already has caller IP, correlatable by timestamp).
39- - ** CI checks** on every PR: ` ruff ` (lint), ` bandit ` (static security lint), ` pip-audit `
40- (dependency CVEs), and the ` tests ` suite.
39+ - ** CI checks.** [ gitleaks] ( .github/workflows/gitleaks.yml ) (committed credentials) runs on ** every**
40+ PR — deliberately not path-filtered, since a credential can be committed in any file.
41+ [ ci.yml] ( .github/workflows/ci.yml ) runs ` ruff ` (lint + format), ` bandit ` (static security lint),
42+ ` pip-audit ` (dependency CVEs), and the ` tests ` suite on Python 3.11/3.12 — but ** only** for PRs
43+ touching ` src/idc_api/** ` , ` tests/** ` , ` pyproject.toml ` , ` uv.lock ` , or ` ci.yml ` itself.
44+ ` actionlint ` likewise runs only when a workflow changes. A docs-only PR therefore runs ` gitleaks `
45+ and nothing else.
46+ - ** Dependency vulnerabilities, caught twice.** ` pip-audit ` fails CI on a PR whose dependencies
47+ carry a known CVE, and ** Dependabot alerts + automated security updates** are enabled on the
48+ repository, so a CVE disclosed * after* a PR merges still opens a fix PR against ` main ` rather
49+ than waiting for someone to notice. [ dependabot.yml] ( .github/dependabot.yml ) separately schedules
50+ weekly grouped * version* updates for the ` uv ` and ` github-actions ` ecosystems; security updates
51+ are the repo setting, not that file, and are ungrouped so a fix ships on its own.
52+ - ** Credential hygiene, in three layers.** The service itself holds no secrets, but the * deploy*
53+ path does: each tier's deployer service-account JSON key. Those live in GitHub ** Environment
54+ secrets** , never in the repo — and three independent guards keep them out of it:
55+ 1 . ** Push protection** (GitHub secret scanning) rejects a push containing a recognized
56+ credential * before* it reaches GitHub. This is the only guard that prevents rather than
57+ detects. It does not cover pushes to forks.
58+ 2 . ** [ gitleaks] ( .github/workflows/gitleaks.yml ) ** scans the full history on every PR, including
59+ from forks, and flags generic private-key blocks and service-account JSON that provider
60+ pattern-matching misses. Findings are redacted in the log (this repo's Actions logs are
61+ public).
62+ 3 . ** ` .gitignore ` ** covers the filenames deployer keys actually land under. It is the weakest
63+ layer — a filename list is never exhaustive — and exists to catch the common ` git add -A ` .
64+
65+ If a credential is ever committed: ** rotate it first.** Deleting the commit does not un-leak it;
66+ the blob stays reachable and this repo is public. Purge from history afterwards, not instead.
67+
68+ > ** These are repository settings, not files.** Secret scanning, push protection, Dependabot alerts,
69+ > and Dependabot security updates are all enabled under * Settings → Code security* . They are not
70+ > visible in any diff, so they can be switched off without a code review — this section is the only
71+ > record that they are meant to be on. Verify with:
72+ >
73+ > ``` bash
74+ > gh api repos/ImagingDataCommons/IDC-REST-MCP \
75+ > --jq ' .security_and_analysis | {secret_scanning, secret_scanning_push_protection, dependabot_security_updates}'
76+ > ` ` `
4177- ** Non-root container** — ` Dockerfile` drops to an unprivileged user before serving.
4278
4379# # Known residual risks (public deployment)
0 commit comments