Security Audit Remediation Plan
Pre-launch security hardening derived from dual-audit cross-reconciliation (April 2026). Each phase commits at completion. Model recommendations reflect task complexity.
Phase 0 — Emergency (Ship-Stoppers) ✅ COMPLETE
Commit: 6749166
Model: claude-4.6-sonnet-medium-thinking (Opus-class for architectural decisions)
Deployed: Canister V6→V7 migration live on IC mainnet. Gateway auth secret active.
| # | Item | Status |
|---|---|---|
| 0.1 | Canister gateway auth (X-Gateway-Auth) — Motoko + all gateway callers |
✅ |
| 0.2 | Remove X-Test-User from canister; update CORS allowed headers |
✅ |
| 0.3 | Timing-safe comparisons for HMAC/secret checks (verifyState) |
✅ |
| 0.4 | captureAuth fail-closed when CAPTURE_WEBHOOK_SECRET unset |
✅ |
| 0.5 | POST /api/v1/attest requires JWT authentication |
✅ |
| 0.6 | MCP hosted server canister calls include X-User-Id + X-Gateway-Auth |
✅ |
| — | 23 new unit tests; 1270 existing tests passing | ✅ |
Post-deploy verification:
curl https://rsovz-byaaa-aaaaa-qgira-cai.raw.icp0.io/api/v1/notes -H "X-User-Id: test"→GATEWAY_AUTH_REQUIRED✅curl https://rsovz-byaaa-aaaaa-qgira-cai.raw.icp0.io/health→{"ok":true}✅
Phase 1 — High Priority (Same Release Train) ✅ COMPLETE
Model: claude-4.6-sonnet-medium-thinking
Branch: feature/landing-overview-video-ui
| # | Item | File(s) | Status |
|---|---|---|---|
| 1.1 | Trust proxy for Express rate limiting — real client IPs behind Netlify CDN | hub/gateway/server.mjs, hub/server.mjs |
✅ |
| 1.2 | Zip-slip protection — validate each AdmZip entry stays under extract dir | hub/bridge/server.mjs, hub/server.mjs |
✅ |
| 1.3 | Self-hosted default-admin startup warning when roleMap.size === 0 in production |
hub/server.mjs |
✅ |
| 1.4 | Header allowlist for proxyToCanister and proxyTo — replace ...req.headers spread |
hub/gateway/server.mjs |
✅ |
| 1.5 | Billing enforcement startup warning when BILLING_ENFORCE unset in hosted mode |
hub/gateway/billing-constants.mjs, hub/gateway/server.mjs |
✅ |
| — | 36 new unit tests; 1306 total tests passing | test/phase1-security.test.mjs |
✅ |
Phase 2 — CI/CD & Infrastructure ✅ COMPLETE
Model: claude-4.6-sonnet-medium-thinking
Branch: feature/landing-overview-video-ui
| # | Item | File(s) | Status |
|---|---|---|---|
| 2.1 | Add npm audit gate to CI — fail on high/critical CVEs |
.github/workflows/ci.yml |
✅ |
| 2.2 | Add secret scanning to CI (TruffleHog action) | .github/workflows/ci.yml |
✅ |
| 2.3 | Add dependency review action on PRs (actions/dependency-review-action) |
.github/workflows/dependency-review.yml |
✅ (workflow later removed — dependency-review-action requires GitHub Advanced Security, which is unavailable on private repos; npm audit gate in ci.yml covers this) |
| 2.4 | Dockerfile: non-root user (knowtation), pinned tag (node:20.19.0-alpine3.21), npm ci |
hub/Dockerfile |
✅ |
| 2.5 | Fix GitHub token encryption salt — random 16-byte per-token salt embedded in ciphertext; v1 tokens gracefully fall back to reconnect | hub/bridge/server.mjs |
✅ |
| 2.6 | Upgraded [email protected] → [email protected]; added sanitizeUploadFilename() — strips path traversal, replaces unsafe chars, truncates to 200 chars |
hub/bridge/server.mjs, package.json |
✅ |
| — | 41 new unit tests; 1347 total tests passing | test/phase2-security.test.mjs |
✅ |
Phase 3 — Defense in Depth ✅ COMPLETE
Model: claude-4.6-opus-high-thinking
Branch: feature/security-audit
Deployed: Canister V7→V8 migration live on IC mainnet. CORS origin locked to gateway.
| # | Item | File(s) | Status |
|---|---|---|---|
| 3.1 | JWT token-in-URL: OAuth redirect uses URL fragment #token=; gateway JWT expiry shortened from 7d → 24h |
hub/gateway/server.mjs, hub/server.mjs |
✅ |
| 3.2 | Image proxy ?token= — short-lived HMAC-signed image token (5 min TTL) replaces full JWT in query param; new /api/v1/vault/image-proxy-token endpoint |
hub/gateway/server.mjs, hub/server.mjs |
✅ |
| 3.3 | Bridge write routes: requireBridgeEditorOrAdmin added to /vault/sync, /index, /memory/store, /memory/clear, /memory/consolidate — viewer role can no longer mutate |
hub/bridge/server.mjs |
✅ |
| 3.4 | MCP in-memory refresh token store: periodic sweep every 10 min deletes expired entries; destroy() cleans up timer |
hub/gateway/mcp-oauth-provider.mjs |
✅ |
| 3.5 | CORS on canister: corsHeaders() locks Access-Control-Allow-Origin to stored origin when gateway_auth_secret + cors_allowed_origin are both set; new admin_set_cors_origin function; V7→V8 migration |
hub/icp/src/hub/main.mo, hub/icp/src/hub/Migration.mo |
✅ |
| 3.6 | path-to-regexp ReDoS CVE resolved: npm audit fix upgraded 0.1.12 → 0.1.13 in all three lock files |
hub/package-lock.json, hub/gateway/package-lock.json, package-lock.json |
✅ |
| — | 33 new unit tests; 1380 total tests passing | test/phase3-security.test.mjs |
✅ |
Deployment steps for each canister-touching phase
After any Motoko change:
# 1. Export backup first (safety)
npm run canister:export-backup
# 2. Deploy
cd hub/icp && dfx deploy hub --network ic
# 3. If new admin functions added, call them
dfx canister call hub <function_name> '("<secret>")' --network ic
# 4. Verify
curl -s https://rsovz-byaaa-aaaaa-qgira-cai.raw.icp0.io/health
Open verification items (resolve before public launch)
- [x] Production canister URL confirmed as
raw.icp0.ioin all Netlify env vars - [x]
CANISTER_AUTH_SECRETset in Netlify gateway env - [x]
admin_set_gateway_auth_secretcalled on IC mainnet canister - [x]
admin_set_cors_origincalled on IC mainnet canister (Phase 3.5 — locked tohttps://knowtation-gateway.netlify.app) - [ ] Netlify function IAM and blob access policies reviewed (not visible in code)
- [ ] Content Security Policy and cookie flags for Hub static hosting
- [ ] Log pipeline confirmed — no
Authorizationor?token=values logged - [ ] Stripe live-mode webhook endpoint idempotency under replay confirmed
- [ ]
BILLING_ENFORCEdecision explicit for production
Canister IDs
- Hub:
rsovz-byaaa-aaaaa-qgira-cai - Attestation:
dejku-syaaa-aaaaa-qgy3q-cai
Key commits
- Phase 0:
6749166— canister gateway auth, timing-safe secrets, fail-closed webhook, attest auth - Phase 1:
9b37569— trust proxy, zip-slip, default-admin warning, header allowlist, billing warning - Phase 2: (see commit on
feature/landing-overview-video-ui) — npm audit CI gate, TruffleHog, dependency review, Dockerfile hardening, per-token salt, multer@2 - Phase 3:
9a362e5— token-in-URL fragment, image proxy signed token, bridge RBAC, MCP token sweep, canister CORS lock, path-to-regexp fix
All four audit phases complete. Remaining items above are operational verification, not code changes.