This audit identified 3 Critical, 5 High, 6 Medium, and 4 Low severity findings across 22+ Sterling-built applications. No evidence of active exploitation was found, but several findings represent immediate, exploitable vulnerabilities.
_middleware.js). Every API endpoint, including admin user management, client data, and team information, is publicly accessible to anyone on the internet with zero authentication. This is a live, exploitable vulnerability affecting a multi-tenant business application.
| ID | Finding | App |
|---|---|---|
| C-01 | OA Client Dashboards auth middleware entirely disabled — all routes public | oa-client-dashboards |
| C-02 | Command Center master API token hardcoded in CLAUDE.md and 30+ script/doc files on disk | brads-command-center |
| C-03 | NYO Football SMS endpoint has NO authentication — anyone can send SMS via Brad's Twilio | nyo-football-schedule |
22 wrangler.toml files found. Key applications with server-side logic (Functions/Workers) listed below.
| App | Type | D1 DB | Auth | Public URL | Status |
|---|---|---|---|---|---|
| brads-command-center | Pages + Functions + D1 | brain-dump-db d70dd166 |
PBKDF2 + SMS 2FA + master token | brads-command-center.pages.dev | Active |
| oa-client-dashboards | Pages + Functions + D1 | c2d686fc |
Cookie sessions (DISABLED) | clients.outsourceaccess.com | Auth Disabled |
| stevens-family-planner | Pages + Functions + D1 | stevens-family-planner-db 9ec1fd47 |
PBKDF2 + master token | stevensfamilyplanner.com | Active |
| stevens-family-planner-api | Worker + D1 + AI | Same as SFP 9ec1fd47 |
None (relies on SFP token) | Worker route | Wildcard CORS |
| core-forum-hub | Pages + Functions + D1 | fd48071a |
HMAC JWT + PBKDF2 + rate limiting | coreforumhub.com | Active |
| forum-update-builder | Pages + Functions + D1 | Shares brain-dump-db d70dd166 |
PBKDF2 + SMS 2FA + rate limiting | forum-update-builder.pages.dev | Active |
| lineup-tool-api | Worker + D1 + Stripe | 8456c065 |
JWT (HMAC-SHA256) + PBKDF2 | Worker route (app.dugoutready.com) | Active |
| nyo-football-schedule | Pages + Functions | None | NONE | nyo-football-schedule.pages.dev | No Auth |
| sterling-social-approval | Pages + Functions + KV | None (uses KV) | Query param token | sterling-social-approval.pages.dev | Token in URL |
| debs-swim-school-dashboard | Pages + Functions + KV + D1 + AI | eb4a1200 |
NONE | debs-swim-school-dashboard.pages.dev | No Auth |
| ai-mba-portal | Pages + Functions + D1 | 91a07df1 |
PBKDF2 + sessions + rate limiting | ai-mba-portal.pages.dev | Active |
| dugoutready-survey | Pages + Functions + D1 | c449e0ca |
None (public form) | dugoutready-survey.pages.dev | By design |
| drill-save-worker | Worker + D1 | f388cbce + SFP DB |
UNVERIFIED | Worker route | Unverified |
| sterling-task-activation | Pages + Functions + D1 | ccdde90f |
Bearer token (prompt on load) | sterling-task-activation.pages.dev | Client-side token prompt |
| brads-brain-dump | Pages + Functions + D1 | Shares brain-dump-db d70dd166 |
Token via getAllowedOrigin | brads-brain-dump.pages.dev (archived) | Archived |
| oa-website-redesign | Pages + Functions + D1 | 17eecb65 |
None (public voting) | oa-website-redesign.pages.dev | By design |
| debs-swim-school-referral | Pages + Functions | None | None (public form) | debs-swim-school-referral pages | By design |
| dugout-ready-feedback | Pages + Functions | None | Minimal | dugout-ready-feedback.pages.dev | Public form |
| oa-recruit-chat | Pages + Functions | None | Origin-validated | oa-recruit-chat.pages.dev | Origin only |
| DB Name | ID | Used By |
|---|---|---|
| brain-dump-db | d70dd166-2fb5-41c0-b3d2-efcf00c3abde | brads-command-center, brads-brain-dump, forum-update-builder |
| stevens-family-planner-db | 9ec1fd47-6e93-49c7-a35a-6e55c2e0ea36 | stevens-family-planner, stevens-family-planner-api, drill-save-worker |
| oa-client-dashboards DB | c2d686fc-9347-4afa-a45f-6f5957a6e353 | oa-client-dashboards |
| core-forum-hub DB | fd48071a-aa04-4536-9a67-b6a1c33317ce | core-forum-hub |
| lineup-tool DB | 8456c065-24a5-414f-92e2-39d9c170a259 | lineup-tool-api |
| drill-library-db | f388cbce-8fa2-479a-af9d-390ada7ed940 | drill-save-worker |
| dugoutready-survey DB | c449e0ca-89ae-40bb-a308-6012d388917c | dugoutready-survey |
| ai-mba-portal DB | 91a07df1-1821-43fa-b216-7be691aed687 | ai-mba-portal |
| debs-swim-school DB | eb4a1200-63dd-42ba-ba3d-a7e67a53c6ae | debs-swim-school-dashboard |
| sterling-task-activation DB | ccdde90f-8e04-4d97-b6bb-721e550708b6 | sterling-task-activation |
| oa-website-redesign DB | 17eecb65-1879-4678-9a8b-e376b79f7c46 | oa-website-redesign |
| ID | Category | Severity | Description | Evidence | Status |
|---|---|---|---|---|---|
| C-01 | Authorization | Critical | OA Client Dashboards middleware auth bypassed. Line 18: return await context.next(); before all auth logic. Every endpoint is public. |
oa-client-dashboards/functions/api/_middleware.js:18-20 |
Reported |
| C-02 | Secrets | Critical | Command Center master token a323a87f...ac08c hardcoded in CLAUDE.md line 987, plus 30+ script/doc/conversation files on disk. Anyone with filesystem access has full API access. |
CLAUDE.md:987, email-scan/process_emails.py:13, scripts/process-inbox-replies.js:23, scripts/finance-cashflow-sync.sh:8, skills/command-center-qa.md:16, etc. |
Reported |
| C-03 | Authentication | Critical | NYO Football SMS send endpoint has zero authentication. Any HTTP POST to /api/send-sms sends SMS via Brad's Twilio account. Attacker can send arbitrary SMS to any phone number, incurring charges. |
nyo-football-schedule/functions/api/send-sms.js:1-54 — no auth check anywhere |
Reported |
| H-01 | Authorization | High | Debs Swim School Dashboard has zero authentication on all 4 API endpoints (braindump, chat, schedule, tasks). Anyone can read/write Deb's data, trigger emails to clients, and save PDFs to her Google Drive. | debs-swim-school-dashboard/functions/api/braindump.js — no auth check. Same for chat.js, schedule.js, tasks.js |
Reported |
| H-02 | CORS | High | Stevens Family Planner API Worker has CORS_ORIGIN = "*" hardcoded in wrangler.toml. Any website can make cross-origin requests to this API. |
stevens-family-planner-api/wrangler.toml:15 — CORS_ORIGIN = "*"; src/index.js:13 — 'Access-Control-Allow-Origin': env.CORS_ORIGIN || '*' |
Reported |
| H-03 | Error Handling | High | Multiple endpoints leak internal error details via e.message in HTTP responses. Leaks stack traces, API errors, and internal state to attackers. |
brads-command-center/functions/api/statement-scan.js:140 — detail: e.message; statement-detect.js:127; lineup-tool-api/src/index.js:542 |
Reported |
| H-04 | Authentication | High | SFP password minimum length is only 6 characters (Command Center requires 12). Weak password policy for a family app. | stevens-family-planner/functions/api/auth.js:114-115 — password.length < 6 |
Reported |
| H-05 | Authentication | High | Rate limiting fails open. If the rate limit table check throws an error, the function returns false (not blocked), allowing unlimited login attempts. |
brads-command-center/functions/api/auth.js:115-117 — catch (e) { return false; } |
Reported |
| M-01 | Session | Medium | Command Center session tokens stored in localStorage (when "Remember me" checked) are vulnerable to XSS theft. No HttpOnly flag possible for client-side tokens. | brads-command-center/public/index.html:5100-5102 |
Reported |
| M-02 | Auth | Medium | Social Approval Dashboard passes auth token as URL query parameter (?token=...), which gets logged in server access logs, browser history, and Referer headers. |
sterling-social-approval/functions/api/posts/index.js:28-29 — url.searchParams.get('token') |
Reported |
| M-03 | Session | Medium | Command Center sessions expire after 48 hours with no server-side session invalidation on password change for the current session (only other sessions are revoked). | brads-command-center/functions/api/auth.js:258,313-314 |
Reported |
| M-04 | CORS | Medium | OA Client Dashboards CORS reflects the request Origin without validation (when auth is re-enabled, the middleware echoes url.origin directly). |
oa-client-dashboards/functions/api/_middleware.js:27 — 'Access-Control-Allow-Origin': url.origin |
Reported |
| M-05 | Auth | Medium | Command Center auth error message inconsistency: line 275 says "at least 6 characters" but the actual check on line 274 enforces 12. Minor but suggests copy-paste error. | brads-command-center/functions/api/auth.js:274-275 |
Reported |
| M-06 | Headers | Medium | Command Center has no Content-Security-Policy header. Core Forum Hub has CSP but Command Center does not. | brads-command-center/public/_headers — no CSP line |
Reported |
| L-01 | Headers | Low | Workers (lineup-tool-api, drill-save-worker, sterling-scheduler-api) have no _headers file. Security headers only apply to Pages, not Workers, but no equivalent headers are set in Worker responses. | No _headers in those directories; Worker response headers don't include X-Frame-Options, HSTS, etc. |
Reported |
| L-02 | Config | Low | Three D1 databases are shared across apps: brain-dump-db (3 apps), SFP DB (3 apps). If any one app is compromised, all data in shared DBs is exposed. | wrangler.toml files cross-referenced |
Reported |
| L-03 | Session | Low | No session revocation endpoint. Users cannot explicitly log out from other devices. Sessions expire naturally after 24-48 hours. | No logout-all or device management functionality found in any app | Reported |
| L-04 | Crypto | Low | Voicemail webhook signature verification uses SHA-1 HMAC. SHA-1 is deprecated though HMAC-SHA1 is not currently considered broken for this use. | brads-command-center/functions/api/voicemail-webhook.js:30 — hash: 'SHA-1' |
Reported |
Strongest auth implementation. PBKDF2-SHA256 (100K iterations), SMS 2FA via Twilio Verify, rate limiting, session tokens in D1.
// brads-command-center/functions/api/auth.js:18-28
async function hashPassword(password, salt) {
const enc = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw', enc.encode(password), 'PBKDF2', false, ['deriveBits']
);
const bits = await crypto.subtle.deriveBits({
name: 'PBKDF2',
salt: enc.encode(salt),
iterations: 100000,
hash: 'SHA-256',
}, keyMaterial, 256);
return Array.from(new Uint8Array(bits))
.map(b => b.toString(16).padStart(2, '0')).join('');
}
// brads-command-center/functions/api/auth.js:95-118
async function checkRateLimit(db, ip) {
try {
// ... rate limit logic ...
return (row?.cnt || 0) >= 5; // blocked if 5+ attempts in 15 min
} catch (e) {
return false; // if rate limit table fails, don't block (but log it)
}
}
catch returns false, allowing unlimited brute-force attempts. Should fail closed (return true to block).// Session creation: auth.js:541-542 const token = generateToken(); // 32 random bytes -> hex const expiresAt = new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString();
// stevens-family-planner/functions/api/auth.js:114-115
if (password.length < 6) {
return jsonResponse({ error: 'Password must be at least 6 characters' }, 400);
}
// oa-client-dashboards/functions/api/_middleware.js:17-20
export async function onRequest(context) {
// AUTH BYPASSED per Brad's directive (July 9, 2026) — all routes public
return await context.next();
/* ... 90 lines of auth code commented out ... */
}
Uses HMAC-signed JWTs with SHA-256. Has rate limiting, login attempt tracking, and password reset flow. Appears well-implemented. Has CSP headers.
/api/send-sms POST endpoint directly calls the Twilio API to send SMS. No auth token, no session check, no API key validation. CORS is restricted to known origins, but CORS only blocks browser requests — any curl or script can call it directly.JWT-based auth with HMAC-SHA256 signatures. PBKDF2 password hashing with 100K iterations. Stripe webhook signature verification implemented correctly with timestamp tolerance checking. CORS properly configured to specific origins in wrangler.toml.
return await context.next(); before any auth check. This means:
GET /api/admin/users — Lists ALL users (email, name, role, status, last login). PUBLIC.POST /api/admin/users — Creates new users including admin accounts. PUBLIC.GET /api/admin/clients — Lists all client data. PUBLIC.GET /api/admin/teams — Lists all team data. PUBLIC.GET /api/dashboard — Returns dashboard data for any user. PUBLIC.POST /api/auth/register — Open registration. Anyone can create an account. PUBLIC.context.data?.user which is never populated (middleware doesn't set it), so the if (!user) return jsonResponse({ error: 'Not authenticated' }, 401) check in individual endpoint files IS triggered. The non-admin endpoints however have no such check and are fully open.
Correction to the above: Individual admin endpoints DO have if (!user) checks that return 401 because context.data.user is never set. So admin endpoints are inaccessible. But non-admin endpoints (dashboard, content, news, tickets, tools, va, hubspot-payments) have NO individual auth checks and are fully open.
Auth check happens at the start of each endpoint handler via authCheck(request, env). Returns 401 if unauthorized. No multi-tenancy, no IDOR risk by design (single user). However, the master token grants identical access to session-based access with no audit trail differentiation.
// brads-command-center/functions/api/_auth.js:21-52
export async function authCheck(request, env) {
const auth = request.headers.get('Authorization');
if (!auth || !auth.startsWith('Bearer ')) {
return jsonResponse({ error: 'Unauthorized' }, 401);
}
const token = auth.slice(7);
if (token === env.BRAIN_DUMP_TOKEN) {
return null; // authorized — no user identity attached
}
// ... session check ...
}
Auth check returns user object including role. Some endpoints check user.role === 'admin' for privileged operations (create_user, delete_user, manage_user). The activities and restaurants endpoints do NOT filter by user_id, meaning any authenticated user can see/modify all records.
// stevens-family-planner/functions/api/activities.js:40-45
// ALL activities returned — no user_id filter
const result = await env.DB.prepare(
'SELECT * FROM activities ORDER BY category, name'
).all();
return jsonResponse({ activities: result.results || [] });
JWT payload includes id, name, role (admin/member). Admin endpoints check role. Meeting endpoints reference the logged-in user's ID for voting, preventing impersonation. Best authorization implementation of all apps.
JWT includes userId. Team/player CRUD filters by user_id in D1 queries. IDOR protection appears adequate based on code review: queries use .bind(userId) consistently.
.bind(). No string concatenation in SQL found. Zero instances of template literals in .prepare() calls. This is clean.Evidence: grep -rn "\.prepare(\`" --include="*.js" --exclude-dir=node_modules returned zero matches. All queries use the pattern .prepare('SELECT ... WHERE x = ?').bind(value).
Multiple apps use innerHTML to render content, but most of this is admin-only content or server-provided data, not raw user input. Key observations:
family-travel-chat-v2/index.html:338,386 — sets innerHTML with AI-generated chat responses. If the AI returns HTML/JS, it would execute. LOW risk since the AI is prompted to answer travel questions, not inject scripts.brads-command-center/public/index.html:4922 — sets innerHTML with avatar URL from settings. If avatar_url contained a malicious payload, it could execute. Medium risk since avatar_url is set by authenticated users.exec(), eval(), or shell command invocations found in any Cloudflare Functions/Workers code. No SSRF vectors detected (no user-controlled URLs passed to fetch() in server-side code, except the Debs swim school schedule endpoint which fetches Google APIs only).Input validation varies by app:
| App | Validation Level | Notes |
|---|---|---|
| brads-command-center | Good | Auth validates types, lengths. Endpoints check required fields. |
| core-forum-hub | Good | Input validation on auth, meeting data, votes. |
| dugoutready-survey | Good | Validates name length (200), email (254), JSON size (50KB). |
| lineup-tool-api | Good | JSON parse errors handled safely, required field checks. |
| debs-swim-school-dashboard | Minimal | Chat validates message is string. Schedule has no input length limits on student names/emails. |
| nyo-football-schedule | None | No validation on to phone number format. Accepts any string and passes to Twilio. |
BRAIN_DUMP_TOKEN (a323a87f73757758f37ed7df942eda23f137bd9115432ba36589cfbe7daac08c) appears in plaintext in 30+ files on the local filesystem. While the token itself is stored as a Cloudflare Worker Secret (not in wrangler.toml), its value has been pasted into scripts, skill files, conversation logs, and most critically, into CLAUDE.md which is version-controlled.
CLAUDE.md:987 email-scan/process_emails.py:13 forum-update-builder/process-regen.sh:10 forum-update-builder/regen-processor.sh:10 scripts/process-inbox-replies.js:23 scripts/finance-cashflow-sync.sh:8 scripts/evening-ops-runner.md:85,452 scripts/evening-ops-verify.md:80 skills/cloudflare-pages-inventory-registration.md:14,43 skills/command-center-action-processor.md:15,22,54,62,71,84 skills/command-center-qa.md:16,28,233 conversations/*.md (20+ conversation logs)
| App | Env Vars (in Cloudflare Secrets) | In wrangler.toml? |
|---|---|---|
| brads-command-center | BRAIN_DUMP_TOKEN, TWILIO_SID, TWILIO_AUTH_TOKEN, TWILIO_VERIFY_SID | No (correct) |
| core-forum-hub | APP_SECRET, RESEND_API_KEY | No (correct) |
| stevens-family-planner | SFP_TOKEN | No (correct) |
| stevens-family-planner-api | GOOGLE_PLACES_API_KEY | No, but CORS_ORIGIN = "*" is in vars (H-02) |
| lineup-tool-api | JWT_SECRET, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET | No (correct) |
| debs-swim-school-dashboard | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN | UNVERIFIED — cannot confirm these are in CF secrets vs. vars |
| nyo-football-schedule | TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_PHONE_NUMBER | UNVERIFIED |
| sterling-social-approval | AUTH_TOKEN | UNVERIFIED |
.js or .html source files that would be deployed to production. The sk-ant / sk-or-v1 grep returned zero matches. Secrets are properly stored as Cloudflare Worker Secrets or environment variables.The risk is entirely in the local development environment files, not in deployed code.
| App | Data Collected | Sensitivity |
|---|---|---|
| brads-command-center | Username, password hash, phone number (for 2FA), tasks, financial uploads, email content, relationships | High |
| oa-client-dashboards | Email, full name, role, team assignments, client company data, VA assignments | High (currently PUBLIC) |
| stevens-family-planner | Username, display name, email, home address, photo URL, family activities, restaurant preferences | High |
| debs-swim-school-dashboard | Student names, student ages, parent/guardian names, guardian email addresses, lesson schedules, payment amounts | COPPA |
| core-forum-hub | Member names, EO forum membership, meeting votes, feedback | Medium |
| lineup-tool-api | Email, password hash, team rosters (player names), Stripe customer IDs | Medium |
| dugoutready-survey | Coach name, email, survey responses | Low |
| nyo-football-schedule | Phone numbers (via SMS send) | Medium |
This app stores first names and ages of minor children. Under COPPA (Children's Online Privacy Protection Act), collecting personal information from children under 13 requires verifiable parental consent and a privacy policy. This app has no privacy policy, no parental consent mechanism, and no authentication (H-01). The data is stored in Cloudflare D1/KV and emailed to clients.
Mitigating factor: This is a small private swim school, not a commercial website directed at children. The operator (Deb) collects the info directly from parents in person. The COPPA applicability may be limited, but the lack of any access control on the stored data remains a concern.
home_address in the users table. This is physical location data for Brad's family (including children Ella and Brayden). While the app requires authentication, the SFP API Worker has wildcard CORS (H-02), and the password minimum is only 6 characters (H-04).| App | Login Rate Limit | API Rate Limit | Fails |
|---|---|---|---|
| brads-command-center | 5 attempts / 15 min per IP | None | Open |
| core-forum-hub | 5 attempts / 15 min per IP | None | Open |
| oa-client-dashboards | 5 attempts / 15 min per IP (when auth enabled) | None | N/A (auth disabled) |
| forum-update-builder | 5 attempts / 15 min per IP | None | Open |
| ai-mba-portal | 5 attempts / 15 min (login + signup) | None | Open |
| dugoutready-survey | N/A | 5 submissions / 15 min per IP | Closed |
| lineup-tool-api | None found in code | None | N/A |
| All others | None | None | N/A |
false and allows the request through.| App | CORS Policy | Assessment |
|---|---|---|
| brads-command-center | Locked to https://brads-command-center.pages.dev | Good |
| core-forum-hub | Locked to https://coreforumhub.com | Good |
| stevens-family-planner | Locked to https://stevensfamilyplanner.com | Good |
| stevens-family-planner-api | CORS_ORIGIN = "*" in wrangler.toml | Wildcard |
| oa-client-dashboards | Reflects url.origin (in commented auth block) | Reflective |
| lineup-tool-api | Configured to specific origins in wrangler.toml vars | Good |
| sterling-social-approval | Allowlist + subdomain wildcard | Good |
| debs-swim-school-dashboard | Locked to production origin + subdomain wildcard | Good |
| nyo-football-schedule | Locked to production origin + subdomain wildcard | Good (but irrelevant for server-side calls) |
| App | Webhook | Verification |
|---|---|---|
| lineup-tool-api | Stripe webhooks | HMAC-SHA256 with 5-min timestamp tolerance + event dedup |
| brads-command-center | Voicemail webhook | HMAC signature verification (SHA-1) |
Most apps are zero-dependency (pure Cloudflare Pages Functions with no npm packages). Only a few have package.json:
| App | Dependencies | npm audit Status |
|---|---|---|
| stevens-family-planner-api | Wrangler dev dependency only | UNVERIFIED (cannot run npm audit in container) |
| lineup-tool-api | Wrangler dev dependency only | UNVERIFIED |
| debs-swim-school-dashboard | Wrangler dev dependency only | UNVERIFIED |
| dugoutready-chatbot | UNVERIFIED | UNVERIFIED |
| drill-save-worker | UNVERIFIED | UNVERIFIED |
| gchat-mcp-server | UNVERIFIED | UNVERIFIED |
wrangler as a dev dependency, which is the official Cloudflare CLI.grep -rn "sk-ant\|sk-or-v1\|api[_-]key.*=.*['\"]" --include="*.js" --include="*.html" --include="*.toml" --exclude-dir=node_modules
Result: 1 match (false positive) — a Puppeteer script searching for API key elements on a page:
scripts/cloudways-get-api-key.js:55 — querySelectorAll('code, pre, .api-key...')
grep -rn "\.prepare(\`\|\.prepare(.*\+\|\.prepare(.*\$\{" --include="*.js" --exclude-dir=node_modules
Result: 0 matches
grep -rn "Access-Control-Allow-Origin" --include="*.js" --exclude-dir=node_modules
Results: 60+ matches analyzed. All use origin validation functions except:
- stevens-family-planner-api/src/index.js:13 — env.CORS_ORIGIN || '*'
wrangler.toml sets CORS_ORIGIN = "*" (FINDING H-02)
grep -rn "err\.message\|e\.message\|error\.message" --include="*.js" --exclude-dir=node_modules | grep -i "response\|json\|return"
Findings:
brads-command-center/functions/api/statement-scan.js:140 — detail: e.message
brads-command-center/functions/api/statement-detect.js:127 — detail: e.message
stevens-family-planner-api/src/index.js:203 — Places API error: ${placesData.error.message}
lineup-tool-api/src/index.js:542 — session.error.message
brads-brain-tabs/netlify/functions/sync-monday.js:80 — detail: e.message
Could not run npm audit in this environment. npm install would need to be run first in each project directory, which is outside the scope of this read-only audit.
| App | Unit Tests | Integration Tests | Auth Tests | E2E Tests | Test Coverage |
|---|---|---|---|---|---|
| brads-command-center | None | None | None | None | 0% |
| oa-client-dashboards | None | None | None | None | 0% |
| stevens-family-planner | None | None | None | None | 0% |
| core-forum-hub | None | None | None | None | 0% |
| lineup-tool-api | None | None | None | None | 0% |
| All other apps | None | None | None | None | 0% |
No test runners (Jest, Vitest, Mocha, etc.) are configured in any package.json. No test scripts. No CI/CD pipeline configuration found (no .github/workflows, no wrangler-action configs). No test files of any kind exist in any application directory.
The only testing evidence is a dugoutready-tests/ directory with a package.json, which appears to be a Puppeteer-based manual test setup, not automated CI tests.
/api/admin/users return 401.
Assessment against OWASP Application Security Verification Standard v4.0, Level 2 requirements. Evaluated against the primary apps (Command Center, OA Client Dashboards, SFP, Core Forum Hub, Lineup Tool).
| Req | Description | Status | Evidence |
|---|---|---|---|
| 2.1.1 | Password min length >= 12 | Fail | SFP: 6 chars. AI MBA: 8 chars. Only CC enforces 12. |
| 2.1.2 | Password max length >= 128 | Pass | No max length restrictions found in any app. |
| 2.1.7 | Password checked against breach lists | Fail | No breach checking (haveibeenpwned, etc.) in any app. |
| 2.1.9 | No password composition rules | Pass | No uppercase/special char requirements. Length only. |
| 2.2.1 | Anti-automation on auth | Pass | Rate limiting present on login in CC, CFH, OA, FUB, AI MBA. |
| 2.4.1 | Passwords stored with approved hash | Pass | PBKDF2-SHA256, 100K iterations, random salt. All apps. |
| 2.5.1 | Password reset does not reveal account existence | N/A | Only OA has password reset. Returns generic messages. |
| 2.7.1 | OTP is time-limited | Pass | CC 2FA temp tokens expire after 10 minutes. |
| 2.8.1 | Session tokens are cryptographically random | Pass | 32 bytes CSPRNG via crypto.getRandomValues(). |
| Req | Description | Status | Evidence |
|---|---|---|---|
| 3.1.1 | Application never reveals session tokens in URL | Fail | Sterling Social Approval passes token as ?token= query param (M-02). |
| 3.2.1 | New session on login | Pass | New token generated on each successful login. All apps. |
| 3.3.1 | Logout invalidates session server-side | Fail | CC/SFP: no logout endpoint found. Token just cleared from browser storage. |
| 3.3.2 | Session timeout | Pass | CC: 48h. OA: 24h (sliding). Lineup: 30d (JWT exp). |
| 3.4.1 | Cookie-based tokens use HttpOnly, Secure, SameSite | Pass | OA login.js:65: HttpOnly, Secure, SameSite=Strict. CC uses Bearer tokens (not cookies). |
| 3.7.1 | Re-authentication for sensitive operations | Pass | CC requires password to disable 2FA (auth.js:442-466). |
| Req | Description | Status | Evidence |
|---|---|---|---|
| 4.1.1 | Access control enforced server-side | Fail | OA Client Dashboards: server-side auth disabled (C-01). NYO: no auth (C-03). Debs: no auth (H-01). |
| 4.1.2 | Deny by default | Fail | 3 apps default to allow-all. |
| 4.1.3 | Principle of least privilege | Fail | CC master token has full access with no audit differentiation from user sessions. |
| 4.2.1 | IDOR/BOLA protection | Pass | Lineup tool filters by user_id. SFP is single-family (no multi-tenant). CC is single-user. |
| Req | Description | Status | Evidence |
|---|---|---|---|
| 5.1.3 | Input validation on all inputs | Fail | NYO: no phone number format validation. Debs schedule: no input length limits. |
| 5.2.1 | HTML sanitization for user content | Fail | innerHTML used with AI responses (family-travel-chat). No DOMPurify or equivalent. |
| 5.3.1 | SQL injection prevention | Pass | All D1 queries use parameterized .bind(). Zero string concatenation. |
| 5.3.4 | Output encoding | Pass | JSON.stringify for all API responses. No HTML rendering server-side. |
The following items could not be verified during this source-code-only audit. They require access to the live Cloudflare dashboard, network testing, or runtime environment.
| Item | Why Not Verified | Risk if Not Checked |
|---|---|---|
| Cloudflare Worker Secrets actually set | Cannot access Cloudflare dashboard from this environment. We can see env vars referenced in code but cannot confirm they are set as encrypted secrets vs. plaintext vars. | High — if secrets are in plaintext vars, they appear in Wrangler logs and API responses. |
| Cloudflare WAF / Bot Protection rules | Dashboard access required. Cannot confirm any custom WAF rules exist. | Medium — default Cloudflare protection applies but custom rules would catch specific attack patterns. |
| D1 database actual contents / PII inventory | Cannot query live D1 databases. Can only infer schema from CREATE TABLE statements in code. | Medium — cannot confirm what data actually exists vs. what the schema allows. |
| npm audit results | Cannot run npm install / npm audit in this container environment. Package.json files exist but node_modules would need to be populated first. | Low — most apps have zero runtime dependencies. |
| OA Client Dashboards live state | Cannot confirm if auth is disabled on the DEPLOYED version vs. just in source code. Source shows disabled, but the deployed version might differ. | Critical — if deployed matches source, all OA client data is public. |
| SSL/TLS configuration | Cannot perform TLS handshake testing from this environment. | Low — Cloudflare enforces modern TLS by default. |
| DNS configuration / subdomain takeover risks | Cannot query DNS from container. Would need to check for dangling CNAME records. | Medium — common attack vector on Cloudflare Pages with custom domains. |
| R2 bucket access policies | Cannot query R2 ACLs. Sterling-media bucket is referenced for public CDN but cannot verify if other buckets are properly restricted. | Medium — misconfigured R2 could expose uploaded files. |
| drill-save-worker auth implementation | Source file not fully read during audit. Worker shares SFP database binding. | Medium — if no auth, it's a second entry point to SFP data. |
| Actual deployment state of archived apps | brads-brain-dump, brads-brain-tabs marked as "archived" but source still exists. Cannot confirm if still receiving traffic. | Low — if still deployed, old vulnerabilities may persist. |
| ID | Action | Effort | Impact |
|---|---|---|---|
| C-01 | Re-enable OA Client Dashboards auth middleware. Remove lines 18-20 (return await context.next();) and uncomment the auth block. If auth was disabled for a reason, add a TEMPORARY_AUTH_BYPASS environment variable instead of code commenting. |
5 minutes | Closes total auth bypass on multi-tenant business app |
| C-03 | Add auth to NYO Football SMS endpoint. Require Bearer token or shared secret. Without this, anyone can send SMS at Brad's expense. | 10 minutes | Prevents Twilio abuse / bill-running attack |
| C-02 | Rotate Command Center master token. Generate a new BRAIN_DUMP_TOKEN, update Cloudflare Worker Secret, update all scripts that reference the old token. Remove token from CLAUDE.md. | 30 minutes | Invalidates leaked token across 30+ files |
| ID | Action | Effort | Impact |
|---|---|---|---|
| H-01 | Add auth to Debs Swim School Dashboard. Simple shared password or PIN like Command Center uses. Protects student data (COPPA concern). | 30 minutes | Secures children's PII |
| H-02 | Fix SFP API wildcard CORS. Change CORS_ORIGIN = "*" in wrangler.toml to CORS_ORIGIN = "https://stevensfamilyplanner.com". |
2 minutes | Prevents cross-origin attacks against family data |
| H-03 | Remove e.message from HTTP responses. Replace detail: e.message with generic detail: 'Internal error' in all catch blocks. |
15 minutes | Stops information disclosure |
| H-04 | Increase SFP password minimum to 12. Match Command Center's policy. | 2 minutes | Stronger passwords for family app |
| H-05 | Make rate limiting fail closed. Change catch (e) { return false; } to catch (e) { return true; } in all rate limit functions. |
5 minutes | Prevents brute force during DB outages |
| ID | Action | Effort | Impact |
|---|---|---|---|
| M-01 | Consider HttpOnly cookie-based sessions for Command Center instead of localStorage Bearer tokens. | 2 hours | Eliminates XSS token theft vector |
| M-02 | Move Social Approval auth from URL query param to Authorization header. | 30 minutes | Prevents token leakage in logs/referer |
| M-04 | Fix OA Client Dashboards CORS to use explicit allowlist instead of reflecting origin. | 10 minutes | Prevents cross-origin attacks when auth is re-enabled |
| M-05 | Fix password change error message: "at least 6" should say "at least 12" to match actual check. | 1 minute | UX consistency |
| M-06 | Add Content-Security-Policy to Command Center and all apps lacking CSP. | 30 minutes | Mitigates XSS impact |
| NEW | Build basic automated tests. At minimum: (1) unauthenticated request to protected endpoint returns 401, (2) valid auth returns 200, (3) rate limit kicks in after 5 attempts. | 2 hours | Prevents regressions like C-01 |
| ID | Action | Effort | Impact |
|---|---|---|---|
| L-01 | Add security headers to Worker responses (not just Pages _headers files). | 30 minutes | Defense in depth |
| L-02 | Evaluate separating shared D1 databases. Low priority unless apps are compromised. | 2 hours | Blast radius reduction |
| L-03 | Add explicit logout/session revocation endpoints to all apps. | 1 hour | Session management hygiene |
| L-04 | Upgrade voicemail webhook HMAC from SHA-1 to SHA-256. | 5 minutes | Cryptographic hygiene |