mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 12:39:28 +00:00
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
152 lines
24 KiB
Markdown
152 lines
24 KiB
Markdown
# 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.
|