๐Ÿ”ฅ 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

DimensionGradeOne-line
๐Ÿ›ก๏ธ SecurityAprepared SQL, sanitized input, token+cap gates, SSRF-bounded egress
๐Ÿงฎ AI cost governanceA+pre-flight micro-budget ledger, price caps, ZDR/deny defaults
๐Ÿงฑ Input validationAschema-validated, size-capped bodies, strict multipart allowlist
๐Ÿ›๏ธ ArchitectureC+static mega-classes, no DI, untestable hot paths
๐Ÿ“ฆ Packaging / footprintD~138k LOC, mostly bundled PDF engine + fonts
๐ŸŒ Compat / reachCPHP 8.3 min excludes much of shared hosting
๐Ÿ“š Docs / provenanceA-readme, BUILDING, SOURCE-PROVENANCE, THIRD-PARTY notices present

โœ… What it gets right (verified)

๐Ÿ… StrengthEvidence
All SQL parameterizedAiBudget.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-validatedRateLimiter::client_key rejects non-^[a-f0-9]{64}$ (:16-39); every $_GET/$_COOKIE wp_unslash+sanitize_*
Public routes gated in depthGenerateController.php:98-105 __return_true then rate-limit + current_user_can('edit_post') (:84,645); mutations manage_options/EDIT_QUIZZES
Guest access = hashed expiring tokensQuizAccess.php:11-24 sha256 + hash_equals; cookie httponly+samesite=Lax+secure-on-ssl (:32-43)
SSRF-bounded AI egressOpenRouterClient.php:115-129 wp_safe_remote_post, redirection=0, limit_response_size, req/resp byte caps (:15-16,108,160)
Pre-flight AI cost ledgerOpenRouterClient.php:70-78 reserveโ†’settle/release; max_price caps + data_collection:deny+zdr (:80-89)
API key never persisted/echoedOpenRouterClient.php:18-27 env-first then constant; never options table
Transactional rate limiterRateLimiter.php:77-108 real START TRANSACTION/COMMIT/ROLLBACK, multi-bucket (session/account/network/global)
Disciplined resource cleanupGenerateController.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)

#๐Ÿ”ง FindingSevFile:lineFix
1Full mPDF locale zoo + 20 TTFs shipped for minority PDF feature๐Ÿ”ด Highvendor/mpdf/** (~138k LOC total)Strip unused collations/fonts via build prune, or make PDF an optional add-on
2PHP 8.3 floor excludes bulk of shared hosting๐Ÿ”ด Highquizwizz.php:8 Requires PHP: 8.3Lower 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)๐ŸŸ  MedRateLimiter.php:116-118Deleted 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
4680-line all-static controller, no DI โ†’ untestable๐ŸŸ  MedGenerateController.phpExtract PDF/logo/selection collaborators; inject PdfGenerator
5new PdfGenerator() hard-wired blocks test doubles๐ŸŸ  MedGenerateController.php:595Constructor/param injection
6Repeated release-or-503 block ร—6๐ŸŸก LowOpenRouterClient.php:109,131,146,155,161,166Behavior-neutral refactor; deferred to release owner (unverifiable on a built artifact, not worth hand-editing)
7โœ… RESOLVED 2026-07-24 โ€” dead QUIZWIZZ_VER alias๐ŸŸก Lowquizwizz.php:23-24Removed (used only at its own definition; QUIZWIZZ_VERSION retained). Verified: defined("QUIZWIZZ_VER") โ†’ false, defined("QUIZWIZZ_VERSION") โ†’ true, no fatal on activate
8Tested up to: 7.0 is coincidental, not a claim๐ŸŸก Lowquizwizz.php:11Test matrix + CI against 6.3โ†’current

๐Ÿ“‹ Remediation checklist

  • ๐Ÿ”ด Prune vendor/mpdf locale/font payload โ€” release-owner (locked build pipeline)
  • ๐Ÿ”ด Drop PHP floor to 8.1; add syntax gate โ€” release-owner
  • ๐ŸŸ  โœ… Delete RateLimiter::allow() and QUIZWIZZ_VER โ€” both removed on bench, verified no fatal
  • ๐ŸŸ  Break GenerateController into 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 depPurposeRoast
mpdf/mpdfHTMLโ†’PDFShips a .php collation for every locale on Earth + 20 TTFs; the bulk of the plugin
setasign/fpdiPDF import/parseHeavyweight for print-a-quiz
psr/http-message, psr/log, shimsInteropReasonable, small
myclabs/deep-copyObject cloningTransitive weight
paragonie/random_compatCSPRNG polyfillRedundant on PHP 8.3 (its own floor!)

Irony: requires PHP 8.3 yet ships random_compat, a polyfill for PHP <7. ๐Ÿƒ


๐Ÿ”— References


  • reviewer: automated bench audit (source-read, no dynamic exploit)
  • next: frontend UX roast of [quizwizz_craft] builder