mirror of
https://github.com/elicpeter/nyx.git
synced 2026-07-12 21:02:11 +02:00
chore: remove stale scheduled_tasks.lock file
This commit is contained in:
parent
a5929bb169
commit
2a4d49b68b
12 changed files with 1059 additions and 21 deletions
|
|
@ -34,3 +34,38 @@ python3 tests/eval_corpus/owasp_gt_convert.py \
|
|||
File: `nist_sard.json`
|
||||
|
||||
Same format. Source: SARD manifest XML converted with `python3 tests/eval_corpus/sard_gt_convert.py`.
|
||||
|
||||
## OWASP NodeGoat / OWASP Juice Shop (JS/TS — Track R.1)
|
||||
|
||||
Files: `nodegoat.json` (Express, `.js`), `juiceshop.json` (TypeScript, `.ts`).
|
||||
Same four-field format as above; all records are `vuln: true`.
|
||||
|
||||
These two apps are intentionally vulnerable end to end, so — unlike OWASP
|
||||
Benchmark — they ship no machine-readable per-file vuln labels and have no
|
||||
benign-control files to pair against. The authoritative source is a curated
|
||||
TOML manifest committed here, one `[[entry]]` per known-vulnerable handler
|
||||
with a `note` citing why:
|
||||
|
||||
- `nodegoat.manifest.toml`
|
||||
- `juiceshop.manifest.toml`
|
||||
|
||||
`manifest_gt_convert.py` turns a manifest into the committed `.json`:
|
||||
|
||||
```sh
|
||||
python3 tests/eval_corpus/manifest_gt_convert.py \
|
||||
--manifest tests/eval_corpus/ground_truth/nodegoat.manifest.toml \
|
||||
--output tests/eval_corpus/ground_truth/nodegoat.json
|
||||
```
|
||||
|
||||
Pass `--corpus-dir <clone>` to validate every labelled path against a real
|
||||
checkout. The converter exits non-zero if any path is missing, so a corpus
|
||||
bump that moves a handler fails loudly instead of silently dropping recall.
|
||||
CI (`.github/workflows/eval.yml`, `jsts` job) regenerates each `.json`
|
||||
against a fresh clone of the pinned ref and asserts it matches the committed
|
||||
file.
|
||||
|
||||
Because the manifests label canonical vulns only, recall (did nyx catch the
|
||||
known vulns) is the meaningful metric; precision vs this partial ground
|
||||
truth is informational. Gate 7 publishes per-cap precision/recall/confirmed
|
||||
report-only by default (`NYX_JSTS_FLOOR_CAPS` empty), matching the OWASP
|
||||
gate.
|
||||
|
|
|
|||
38
tests/eval_corpus/ground_truth/juiceshop.json
Normal file
38
tests/eval_corpus/ground_truth/juiceshop.json
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
[
|
||||
{
|
||||
"path": "lib/insecurity.ts",
|
||||
"line": 0,
|
||||
"cap": "crypto",
|
||||
"vuln": true
|
||||
},
|
||||
{
|
||||
"path": "routes/fileServer.ts",
|
||||
"line": 0,
|
||||
"cap": "path_traversal",
|
||||
"vuln": true
|
||||
},
|
||||
{
|
||||
"path": "routes/login.ts",
|
||||
"line": 0,
|
||||
"cap": "sqli",
|
||||
"vuln": true
|
||||
},
|
||||
{
|
||||
"path": "routes/profileImageUrlUpload.ts",
|
||||
"line": 0,
|
||||
"cap": "ssrf",
|
||||
"vuln": true
|
||||
},
|
||||
{
|
||||
"path": "routes/redirect.ts",
|
||||
"line": 0,
|
||||
"cap": "redirect",
|
||||
"vuln": true
|
||||
},
|
||||
{
|
||||
"path": "routes/search.ts",
|
||||
"line": 0,
|
||||
"cap": "sqli",
|
||||
"vuln": true
|
||||
}
|
||||
]
|
||||
66
tests/eval_corpus/ground_truth/juiceshop.manifest.toml
Normal file
66
tests/eval_corpus/ground_truth/juiceshop.manifest.toml
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# OWASP Juice Shop — curated vuln ground-truth manifest (Phase 28, Track R.1).
|
||||
#
|
||||
# Juice Shop is an intentionally-vulnerable TypeScript/Express + Angular
|
||||
# app. Its `data/static/challenges.yml` enumerates challenges but pins no
|
||||
# source file/line, so it cannot drive file-level ground truth on its own.
|
||||
# This manifest IS the authoritative source: one [[entry]] per known-
|
||||
# vulnerable server-side handler, curated from the project's own challenge
|
||||
# definitions + companion guide, each with a `note` citing the challenge.
|
||||
#
|
||||
# tests/eval_corpus/manifest_gt_convert.py turns this into the committed
|
||||
# ground_truth/juiceshop.json. CI regenerates it against a fresh clone of
|
||||
# the pinned tag and asserts byte-equality; the converter HARD-ERRORS on
|
||||
# any path that no longer exists in the corpus, so a Juice Shop bump that
|
||||
# refactors a route fails the eval job loudly instead of silently dropping
|
||||
# recall. Re-pin `pinned_ref` and re-validate the paths together.
|
||||
#
|
||||
# `cap` is a nyx cap label (tabulate.py). `path` is relative to the Juice
|
||||
# Shop clone root, POSIX separators. Lang is inferred from the extension
|
||||
# (.ts -> typescript). All `vuln = true`: Juice Shop is all-vulnerable, so
|
||||
# there is no benign-control file to pair against. As with NodeGoat,
|
||||
# precision vs this manifest is informational (an unlabelled finding may be
|
||||
# a real uncurated vuln, not a false positive) while recall is the
|
||||
# meaningful floor. See tests/eval_corpus/budget.toml for the gate policy.
|
||||
|
||||
corpus = "juiceshop"
|
||||
upstream = "https://github.com/juice-shop/juice-shop"
|
||||
# Pinned to a stable release tag. The server-side handlers below
|
||||
# (routes/*.ts, lib/insecurity.ts) have been stable across the TypeScript
|
||||
# era of Juice Shop; re-validate if the tag is bumped.
|
||||
pinned_ref = "v15.0.0"
|
||||
|
||||
[[entry]]
|
||||
path = "routes/login.ts"
|
||||
cap = "sqli"
|
||||
vuln = true
|
||||
note = "login builds a raw `models.sequelize.query(\"... WHERE email = '\" + req.body.email + \"' ...\")` — SQL injection auth bypass (challenge: loginAdmin / loginBender)."
|
||||
|
||||
[[entry]]
|
||||
path = "routes/search.ts"
|
||||
cap = "sqli"
|
||||
vuln = true
|
||||
note = "product search concatenates the `q` criteria into a raw `models.sequelize.query` LIKE clause — UNION-based SQL injection (challenge: unionSqlInjection / dbSchema)."
|
||||
|
||||
[[entry]]
|
||||
path = "routes/fileServer.ts"
|
||||
cap = "path_traversal"
|
||||
vuln = true
|
||||
note = "serveKeyFiles / file download resolves a user-controlled filename under the ftp dir without containment — path traversal (challenge: accessLogDisclosure / forgottenDevBackup)."
|
||||
|
||||
[[entry]]
|
||||
path = "routes/redirect.ts"
|
||||
cap = "redirect"
|
||||
vuln = true
|
||||
note = "redirect endpoint forwards to the `to` query param via an allow-list that is bypassable by substring — open redirect (challenge: redirectCryptoCurrency / redirect)."
|
||||
|
||||
[[entry]]
|
||||
path = "routes/profileImageUrlUpload.ts"
|
||||
cap = "ssrf"
|
||||
vuln = true
|
||||
note = "profile image upload fetches an arbitrary user-supplied imageUrl server-side — SSRF (challenge: ssrf)."
|
||||
|
||||
[[entry]]
|
||||
path = "lib/insecurity.ts"
|
||||
cap = "crypto"
|
||||
vuln = true
|
||||
note = "hardcoded HMAC/JWT key material and weak hashing (md5-based `hash`) — broken cryptography / hardcoded secret (challenge: weakCryptography / jwt*)."
|
||||
32
tests/eval_corpus/ground_truth/nodegoat.json
Normal file
32
tests/eval_corpus/ground_truth/nodegoat.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
[
|
||||
{
|
||||
"path": "app/routes/allocations.js",
|
||||
"line": 0,
|
||||
"cap": "unauthorized_id",
|
||||
"vuln": true
|
||||
},
|
||||
{
|
||||
"path": "app/routes/contributions.js",
|
||||
"line": 0,
|
||||
"cap": "cmdi",
|
||||
"vuln": true
|
||||
},
|
||||
{
|
||||
"path": "app/routes/memos.js",
|
||||
"line": 0,
|
||||
"cap": "xss",
|
||||
"vuln": true
|
||||
},
|
||||
{
|
||||
"path": "app/routes/profile.js",
|
||||
"line": 0,
|
||||
"cap": "xss",
|
||||
"vuln": true
|
||||
},
|
||||
{
|
||||
"path": "config/env/all.js",
|
||||
"line": 0,
|
||||
"cap": "crypto",
|
||||
"vuln": true
|
||||
}
|
||||
]
|
||||
62
tests/eval_corpus/ground_truth/nodegoat.manifest.toml
Normal file
62
tests/eval_corpus/ground_truth/nodegoat.manifest.toml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
# OWASP NodeGoat — curated vuln ground-truth manifest (Phase 28, Track R.1).
|
||||
#
|
||||
# NodeGoat is an intentionally-vulnerable Express/Node app that maps the
|
||||
# OWASP Top 10 to concrete handlers. It ships no machine-readable per-file
|
||||
# vuln labels (unlike OWASP Benchmark's expectedresults CSV), so this
|
||||
# manifest IS the authoritative source: one [[entry]] per known-vulnerable
|
||||
# location, each curated from the project's own tutorial + the canonical
|
||||
# vuln walk-through, with a `note` citing why.
|
||||
#
|
||||
# tests/eval_corpus/manifest_gt_convert.py turns this into the committed
|
||||
# ground_truth/nodegoat.json. CI regenerates it against a fresh clone of
|
||||
# the pinned ref and asserts byte-equality, and the converter HARD-ERRORS
|
||||
# on any path that no longer exists in the corpus, so a NodeGoat bump that
|
||||
# moves a handler fails the eval job loudly rather than silently dropping
|
||||
# recall. Update `pinned_ref` + the paths together when re-pinning.
|
||||
#
|
||||
# `cap` is a nyx cap label (tabulate.py). `path` is relative to the
|
||||
# NodeGoat clone root, POSIX separators. Lang is inferred from the
|
||||
# extension (.js -> javascript). These are all `vuln = true`: NodeGoat is
|
||||
# all-vulnerable, so there is no benign-control file to pair against (the
|
||||
# OWASP Benchmark vuln/benign pairing does not exist here). Precision vs
|
||||
# this manifest is therefore informational (an unlabelled finding is not
|
||||
# necessarily a false positive — it may be a real vuln we did not curate),
|
||||
# while recall (did nyx catch the canonical vulns) is the meaningful floor.
|
||||
# See tests/eval_corpus/budget.toml for how the gate treats these cells.
|
||||
|
||||
corpus = "nodegoat"
|
||||
upstream = "https://github.com/OWASP/NodeGoat"
|
||||
# NodeGoat publishes no semver tags; the eval job pins the default branch
|
||||
# via the CI cache key. The `app/` + `config/` layout below has been
|
||||
# stable for years; re-validate the paths if the cache key is bumped.
|
||||
pinned_ref = "master"
|
||||
|
||||
[[entry]]
|
||||
path = "app/routes/contributions.js"
|
||||
cap = "cmdi"
|
||||
vuln = true
|
||||
note = "handleContributionsUpdate eval()s the pre-tax/after-tax/roth form fields — server-side JS injection (OWASP A1 Injection); the textbook NodeGoat RCE."
|
||||
|
||||
[[entry]]
|
||||
path = "app/routes/profile.js"
|
||||
cap = "xss"
|
||||
vuln = true
|
||||
note = "profile fields (firstName/lastName/bankAcc/...) are persisted then rendered unescaped — stored XSS (OWASP A3 / A7 XSS)."
|
||||
|
||||
[[entry]]
|
||||
path = "app/routes/memos.js"
|
||||
cap = "xss"
|
||||
vuln = true
|
||||
note = "memo body is stored and echoed back into the memos view without output encoding — stored XSS."
|
||||
|
||||
[[entry]]
|
||||
path = "app/routes/allocations.js"
|
||||
cap = "unauthorized_id"
|
||||
vuln = true
|
||||
note = "allocations are looked up by a userId taken from the request with no ownership check — insecure direct object reference / broken access control (OWASP A4)."
|
||||
|
||||
[[entry]]
|
||||
path = "config/env/all.js"
|
||||
cap = "crypto"
|
||||
vuln = true
|
||||
note = "hardcoded cookieSecret / session secret committed in source — sensitive-data / weak-secret smell (OWASP A6)."
|
||||
Loading…
Add table
Add a link
Reference in a new issue