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
24 KiB
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-directorysortingrequest pass only throughesc_sql(). That does not protect a context without quotes. Only exploitable when the "custom usermeta table" option is on. - Fix:
476de92fchecks$order/$custom_sort_orderagainstASC/DESCand$custom_sort_typeagainst$this->sort_data_types.c61008bd("fixed security issue with mysql query escape") is a larger WPCS refactor of the same file. Use476de92fas 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()putexplode(',', $params['c_only_fields'])straight into$filter->only_fields.LP_Database::execute()then runsimplode(',', 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 touchesassets/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
orderparameter oftribe_has_next_event, ≤ 6.6.4. - Flaw:
redirect_posts_orderby()split ORDER BY fragments with a regex and kept any trailingordertoken as is.parse_orderby()inserted$orderinto"ID $order, ...". - Fix (6.6.4.1, commit
e128ec5e): addssanitize_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 anesc_sql()inget_coupon_details_for_checkout(). Other coupon paths (get_coupon_by_code(),get_coupon_usage_count(),get_coupon_applications()) pass$coupon_codethrough without escaping. - Fix
e4a25f69(parent = tag v3.9.6):make_clause()now binds every value through$wpdb->prepare('%s'|'%d', ...), and the manual quoting andesc_sqlare 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
b9f25109is the vulnerable master commit the reporter tested, not the fix. - Flaw: the constructor stores
$request->headers->get('user-agent')andgetClientIp().garbageCollector()(DELETE) andsaveCaptcha()(SELECT/INSERT) put them intosprintfSQL without escaping. Both run on everyGET /api/captcha. - Fix
545bdffb("fix: added missing escaping"): addsescapeQueryValue()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 intoINSERT ... VALUES (. The data comes from$_POST['imported-form']JSON inFormController::displayAll(), and that path had no permission check. - Fix
fe7244bf:intval($data['bn_id_nature'])plus aGuard::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)passesreq.body.host/porttohttp(s).requestwithout 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 intomakeRemoteRequest, and UUID validation ofrequestId. - 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/serverandpackages/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}(fromreq.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.jstoBSON()evaluates user-supplied document/query text withvm.runInNewContextin a BSON sandbox. The sandbox can be escaped, which gives RCE through the/checkValidand document/collection routes. - Fix
d8c9bda4: replaces the vm-based parser withmongodb-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 configurableadminApiCorsOriginsallowlist (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)intof"SELECT id FROM links WHERE url IN ('{statuses}')". The URLs come fromadd_linkson/json/add_package. Chain: json_blueprintadd_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, parent6aa71cd99a6f510de7137d17ec99f930916704c0). I kept it as an alternate only, because that fix just callsjs2py.disable_pyimport(). Theeval_jssink 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
aeba85a3reference (2018) is the commit that introduced the parser. The fix isfdd52cd1. - Flaw: the Postgres query-string parser (
?q=) turned parsed search terms into SQL with f-strings inSearchTerm.__repr__.QueryBuilder.from_params()then used that as a WHERE clause with an emptyqvars. - Fix: new
to_sql(params)methods produce%(name)splaceholders, andfrom_params()passes the parsed vars. - Privilege: alerta requires an API key or login when
AUTH_REQUIRED=True. I believe the default isFalse, 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()returnsHttpResponseRedirect(request.GET['next']).use_browser_language()redirects toremove_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_languageis unauthenticated,trainer_loginneeds a trainer. A later follow-up,cfa27a7fc78c682ad4244020505dce4825fe1e26, fixes the same pattern inwger/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()readsdebugwithconfig[...].get('debug'), which returns a string. Any non-empty string, including"False", is truthy, soapp.run(debug=...)always enables the Werkzeug debugger. - Fix
4c0a07ab:getboolean('debug')anddebug = falsein 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_jsonwith urllib) and do not restrict the destination. - Fix
0af7f894: new web/opencve/utils/ssrf.py (validate_public_http_url,safe_requestwith DNS pinning),clean_url/clean_webhook_urlvalidators in forms.py, and notifications.py switched tosafe_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 acors_allowed_originsoption 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 setsallow_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.