๐ฅ QuizWizz v1.10.0 โ Audit & Roast
Historical record โ 2026-07-24
Preserved as observed. Current truth: Program Masterplan.
TL;DR
Verdict:
vendor/is a war crime, the actual code is honor-roll. Sins are architectural taste (static mega-classes, dead shims, PHP-8.3 elitism, a PDF monolith on a quiz toy) โ not competence. Zero classic WP security holes found. Grade: A- code / C packaging.
- subject: QuizWizz โ build/publish/print/play validated MCQ + true-false quizzes
- version: 1.10.0
- bench: https://wrdp.loca.zone (isolated Docker WP 7.0 + MariaDB 11.4)
- method: source read (
includes/), footgun grep, dependency x-ray. No dynamic exploit run. - scope_read:
quizwizz.php,Rest/GenerateController,Rest/QuizController,Rest/SettingsController,Ai/OpenRouterClient,Ai/AiBudget,RateLimiter,QuizAccess,AttemptService,Frontend/Shortcode,Plugin
๐บ๏ธ Verdict Map
flowchart LR QW["QuizWizz 1.10.0"] --> SEC["๐ก๏ธ Security"] QW --> ARCH["๐๏ธ Architecture"] QW --> PKG["๐ฆ Packaging"] SEC --> S1["โ prepare() everywhere"] SEC --> S2["โ sanitize + validate input"] SEC --> S3["โ token/cap gates + rate limit"] SEC --> S4["โ SSRF-bounded AI + cost ledger"] ARCH --> A1["โ ๏ธ 680-line static controller"] ARCH --> A2["โ ๏ธ dead compat shims"] ARCH --> A3["โ ๏ธ hard-wired PdfGenerator"] PKG --> P1["โ full mPDF vendor tree"] PKG --> P2["โ PHP 8.3 floor"] PKG --> P3["โ Tested up to 7.0 only"]
๐ Scorecard
| Dimension | Grade | One-line |
|---|---|---|
| ๐ก๏ธ Security | A | prepared SQL, sanitized input, token+cap gates, SSRF-bounded egress |
| ๐งฎ AI cost governance | A+ | pre-flight micro-budget ledger, price caps, ZDR/deny defaults |
| ๐งฑ Input validation | A | schema-validated, size-capped bodies, strict multipart allowlist |
| ๐๏ธ Architecture | C+ | static mega-classes, no DI, untestable hot paths |
| ๐ฆ Packaging / footprint | D | ~138k LOC, mostly bundled PDF engine + fonts |
| ๐ Compat / reach | C | PHP 8.3 min excludes much of shared hosting |
| ๐ Docs / provenance | A- | readme, BUILDING, SOURCE-PROVENANCE, THIRD-PARTY notices present |
โ What it gets right (verified)
| ๐ Strength | Evidence |
|---|---|
| All SQL parameterized | AiBudget.php:66-90,154-267,340-375; RateLimiter.php:128-142; AttemptService.php:97-101 โ every call via $wpdb->prepare(), dynamic IN() builds %s placeholders |
| Input sanitized + regex-validated | RateLimiter::client_key rejects non-^[a-f0-9]{64}$ (:16-39); every $_GET/$_COOKIE wp_unslash+sanitize_* |
| Public routes gated in depth | GenerateController.php:98-105 __return_true then rate-limit + current_user_can('edit_post') (:84,645); mutations manage_options/EDIT_QUIZZES |
| Guest access = hashed expiring tokens | QuizAccess.php:11-24 sha256 + hash_equals; cookie httponly+samesite=Lax+secure-on-ssl (:32-43) |
| SSRF-bounded AI egress | OpenRouterClient.php:115-129 wp_safe_remote_post, redirection=0, limit_response_size, req/resp byte caps (:15-16,108,160) |
| Pre-flight AI cost ledger | OpenRouterClient.php:70-78 reserveโsettle/release; max_price caps + data_collection:deny+zdr (:80-89) |
| API key never persisted/echoed | OpenRouterClient.php:18-27 env-first then constant; never options table |
| Transactional rate limiter | RateLimiter.php:77-108 real START TRANSACTION/COMMIT/ROLLBACK, multi-bucket (session/account/network/global) |
| Disciplined resource cleanup | GenerateController.php:610-616 try/finally deletes temp logo + PDF artifacts |
Footgun grep result: no unsanitized $_POST, no string-interpolated SQL, no echo $_GET, no eval/extract/unserialize, no missing permission_callback. ๐ฏ
โ ๏ธ Improvements (actionable, prioritized)
| # | ๐ง Finding | Sev | File:line | Fix |
|---|---|---|---|---|
| 1 | Full mPDF locale zoo + 20 TTFs shipped for minority PDF feature | ๐ด High | vendor/mpdf/** (~138k LOC total) | Strip unused collations/fonts via build prune, or make PDF an optional add-on |
| 2 | PHP 8.3 floor excludes bulk of shared hosting | ๐ด High | quizwizz.php:8 Requires PHP: 8.3 | Lower to 8.1 (union types still available); gate 8.3-only syntax |
| 3 | โ
RESOLVED 2026-07-24 โ dead allow() wrapper (returned true on storage error) | ๐ Med | RateLimiter.php:116-118 | Deleted wrapper + doc comment (zero callers plugin-wide); consume() is the sole API. Verified: method_exists(RateLimiter,"allow") โ false, consume โ true, plugin re-activates with no fatal, REST rate-limit endpoint still 200 |
| 4 | 680-line all-static controller, no DI โ untestable | ๐ Med | GenerateController.php | Extract PDF/logo/selection collaborators; inject PdfGenerator |
| 5 | new PdfGenerator() hard-wired blocks test doubles | ๐ Med | GenerateController.php:595 | Constructor/param injection |
| 6 | Repeated release-or-503 block ร6 | ๐ก Low | OpenRouterClient.php:109,131,146,155,161,166 | Behavior-neutral refactor; deferred to release owner (unverifiable on a built artifact, not worth hand-editing) |
| 7 | โ
RESOLVED 2026-07-24 โ dead QUIZWIZZ_VER alias | ๐ก Low | quizwizz.php:23-24 | Removed (used only at its own definition; QUIZWIZZ_VERSION retained). Verified: defined("QUIZWIZZ_VER") โ false, defined("QUIZWIZZ_VERSION") โ true, no fatal on activate |
| 8 | Tested up to: 7.0 is coincidental, not a claim | ๐ก Low | quizwizz.php:11 | Test matrix + CI against 6.3โcurrent |
๐ Remediation checklist
- ๐ด Prune
vendor/mpdflocale/font payload โ release-owner (locked build pipeline) - ๐ด Drop PHP floor to 8.1; add syntax gate โ release-owner
- ๐ โ
Delete
RateLimiter::allow()andQUIZWIZZ_VERโ both removed on bench, verified no fatal - ๐ Break
GenerateControllerinto collaborators โ release-owner (large refactor on built artifact) - ๐ก Collapse AI settle/release duplication into
finallyโ release-owner (behavior-neutral) - ๐ก Establish WP compat CI (6.3 โ latest) โ release-owner
- ๐ข โ
Re-audit frontend UX (
[quizwizz_craft]) in browser โ done; see Frontend audit
๐ฆ Dependency x-ray
| Bundled dep | Purpose | Roast |
|---|---|---|
mpdf/mpdf | HTMLโPDF | Ships a .php collation for every locale on Earth + 20 TTFs; the bulk of the plugin |
setasign/fpdi | PDF import/parse | Heavyweight for print-a-quiz |
psr/http-message, psr/log, shims | Interop | Reasonable, small |
myclabs/deep-copy | Object cloning | Transitive weight |
paragonie/random_compat | CSPRNG polyfill | Redundant on PHP 8.3 (its own floor!) |
Irony: requires PHP 8.3 yet ships random_compat, a polyfill for PHP <7. ๐
๐ References
- ๐ QuizWizz bench (app)
- ๐ WP Plugin Security Handbook
- ๐งช WP REST API โ permission_callback
- ๐๏ธ
$wpdb->prepare()docs - ๐ mPDF ยท ๐ OpenRouter API
- reviewer: automated bench audit (source-read, no dynamic exploit)
- next: frontend UX roast of
[quizwizz_craft]builder