spec(pre-push-safety-net): reserve the independent population v1 (15.1), unscanned

Eleven cases chosen by the maintainer from fifteen researched by advisory
and fix diff only -- nothing cloned, nothing scanned, none among the 262
repositories the benchmarks reference -- plus the earlier Ghost
reservation. Four are PHP repositories that ship JavaScript (two WordPress
plugins), satisfying the multi-language requirement; families are limited
to the v1 scope (injection, untrusted destination, permissive config).

The development subjects shaped every rule of the last week and can no
longer measure whether those rules generalise; this population can, until
something scans it. A guard test fails the moment a reserved repository is
referenced by a recipe, catalog, manifest or measurement outside
benchmarks/independent/. Freezing (pins and sinks verified by reading
source, a benign change per case) is the next step.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-24 16:12:22 +02:00
co-authored by Claude Opus 5.5
parent d5277c60d0
commit 451d4e4be7
4 changed files with 388 additions and 0 deletions
+1
View File
@@ -597,6 +597,7 @@ Preserve all completed task evidence below.
- Reserve the cases untouched: do not examine their scan results before the population is frozen, and record the reservation so a later run cannot be mistaken for a blind one.
- **The population MUST hold a multi-language repository.** Task 13.4 found that a second language in a repository cost every question its completion, and so cost every finding its admission — on the declared v0.1 target, which is PHP plus admin JavaScript. Neither subject that passes today could reveal it, because both hold exactly one language. A population of single-language repositories would qualify a capability that fails on the product's own target.
- Done when a frozen population exists with its provenance and review recorded, at least one member holds more than one language, and nothing in it has been scanned.
- _Reserved 2026-09-24, not yet frozen._ Eleven cases selected by the maintainer from fifteen researched by advisory and fix diff alone (no clone, no scan), none among the 262 repositories this project's benchmarks reference, plus the earlier Ghost reservation: 4 PHP with JavaScript (Ultimate Member and LearnPress WordPress plugins, phpMyFAQ, YesWiki; all SQL injection), 4 Node/TypeScript (Signal K SSRF, Saltcorn SQLi, mongo-express code injection, Mockoon permissive CORS), 3 Python (alerta SQLi, wger open redirect, OpenCVE SSRF). Record: `benchmarks/independent/population-v1.toml`; research and rejected alternates: `research-2026-09-24.md`. A guard test fails if any reserved repository is referenced elsewhere in the tree. Remaining to FREEZE: per case, verify the pins and the sink by reading source, and choose a benign change from the same history without scanning; the OpenCVE licence must be checked before any result is published.
- _Boundary: Evaluation harness, corpus provenance_
- _Requirements: 8.1, 8.2_
- [ ] 15.2 Declare the v1 advisory scope as the families whose claims can be admitted
+174
View File
@@ -0,0 +1,174 @@
# Independent evaluation population v1 (pre-push-safety-net task 15.1, milestone M4).
#
# RESERVED, NOT SCANNED. Every case below was chosen from public advisories and fix diffs by web research alone:
# no repository was cloned, no scanner or project tooling was run on any of them, and none appears among the
# 262 repositories referenced anywhere in this project's benchmarks. They exist to answer the question the
# development subjects can no longer answer honestly -- PMPro, WP Statistics, MW WP Form, NodeGoat and VAmPI all
# shaped the rules they would be measured by.
#
# Rules for this file:
# * Do not scan, trace, probe or census any of these repositories until the population is FROZEN (status
# below) and the freeze commit is recorded. A scan before that makes the case a development subject.
# * Freezing requires, per case: the vulnerable and fixed pins verified in the checkout, the sink confirmed at
# the vulnerable pin by reading source, and a BENIGN change chosen from the same history without scanning.
# * No rule, fact or query change may cite anything learned from these cases before their evaluation is
# recorded; after it, a retune needs a new untouched population (release-milestones M4).
#
# Selected by the maintainer on 2026-09-24 from 15 researched candidates (menu selection); rejected alternates
# and the research notes are in the research log referenced below.
schema_version = 1
status = "reserved-unscanned"
reserved_on = "2026-09-24"
selected_by = "maintainer"
research = "web research of GitHub advisories and fix diffs via the GitHub API; no clone, no scan"
multi_language_members = ["wp-ultimate-member-sqli", "wp-learnpress-sqli", "phpmyfaq-captcha-ua-sqli", "yeswiki-bazar-form-sqli"]
also_reserved = ["../push/ghost-reserved.toml"]
[[case]]
id = "wp-ultimate-member-sqli"
repo = "https://github.com/ultimatemember/ultimatemember"
language = "php"
also = ["javascript"]
family = "injection"
advisory = ["CVE-2024-1071", "GHSA-mh7h-gpmx-fggj"]
vulnerable = "a49b3061ded1630e4f6860b4ebece5ccf0a493d2"
fixed = "476de92f4c9aaa56c3e2f5527b242ce0476f4655"
sink = "includes/core/class-member-directory-meta.php :: Member_Directory_Meta::ajax_get_members (ORDER BY built from esc_sql'd sort values)"
privilege = "unauthenticated (only with the custom usermeta table option on)"
license = "GPL"
notes = "Release pair 2.8.2 (e1885a73) -> 2.8.3 (a6a7d13a) spans 94 commits; the commit pair is the clean one. c61008bd is a larger follow-up refactor of the same file."
[[case]]
id = "wp-learnpress-sqli"
repo = "https://github.com/LearnPress/learnpress"
language = "php"
also = ["javascript"]
family = "injection"
advisory = ["CVE-2024-8522", "GHSA-3w3r-r6g6-w8x5", "CVE-2024-8529", "GHSA-98wp-w76v-f75p"]
vulnerable = "73b3cbceef8669056e9da5e2f2c78944eb139af5"
fixed = "aaf42fe0c6bf05dcf2ff125750b7bd836e76fa3c"
sink = "inc/Databases/class-lp-db.php :: LP_Database::execute (implode of only_fields into SELECT); source REST get_courses c_only_fields/c_fields"
privilege = "unauthenticated (REST /wp-json/learnpress/v1/courses)"
license = "GPL"
notes = "The fix also touches assets/src/js/loadAJAX.js: the change itself is multi-language."
[[case]]
id = "phpmyfaq-captcha-ua-sqli"
repo = "https://github.com/thorsten/phpMyFAQ"
language = "php"
also = ["typescript", "javascript"]
family = "injection"
advisory = ["CVE-2026-46364", "GHSA-289f-fq7w-6q2w"]
vulnerable = "11583abcf4787543d00c25d6c7926af6f509faf0"
fixed = "545bdffb11244a4741cd29ac909a849c9a6a2e53"
sink = "phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php :: garbageCollector, saveCaptcha (User-Agent and client IP in sprintf SQL)"
privilege = "unauthenticated (GET /api/captcha)"
license = "MPL-2.0"
notes = "The advisory's commit b9f25109 is the vulnerable master commit the reporter tested, not the fix. Release pair 4.1.1 (f791858b) -> 4.1.2 (66792f25)."
[[case]]
id = "yeswiki-bazar-form-sqli"
repo = "https://github.com/YesWiki/yeswiki"
language = "php"
also = ["javascript"]
family = "injection"
advisory = ["CVE-2026-46670", "GHSA-jwvv-qr7q-cv8j"]
vulnerable = "6f222915df36fca628fc83325cb0feed8dc2aca0"
fixed = "fe7244bffcbeffd42777496ebc2a6f6118cc2388"
sink = "tools/bazar/services/FormManager.php :: FormManager::create (bn_id_nature unquoted in INSERT); source FormController::displayAll imported-form JSON"
privilege = "unauthenticated"
license = "AGPL-3.0"
notes = "Four-line fix: intval plus a Guard::isAllowed check. Default branch doryphore-dev."
[[case]]
id = "signalk-remote-conn-ssrf"
repo = "https://github.com/SignalK/signalk-server"
language = "typescript"
family = "untrusted_destination"
advisory = ["CVE-2026-55591", "GHSA-q59x-jc9f-gfqf"]
vulnerable = "1c479cc0d2d26d14ed74416cff36fa7336c7bf9e"
fixed = "2816b0dda1bdde893d85c969a9c5950e7825eee7"
sink = "src/serverroutes.ts :: makeRemoteRequest (req.body.host/port to http(s).request)"
privilege = "unauthenticated by default (security not configured); admin otherwise"
license = "Apache-2.0"
notes = "Fix adds a schema, an ssrfGuard module and requestId validation (~450 added lines, mostly guard code and tests)."
[[case]]
id = "saltcorn-sync-sqli"
repo = "https://github.com/saltcorn/saltcorn"
language = "javascript"
also = ["typescript"]
family = "injection"
advisory = ["CVE-2026-41478", "GHSA-jp74-mfrx-3qvh"]
vulnerable = "e7e09254a3c69dc6582eda9e5b5769935db108af"
fixed = "2b6c8a2eab88955ea6b12b1b4e5c2e613faefef5"
sink = "packages/server/routes/sync.js :: getSyncRows, getDelRows (maxLoadedId and timestamps in SQL template literals)"
privilege = "authenticated low-privilege user"
license = "MIT"
notes = "Large multi-package repository; the fix is on the main line (v1.4.6 is a separate backport), so use the commit pair."
[[case]]
id = "mongo-express-bson-rce"
repo = "https://github.com/mongo-express/mongo-express"
language = "javascript"
family = "injection"
advisory = ["CVE-2019-10758", "GHSA-h47j-hc6x-h3qq"]
vulnerable = "ea02b364d43f179f191fc91fb9962efdb0843a8d"
fixed = "d8c9bda46a204ecba1d35558452685cd0674e6f2"
sink = "lib/bson.js :: toBSON (vm.runInNewContext over request text); callers lib/routes/collection.js, lib/routes/document.js"
privilege = "authenticated (HTTP basic auth, default admin:pass)"
license = "MIT"
notes = "Code injection, not SQL. The advisory's first reference 7d365141 is only the v0.54.0 release commit."
[[case]]
id = "mockoon-admin-api-cors"
repo = "https://github.com/mockoon/mockoon"
language = "typescript"
family = "config_secrets"
advisory = ["CVE-2026-59148", "GHSA-rqx4-3f6q-3x2v"]
vulnerable = "b42bdfb7f82e83f0e81bea8e6fe41adf5ec82585"
fixed = "c420b5a56918475b8663977b51e5f986e45b3299"
sink = "packages/commons-server/src/libs/server/admin-api.ts :: admin-API middleware (Access-Control-Allow-Origin: *)"
privilege = "unauthenticated"
license = "MIT"
notes = "Weaker fit: the fix commit touches 34 files; only one hunk in admin-api.ts is the CORS fix."
[[case]]
id = "alerta-query-sqli"
repo = "https://github.com/alerta/alerta"
language = "python"
family = "injection"
advisory = ["CVE-2026-34400", "GHSA-8prr-286p-4w7j"]
vulnerable = "71c098d8bb8091e76edaefeef4662eb77c753ce7"
fixed = "fdd52cd1abad8d02d1dfb8ecdcdbb43b6af3b883"
sink = "alerta/database/backends/postgres/queryparser.py :: SearchTerm.__repr__ (f-string WHERE) used by utils.py QueryBuilder.from_params"
privilege = "API user; possibly unauthenticated if AUTH_REQUIRED defaults to False (unverified)"
license = "Apache-2.0"
notes = "The advisory's aeba85a3 introduced the parser; the fix is fdd52cd1."
[[case]]
id = "wger-next-open-redirect"
repo = "https://github.com/wger-project/wger"
language = "python"
family = "untrusted_destination"
advisory = ["GHSA-vqv8-j3mj-wjxj"]
vulnerable = "d30e85b23fb1307ac6579e9e3ae393c50470cea7"
fixed = "0ba371bbb50cd5668820f240995405e1b2483384"
sink = "wger/core/views/user.py :: trainer_login; wger/core/views/languages.py :: use_browser_language (request.GET next redirected unchecked)"
privilege = "use_browser_language: unauthenticated; trainer_login: authenticated trainer"
license = "AGPL-3.0"
notes = "No CVE. A later commit cfa27a7f fixes the same pattern in core/middleware.py; not part of this fix."
[[case]]
id = "opencve-notification-ssrf"
repo = "https://github.com/opencve/opencve"
language = "python"
family = "untrusted_destination"
advisory = ["CVE-2026-62282", "GHSA-ch3g-4xvr-674q"]
vulnerable = "928bbadbb1672051bcfc2a6289a9e6a8f029c106"
fixed = "0af7f894debc95c67fa53e7ba5a6cdecb10c34f0"
sink = "web/projects/notifications.py :: _http_post_json (webhook and Slack test POST to a user-supplied URL)"
privilege = "authenticated (can configure project notifications)"
license = "unverified (GitHub reports NOASSERTION; possibly non-OSS BSL) -- verify before publishing any result"
notes = "Selected by the maintainer despite the licence question; results must not be redistributed until it is checked."
@@ -0,0 +1,151 @@
# Independent evaluation population: candidate real-world cases
Research date: 2026-09-24. Selected by the maintainer: 11 of these 15 (see population-v1.toml); not selected: The Events Calendar, Tutor LMS, pyLoad, unfurl. Method: web research only. I read GitHub advisories (GHSA/CVE), commit pages, compare views and diffs through the GitHub REST API (`gh api`) and the web. I did not clone any repository or run any scanner or project tooling.
Exclusion check: I compared every repo URL below against `used-repos.txt` (case-insensitive exact match on `https://github.com/<owner>/<repo>`). None of them is on the list. None is NodeGoat, VAmPI, PMPro, WP Statistics, MW WP Form, libpng, a deliberately vulnerable training app or a benchmark suite. I did not use Ghost.
"Diff seen" means I read the patch hunks of the named fix commit through the GitHub API. "Vuln" is the parent of the fix commit unless a tag is given. Tags come with the commit SHA they resolve to.
## Summary table
| # | id | repo | lang | family | advisory | vulnerable (commit / tag) | fixed (commit / tag) | sink file :: function | privilege | diff seen |
|---|----|------|------|--------|----------|---------------------------|----------------------|-----------------------|-----------|-----------|
| 1 | wp-ultimate-member-sqli | https://github.com/ultimatemember/ultimatemember | PHP+JS (WP plugin) | SQLi | CVE-2024-1071 / GHSA-mh7h-gpmx-fggj | tag 2.8.2 = `e1885a73a8ad542cfd2ed60aef38a6bce4ab8553`; commit-level parent `a49b3061ded1630e4f6860b4ebece5ccf0a493d2` | `476de92f4c9aaa56c3e2f5527b242ce0476f4655` (+ `c61008bdbe1ce8de4597b41c7aad8dfda88f527d`); tag 2.8.3 = `a6a7d13a433b24e468ee10e778e3da5668b6ef59` | includes/core/class-member-directory-meta.php :: `Member_Directory_Meta::ajax_get_members()` (`$this->sql_order` ORDER BY) | unauthenticated | yes |
| 2 | wp-learnpress-sqli | https://github.com/LearnPress/learnpress | PHP+JS (WP plugin) | SQLi | CVE-2024-8522 / GHSA-3w3r-r6g6-w8x5 (+ CVE-2024-8529 / GHSA-98wp-w76v-f75p) | `73b3cbceef8669056e9da5e2f2c78944eb139af5` (4.2.7) | `aaf42fe0c6bf05dcf2ff125750b7bd836e76fa3c` (4.2.7.1) | inc/Databases/class-lp-db.php :: `LP_Database::execute()` (`implode(',', $filter->only_fields)` into SELECT); source inc/jwt/rest-api/version1/class-lp-rest-courses-v1-controller.php :: `get_courses()` | unauthenticated (REST `/wp-json/learnpress/v1/courses`) | yes |
| 3 | wp-events-calendar-sqli | https://github.com/the-events-calendar/the-events-calendar | PHP+JS (WP plugin) | SQLi | CVE-2024-8275 / GHSA-v6x4-m48j-jm68 | tag 6.6.4 = `4e4baa3fee7ef49abdd4cb277d963d555ffab4c7` | tag 6.6.4.1 = `419965a87d99195ad3fca22738e19dd287051f1c` (fix hunk in `e128ec5e2a25fe4fdc4542a9023918bf915b4084`) | src/Events/Custom_Tables/V1/WP_Query/Custom_Tables_Query.php :: `redirect_posts_orderby()` / `parse_orderby()` | unauthenticated per Wordfence (reached through `tribe_has_next_event` `order`) | yes |
| 4 | wp-tutor-lms-coupon-sqli | https://github.com/themeum/tutor | PHP+JS/TS (WP plugin) | SQLi | CVE-2025-13673 / GHSA-f7q9-gg7h-5rff | tag v3.9.6 = `71187a525dbdbdb045137af702307f05dda6a7a6` (= parent of fix) | `e4a25f69f0fb63a46d57eff6ba5b58378a4c088d` (3.9.7, no GitHub tag) | helpers/QueryHelper.php :: `prepare_where_clause()` / `make_clause()`; source models/CouponModel.php :: `get_coupon_details_for_checkout()`, `get_coupon_by_code()` | unauthenticated (checkout `coupon_code`) | yes (mapping partly inferred) |
| 5 | phpmyfaq-captcha-ua-sqli | https://github.com/thorsten/phpMyFAQ | PHP+TS/JS | SQLi | CVE-2026-46364 / GHSA-289f-fq7w-6q2w | `11583abcf4787543d00c25d6c7926af6f509faf0`; tag 4.1.1 = `f791858b2aa4b811f5b19d10c20ba5c4a30786de` | `545bdffb11244a4741cd29ac909a849c9a6a2e53`; tag 4.1.2 = `66792f2575b07d65896b504d47deb5f4f903f9ff` | phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php :: `garbageCollector()`, `saveCaptcha()` | unauthenticated (`GET /api/captcha`, User-Agent header) | yes |
| 6 | yeswiki-bazar-form-sqli | https://github.com/YesWiki/yeswiki | PHP+JS | SQLi | CVE-2026-46670 / GHSA-jwvv-qr7q-cv8j | `6f222915df36fca628fc83325cb0feed8dc2aca0`; tag v4.6.3 = `5e8fc114d01d558dc4799b95219549ccf02ebd11` | `fe7244bffcbeffd42777496ebc2a6f6118cc2388`; tag v4.6.4 = `c1bae9fc260eb3e4346f3b0bc65ab609d1cf783a` | tools/bazar/services/FormManager.php :: `FormManager::create()`; source tools/bazar/controllers/FormController.php :: `displayAll()` | unauthenticated | yes |
| 7 | signalk-remote-conn-ssrf | https://github.com/SignalK/signalk-server | TypeScript (Node/Express) | SSRF | CVE-2026-55591 / GHSA-q59x-jc9f-gfqf | `1c479cc0d2d26d14ed74416cff36fa7336c7bf9e`; tag v2.27.0 = `4592992aaf7c44548842be9390b651cfb816eaf4` | `2816b0dda1bdde893d85c969a9c5950e7825eee7`; tag v2.28.0 = `3645160b235364e1fd3afe1f2f6b2270b8194b7d` | src/serverroutes.ts :: `makeRemoteRequest()` (from `testSignalKConnection`, `requestAccess`, `checkAccessRequest` routes) | unauthenticated by default (security not configured); admin otherwise | yes |
| 8 | saltcorn-sync-sqli | https://github.com/saltcorn/saltcorn | JavaScript/TS (Node/Express) | SQLi | CVE-2026-41478 / GHSA-jp74-mfrx-3qvh | `e7e09254a3c69dc6582eda9e5b5769935db108af`; tag v1.4.5 = `92d24ca5ce8480d3b0cf1067e33113f2a790030c` | `2b6c8a2eab88955ea6b12b1b4e5c2e613faefef5`; tag v1.4.6 = `d49e6a947de777053a203a27ab4d9e1a1754ccd0` (backport) | packages/server/routes/sync.js :: `getSyncRows()`, `getDelRows()` (+ packages/saltcorn-data/models/table.ts sync_info queries) | authenticated low-privilege user | yes |
| 9 | mongo-express-bson-rce | https://github.com/mongo-express/mongo-express | JavaScript (Node/Express) | code injection (vm eval) | CVE-2019-10758 / GHSA-h47j-hc6x-h3qq | `ea02b364d43f179f191fc91fb9962efdb0843a8d`; tag v0.53.0 = `9eb49994f3842260f7c6db75592ea6ba9376fad6` | `d8c9bda46a204ecba1d35558452685cd0674e6f2`; tag v0.54.0 = `7d365141deadbd38fa961cd835ce68eab5731494` | lib/bson.js :: `toBSON()` (`vm.runInNewContext` over request text); callers lib/routes/collection.js, lib/routes/document.js | authenticated (HTTP basic auth, default admin:pass) | yes |
| 10 | mockoon-admin-api-cors | https://github.com/mockoon/mockoon | TypeScript (Node/Express) | permissive CORS | CVE-2026-59148 / GHSA-rqx4-3f6q-3x2v | `b42bdfb7f82e83f0e81bea8e6fe41adf5ec82585`; tag v9.6.1 = `1c874584c058b891f6affbb44d5a5223f3f8f812` | `c420b5a56918475b8663977b51e5f986e45b3299`; tag v9.7.0 = `df02ba3b0d9e8b88651230f3b38d8e6fa3679b9b` | packages/commons-server/src/libs/server/admin-api.ts :: admin-API middleware (`app.use(`${adminApiPrefix}*`)`, `Access-Control-Allow-Origin: *`) | unauthenticated | yes (large diff, one CORS hunk) |
| 11 | pyload-add-package-sqli | https://github.com/pyload/pyload | Python (Flask) | SQLi | CVE-2025-55156 / GHSA-pwh4-6r3m-j2rf | `a123accec0618798db77f9f5bc66838b741d9f58` | `134edcdf6e2a10c393743c254da3d9d90b74258f` (0.5.0b3.dev91) | src/pyload/core/database/file_database.py :: `FileDatabaseMethods.update_link_info()`; source src/pyload/webui/app/blueprints/json_blueprint.py :: `add_package` | authenticated (ADD permission) | yes |
| 12 | alerta-query-sqli | https://github.com/alerta/alerta | Python (Flask) | SQLi | CVE-2026-34400 / GHSA-8prr-286p-4w7j | `71c098d8bb8091e76edaefeef4662eb77c753ce7`; tag v9.0.4 = `b7111d23ae9aefb5064ef107976f62e3836217b6` | `fdd52cd1abad8d02d1dfb8ecdcdbb43b6af3b883`; tag v9.1.0 = `f4312727783dad64198b62ed778bbca98a5144a3` | alerta/database/backends/postgres/queryparser.py :: `SearchTerm.__repr__` (f-string WHERE); used by alerta/database/backends/postgres/utils.py :: `QueryBuilder.from_params()` | API user (API key when `AUTH_REQUIRED`; see notes) | yes |
| 13 | wger-next-open-redirect | https://github.com/wger-project/wger | Python (Django) | open redirect | GHSA-vqv8-j3mj-wjxj (no CVE) | `d30e85b23fb1307ac6579e9e3ae393c50470cea7`; tag 2.5 = `c1fca6bdf77d8ed80048d1b73a8da80cf36ee751` | `0ba371bbb50cd5668820f240995405e1b2483384`; tag 2.6 = `e1d70bcc38cd56ae4a254dca1713c404d069f319` | wger/core/views/user.py :: `trainer_login()`; wger/core/views/languages.py :: `use_browser_language()` | `use_browser_language`: unauthenticated; `trainer_login`: authenticated trainer | yes |
| 14 | unfurl-flask-debug | https://github.com/obsidianforensics/unfurl | Python (Flask) | permissive config (debug on) | GHSA-vg9h-jx4v-cwx2 (no CVE) | `6b8e97853090c909936a18d41228168b7a35fe94` | `4c0a07ab1e9af3a1ddf0e7f47153ec9ba77946dd` | unfurl/app.py :: `web_app()` → `UnfurlApp.__init__` `app.run(debug=...)` | unauthenticated (Werkzeug debugger) | yes |
| 15 | opencve-notification-ssrf | https://github.com/opencve/opencve | Python (Django) | SSRF | CVE-2026-62282 / GHSA-ch3g-4xvr-674q | `928bbadbb1672051bcfc2a6289a9e6a8f029c106` | `0af7f894debc95c67fa53e7ba5a6cdecb10c34f0`; tag v3.0.0 = `928943aa87ceac789061114159d74c2067cc6ae6` | web/projects/notifications.py :: `_http_post_json()` (webhook and Slack test); forms in web/projects/forms.py | authenticated (can configure project notifications) | yes |
Mix: 6 PHP (all six repos ship JS, four are WordPress plugins), 4 JS/TS, 5 Python. By family: 10 injection (9 SQLi: #1–6, #8, #11, #12; 1 code injection: #9), 3 untrusted destination (SSRF #7 and #15, open redirect #13), 2 permissive configuration (CORS #10, debug #14). By privilege: 10 unauthenticated or unauthenticated by default (#1–7, #10, #14, plus #13's `use_browser_language` sink) and 5 authenticated (#8, #9, #11, #15, and #12 depending on `AUTH_REQUIRED`).
## Per-candidate notes
### 1. wp-ultimate-member-sqli (PHP+JS, WordPress)
- Repo: https://github.com/ultimatemember/ultimatemember (GPL, PHP 2.7 MB + JS 0.6 MB).
- Advisory: CVE-2024-1071, GHSA-mh7h-gpmx-fggj (unreviewed GHSA mirror of the Wordfence record). Affects 2.1.3–2.8.2, CVSS 9.8.
- Flaw: `ajax_get_members()` builds `$this->sql_order = " ORDER BY CAST(umm_sort.um_value AS {$custom_sort_type}) {$custom_sort_order} "` and similar ORDER BY strings. Values from the member-directory `sorting` request pass only through `esc_sql()`. That does not protect a context without quotes. Only exploitable when the "custom usermeta table" option is on.
- Fix: `476de92f` checks `$order` / `$custom_sort_order` against `ASC`/`DESC` and `$custom_sort_type` against `$this->sort_data_types`. `c61008bd` ("fixed security issue with mysql query escape") is a larger WPCS refactor of the same file. Use `476de92f` as the minimal fix and the 2.8.2 → 2.8.3 tags for a release-level pair.
- Confidence: high. I read both diffs. The 2.8.2...2.8.3 compare has 94 commits, so a tag pair gives a noisy diff. The commit pair is cleaner.
### 2. wp-learnpress-sqli (PHP+JS, WordPress)
- Repo: https://github.com/LearnPress/learnpress (PHP 5.9 MB, JS 1.4 MB).
- Advisories: CVE-2024-8522 (`c_only_fields`, CVSS 10.0) and CVE-2024-8529 (`c_fields`). Both are fixed by the same commit.
- Flaw: the REST controller `get_courses()` put `explode(',', $params['c_only_fields'])` straight into `$filter->only_fields`. `LP_Database::execute()` then runs `implode(',', array_unique($filter->only_fields))` into the SELECT column list (class-lp-db.php around line 607).
- Fix `aaf42fe0`: `Courses::handle_params_for_query_courses()` now runs every field through `$wpdb->prepare('%i', $field)` (identifier quoting), and the controller's direct assignment is removed. It also touches `assets/src/js/loadAJAX.js`, so the commit is itself multi-language.
- Confidence: high. I read the diff and checked the sink line at the vulnerable commit.
### 3. wp-events-calendar-sqli (PHP+JS, WordPress)
- Repo: https://github.com/the-events-calendar/the-events-calendar (large plugin: PHP 10 MB, JS 0.7 MB; license field NOASSERTION, the plugin is GPL). **It has a git submodule `common` (tribe-common), so a checkout needs `--recursive`.**
- Advisory: CVE-2024-8275, GHSA-v6x4-m48j-jm68. The `order` parameter of `tribe_has_next_event`, ≤ 6.6.4.
- Flaw: `redirect_posts_orderby()` split ORDER BY fragments with a regex and kept any trailing `order` token as is. `parse_orderby()` inserted `$order` into `"ID $order, ..."`.
- Fix (6.6.4.1, commit `e128ec5e`): adds `sanitize_order()` (only ASC/DESC) and applies it at both sites. It also bumps the submodule.
- Privilege: Wordfence rates it unauthenticated. Reachability in practice depends on how the template tag's arguments reach WP_Query. The ground-truth sink is still unambiguous.
- Confidence: high for the diff. Medium for how exploitable it is from a clean install.
### 4. wp-tutor-lms-coupon-sqli (PHP+JS/TS, WordPress)
- Repo: https://github.com/themeum/tutor (GPL-3.0; PHP 3.8 MB, TS 3.3 MB, JS 0.4 MB).
- Advisory: CVE-2025-13673, GHSA-f7q9-gg7h-5rff. ≤ 3.9.6, unauthenticated `coupon_code`. Partial fixes shipped in 3.9.4 and 3.9.6.
- Flaw: `QueryHelper::prepare_where_clause()` built values as `"'" . $val . "'"`. The only escaping was an `esc_sql()` in `get_coupon_details_for_checkout()`. Other coupon paths (`get_coupon_by_code()`, `get_coupon_usage_count()`, `get_coupon_applications()`) pass `$coupon_code` through without escaping.
- Fix `e4a25f69` (parent = tag v3.9.6): `make_clause()` now binds every value through `$wpdb->prepare('%s'|'%d', ...)`, and the manual quoting and `esc_sql` are removed.
- Confidence: medium. I read the diff and it is the only SQL change right after v3.9.6. The trac changeset cited by the CVE (3469242) returned 403, so the exact exploited call chain from the checkout request to QueryHelper is my inference. There is no v3.9.7 tag on GitHub. Use the commit pair.
### 5. phpmyfaq-captcha-ua-sqli (PHP+TS/JS)
- Repo: https://github.com/thorsten/phpMyFAQ (MPL-2.0; PHP 14.7 MB, TS 1.7 MB).
- Advisory: CVE-2026-46364, GHSA-289f-fq7w-6q2w. ≤ 4.1.1. The advisory's commit reference `b9f25109` is the **vulnerable** master commit the reporter tested, not the fix.
- Flaw: the constructor stores `$request->headers->get('user-agent')` and `getClientIp()`. `garbageCollector()` (DELETE) and `saveCaptcha()` (SELECT/INSERT) put them into `sprintf` SQL without escaping. Both run on every `GET /api/captcha`.
- Fix `545bdffb` ("fix: added missing escaping"): adds `escapeQueryValue()` for userAgent, ip, language and code.
- Confidence: high. I read the diff in the 4.1.1...4.1.2 compare and the commit.
### 6. yeswiki-bazar-form-sqli (PHP+JS)
- Repo: https://github.com/YesWiki/yeswiki (AGPL-3.0; PHP 4 MB, JS 0.8 MB). Default branch `doryphore-dev`.
- Advisory: CVE-2026-46670, GHSA-jwvv-qr7q-cv8j. < 4.6.4.
- Flaw: `FormManager::create()` concatenates `$data['bn_id_nature']` unquoted into `INSERT ... VALUES (`. The data comes from `$_POST['imported-form']` JSON in `FormController::displayAll()`, and that path had no permission check.
- Fix `fe7244bf`: `intval($data['bn_id_nature'])` plus a `Guard::isAllowed('saisie_formulaire')` check. The diff is 4 lines.
- Confidence: high. I read the diff. Related YesWiki SQLi advisories (GHSA-qg78-vmvc-fhjw, GHSA-f58v-p6j9-24c2, GHSA-4pf7-cc4r-g63h) could add more cases from the same repo.
### 7. signalk-remote-conn-ssrf (TypeScript, Node/Express)
- Repo: https://github.com/SignalK/signalk-server (Apache-2.0, about 24 MB).
- Advisory: CVE-2026-55591, GHSA-q59x-jc9f-gfqf. ≤ 2.27.0.
- Flaw: `makeRemoteRequest(host, port, useTLS, selfsignedcert, path)` passes `req.body.host`/`port` to `http(s).request` without validation. It is reached from three admin routes.
- Fix `2816b0dd`: TypeBox schema validation (new src/remoteConnectionSchemas.ts), a new src/ssrfGuard.ts (BlockList for loopback, link-local and metadata, plus a DNS lookup guard) wired into `makeRemoteRequest`, and UUID validation of `requestId`.
- Confidence: high. I read the diff. It is medium-sized, about 450 added lines, mostly new guard code and tests.
### 8. saltcorn-sync-sqli (JavaScript/TS, Node/Express)
- Repo: https://github.com/saltcorn/saltcorn (MIT). It is a monorepo of packages and on the large side (about 340 MB of git data), but the relevant code is in `packages/server` and `packages/saltcorn-data`.
- Advisory: CVE-2026-41478, GHSA-jp74-mfrx-3qvh. < 1.4.6 / < 1.5.6 / < 1.6.0-beta.5.
- Flaw: `getSyncRows()` interpolates `${syncInfo.maxLoadedId}` (from `req.body.syncInfos[table].maxLoadedId`) and `${user.id}` into SQL template literals. `getDelRows()` does the same with timestamps.
- Fix `2b6c8a2e`: integer and date validation plus `$1`-style parameterised queries in sync.js and table.ts.
- Confidence: high. I read the diff. The fix commit is on the main line, and v1.4.6 is a separate backport. For a clean pair, use the commit and its parent `e7e09254`.
### 9. mongo-express-bson-rce (JavaScript, Node/Express)
- Repo: https://github.com/mongo-express/mongo-express (MIT, about 16 MB).
- Advisory: CVE-2019-10758, GHSA-h47j-hc6x-h3qq (CWE-94/78). < 0.54.0.
- Flaw: `lib/bson.js` `toBSON()` evaluates user-supplied document/query text with `vm.runInNewContext` in a BSON sandbox. The sandbox can be escaped, which gives RCE through the `/checkValid` and document/collection routes.
- Fix `d8c9bda4`: replaces the vm-based parser with `mongodb-query-parser`. It removes 101 lines from bson.js, deletes lib/json.js and changes collection.js.
- Confidence: high. I read the diff. It is an older case (2019), useful as a known-answer eval sink. Note that the advisory's first reference, `7d365141`, is only the version-bump or release commit (= tag v0.54.0).
### 10. mockoon-admin-api-cors (TypeScript, Node/Express)
- Repo: https://github.com/mockoon/mockoon (MIT; monorepo of app, cli, commons-server and serverless; about 42 MB).
- Advisory: CVE-2026-59148, GHSA-rqx4-3f6q-3x2v (CWE-942/306/352/732). < 9.7.0.
- Flaw: the admin API middleware sets `Access-Control-Allow-Origin: *` with all methods on every `/mockoon-admin/*` endpoint. The admin API is on by default and has no authentication.
- Fix `c420b5a5`: adds a configurable `adminApiCorsOrigins` allowlist (no header by default, 403 on preflight), optional admin API auth, and an env-var prefix check.
- Confidence: high that the CORS hunk is the fix. The commit is **large (34 files)**, but the permissive-CORS sink sits in one clearly identifiable hunk of admin-api.ts. It is a weaker fit for "small diff" than the others.
### 11. pyload-add-package-sqli (Python, Flask)
- Repo: https://github.com/pyload/pyload (AGPL-3.0, GitHub reports NOASSERTION; about 50 MB).
- Advisory: CVE-2025-55156, GHSA-pwh4-6r3m-j2rf. < 0.5.0b3.dev91.
- Flaw: `update_link_info()` builds `"','".join(x[3] for x in data)` into `f"SELECT id FROM links WHERE url IN ('{statuses}')"`. The URLs come from `add_links` on `/json/add_package`. Chain: json_blueprint `add_package` → `Api.add_package` → `FileManager.add_links` → `InfoThread` → `update_file_info` → `update_link_info`.
- Fix `134edcdf`: `?` placeholders. The diff is 5 lines. The source-to-sink path crosses a thread, which makes it a good cross-thread taint test.
- Confidence: high. I read the diff. The same repo also has CVE-2023-0297 (GHSA-pf38-5p22-x6h6, unauthenticated js2py code injection, fix `7d73ba7919e594d783b3411d7ddb87885aea782d`, parent `6aa71cd99a6f510de7137d17ec99f930916704c0`). I kept it as an alternate only, because that fix just calls `js2py.disable_pyimport()`. The `eval_js` sink stays in the fixed tree, so a sink-based analyzer would still correctly flag the fixed version.
### 12. alerta-query-sqli (Python, Flask)
- Repo: https://github.com/alerta/alerta (Apache-2.0, about 11 MB).
- Advisory: CVE-2026-34400, GHSA-8prr-286p-4w7j. < 9.1.0. The advisory's `aeba85a3` reference (2018) is the commit that **introduced** the parser. The fix is `fdd52cd1`.
- Flaw: the Postgres query-string parser (`?q=`) turned parsed search terms into SQL with f-strings in `SearchTerm.__repr__`. `QueryBuilder.from_params()` then used that as a WHERE clause with an empty `qvars`.
- Fix: new `to_sql(params)` methods produce `%(name)s` placeholders, and `from_params()` passes the parsed vars.
- Privilege: alerta requires an API key or login when `AUTH_REQUIRED=True`. I believe the default is `False`, which would make this unauthenticated on a default install, but I did not check settings.py. Record it as "API user, possibly unauthenticated".
- Confidence: high for the diff. The fix is about 100 lines in one file plus tests.
### 13. wger-next-open-redirect (Python, Django)
- Repo: https://github.com/wger-project/wger (AGPL-3.0, about 91 MB with a JS frontend).
- Advisory: GHSA-vqv8-j3mj-wjxj (no CVE assigned). ≤ 2.5, fixed in 2.6.
- Flaw: `trainer_login()` returns `HttpResponseRedirect(request.GET['next'])`. `use_browser_language()` redirects to `remove_language_code(request.GET.get('next','/'))`. Neither checks host or scheme.
- Fix `0ba371bb`: `url_has_allowed_host_and_scheme()` at both sites. The diff is 20 lines of code plus tests.
- Two sinks, two privilege levels: `use_browser_language` is unauthenticated, `trainer_login` needs a trainer. A later follow-up, `cfa27a7fc78c682ad4244020505dce4825fe1e26`, fixes the same pattern in `wger/core/middleware.py` (AuthProxyMiddleware). I found no separate advisory for it. It is optional as a third sink, but it is not in the 2.6 fix.
- Confidence: high. I read the diff.
### 14. unfurl-flask-debug (Python, Flask)
- Repo: https://github.com/obsidianforensics/unfurl (Apache-2.0, about 24 MB).
- Advisory: GHSA-vg9h-jx4v-cwx2 (critical, CWE-489, no CVE). The GHSA lists "patched: none" as a package version, but it references the fix commit.
- Flaw: `web_app()` reads `debug` with `config[...].get('debug')`, which returns a string. Any non-empty string, including `"False"`, is truthy, so `app.run(debug=...)` always enables the Werkzeug debugger.
- Fix `4c0a07ab`: `getboolean('debug')` and `debug = false` in unfurl.ini. The diff is 2 lines.
- Caveat: the function default `debug='True'` is unchanged in the fixed version, so a run without an `[UNFURL_APP]` section still starts in debug mode. The fixed revision may therefore still, correctly, be flagged for debug-on by a strict analyzer.
- Confidence: high. I read the diff.
### 15. opencve-notification-ssrf (Python, Django)
- Repo: https://github.com/opencve/opencve (GitHub license: NOASSERTION. I believe OpenCVE v2+ is under a non-OSI license (BSL), but I did not verify this. Check before use).
- Advisory: CVE-2026-62282, GHSA-ch3g-4xvr-674q. The advisory's version ranges are confusing ("≤ 1.5.0, patched 3.0.0"). Use the commit pair.
- Flaw: webhook and Slack notification "test" requests POST to a user-supplied URL (`_http_post_json` with urllib) and do not restrict the destination.
- Fix `0af7f894`: new web/opencve/utils/ssrf.py (`validate_public_http_url`, `safe_request` with DNS pinning), `clean_url`/`clean_webhook_url` validators in forms.py, and notifications.py switched to `safe_request`.
- Confidence: high for the diff. The license is the concern. It is the weakest Python pick; drop it if an OSS license is mandatory.
## Considered and rejected (for traceability)
- vantage6 CVE-2024-23823 (permissive CORS, fix `70bb4e1d889230a841eb364d6c03accd7dd01a41`): the fix only adds a `cors_allowed_origins` option whose default is still `"*"`, so the fixed version is still permissive by default.
- FlaskBB CVE-2026-46556 (SSRF): the advisory has no patched version. The only related commit (`e87e585f`) just sets `allow_redirects=False`.
- SillyTavern CVE-2026-46372 (SSRF): the fix is a global request filter that is off by default.
- NodeBB CVE-2025-50979 (SQLi): no patched version or fix commit is listed, and I could not confirm the claim.
- Etherpad CVE-2026-55087 (open redirect): the referenced commits introduced the code. The fix is inside a large "assorted hardening" PR (#7784).
- EverShop CVE-2023-46943 (hardcoded HMAC secret): the fix is a large dev→main merge commit.
- claude-code-router CVE-2025-57755 (CORS): no tags, and I found no identifiable fix commit.
- calibre-web CVE-2022-0339 (SSRF): the fix commit is mixed with unrelated Kobo-sync changes. calibre-web SQLi CVE-2022-30765 (GHSA-8ppf-x4gr-2x7g) could be a Python alternate, but I did not check its diff.
- Tutor LMS PRs #3011/#3014/#3018 (SQLi hardening, Sept 2026): no CVE linked yet.
+62
View File
@@ -0,0 +1,62 @@
"""The independent population is reserved, well formed, and untouched by anything in the development tree.
Every rule this project has learned was learned on its development subjects, so those can no longer measure
whether the rules generalise. This reservation is the population that can -- until something scans it. The
check below fails the moment a reserved repository is referenced by a recipe, catalog, manifest or measurement
anywhere else, which is how an evaluation set quietly becomes a development one.
"""
from __future__ import annotations
import re
import tomllib
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
POPULATION = ROOT / "benchmarks" / "independent" / "population-v1.toml"
FAMILIES = {"injection", "untrusted_destination", "config_secrets"}
SHA = re.compile(r"[0-9a-f]{40}")
def _population() -> dict:
return tomllib.loads(POPULATION.read_text())
def test_the_reservation_is_well_formed() -> None:
data = _population()
assert data["status"] in {"reserved-unscanned", "frozen"}
cases = data["case"]
ids = [case["id"] for case in cases]
assert len(ids) == len(set(ids)) >= 10
for case in cases:
assert case["family"] in FAMILIES, case["id"]
assert SHA.fullmatch(case["vulnerable"]) and SHA.fullmatch(case["fixed"]), case["id"]
assert case["vulnerable"] != case["fixed"], case["id"]
assert case["repo"].startswith("https://github.com/"), case["id"]
assert case["advisory"] and case["sink"] and case["privilege"], case["id"]
languages = {case["language"] for case in cases}
assert {"php", "python"} <= languages and languages & {"javascript", "typescript"}
def test_a_multi_language_repository_is_reserved() -> None:
"""The declared v0.1 target is a WordPress plugin: PHP plus its admin JavaScript (task 13.4)."""
data = _population()
by_id = {case["id"]: case for case in data["case"]}
members = data["multi_language_members"]
assert members and all(by_id[m]["language"] == "php" and "javascript" in by_id[m].get("also", []) for m in members)
def test_no_reserved_repository_is_referenced_by_the_development_tree() -> None:
reserved = {case["repo"].rstrip("/").lower() for case in _population()["case"]}
names = {url.rsplit("/", 2)[-2] + "/" + url.rsplit("/", 1)[-1] for url in reserved}
offenders = []
for top in ("benchmarks", "src", "tests"):
for path in (ROOT / top).rglob("*"):
if not path.is_file() or POPULATION.parent in path.parents or path == Path(__file__) or path.suffix in {".pyc", ".bin", ".gz"}:
continue
try:
text = path.read_text(errors="ignore").lower()
except OSError:
continue
offenders += [f"{path.relative_to(ROOT)}: {name}" for name in names if f"github.com/{name}" in text]
assert not offenders, "a reserved repository is referenced outside the reservation: " + "; ".join(offenders[:10])