audit-labs/gh-attest
GitHub Audit Evidence Extractor
clone: git clone https://gitbay.org/audit-labs/gh-attest.git
v1.0.4: README.md · raw
1# gh-attest
2
3[](https://sonarcloud.io/summary/new_code?id=audit-labs_gh-attest)
4[](https://sonarcloud.io/summary/new_code?id=audit-labs_gh-attest)
5[](https://sonarcloud.io/summary/new_code?id=audit-labs_gh-attest)
6[](https://sonarcloud.io/summary/new_code?id=audit-labs_gh-attest)
7[](LICENSE)
8
9Turns GitHub security settings into auditor-ready evidence. A GitHub App that
10records branch protection, scanning alerts, and organization access as they
11change, maps them to SOC 2 and ISO 27001 controls, and exports point-in-time
12CSV and PDF evidence packages.
13
14Read-only — it never modifies your repositories, permissions, or membership.
15
16
17
18## What it collects
19
20| Signal | Evidence for |
21| --- | --- |
22| Branch protection, repository rulesets | SOC 2 CC8.1 · ISO 27001 A.8.32 |
23| Secret scanning alerts | SOC 2 CC6.1, CC6.6 · ISO 27001 A.5.17 |
24| Dependabot alerts | SOC 2 CC7.1 · ISO 27001 A.8.8 |
25| Code scanning alerts | SOC 2 CC7.1 · ISO 27001 A.8.28, A.8.29 |
26| Organization / team membership | SOC 2 CC6.2, CC6.3 · ISO 27001 A.5.18 |
27| Repository inventory | ISO 27001 A.5.9 |
28
29Mappings live in `migrations/` and are applied as a join at query time, so a
30mapping can be corrected without re-ingesting history. Every mapping — and the
31plain-language rationale for why each signal is evidence for its control — is
32documented in [docs/framework-mapping.md](docs/framework-mapping.md).
33
34## How it works
35
36Webhooks capture changes as they happen; an hourly cron polls for state that
37webhooks never announce (protection and open alerts that predate the install,
38whether each scanner is enabled, and current membership). Each observation is stored as a timestamped snapshot in D1.
39Exports render off the request path via a queue, into R2.
40
41**Stack:** Cloudflare Workers · D1 · R2 · Queues. All storage is provisioned
42under Cloudflare's `eu` jurisdiction.
43
44## Endpoints
45
46| Route | Auth | Purpose |
47| --- | --- | --- |
48| `GET /` | session | Dashboard: current posture, exports |
49| `GET /access-review` | session | Membership changes since a date |
50| `POST /exports`, `/resync`, `/switch`, `/exclusions` | session | Dashboard actions |
51| `GET /exports/:id[/download]` | session | Export status / file |
52| `POST /webhooks/github` | HMAC | App events |
53| `POST /webhooks/marketplace` | HMAC | Marketplace events |
54| `POST /admin/{poll,export,cleanup,purge}` | bearer | Operations |
55
56Session routes are scoped by installation; admin routes require `ADMIN_TOKEN`.
57
58Repositories can be excluded from the dashboard: an excluded repo is skipped by
59the poll and contributes no evidence, while its existing history is retained so
60the exclusion can be undone.
61
62## Development
63
64```sh
65npm install
66cp .dev.vars.example .dev.vars # fill in, see below
67npm run db:migrate:local
68npm run dev
69```
70
71Type checking: `npm run typecheck`. Generate binding types after editing
72`wrangler.jsonc`: `npm run types`.
73
74## Deployment
75
76```sh
77npm run db:migrate:remote
78npm run deploy
79```
80
81Secrets are set with `wrangler secret put <NAME>` — never in `wrangler.jsonc`:
82
83| Secret | Purpose |
84| --- | --- |
85| `GITHUB_APP_ID`, `GITHUB_APP_PRIVATE_KEY` | Mint installation tokens |
86| `GITHUB_WEBHOOK_SECRET` | Verify webhook signatures |
87| `GITHUB_APP_CLIENT_ID`, `GITHUB_APP_CLIENT_SECRET` | Dashboard OAuth |
88| `SESSION_SECRET` | Sign session cookies |
89| `ADMIN_TOKEN` | Bearer for `/admin/*` |
90
91Pipe the private key from its file rather than pasting it:
92`wrangler secret put GITHUB_APP_PRIVATE_KEY < key.pem`
93
94> Cron triggers occasionally stop firing after a deploy. If the hourly poll
95> goes quiet, set `triggers.crons` to `[]`, deploy, restore it, and deploy
96> again — a same-value redeploy does not clear it.
97
98## Data handling
99
100Evidence is retained for 13 months and exports for 90 days; everything for an
101installation is deleted when the App is uninstalled. No source code or access
102tokens are stored. See [PRIVACY.md](PRIVACY.md).
103
104Retention periods are defined in `src/index.ts` and restated in `PRIVACY.md` —
105change both together.
106
107## License
108
109GPL-3.0. See [LICENSE](LICENSE).