LumiBaseDocs

CWE Top 100 Security Audit — LumiBase

Status: Living document. Re-audit after any change to auth, upload, storage, or query-building code. Audit date: 2026-07-06 · Branch: lumibase/inspiring-driscoll-2c1fd8 (base main @ 87a82fcb) Method: Static code review of the monorepo (agent-assisted sweep + call-site verification of key claims). Not a penetration test — a "Mitigated" verdict means a correct defense exists in code with cited evidence, not a guarantee of absence of vulnerabilities.

Scope and list composition

There is no official "CWE Top 100." MITRE publishes a ranked list only to rank 40:

  • Part A (ranks 1–40, official): 2025 CWE Top 25 + 2025 "On the Cusp" (ranks 26–40).
  • Part B (60 additional weaknesses, unranked): selected by the audit team from OWASP Top 10 (2021) / OWASP API Security Top 10 (2023) CWE mappings and web/API security practice, filtered for relevance to LumiBase's stack (TypeScript, Hono, Drizzle/Postgres, Cloudflare Workers + Node/Docker). This selection is editorial, not an official ranking.

Verdict legend

VerdictMeaning
✅ MitigatedA correct, tested-or-verifiable defense exists at the cited location.
⚠️ PartialDefense exists but has a documented gap or inconsistency.
❌ Not addressedNo defense found for an applicable weakness.
— N/ANot applicable to this stack (memory-safety/native/PHP class) or no attack surface exists in the codebase.

Scorecard

Count
✅ Mitigated78
⚠️ Partial0
❌ Not addressed0
— N/A (memory-safety class, no surface, or no cookies)22
Total100

Of the 78 applicable weaknesses: 78 mitigated (100%), 0 partial, 0 not addressed.

Changelog — 2026-07-06:

  • First pass fixed the two ⚠️ items in the official top 20 (CWE-89 SQL Injection, CWE-284 Improper Access Control) plus the related CWE-668.
  • Second pass closed every remaining Partial/Not-addressed item: CWE-521/203 (register password policy + validation), CWE-620/613 (change-password endpoint + token_version revocation), CWE-302 (Cloudflare Access role now mapped from DB), CWE-362/367 (atomic guarded approval decide), CWE-942 (no arbitrary-origin credentialed CORS), CWE-321 (removed in-repo CDC fallback key, fail-closed), CWE-400 (general API rate limiter), CWE-359 (audit-log payload redaction + string caps), CWE-1104 (dependabot + pnpm audit CI gate). All applicable weaknesses are now Mitigated. As always, "Mitigated" means a verified defense exists — not a guarantee; a penetration test remains the way to confirm.

Part A — MITRE 2025 official ranking (1–40)

