Security Audit Report

Post-Remediation Verification -- July 12, 2026

Executive Summary
Verification Results
Remaining Findings
Scan Results
Remediation Log
Cannot Verify

Risk Rating

LOW Down from HIGH (original audit)

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.

13
Fixed
1
Partially Fixed
3
Accepted Risk
1
New Issue Found

Finding Breakdown

SeverityTotalFixedPartialAcceptedNew Issue
Critical33000
High55000
Medium63111
Low42020
Key Outcome: All Critical and High severity vulnerabilities are fully remediated. The authentication bypass (C-01), exposed master token (C-02), and unauthenticated SMS endpoint (C-03) are all confirmed fixed. No Critical or High issues remain open.

Per-Finding Verification

Critical C-01: OA Client Dashboards Auth Bypass Fixed

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.

Evidence: oa-client-dashboards/functions/api/_middleware.js
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.
Critical C-02: Command Center Master Token Exposure Fixed

The old token a323a87f... returns zero matches in all active code files (.js, .py, .sh, .html, .json).

Evidence: 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).
Critical C-03: NYO Football SMS Unauthenticated Fixed

The SMS endpoint now validates a Bearer token against env.SMS_AUTH_TOKEN before processing any request. Unauthorized requests receive a 401 response.

Evidence: nyo-football-schedule/functions/api/send-sms.js
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).
High H-01: Deb's Swim School Dashboard Unauthenticated Fixed

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.

Evidence:
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).
High H-02: SFP API Wildcard CORS Fixed

The wrangler.toml now sets CORS_ORIGIN = "https://stevensfamilyplanner.com" (specific domain, not *). The index.js uses env.CORS_ORIGIN directly with no || '*' fallback.

Evidence: wrangler.toml line 15: CORS_ORIGIN = "https://stevensfamilyplanner.com". index.js line 13: 'Access-Control-Allow-Origin': env.CORS_ORIGIN. Grep for || '*' returns zero matches.
High H-03: Error Message Leaks Fixed

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).

Evidence:
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).
Note: One residual leak found in 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.
High H-04: SFP Password Minimum Too Low Fixed

Password minimum is now 12 characters in both setup and change_password flows.

Evidence: stevens-family-planner/functions/api/auth.js
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."
High H-05: Rate Limiting Fails Open Fixed

Both rate limiting implementations now fail closed (return true = blocked) when the rate limit table or query fails.

Evidence:
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).
Medium M-01: localStorage Session Tokens Accepted Risk

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.

Medium M-02: Social Approval Auth Header Support Fixed

The Social Approval API now accepts auth tokens via both Authorization: Bearer header and ?token= query parameter. Header-based auth is checked first.

Evidence: sterling-social-approval/functions/api/posts/index.js
Line 29: const authToken = (request.headers.get('Authorization') || '').replace('Bearer ', '') || url.searchParams.get('token');
Medium M-04: OA Client Dashboards CORS Origin Reflection Fixed

CORS now uses an explicit allowlist of two domains. Non-matching origins get the default allowed origin (no reflection of arbitrary origins).

Evidence: oa-client-dashboards/functions/api/_middleware.js
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.
Medium M-05: Password Error Message Mismatch Fixed

All password validation error messages now correctly state "12 characters" to match the actual enforcement.

Evidence: brads-command-center/functions/api/auth.js
Line 205: 'Password must be at least 12 characters'. Line 275: 'New password must be at least 12 characters'.
Medium M-06: Missing CSP on Command Center Fixed

A comprehensive _headers file has been added to the Command Center with full security headers including CSP.

Evidence: brads-command-center/public/_headers contains:
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.
Low L-01: Lineup Tool Missing Security Headers Fixed

Security headers are now included in every JSON response via the jsonResponse() helper.

Evidence: lineup-tool-api/src/index.js
Lines 44-47 in jsonResponse: Includes X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin.
Low L-02: Shared D1 Databases Accepted Risk

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.

Low L-03: No Session Revocation 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.

Low L-04: Voicemail Webhook HMAC SHA-1 Fixed

The voicemail webhook now uses HMAC-SHA256 for Twilio signature validation, replacing the weaker SHA-1. Includes constant-time comparison to prevent timing attacks.

Evidence: brads-command-center/functions/api/voicemail-webhook.js
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.

Medium M-01: localStorage Session Tokens Accepted Risk

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.

Low L-02: Shared D1 Databases Accepted Risk

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.

Low L-03: No Session Revocation Accepted Risk

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.

Medium N-01: SFP Places API Error Message Leak (NEW) New Issue

Discovered during H-03 verification. The SFP API exposes Google Places API error messages directly to the client.

Location: stevens-family-planner-api/src/index.js line 203
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)
PASS: The old master token does not appear in any active code file. Token rotation is complete.

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': '*',
NOTE: 3 instances remain in legacy/archived projects:
1. two-question-exercise-lp/worker.js (2 instances) -- landing page worker, public-facing by design
2. 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
1 genuine leak found in active code (SFP Places API, N-01). All originally flagged files (statement-scan.js, statement-detect.js, calendar-sync.js, lineup-tool-api) are confirmed clean. The 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)
NOTE: The || '*' 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

IDFindingFiles ChangedWhat ChangedDate
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)

ItemReasonRisk
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.