Risk Rating
All 3 Critical and 5 High severity findings have been successfully remediated and verified. The remaining open items are Medium and Low severity with accepted risk or backlog status.
Finding Breakdown
| Severity | Total | Fixed | Partial | Accepted | New Issue |
|---|---|---|---|---|---|
| Critical | 3 | 3 | 0 | 0 | 0 |
| High | 5 | 5 | 0 | 0 | 0 |
| Medium | 6 | 3 | 1 | 1 | 1 |
| Low | 4 | 2 | 0 | 2 | 0 |
Per-Finding Verification
The early return await context.next() bypass has been removed. The middleware now properly validates session tokens via cookie, checks session expiry against D1, verifies user status (blocks inactive accounts), loads role assignments, and enforces admin-only route restrictions.
Lines 42-47: Public paths skip auth. Lines 50-54: Missing session token returns 401. Lines 58-67: Session validated against D1 with expiry check. Lines 69-71: Inactive user returns 403. No early bypass exists.
The old token a323a87f... returns zero matches in all active code files (.js, .py, .sh, .html, .json).
grep -rn "a323a87f..." /workspace/group/ --include="*.js" --include="*.html" --include="*.json" returned 0 results. Token has been rotated. Note: CLAUDE.md still references the old token as documentation context, which is expected and acceptable (it is not used in any code path).
The SMS endpoint now validates a Bearer token against env.SMS_AUTH_TOKEN before processing any request. Unauthorized requests receive a 401 response.
Lines 5-8: Auth header extracted, validated against
env.SMS_AUTH_TOKEN. Returns 401 JSON if invalid. CORS also fixed with explicit origin allowlist (line 62).
All four API files now include a checkAuth() function that validates a Bearer token against env.DASHBOARD_AUTH_TOKEN. Every handler (GET, POST, PUT, DELETE) calls checkAuth() before processing.
braindump.js: Lines 22-28 (checkAuth), Lines 45-46, 52-53, 79-80, 108-109 (called in each handler).
chat.js: Lines 23-25 (auth check in onRequestPost).
schedule.js: Lines 27-33 (checkAuth), Lines 212-213, 375-376 (called in POST/GET handlers).
tasks.js: Lines 22-28 (checkAuth), Lines 36-37, 62-63, 99-100, 136-137 (called in all handlers).
The wrangler.toml now sets CORS_ORIGIN = "https://stevensfamilyplanner.com" (specific domain, not *). The index.js uses env.CORS_ORIGIN directly with no || '*' fallback.
CORS_ORIGIN = "https://stevensfamilyplanner.com". index.js line 13: 'Access-Control-Allow-Origin': env.CORS_ORIGIN. Grep for || '*' returns zero matches.
The originally flagged files now return generic error messages in all HTTP responses. e.message is only used in console.error() (server-side logging, never sent to clients).
statement-scan.js line 140: Returns
{ error: 'Internal error', detail: 'Internal error' } (not e.message).statement-detect.js line 127: Same pattern.
calendar-sync.js line 215-216: Returns generic
'Calendar API error' or 'Calendar API not configured'.lineup-tool-api/src/index.js line 2074: Returns
'Internal error'. Line 2070: Checks err.message only to identify SyntaxError type, returns generic 'Invalid JSON in request body' (safe).
stevens-family-planner-api/src/index.js line 203: Places API error: ${placesData.error.message}. This exposes the Google Places API error message to the client. See NEW ISSUE N-01 in Remaining Findings.
Password minimum is now 12 characters in both setup and change_password flows.
Line 114:
if (password.length < 12). Line 140: if (new_password.length < 12). Line 192: if (password.length < 12) (create_user). All error messages say "at least 12 characters."
Both rate limiting implementations now fail closed (return true = blocked) when the rate limit table or query fails.
brads-command-center/functions/api/auth.js line 117:
catch (e) { return true; } (blocked on error).core-forum-hub/functions/api/_auth.js line 96:
catch (e) { return true; } (blocked on error).
Session tokens are still stored in localStorage across SFP and Command Center. Migration to httpOnly cookies is a larger architectural change. Accepted as medium risk given these are personal/family apps with limited user base.
The Social Approval API now accepts auth tokens via both Authorization: Bearer header and ?token= query parameter. Header-based auth is checked first.
Line 29:
const authToken = (request.headers.get('Authorization') || '').replace('Bearer ', '') || url.searchParams.get('token');
CORS now uses an explicit allowlist of two domains. Non-matching origins get the default allowed origin (no reflection of arbitrary origins).
Lines 22-27:
ALLOWED_ORIGINS = ['https://clients.outsourceaccess.com', 'https://oa-client-dashboards.pages.dev']. Origin checked against allowlist; falls back to ALLOWED_ORIGINS[0] if not matched.
All password validation error messages now correctly state "12 characters" to match the actual enforcement.
Line 205:
'Password must be at least 12 characters'. Line 275: 'New password must be at least 12 characters'.
A comprehensive _headers file has been added to the Command Center with full security headers including CSP.
X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, Permissions-Policy (camera, microphone, geolocation denied), X-XSS-Protection, HSTS (1 year, includeSubDomains), and a Content-Security-Policy with specific src directives.
Security headers are now included in every JSON response via the jsonResponse() helper.
Lines 44-47 in jsonResponse: Includes
X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin.
Multiple apps share D1 databases. This is an accepted architectural decision given the current scale and the fact that all apps serve the same user (Brad). Documented as accepted risk.
Session revocation (logout invalidates all sessions for a user) is a backlog item. Sessions do expire naturally and password changes invalidate other sessions in SFP. Accepted risk given limited user base.
The voicemail webhook now uses HMAC-SHA256 for Twilio signature validation, replacing the weaker SHA-1. Includes constant-time comparison to prevent timing attacks.
Line 30:
{ name: 'HMAC', hash: 'SHA-256' }. Lines 39-46: Constant-time byte comparison using XOR mismatch accumulator. Also includes AccountSid verification (lines 99-106) as a secondary check.
Open Findings (4)
These items remain open, either as accepted risk, backlog, or newly discovered during verification.
SFP and Command Center store session tokens in localStorage. XSS on these domains could exfiltrate tokens. Mitigation: CSP is now in place (M-06 fix) which reduces XSS risk. Migration to httpOnly cookies is in the backlog but not prioritized given the personal/family user base.
Multiple apps share D1 databases. Accepted per Brad's directive: security and isolation are priorities, but at current scale (~single user), the risk is minimal. Will revisit if user base grows.
No global "log out everywhere" button. Sessions expire naturally (30-48 hours). Password change in SFP does invalidate other sessions. Backlog item for Command Center.
Discovered during H-03 verification. The SFP API exposes Google Places API error messages directly to the client.
Code:
return errorResponse(`Places API error: ${placesData.error.message}`, 502, env);Risk: Could leak internal API configuration details (wrong key format, quota exceeded reasons, etc.) to the browser.
Fix: Replace with
return errorResponse('Places API error', 502, env);
Secrets Scan: Old Master Token
Searching for a323a87f73757758f37ed7df942eda23f137bd9115432ba36589cfbe7daac08c across all .js, .py, .sh, .html, .json files.
$ grep -rn "a323a87f..." /workspace/group/ --include="*.js" --include="*.py" \
--include="*.sh" --include="*.html" --include="*.json"
(zero results)
Wildcard CORS Scan
Searching for Access-Control-Allow-Origin: * in .js and .toml files (excluding node_modules).
$ grep -rn "Access-Control-Allow-Origin.*\*" /workspace/group/ \
--include="*.js" --include="*.toml" --exclude-dir=node_modules
/workspace/group/two-question-exercise-lp/worker.js:91: 'Access-Control-Allow-Origin': '*',
/workspace/group/two-question-exercise-lp/worker.js:103: 'Access-Control-Allow-Origin': '*',
/workspace/group/brads-brain-tabs/netlify/functions/sync-monday.js:53: 'Access-Control-Allow-Origin': '*',
1.
two-question-exercise-lp/worker.js (2 instances) -- landing page worker, public-facing by design2.
brads-brain-tabs/netlify/functions/sync-monday.js (1 instance) -- archived project (Brain Dump predecessor, paused/inactive)These are not in active high-security applications. The two-question-exercise is a public form. The brain-tabs project is deprecated. No action required unless these are reactivated.
Error Message Leak Scan
Searching for e.message, err.message, error.message in response-related contexts.
$ grep -rn "e\.message\|err\.message\|error\.message" /workspace/group/ \
--include="*.js" --exclude-dir=node_modules | grep -i "response\|json\|return\|detail"
brads-command-center/.../prompt-ai.js:53: responseText = choice.message.content;
^^ FALSE POSITIVE: "message.content" is reading AI response, not leaking errors
stevens-family-planner-api/src/index.js:203:
return errorResponse(`Places API error: ${placesData.error.message}`, 502, env);
^^ GENUINE LEAK: Google Places API error exposed to client (NEW ISSUE N-01)
brads-brain-tabs/netlify/functions/sync-monday.js:80/215:
^^ ARCHIVED PROJECT: Brain Dump predecessor, inactive
lineup-tool-api/src/index.js:2070:
if (err instanceof SyntaxError && err.message.includes('JSON'))
^^ SAFE: Checks message content internally, returns generic "Invalid JSON" string
e.message usage in those files is console.error() only (server-side logging).
Lineup Tool CORS Configuration
The Lineup Tool uses a split CORS implementation.
lineup-tool-api/src/index.js lines 11-24:
const allowedOrigins = (env.CORS_ORIGIN || '*').split(',').map(s => s.trim());
-- Falls back to '*' only if CORS_ORIGIN env var is completely unset
-- When set (e.g., "https://lineuptool.com"), origin is validated against allowlist
-- Non-matching origins get NO Access-Control-Allow-Origin header (correct behavior)
|| '*' fallback in the Lineup Tool is a deployment safety net (if env var is missing, don't break the app). The env var CORS_ORIGIN should be set in production. This is acceptable but documented for awareness.
Remediation Log
| ID | Finding | Files Changed | What Changed | Date |
|---|---|---|---|---|
| C-01 | Auth bypass | oa-client-dashboards/functions/api/_middleware.js | Removed early return await context.next(). Restored full auth middleware: session validation, expiry check, role-based access, sliding window session extension. |
Jul 12, 2026 |
| C-02 | Token exposure | All files referencing old token | Rotated master token. Old value a323a87f... removed from all active code. New token deployed via Worker secrets. |
Jul 12, 2026 |
| C-03 | Unauth SMS | nyo-football-schedule/functions/api/send-sms.js | Added Bearer token auth check (lines 5-8). Added explicit CORS origin allowlist. Catch block returns generic error. | Jul 12, 2026 |
| H-01 | Unauth endpoints | debs-swim-school-dashboard/functions/api/{braindump,chat,schedule,tasks}.js | Added checkAuth() function to all four files. Every HTTP method handler validates Bearer token before processing. Added explicit CORS origin allowlists. |
Jul 12, 2026 |
| H-02 | Wildcard CORS | stevens-family-planner-api/wrangler.toml, src/index.js | Set CORS_ORIGIN = "https://stevensfamilyplanner.com". Removed || '*' fallback from corsHeaders function. |
Jul 12, 2026 |
| H-03 | Error leaks | brads-command-center/functions/api/{statement-scan,statement-detect,calendar-sync}.js, lineup-tool-api/src/index.js | All catch blocks now return generic 'Internal error' in HTTP responses. e.message used only in console.error(). |
Jul 12, 2026 |
| H-04 | Weak password | stevens-family-planner/functions/api/auth.js | Changed password.length < 6 to password.length < 12 in all validation paths (setup, change_password, create_user). |
Jul 12, 2026 |
| H-05 | Rate limit fail-open | brads-command-center/functions/api/auth.js, core-forum-hub/functions/api/_auth.js | Changed catch block from return false to return true so rate limiting fails closed (blocks on error). |
Jul 12, 2026 |
| M-02 | Auth header | sterling-social-approval/functions/api/posts/index.js | Added Authorization: Bearer header parsing with fallback to query param. |
Jul 12, 2026 |
| M-04 | CORS reflection | oa-client-dashboards/functions/api/_middleware.js | Replaced origin reflection with explicit 2-domain allowlist. | Jul 12, 2026 |
| M-05 | Error msg mismatch | brads-command-center/functions/api/auth.js | Updated error messages from "6 characters" to "12 characters". | Jul 12, 2026 |
| M-06 | Missing CSP | brads-command-center/public/_headers | Created _headers file with full security header suite including CSP with specific src directives. | Jul 12, 2026 |
| L-01 | Missing headers | lineup-tool-api/src/index.js | Added X-Frame-Options, X-Content-Type-Options, Referrer-Policy to jsonResponse helper. | Jul 12, 2026 |
| L-04 | Weak HMAC | brads-command-center/functions/api/voicemail-webhook.js | Upgraded from SHA-1 to SHA-256 for Twilio signature validation. Added constant-time comparison and AccountSid verification. | Jul 12, 2026 |
Cannot Verify (Honest Gaps)
| Item | Reason | Risk |
|---|---|---|
| C-02: New token value | The new token is stored as a Cloudflare Worker secret (env.BRAIN_DUMP_TOKEN). Cannot verify the actual deployed secret value from source code alone. Verification confirms old token is gone from code; cannot confirm the live Worker has the new secret set. |
Low |
| H-01: DASHBOARD_AUTH_TOKEN deployed | The Deb's Swim School auth checks validate against env.DASHBOARD_AUTH_TOKEN. Code is correct, but cannot verify the Worker secret is actually set in the Cloudflare dashboard. If unset, all requests would get 401. |
Low |
| C-03: SMS_AUTH_TOKEN deployed | Same as above. Code checks env.SMS_AUTH_TOKEN. Cannot verify the secret is set in production from source alone. |
Low |
| H-02: CORS_ORIGIN env var in production | The wrangler.toml sets CORS_ORIGIN, which should carry to production. Cannot verify the live Worker's environment without API access to the deployed Worker. |
Low |
| Live deployment status | All verification is source-code level. Cannot confirm that the latest code has been deployed to Cloudflare Pages/Workers in production. Source fixes are confirmed; deployment status requires a separate check via wrangler pages deploy or the Cloudflare dashboard. |
Medium |
| M-03 (if applicable) | M-03 was not listed in the remediation scope. If an M-03 finding existed in the original audit, it was not included in this verification pass. | Low |
Recommendations
1. Deploy Verification
Run wrangler pages deploy for each project and verify the deployed code matches the source. This audit verifies source code only.
2. Fix N-01 (SFP Places API Leak)
Replace line 203 in stevens-family-planner-api/src/index.js with a generic error message. 2-minute fix.
3. Worker Secrets Verification
Verify via Cloudflare dashboard that BRAIN_DUMP_TOKEN, DASHBOARD_AUTH_TOKEN, SMS_AUTH_TOKEN, and CORS_ORIGIN are all set in their respective Workers.
4. Legacy Project Cleanup
Consider removing or archiving brads-brain-tabs and tightening CORS on two-question-exercise-lp if it handles any sensitive data.