#CWEWeaknessVerdictEvidence / note
179Cross-site ScriptingCentral DOMPurify allowlist apps/studio/src/lib/sanitize-html.ts; markdown escaped then sanitized; CSP script-src 'self' in apps/cms/src/middleware/security-headers.ts; SVG active-content scan on upload.
289SQL InjectionFixed 2026-07-06. materialize-service.ts now uses Drizzle bind parameters for all values and sql.identifier() for the validated physical-table name; the one spot where bind params are impossible (the PL/pgSQL trigger body) validates ids fail-closed against /^[A-Za-z0-9_-]+$/ instead of escaping. Elsewhere Drizzle is parameterized throughout. Covered by materialize-sql-injection.test.ts + materialize-service.test.ts.
3352CSRFAuth is bearer-header only on this branch — no cookies are issued, so no ambient credentials exist for CSRF to ride on. CORS origins validated (apps/cms/src/config/cors.ts). Re-verify if cookie-based refresh lands (exists on a feature branch, commits 807d0fbd / 4469c36c — not merged here).
4862Missing AuthorizationwithAuth() on the whole /api/v1 stack (apps/cms/src/index.ts); only intentional bypasses: login, setup wizard (state-gated), recovery (rate-limited), realtime ticket exchange.
5787Out-of-bounds WriteMemory-safety class; no native code.
622Path TraversalisInvalidKey() rejects ../leading / (apps/cms/src/routes/media.ts:22); all storage keys tenant-prefixed sites/{siteId}/media/.
7416Use After FreeMemory-safety class.
8125Out-of-bounds ReadMemory-safety class.
978OS Command InjectionNo user input reaches shell execution; only build-time execFileSync('git', [...]) with a fixed argv.
1094Code InjectionExtension bundles only from EXTENSION_BUNDLE_ORIGINS allowlist + SSRF guard + timeout (apps/cms/src/extensions/sandbox.ts); AI skills are declarative schemas, no eval of user strings.
11120Classic Buffer OverflowMemory-safety class.
12434Dangerous File Upload5 layers in apps/cms/src/middleware/file-upload-policy.ts: real body size, DB-backed MIME allowlist, extension↔MIME cross-check, magic-byte sniffing (rejects MZ/ELF/Mach-O), SVG script/XXE scan. Served with Content-Disposition: attachment + nosniff.
13476NULL Pointer DereferenceMemory-safety class (a JS TypeError is an exception, not memory corruption).
14121Stack Buffer OverflowMemory-safety class.
15502Unsafe DeserializationConfig import via strict Zod schemas before any processing (apps/cms/src/services/config-import-service.ts:79); no yaml.load/vm/unserialize.
16122Heap Buffer OverflowMemory-safety class.
17863Incorrect AuthorizationPostgres RLS on 25+ tables with transaction-scoped SET LOCAL app.site_id (packages/database/migrations/rls-policies.sql); app-level siteId scoping in services; field-level masking in permission-service.ts.
1820Improper Input ValidationZod safeParse() consistently on all route inputs (shared schemas in packages/shared/src/schemas).
19284Improper Access ControlFixed 2026-07-06. Route surfaces (flows, ai, cdc, uploads, shares) enforce admin/permission checks. /health now returns only overall status to anonymous callers (per-subsystem detail requires the observability token); /metrics enforces METRICS_TOKEN in all environments when configured (previously non-prod bypass). See CWE-668.
20200Sensitive Info ExposureformatSafeError() strips request/response objects; clients get generic error codes; stack traces log server-side only (apps/cms/src/index.ts:328).
21306Missing AuthN for Critical FunctionSetup wizard 404s once system_state = initialized; recovery is intentionally public but 3/IP/hour; metrics token-gated in prod.
22918SSRFvalidateOutboundUrl() blocks RFC1918, loopback, link-local, cloud metadata endpoints, IPv6 ULA/link-local (apps/cms/src/services/ssrf-guard.ts:33), with tests.
2377Command InjectionSame as CWE-78: no surface.
24639AuthZ Bypass via User-Controlled Key (IDOR)Every item/revision query filters scopeSite(items.siteId, deps.siteId) (apps/cms/src/services/item-service.ts:558); integration tests idor-tenant-isolation.integration.test.ts; guidelines in docs/en/security/idor-testing.md; nanoid IDs are non-guessable.
25770Resource Allocation Without LimitsPagination limit ≤ 200, filter clauses ≤ 100, filter path depth ≤ 8 (item-service.ts:227), upload size caps, event-loop pressure limiter returning 503. See CWE-400 for the remaining rate-limit gap.
26266Incorrect Privilege AssignmentBootstrap admin role explicit (modules/setup/service.ts:935); invited members default to roleId: null, member role has adminAccess: false.
27276Incorrect Default PermissionsDocker runs as non-root lumibase user (docker/Dockerfile); upload policy fails safe to restrictive defaults; no public-read default policies.
2898PHP File InclusionNot a PHP codebase.
29269Improper Privilege ManagementControl-plane routes admin-only (middleware/control-plane-access-guard.ts); users cannot change their own role (requireSiteAdmin() on /users); HITL ai_approvals for schema:write/delete skills.
30190Integer OverflowJS 64-bit floats; no unsafe size arithmetic with user input found.
31287Improper AuthenticationPBKDF2-SHA256 100k iterations + 16-byte salt, constant-time compare (services/auth/password.ts); JWT HS256 24h; API keys SHA-256-hashed; lockout + per-IP window + 500ms stall + dummy-hash anti-enumeration.
32400Uncontrolled Resource ConsumptionFixed 2026-07-06. Added withRateLimit() to the authenticated API chain (apps/cms/src/middleware/rate-limit.ts): a per-principal (userId/API-key) or per-IP fixed-window throttle over the runtime cache, on top of the existing auth/recovery limiters and event-loop backpressure. Emits X-RateLimit-* + Retry-After; configurable via LUMIBASE_RATE_LIMIT_*.
33288AuthN Bypass via Alternate PathGraphQL inherits the same withAuth/withRls chain; realtime uses 1-minute signed tickets; SCIM uses its own hashed-token store with expiry; MCP inherits bearer auth.
34427Uncontrolled Search PathNo child-process execution in app code.
35798Hard-coded CredentialsSecrets via env/*_FILE; production startup rejects known dev-default values (apps/cms/src/config/production.ts:25); dev-auth bypass triple-gated to development.
36362Race ConditionFixed 2026-07-06. The AI approval commit/reject UPDATEs are now guarded by WHERE status = 'pending' and use .returning(); a zero-row result means a concurrent decision already won, and the caller gets a conflict instead of silently overwriting (services/ai-harness.ts). Recovery tokens remain single-use via atomic consume.
37401Memory LeakMemory-safety class.
38732Permissions on Critical ResourceFORCE ROW LEVEL SECURITY prevents table-owner bypass; migration mandates a non-superuser app role (rls-policies.sql:12,59).
39119Memory Buffer BoundsMemory-safety class.
40601Open RedirectNo returnTo/redirect-by-user-URL mechanism found in auth routes; external-JWT issuers are configured server-side. No surface today; add a centralized redirect validator if login redirects are ever introduced.

Part B — 60 additional weaknesses (OWASP-mapped selection, unranked)

B1. Cryptography & key management (12)

CWEWeaknessVerdictEvidence / note
327Broken crypto algorithmAES-GCM via WebCrypto (services/crypto-service.ts:91); no MD5/SHA1/DES/RC4/ECB/createCipher.
328Weak hashSHA-256 for token hashing; PBKDF2-SHA256 for passwords.
326Inadequate encryption strengthAES-256 DEKs (services/crypto/envelope-encryption.ts:21); key-size validation at startup.
330Insufficient randomnesscrypto.getRandomValues() for API-key seeds, setup tokens, backup codes (rejection-sampled); Math.random() only in non-security fallbacks.
338Weak PRNGSame as CWE-330; recovery timing jitter explicitly uses CSPRNG.
311Missing encryption of sensitive dataDeployment tokens stored as AES-GCM envelope ciphertext + key id (packages/database/src/schema/deployments.ts:47).
312Cleartext storage of secretsAPI keys/share tokens stored as SHA-256 hashes; plaintext returned exactly once at creation.
319Cleartext transmissionProduction requires sslmode=require+ for Postgres (config/production.ts:107); external issuers must be HTTPS.
321Hard-coded crypto keyFixed 2026-07-06. The in-repo CDC fallback key is removed; encrypt/decrypt now require a key and throw otherwise, and the CDC route factory fails closed with a 503 ENCRYPTION_KEY_MISSING when ENCRYPTION_KEY is unset (modules/cdc/registry/encryption.ts, modules/cdc/routes.ts).
347Improper signature verificationjose with explicit algorithm allowlists (HS256 custom / RS256 CF Access); external-JWT verifier forbids none/HS* (modules/external-auth/verifier.ts:72); Vercel/Netlify inbound webhooks verified with constant-time compares.
916Weak password hashPBKDF2-SHA256 100k everywhere a password/backup code is stored; no weaker path (setup, SCIM checked).
295Improper certificate validationNo rejectUnauthorized:false / NODE_TLS_REJECT_UNAUTHORIZED anywhere; JWKS via createRemoteJWKSet.

B2. Secrets handling (2)

CWEWeaknessVerdictEvidence / note
522Insufficiently protected credentialsOne-time plaintext return for API keys/share tokens; setup token printed once, hash persisted.
526Exposure via environmentNo env dumps; error paths use formatSafeError(); prod config validation fails closed.

B3. Session & account management (12)

CWEWeaknessVerdictEvidence / note
384Session fixationFresh JWT per login; no session identifier carried across authentication.
613Insufficient session expirationFixed 2026-07-06. Added a users.token_version column (migration 0002_add_user_token_version.sql) embedded in every JWT and checked on verify (middleware/auth.ts). A password change or reset bumps it, instantly invalidating all outstanding tokens for that user.
307Excessive auth attemptsLogin lockout (users.lockedUntil) + per-IP sliding window (423/429); recovery 3/IP/h; setup state endpoint 60/min/IP.
521Weak password requirementsFixed 2026-07-06; reset-password gap closed 2026-07-09. Introduced a shared PasswordSchema (min 12 + complexity) in packages/shared/src/schemas/password.ts, now used by register, change-password, setup, recovery reset, and the public POST /api/v1/auth/reset-password endpoint so the policy is uniform and can't drift. (That endpoint's schema had lingered on z.string().min(6), letting a holder of a valid reset token set a 6-char password that bypassed the policy; it now uses the shared schema.)
620Unverified password changeFixed 2026-07-06. Added POST /api/v1/me/change-password which requires the current password (constant-time verify) and bumps token_version to revoke other sessions, returning a fresh token for the caller (routes/auth.ts).
640Weak password recovery32-byte CSPRNG tokens, 15/30-min TTL, single-use atomic consume, uniform 200–500ms delay, generic responses (modules/recovery/service.ts).
203Observable discrepancy (user enumeration)Login and recovery are uniform (dummy hash, generic messages). Register is public self-service (unauthenticated end-user sign-up; see middleware/auth.ts and POST /register in routes/auth.ts), but it is non-enumerating: an already-registered email and a fresh one both return an identical generic 202, so the reply is not an anonymous-enumeration vector. Input is validated before any lookup.
208Timing discrepancyConstant-time byte compare for password and admin-path matching; 500ms login stall; CSPRNG jitter in recovery.
302AuthN bypass via assumed-immutable dataFixed 2026-07-06. withSiteMembership already binds the X-Lumi-Site tenant to a verified membership before RLS. The remaining gap — Cloudflare Access principals hardcoded to ['admin'] — is closed: the Access identity is now resolved to a real active user + site-membership role from the DB, denying non-provisioned/non-member users (middleware/auth.ts).
614Cookie without Secure flagNo cookies issued on this branch (bearer-only).
1004Cookie without HttpOnlySame.
1275Cookie SameSiteSame. Re-audit all three if cookie-based refresh merges.

B4. Web platform (12)

CWEWeaknessVerdictEvidence / note
611XXENo XML parser in the pipeline; SVG screened by regex token scan (blocks <!DOCTYPE, <!ENTITY) — no entity expansion possible.
776XML entity expansionSame as CWE-611.
1021ClickjackingX-Frame-Options: DENY + CSP frame-ancestors 'none' (middleware/security-headers.ts).
942Permissive CORSFixed 2026-07-06. resolveCorsOrigin never returns * and never reflects an arbitrary internet origin: an explicit allowlist wins in every env, and with no allowlist only loopback origins are reflected (dev only). Credentialed CORS can no longer be abused by a third-party site (config/cors.ts).
444Request smugglingNo custom proxy/header-forwarding code; framework/infra concern.
113Response splittingsanitizeDownloadFilename strips control chars/quotes/backslash before Content-Disposition (routes/media.ts:44).
93CRLF injectionSame sanitizer; no other user input reaches headers.
116Improper output encodingAPI is JSON-only via c.json().
117Log injectionStructured JSON.stringify logging (middleware/logger.ts) — control characters cannot forge log lines.
1336SSTIEmail templates use a micro-engine with HTML-escaped {{var}} substitution only (services/email/render.ts:49); no Handlebars/EJS/Liquid.
425Forced browsingAdmin path guard returns indistinguishable 404s (middleware/admin-path-guard.ts); backup/restore require admin bundle.
602Client-side-only enforcementSpot-checked: admin backup/restore and role routes enforce requireSiteAdmin/PermissionService server-side, independent of Studio UI state.

B5. JavaScript / API specific (12)

CWEWeaknessVerdictEvidence / note
1321Prototype pollutionNo recursive user-JSON merges; explicit field mapping on upserts; strict Zod objects.
915Mass assignmentNo ...req.body into DB writes; Zod-whitelisted fields only (routes/items.ts:35, routes/webhooks.ts:11).
1333ReDoSUser-supplied regex in permission DSL wrapped in try/catch and bounded (services/permission-dsl.ts:384); filter recursion bounded.
407Algorithmic complexityFilter clauses ≤ 100, path depth ≤ 8 (item-service.ts:227).
674Uncontrolled recursionRelation expansion is single-pass; no unbounded recursion on user data.
799Interaction frequencyBulk operations bounded; pagination capped. (Per-user API rate limiting tracked under CWE-400.)
73External control of file pathStorage keys validated and tenant-prefixed; export filenames derived server-side.
59Symlink followingFilesystem reads limited to env-configured secret paths; object storage is key-based.
552Exposed files/directoriesNo static serving of project dirs; media behind permission checks; MinIO bucket not public.
494Code download without integrity checkExtension bundles restricted to HTTPS allowlist origins + SSRF guard; pnpm lockfile integrity for dependencies. Consider adding SRI hashes per bundle as hardening.
829Inclusion from untrusted sphereStudio index.html loads only local modules; no CDN scripts.
345Data authenticityOutbound webhooks signed HMAC-SHA256 with timestamp (modules/notifications/webhook-channel.ts:144); inbound deployment webhooks verified per provider with constant-time compare.

B6. Authorization & operations (10)

CWEWeaknessVerdictEvidence / note
285Improper authorization (broad)Flows/AI/CDC/uploads/shares routes each enforce admin or PermissionService checks at middleware level.
565Reliance on cookies without validationNo cookies.
367TOCTOUFixed 2026-07-06. Same fix as CWE-362 — the approval decide/reject is now an atomic guarded UPDATE (WHERE status='pending' + affected-row check).
778Insufficient logging15 audit event codes incl. login success/failure, lockouts, recovery, API-key denials, role operations (modules/audit/logger.ts:134).
223Omission of security-relevant infoAudit entries carry actor, IP, user-agent, requestId, timestamp.
532Sensitive info in logsmaskSensitive replaces secret fields with 8-char SHA-256 prefixes, recursively (modules/audit/logger.ts:67).
548Directory listingNo static directory serving.
668Resource exposed to wrong sphereFixed 2026-07-06. /health reveals per-subsystem detail only to callers with the observability token; anonymous probes get { status } only. /metrics enforces METRICS_TOKEN in all environments when set.
1104Unmaintained third-party componentsFixed 2026-07-06. Added .github/dependabot.yml (weekly npm + github-actions, grouped minor/patch) and a dependency-audit CI job that reports the full pnpm audit and fails on high/critical advisories.
359Privacy violationFixed 2026-07-06. The audit masker now redacts raw content-payload keys (data/payload/content/body[redacted]) and caps long free-form strings, so item PII cannot land verbatim in audit metadata (modules/audit/logger.ts). Item audit events already logged only ids + redacted SQL.

Remediation backlog (priority order)

All items are complete as of 2026-07-06.

  1. CWE-620/613 — Password change & session revocation. ✅ Change-password endpoint requiring the current password + token_version revocation on change/reset.
  2. CWE-302 — Trust boundaries in middleware.withSiteMembership binds X-Lumi-Site; Cloudflare Access roles now mapped from the DB.
  3. CWE-400 — API rate limiting.withRateLimit() per-principal/per-IP throttle on the authenticated API (covers REST + GraphQL, which share the chain).
  4. CWE-521/203 — Registration hardening. ✅ Shared PasswordSchema (12+complexity) at register/change-password/setup/recovery reset and reset-password (the last had lingered on min(6) — closed 2026-07-09); register validates with a 400. (Register is public self-service but non-enumerating — both new and existing emails return an identical generic 202.)
  5. CWE-89 — Materialize DDL.sql.raw() replaced with bind parameters + sql.identifier(); trigger-body ids validated fail-closed.
  6. CWE-362/367 — Atomic approval decide.UPDATE … WHERE id = ? AND status = 'pending' + affected-rows check.
  7. CWE-1104 — Dependency hygiene. ✅ dependabot + a pnpm audit CI gate (fails on high/critical).
  8. CWE-359 — Audit-log privacy. ✅ Payload keys redacted + long strings capped in the audit masker.
  9. CWE-942 — Dev CORS. ✅ Never wildcard-with-credentials; only loopback reflected without an allowlist.
  10. CWE-321 — CDC fallback key. ✅ In-repo fallback removed; fail-closed when ENCRYPTION_KEY is unset.
  11. CWE-668/284 — Health & metrics. ✅ Subsystem detail gated behind the observability token; /metrics token-enforced in all envs.

Future hardening (not blocking; beyond the top-100 scope)

  • Precise per-endpoint API quotas via an atomic counter (Durable Object / Redis INCR) — the current limiter is a fixed-window approximation over the KV-style cache.
  • Include audit logs in the data-erasure workflow (or publish an explicit retention/rotation policy) now that item PII is kept out of them.
  • docs/en/security/route-guards.md — route-level guard inventory
  • docs/en/security/idor-testing.md — IDOR test guidelines
  • docs/en/security/runtime-security-guards-plan.md — guards roadmap
  • docs/en/security/external-jwt-auth.md — external issuer verification
  • docs/en/security/dependency-overrides.md — pinned dependency rationale
Last modified: 23/07/2026