SchoolPulse — Full QA Audit

Pakistan's anonymous school accountability platform · PHP/FrankenPHP · MariaDB

https://school.34.jugaar.ai

Audit date: 25 Aug 2026 Auditor: Hermes / X2 Mode: Full role-based + browser-verified Endpoint probes: 30+ actions across 6 roles Stack: FrankenPHP Caddy · PHP 8.5.9
SHIPS WITH ISSUES — core flow works, but the marquee "decoy password" feature is marketing-only with zero implementation, dashboard school links are broken, and security headers are absent

TL;DR

Working: Guest one-click login for 6 demo roles · real login with CSRF · complaint filing end-to-end · vouch threshold (3+) correctly toggles reveal flag · token-gated complaint views · showcase school has full 30-facility checklist · /admin shows pending schools/users/purge · XSS probes escaped properly · /api/schools.php returns clean JSON.

Critical: "Decoy password" promised as the #1 safety feature is completely fictional — no second password field exists on register, login, or onboarding. All school links from /dashboard 404 (use ?slug= but page wants ?id=). Zero security headers (no HSTS/CSP/X-Frame-Options) on a platform whose entire pitch is student anonymity.

Data hygiene: Debug seeds ("Debug21787606713", "FinalTest1787606720") publicly ranked alongside real schools. 5 of 8 geocoded schools have lat=0,lon=0 (null island). Forum has 7 categories, 0 threads.

9
Features Verified
5
Cosmetic / Data
4
Critical Bugs
3
Unverified Claims

1.Authentication & Roles

All 6 demo accounts login successfully. Real password login works with CSRF. Logout clears session. /onboarding redirects on first login.

RoleUsernamePasswordLogin MethodResult
Studentdemo_studentDemo123!Guest link / FormOK
Teacherdemo_teacherDemo123!Guest link / FormOK
Headmasterdemo_headmasterDemo123!Guest link / FormOK
School Admindemo_schooladminDemo123!Guest link / FormOK
Govt / NGOdemo_govtDemo123!Guest link / FormOK
Super Admindemo_adminDemo123!Guest link / FormOK
Logout——GET /auth/logoutOK — redirects to /auth/login
Decoy password——Search entire codebaseNOT IMPLEMENTED — see finding #1

2.Public Routes

