Skip to content
Merged
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
89 changes: 89 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# SOFE Security Checklist

Formal security checklist for anyone who deploys or operates SOFE (self-hosted
or the hosted SaaS). Built from a real audit of the SOFE stack (2026-09).

Severity legend: 🔴 Critical · 🟠 High · 🟡 Medium · 🟢 Good practice

---

## 1. Repositories & Supply Chain

| # | Check | Sev | Status |
|---|-------|-----|--------|
| 1.1 | No secrets committed: `AKIA*`, `sk_*`, `AIzaSy*`, private keys, `.env`, `.dev.vars`, `wrangler.toml` `[vars]` with credentials | 🔴 | |
| 1.2 | No secrets in git history (`git log -p` / `trufflehog` / `gitleaks`) | 🔴 | |
| 1.3 | `build/`, `.terraform/`, `*.tfstate*`, `lambda.zip`, `node_modules/`, `.wrangler/`, `.DS_Store` are gitignored and NOT tracked | 🟡 | |
| 1.4 | CI workflows use least-privilege `permissions:` and no plaintext secrets | 🟠 | |
| 1.5 | Release artifacts (PyPI, GoReleaser, Docker) publish `checksums.txt` / SBOM | 🟢 | |
| 1.6 | Install scripts verify checksum/signature instead of bare `curl | bash` | 🟠 | |
| 1.7 | Only necessary collaborators with admin access on public repos | 🟡 | |

## 2. Authentication

| # | Check | Sev | Status |
|---|-------|-----|--------|
| 2.1 | Bearer tokens (Firebase ID tokens / JWTs) are **signature-verified** (JWKS) with `iss`/`aud`/`exp` checks — never just base64-decoded | 🔴 | |
| 2.2 | API keys stored as SHA-256 hashes, not plaintext (even in a field named `keyHash`) | 🔴 | |
| 2.3 | Internal secrets (Lambda ↔ Worker) use a shared secret header and the backend rejects requests without it | 🔴 | |
| 2.4 | Device-code flow completes on the backend; browser never writes `device_codes`/`api_keys` via unauthenticated REST | 🔴 | |
| 2.5 | Admin-only endpoints check role via a trusted path (backend/service account), not client-supplied claims | 🟠 | |
| 2.6 | Rate limiting applied at every public entry point (incl. raw backend URLs, not just the edge) | 🟠 | |

## 3. Data Store (Firestore / DB)

| # | Check | Sev | Status |
|---|-------|-----|--------|
| 3.1 | Security rules require `request.auth != null` — no anonymous reads/writes with only a public API key | 🔴 | |
| 3.2 | Owner-scoped collections (`users/{uid}/**`, `connectedAccounts`, `evaluations`) only readable/writable by owner or backend | 🔴 | |
| 3.3 | `api_keys` and `device_codes` (post-approval) never readable by other users | 🔴 | |
| 3.4 | Backend Workers authenticate with a service account (OAuth2 token), never the web API key | 🔴 | |
| 3.5 | Sensitive user fields (third-party AI API keys, external IDs) encrypted at rest | 🟠 | |
| 3.6 | Rules have a default-deny catch-all; `get()`/`exists()` used only for trusted checks | 🟢 | |

## 4. Compute (Lambda / Workers / Pages)

| # | Check | Sev | Status |
|---|-------|-----|--------|
| 4.1 | No publicly reachable backend URL without auth (API Gateway catch-all `$default` + unauthenticated Lambda = 🔴) | 🔴 | |
| 4.2 | Lambda/IAM role follows least privilege: no `sts:AssumeRole Resource="*"`, no `List*`/`Describe*` beyond what's needed | 🔴 | |
| 4.3 | Cross-account endpoints (`/connect/test`, Bedrock proxy) validate ARN format and require an external ID | 🟠 | |
| 4.4 | Workers reject requests to internal endpoints unless a valid `x-sofe-internal-secret` is present | 🟠 | |
| 4.5 | `reload=True` and dev flags disabled in production | 🟡 | |
| 4.6 | WAF / throttling on public endpoints; function URLs not exposed | 🟢 | |

## 5. Secrets Management

| # | Check | Sev | Status |
|---|-------|-----|--------|
| 5.1 | All credentials stored as Cloudflare secrets / AWS SSM / env — never in committed `[vars]`/`wrangler.toml` | 🔴 | |
| 5.2 | Rotation plan: API keys, external IDs, cron secrets, web API keys on any exposure | 🔴 | |
| 5.3 | `.dev.vars.example` committed instead of `.dev.vars` | 🟢 | |
| 5.4 | Service accounts scoped to one project with `datastore` (or narrower) access | 🟢 | |
| 5.5 | **Follow-up**: migrate Workers to Workload Identity Federation (OIDC) to avoid long-lived service-account keys (Google-recommended) | 🟡 | |

## 6. Client / CLI

| # | Check | Sev | Status |
|---|-------|-----|--------|
| 6.1 | CLI config file (`~/.sofe/config.yaml`) written `0600` and API keys stored securely (or OS keychain) | 🟡 | |
| 6.2 | Remote templates/fixtures fetched over HTTPS and validated (no `http.Get` of untrusted URLs) | 🟠 | |
| 6.3 | CLI updater verifies artifact checksum | 🟠 | |

## 7. Documentation & Disclosure

| # | Check | Sev | Status |
|---|-------|-----|--------|
| 7.1 | No real credentials, internal URLs, account IDs, or external IDs in docs/README/blog | 🟠 | |
| 7.2 | `SECURITY.md` with a private disclosure contact and response expectations | 🟢 | |
| 7.3 | Public-facing blog posts about audits are anonymized (no real endpoints/secrets/account IDs) | 🟡 | |

---

## Remediation priority (if you're starting from 🔴)

1. **Firestore rules** → require auth; owner-scoped collections; default deny.
2. **Rotate** every exposed credential (API keys, external IDs, web API key, cron secret).
3. **Workers** → verify JWT signatures, hash API keys, use a service account for Firestore.
4. **Lambda** → require the internal secret, restrict `sts:AssumeRole`, remove the public catch-all.
5. **Supply chain** → checksummed installs, gitignore hygiene, no secrets in git history.
2 changes: 0 additions & 2 deletions build/lib/sofe/__init__.py

This file was deleted.

121 changes: 0 additions & 121 deletions build/lib/sofe/cli.py

This file was deleted.

113 changes: 0 additions & 113 deletions build/lib/sofe/collectors/__init__.py

This file was deleted.

44 changes: 0 additions & 44 deletions build/lib/sofe/collectors/aws/__init__.py

This file was deleted.

Loading
Loading