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