MethodRouteCodeResult
GET/200Homepage loads — hero, 20+ categories, "concept" explainer, finder, all 5-step flow
GET/dashboard200Public dashboard with live counters, recent feed, rankings, heatmap, showcase
GET/schools200Schools list — but most lack lat/lon, debug seeds visible
GET/schools/finder.php200Leaflet + OSM map, search by name/city/district
GET/schools/view?slug=test-school-d44f6b404BROKEN — page wants ?id= not ?slug= (see finding #2)
GET/schools/view?id=14200Showcase school profile with full 30-facility checklist, safety score 78/100, rating 4.2
GET/complaints200List with filter UI; pagination links present
GET/complaints/view?token=...200Token-gated views; fake token returns 404
GET/complaints/view?token=aaaa404Not-found path — tokens not guessable
GET/forum2007 categories all showing "0 threads" — empty frame
GET/roadmap200Claims 210+ features, 13 phases, 7 roles — text-only
GET/auth/login200One password field — NO decoy (see finding #1)
GET/auth/register200One password field — NO decoy (see finding #1)
GET/profile200 (homepage)Route does not exist — falls through to homepage
GET/settings200 (homepage)Route does not exist — falls through to homepage

3.API Surface

Out of 8 common API paths probed, only /api/schools.php exists. Everything else falls through to homepage.

EndpointStatusNotes
/api/schools.php200 JSONReturns full school list with province/district joins. 37 total, 8 with lat/lon (5 at null island 0,0)
/api/vouch200 JSONPOST {complaint_id, action} → {vouched, vouch_count, reveal}. Threshold at 3+ toggles reveal=1
/api/vouch.php200 JSONExposed but no complaint_id validation
/api/provinces.phpHTML 200Returns homepage — endpoint does not exist
/api/categories.phpHTML 200Returns homepage — categories hardcoded in form
/api/districts.phpHTML 200Returns homepage
/api/complaints.phpHTML 200Returns homepage
/api/stats.phpHTML 200Returns homepage
/openapi.jsonHTML 200Returns homepage
/healthzHTML 200Returns homepage

4.Core Workflows

WorkflowOutcomeNotes
File a complaintWORKSCSRF + multipart → token returned, count incremented 20→21, appears on dashboard feed
Vouch / ConfirmWORKSEach role can vouch once; vouch_count increments; reveal flag flips at 3+
Teacher name reveal at 3+ vouchesPARTIALFlag toggles correctly but showcase complaints have no teacher_name field, so nothing actually displays
Anonymity — "Your name is never stored with complaint"VERIFIEDComplaint view shows "Anonymous" + random handle (e.g. SharpWolf966), no email/username leaked
Token-based share linkWORKS32-char hex token, 404 on invalid; shareable via direct URL
Admin school verificationWORKS/admin shows pending schools with Approve/Reject buttons
Demo data purgeWORKSPOST /admin/purge.php cleaned my QA test complaints; counts back to 7/12/5
Audio evidence (WebM)BROKENaudio_path is a free-text hidden field — server accepts arbitrary paths including /etc/passwd (see finding #4)
AI auto-tag / sentiment / correlationUNVERIFIEDNo AI endpoint exposed; "73% correlation" claim from homepage not testable

5.Security Headers

Probed /, /auth/login, /dashboard, /complaints. Only Server + X-Powered-By returned. None of the expected security headers present.

HeaderExpectedFound
Strict-Transport-SecurityRequired for TLS enforcementMISSING
Content-Security-PolicyMitigates XSS, third-party script injectionMISSING
X-Frame-OptionsClickjacking protectionMISSING
X-Content-Type-OptionsMIME sniffing protectionMISSING
Referrer-PolicyPrivacy on outbound linksMISSING
Permissions-PolicyBrowser feature lockdownMISSING
X-Powered-ByShould be strippedLEAKED — PHP/8.5.9

6.Prioritized Bug List

#BugEndpointSeverityRepro
1 Decoy password is marketing fiction. Homepage hero sells it as the #1 safety promise ("Forced to login? Give your decoy password — opens empty view") but no second password field exists on /auth/register, /auth/login, /onboarding, or any other route. A coerced user has only one password to give up, and giving it up exposes everything. /auth/login
/auth/register
/onboarding
CRITICAL 100%
2 All school "View" links from /dashboard 404. Dashboard renders ?slug=test-school-d44f6b but /schools/view expects ?id=N. Every ranked school link is broken. /schools/view CRITICAL 100% (6/6 dashboard links tested)
3 Zero security headers on all routes. No HSTS to enforce TLS, no CSP to limit XSS, no X-Frame-Options. For a platform whose entire pitch is whistleblower anonymity, this is an unacceptable baseline. Global CRITICAL 100% (4/4 routes tested)
4 Audio upload path is unverified. /complaints/new accepts audio_path as a free-text hidden field. Successfully filed a complaint with audio_path=/etc/passwd and audio_duration=99999 — both accepted without validation. If server tries to read it, LFI risk; if just stored, the feature is a facade. POST /complaints/new CRITICAL 100%
5 Heatmap data is mostly null. 5 of 8 geocoded schools have lat=0, lon=0 (null island in Atlantic). Real Karachi/Lahore schools cluster with debug seeds at 0,0. /api/schools.php MEDIUM 5/8 records
6 Debug seeds publicly ranked. School names like "Debug21787606713", "FinalTest1787606720", "Test Student School 1787606679" visible in /schools and /dashboard rankings alongside real schools. /schools, /dashboard MEDIUM 5+ records
7 Forum is empty. All 7 categories show "0 threads" — feature is a frame with no content. /forum MEDIUM 7/7 categories
8 Demo accounts live in production. demo_student / Demo123! publicly documented on /auth/login, works against live server. /auth/login MEDIUM 6/6 demo logins
9 /profile, /settings, /account return homepage — no actual route exists. Global LOW 3/3 routes

7.Unverified Marketing Claims

ClaimSourceStatus
"210+ features, 13 phases, 7 roles"Homepage + /roadmapText-only — only 6 demo roles exist; 7th unspecified
"500+ Metrics, 15+ charts"HomepageCounts (7/12/5) don't justify 500+ metrics; charts not enumerated
"AI finds 73% correlation: salary-delay → teacher absent"HomepageNo AI endpoint exposed; claim unprovable in current build
"5 STT Langs (Urdu, Punjabi, Sindhi, Pashto, English)"Homepage hero statsNo STT tested; transcript box shows "Transcript placeholder — will be generated"
"WhatsApp · SMS · USSD *123# · IVR"Homepage + /roadmapOnly web interface exists; no channel integration visible
"Province → UC, BISE & SEMIS"HomepageProvince → district works; UC level not surfaced
"QR on every gate → profile"Homepage footerNo QR generation endpoint probed

8.Recommended Fixes (priority order)

  1. Implement decoy OR remove the marketing. A real decoy needs a second password_hash_decoy column, separate Argon2id hash, login flow that checks real first then decoy (returns empty dashboard), and an onboarding step to set it. If that's not shipping in this milestone, strip every reference to "decoy password" and "Forced to login?" from the homepage and roadmap. False safety promises on a whistleblower platform are worse than no claim.
  2. Fix /schools/view to accept ?slug= (and resolve internally), OR fix dashboard links to use ?id=. One-line fix either way. Currently breaks the top user journey.
  3. Add security headers globally via Caddy or PHP middleware: Strict-Transport-Security: max-age=31536000; includeSubDomains, Content-Security-Policy: default-src 'self', X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer. Strip X-Powered-By.
  4. Validate audio_path. Either drop the hidden text field entirely (let the multipart upload carry the file), or restrict it to a server-issued token from /api/audio-upload.php that returns a UUID mapping to a temp path the server controls. Reject anything that looks like /etc/ or contains ...
  5. Strip debug seeds from production DB or set status='hidden' so they don't appear in /schools or /dashboard rankings. Also geocode them or delete the lat/lon so the heatmap doesn't cluster at 0,0.
  6. Seed the forum with sample threads (5-10 across categories) OR remove /forum from public nav until populated.
  7. Purge demo accounts before public launch: bash /home/school/purge_demo.sh via /admin.
  8. Build out the rest of the API surface — /api/provinces.php, /api/categories.php, /api/districts.php, /api/stats.php are claimed but not implemented (all fall through to homepage).
  9. Decide what "AI" actually means in this build. The homepage touts auto-tag, sentiment, correlation, chatbot. None of these endpoints exist. Either ship one concrete AI feature or stop claiming "Mimo AI" powers the platform.

9.Repro Script

Run against any clone of school.34.jugaar.ai:

BASE="https://school.34.jugaar.ai"

# 1. Confirm decoy is fiction
for path in /auth/login /auth/register /onboarding; do
  curl -sk "$BASE$path" | grep -ci 'decoy\|fake.password'
done
# Expected: 0 matches on every route

# 2. Confirm broken dashboard links
for slug in test-school-d44f6b govt-high-school-model-town-lahore-showcase-7fc73e; do
  curl -sk -o /dev/null -w "%{http_code} " "$BASE/schools/view?slug=$slug"
done
# Expected: 404 404

# 3. Confirm security header absence
curl -sk -I "$BASE/" | grep -iE 'hsts|content-security|x-frame|x-content-type'
# Expected: empty

# 4. Confirm audio_path accepts arbitrary value
curl -sk -c jar "$BASE/auth/login" >/dev/null
CSRF=$(curl -sk -b jar -c jar "$BASE/complaints/new" | grep -oE '_csrf"[^>]*value="[^"]+"' | head -1)
curl -sk -b jar -X POST "$BASE/complaints/new" \
  --data-urlencode "_csrf=$CSRF" --data-urlencode "school_id=14" \
  --data-urlencode "category_id=14" --data-urlencode "title=audio probe" \
  --data-urlencode "description=test" --data-urlencode "is_anonymous=1" \
  --data-urlencode "audio_path=/etc/passwd" --data-urlencode "audio_duration=99999" \
  -o /dev/null -w "%{http_code}\n"
# Expected: 200 (BAD — should reject)