diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index a716b06..e2b6554 100644 --- a/docs/VULNERABILITY_CATALOG.md +++ b/docs/VULNERABILITY_CATALOG.md @@ -7,11 +7,11 @@ from each file's header comment, so this page cannot drift from the source. ## Totals -- **Test cases:** 218 -- **Expected detections:** 218 -- **`VULNERABLE:` markers:** 330 (individual lines a scanner should flag) -- **`SAFE:` markers:** 235 (lines a scanner must not flag — the false-positive control group) -- **Languages:** 11 — c, dotenv, go, java, javascript, json, python, ruby, rust, swift, text +- **Test cases:** 233 +- **Expected detections:** 233 +- **`VULNERABLE:` markers:** 345 (individual lines a scanner should flag) +- **`SAFE:` markers:** 250 (lines a scanner must not flag — the false-positive control group) +- **Languages:** 12 — c, cpp, dotenv, go, java, javascript, json, python, ruby, rust, swift, text - **CWE categories:** 101 — CWE-20, CWE-22, CWE-78, CWE-79, CWE-89, CWE-90, CWE-94, CWE-95, CWE-113, CWE-117, CWE-121, CWE-129, CWE-134, CWE-187, CWE-190, CWE-201, CWE-203, CWE-208, CWE-209, CWE-256, CWE-285, CWE-287, CWE-288, CWE-291, CWE-295, CWE-297, CWE-306, CWE-307, CWE-311, CWE-312, CWE-319, CWE-321, CWE-326, CWE-327, CWE-328, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-367, CWE-377, CWE-384, CWE-400, CWE-409, CWE-415, CWE-416, CWE-434, CWE-441, CWE-457, CWE-460, CWE-472, CWE-475, CWE-476, CWE-480, CWE-488, CWE-489, CWE-502, CWE-506, CWE-509, CWE-512, CWE-521, CWE-522, CWE-525, CWE-532, CWE-598, CWE-601, CWE-602, CWE-611, CWE-613, CWE-614, CWE-620, CWE-639, CWE-640, CWE-643, CWE-681, CWE-693, CWE-732, CWE-759, CWE-776, CWE-789, CWE-798, CWE-835, CWE-862, CWE-863, CWE-915, CWE-916, CWE-918, CWE-922, CWE-942, CWE-943, CWE-1004, CWE-1021, CWE-1236, CWE-1321, CWE-1333, CWE-1336, CWE-1357 ## How coverage is scored @@ -42,6 +42,26 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | Uninitialized stack bytes copied into a response | [`uninitialized-memory-exposure.c`](../vulns/c/uninitialized-memory-exposure.c) | CWE-457 | high | yes | 1 vuln / 1 safe | | Heap object used after it is freed | [`use-after-free.c`](../vulns/c/use-after-free.c) | CWE-416 | critical | yes | 1 vuln / 1 safe | +## Cpp + +| Test case | File | CWE | Severity | Expected | Markers | +|---|---|---|---|---|---| +| Command injection through std::system | [`command-injection-system.cpp`](../vulns/cpp/command-injection-system.cpp) | CWE-78 | critical | yes | 1 vuln / 1 safe | +| Dangling string_view returned from local storage | [`dangling-string-view.cpp`](../vulns/cpp/dangling-string-view.cpp) | CWE-416 | high | yes | 1 vuln / 1 safe | +| Data race on a shared request counter | [`data-race-counter.cpp`](../vulns/cpp/data-race-counter.cpp) | CWE-362 | high | yes | 1 vuln / 1 safe | +| Heap allocation deleted twice | [`double-delete.cpp`](../vulns/cpp/double-delete.cpp) | CWE-415 | critical | yes | 1 vuln / 1 safe | +| Authorization failure defaults to allow | [`fail-open-authorization.cpp`](../vulns/cpp/fail-open-authorization.cpp) | CWE-285 | critical | yes | 1 vuln / 1 safe | +| Uncontrolled format string passed to printf | [`format-string-printf.cpp`](../vulns/cpp/format-string-printf.cpp) | CWE-134 | high | yes | 1 vuln / 1 safe | +| Hard-coded encryption key embedded in source | [`hardcoded-crypto-key.cpp`](../vulns/cpp/hardcoded-crypto-key.cpp) | CWE-321 | high | yes | 1 vuln / 1 safe | +| Security-sensitive length truncated to a smaller integer | [`integer-truncation.cpp`](../vulns/cpp/integer-truncation.cpp) | CWE-681 | high | yes | 1 vuln / 1 safe | +| Iterator dereferenced after vector reallocation | [`invalidated-vector-iterator.cpp`](../vulns/cpp/invalidated-vector-iterator.cpp) | CWE-416 | high | yes | 1 vuln / 1 safe | +| Authentication bypass through prefix comparison | [`partial-api-key-comparison.cpp`](../vulns/cpp/partial-api-key-comparison.cpp) | CWE-187 | critical | yes | 1 vuln / 1 safe | +| Path traversal through an untrusted child path | [`path-traversal-filesystem.cpp`](../vulns/cpp/path-traversal-filesystem.cpp) | CWE-22 | high | yes | 1 vuln / 1 safe | +| Predictable recovery token generated with mt19937 | [`predictable-reset-token.cpp`](../vulns/cpp/predictable-reset-token.cpp) | CWE-330 | high | yes | 1 vuln / 1 safe | +| SQL injection through string concatenation | [`sql-injection-concat.cpp`](../vulns/cpp/sql-injection-concat.cpp) | CWE-89 | critical | yes | 1 vuln / 1 safe | +| Unbounded allocation from a request-controlled count | [`unbounded-vector-allocation.cpp`](../vulns/cpp/unbounded-vector-allocation.cpp) | CWE-789 | high | yes | 1 vuln / 1 safe | +| Out-of-bounds vector access through an unvalidated index | [`vector-out-of-bounds.cpp`](../vulns/cpp/vector-out-of-bounds.cpp) | CWE-129 | high | yes | 1 vuln / 1 safe | + ## Go | Test case | File | CWE | Severity | Expected | Markers | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index e7202cf..e2f8fc0 100644 --- a/vulns/VULNERABILITY_CATALOG.json +++ b/vulns/VULNERABILITY_CATALOG.json @@ -2,12 +2,13 @@ "schema": "threatcrush-testbed-catalog/1", "note": "Generated by scripts/generate-catalog.py \u2014 do not edit by hand.", "totals": { - "test_cases": 218, - "expected_detections": 218, - "vulnerable_markers": 330, - "safe_markers": 235, + "test_cases": 233, + "expected_detections": 233, + "vulnerable_markers": 345, + "safe_markers": 250, "languages": [ "c", + "cpp", "dotenv", "go", "java", @@ -21,6 +22,7 @@ ], "categories": [ "c", + "cpp", "go", "java", "javascript", @@ -481,6 +483,351 @@ 33 ] }, + { + "id": "cpp-command-injection-system", + "file": "vulns/cpp/command-injection-system.cpp", + "title": "Command injection through std::system", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-78", + "cwes": [ + "CWE-78" + ], + "severity": "critical", + "expected_detection": true, + "description": "Request text is concatenated into a shell command.", + "detection_target": "Tainted strings passed to std::system.", + "safe_guard": "Every example is behind `NEVER_RUN`; no process is spawned.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 28 + ] + }, + { + "id": "cpp-dangling-string-view", + "file": "vulns/cpp/dangling-string-view.cpp", + "title": "Dangling string_view returned from local storage", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-416", + "cwes": [ + "CWE-416" + ], + "severity": "high", + "expected_detection": true, + "description": "A non-owning view escapes the lifetime of a local string.", + "detection_target": "Views or references that outlive local backing storage.", + "safe_guard": "Every example is behind `NEVER_RUN`; no view is returned.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 29 + ] + }, + { + "id": "cpp-data-race-counter", + "file": "vulns/cpp/data-race-counter.cpp", + "title": "Data race on a shared request counter", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-362", + "cwes": [ + "CWE-362" + ], + "severity": "high", + "expected_detection": true, + "description": "Multiple threads update a shared integer without synchronization.", + "detection_target": "Unsynchronized writes to shared mutable state.", + "safe_guard": "Every example is behind `NEVER_RUN`; no thread runs.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 27 + ] + }, + { + "id": "cpp-double-delete", + "file": "vulns/cpp/double-delete.cpp", + "title": "Heap allocation deleted twice", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-415", + "cwes": [ + "CWE-415" + ], + "severity": "critical", + "expected_detection": true, + "description": "The same raw pointer is deleted on two paths.", + "detection_target": "Duplicate delete operations for one allocation.", + "safe_guard": "Every example is behind `NEVER_RUN`; no memory is allocated.", + "attribution": "line", + "vulnerable_lines": [ + 20 + ], + "safe_lines": [ + 26 + ] + }, + { + "id": "cpp-fail-open-authorization", + "file": "vulns/cpp/fail-open-authorization.cpp", + "title": "Authorization failure defaults to allow", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-285", + "cwes": [ + "CWE-285" + ], + "severity": "critical", + "expected_detection": true, + "description": "An authorization exception returns an allow decision.", + "detection_target": "Error paths that grant access when checks fail.", + "safe_guard": "Every example is behind `NEVER_RUN`; no decision is evaluated.", + "attribution": "line", + "vulnerable_lines": [ + 23 + ], + "safe_lines": [ + 34 + ] + }, + { + "id": "cpp-format-string-printf", + "file": "vulns/cpp/format-string-printf.cpp", + "title": "Uncontrolled format string passed to printf", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-134", + "cwes": [ + "CWE-134" + ], + "severity": "high", + "expected_detection": true, + "description": "Request text is used as the printf format string.", + "detection_target": "Tainted strings used as variadic format arguments.", + "safe_guard": "Every example is behind `NEVER_RUN`; nothing is printed.", + "attribution": "line", + "vulnerable_lines": [ + 19 + ], + "safe_lines": [ + 25 + ] + }, + { + "id": "cpp-hardcoded-crypto-key", + "file": "vulns/cpp/hardcoded-crypto-key.cpp", + "title": "Hard-coded encryption key embedded in source", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-321", + "cwes": [ + "CWE-321" + ], + "severity": "high", + "expected_detection": true, + "description": "Encryption key material is stored as a source-code literal.", + "detection_target": "Static key literals used as cryptographic secrets.", + "safe_guard": "Every example is behind `NEVER_RUN`; the literal is synthetic.", + "attribution": "line", + "vulnerable_lines": [ + 20 + ], + "safe_lines": [ + 28 + ] + }, + { + "id": "cpp-integer-truncation", + "file": "vulns/cpp/integer-truncation.cpp", + "title": "Security-sensitive length truncated to a smaller integer", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-681", + "cwes": [ + "CWE-681" + ], + "severity": "high", + "expected_detection": true, + "description": "A request size is narrowed without a range check.", + "detection_target": "Narrowing conversions of attacker-controlled sizes.", + "safe_guard": "Every example is behind `NEVER_RUN`; no conversion occurs.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 29 + ] + }, + { + "id": "cpp-invalidated-vector-iterator", + "file": "vulns/cpp/invalidated-vector-iterator.cpp", + "title": "Iterator dereferenced after vector reallocation", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-416", + "cwes": [ + "CWE-416" + ], + "severity": "high", + "expected_detection": true, + "description": "A vector mutation invalidates an iterator before dereference.", + "detection_target": "Iterator use after a container-invalidating mutation.", + "safe_guard": "Every example is behind `NEVER_RUN`; no iterator is used.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 29 + ] + }, + { + "id": "cpp-partial-api-key-comparison", + "file": "vulns/cpp/partial-api-key-comparison.cpp", + "title": "Authentication bypass through prefix comparison", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-187", + "cwes": [ + "CWE-187" + ], + "severity": "critical", + "expected_detection": true, + "description": "Only the supplied prefix of an API key is compared.", + "detection_target": "Prefix comparisons used for authentication secrets.", + "safe_guard": "Every example is behind `NEVER_RUN`; no key is checked.", + "attribution": "line", + "vulnerable_lines": [ + 20 + ], + "safe_lines": [ + 30 + ] + }, + { + "id": "cpp-path-traversal-filesystem", + "file": "vulns/cpp/path-traversal-filesystem.cpp", + "title": "Path traversal through an untrusted child path", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-22", + "cwes": [ + "CWE-22" + ], + "severity": "high", + "expected_detection": true, + "description": "A request path is appended without containment validation.", + "detection_target": "Tainted filesystem paths without canonical containment checks.", + "safe_guard": "Every example is behind `NEVER_RUN`; no path is accessed.", + "attribution": "line", + "vulnerable_lines": [ + 20 + ], + "safe_lines": [ + 29 + ] + }, + { + "id": "cpp-predictable-reset-token", + "file": "vulns/cpp/predictable-reset-token.cpp", + "title": "Predictable recovery token generated with mt19937", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-330", + "cwes": [ + "CWE-330" + ], + "severity": "high", + "expected_detection": true, + "description": "A deterministic PRNG produces a password-reset token.", + "detection_target": "Non-cryptographic PRNG output used as a security token.", + "safe_guard": "Every example is behind `NEVER_RUN`; no token is generated.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 29 + ] + }, + { + "id": "cpp-sql-injection-concat", + "file": "vulns/cpp/sql-injection-concat.cpp", + "title": "SQL injection through string concatenation", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-89", + "cwes": [ + "CWE-89" + ], + "severity": "critical", + "expected_detection": true, + "description": "Request text is embedded directly in a SQL statement.", + "detection_target": "Tainted strings concatenated into SQL queries.", + "safe_guard": "Every example is behind `NEVER_RUN`; no database is queried.", + "attribution": "line", + "vulnerable_lines": [ + 20 + ], + "safe_lines": [ + 27 + ] + }, + { + "id": "cpp-unbounded-vector-allocation", + "file": "vulns/cpp/unbounded-vector-allocation.cpp", + "title": "Unbounded allocation from a request-controlled count", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-789", + "cwes": [ + "CWE-789" + ], + "severity": "high", + "expected_detection": true, + "description": "A request count directly sizes an in-memory vector.", + "detection_target": "Tainted sizes passed to container constructors.", + "safe_guard": "Every example is behind `NEVER_RUN`; no allocation occurs.", + "attribution": "line", + "vulnerable_lines": [ + 20 + ], + "safe_lines": [ + 28 + ] + }, + { + "id": "cpp-vector-out-of-bounds", + "file": "vulns/cpp/vector-out-of-bounds.cpp", + "title": "Out-of-bounds vector access through an unvalidated index", + "category": "cpp", + "language": "cpp", + "cwe": "CWE-129", + "cwes": [ + "CWE-129" + ], + "severity": "high", + "expected_detection": true, + "description": "A request index is used with unchecked vector access.", + "detection_target": "Tainted indices passed to unchecked operator[].", + "safe_guard": "Every example is behind `NEVER_RUN`; no vector is accessed.", + "attribution": "line", + "vulnerable_lines": [ + 20 + ], + "safe_lines": [ + 27 + ] + }, { "id": "go-cmd-injection-exec", "file": "vulns/go/cmd-injection-exec.go", diff --git a/vulns/cpp/command-injection-system.cpp b/vulns/cpp/command-injection-system.cpp new file mode 100644 index 0000000..14a0deb --- /dev/null +++ b/vulns/cpp/command-injection-system.cpp @@ -0,0 +1,31 @@ +// @id cpp-command-injection-system +// @test-case Command injection through std::system +// @cwe CWE-78 +// @severity critical +// @language cpp +// @expected-detection true +// @description Request text is concatenated into a shell command. +// @safe-guard Every example is behind `NEVER_RUN`; no process is spawned. +// @detection-target Tainted strings passed to std::system. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include +#include +#include + +constexpr bool NEVER_RUN = false; + +int inspectVulnerable(const std::string& name) { + if (NEVER_RUN) { + return std::system(("inspect " + name).c_str()); // VULNERABLE: CWE-78 + } + return 0; +} + +std::vector inspectSafe(const std::string& name) { + if (NEVER_RUN) { + return {"inspect", "--", name}; // SAFE: literal argv without a shell + } + return {}; +} diff --git a/vulns/cpp/dangling-string-view.cpp b/vulns/cpp/dangling-string-view.cpp new file mode 100644 index 0000000..bce5906 --- /dev/null +++ b/vulns/cpp/dangling-string-view.cpp @@ -0,0 +1,32 @@ +// @id cpp-dangling-string-view +// @test-case Dangling string_view returned from local storage +// @cwe CWE-416 +// @severity high +// @language cpp +// @expected-detection true +// @description A non-owning view escapes the lifetime of a local string. +// @safe-guard Every example is behind `NEVER_RUN`; no view is returned. +// @detection-target Views or references that outlive local backing storage. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include +#include + +constexpr bool NEVER_RUN = false; + +std::string_view labelVulnerable() { + if (NEVER_RUN) { + std::string local = "temporary"; + return std::string_view(local); // VULNERABLE: CWE-416 + } + return {}; +} + +std::string labelSafe() { + if (NEVER_RUN) { + std::string local = "temporary"; + return local; // SAFE: owning value returned + } + return {}; +} diff --git a/vulns/cpp/data-race-counter.cpp b/vulns/cpp/data-race-counter.cpp new file mode 100644 index 0000000..0f67165 --- /dev/null +++ b/vulns/cpp/data-race-counter.cpp @@ -0,0 +1,29 @@ +// @id cpp-data-race-counter +// @test-case Data race on a shared request counter +// @cwe CWE-362 +// @severity high +// @language cpp +// @expected-detection true +// @description Multiple threads update a shared integer without synchronization. +// @safe-guard Every example is behind `NEVER_RUN`; no thread runs. +// @detection-target Unsynchronized writes to shared mutable state. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include + +constexpr bool NEVER_RUN = false; +int sharedCounter = 0; +std::atomic atomicCounter{0}; + +void incrementVulnerable() { + if (NEVER_RUN) { + ++sharedCounter; // VULNERABLE: CWE-362 + } +} + +void incrementSafe() { + if (NEVER_RUN) { + atomicCounter.fetch_add(1, std::memory_order_relaxed); // SAFE: atomic update + } +} diff --git a/vulns/cpp/double-delete.cpp b/vulns/cpp/double-delete.cpp new file mode 100644 index 0000000..9ad9018 --- /dev/null +++ b/vulns/cpp/double-delete.cpp @@ -0,0 +1,28 @@ +// @id cpp-double-delete +// @test-case Heap allocation deleted twice +// @cwe CWE-415 +// @severity critical +// @language cpp +// @expected-detection true +// @description The same raw pointer is deleted on two paths. +// @safe-guard Every example is behind `NEVER_RUN`; no memory is allocated. +// @detection-target Duplicate delete operations for one allocation. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include + +constexpr bool NEVER_RUN = false; + +void releaseVulnerable(int* value) { + if (NEVER_RUN) { + delete value; + delete value; // VULNERABLE: CWE-415 + } +} + +void releaseSafe(std::unique_ptr value) { + if (NEVER_RUN) { + value.reset(); // SAFE: unique ownership releases once + } +} diff --git a/vulns/cpp/fail-open-authorization.cpp b/vulns/cpp/fail-open-authorization.cpp new file mode 100644 index 0000000..8b1e123 --- /dev/null +++ b/vulns/cpp/fail-open-authorization.cpp @@ -0,0 +1,38 @@ +// @id cpp-fail-open-authorization +// @test-case Authorization failure defaults to allow +// @cwe CWE-285 +// @severity critical +// @language cpp +// @expected-detection true +// @description An authorization exception returns an allow decision. +// @safe-guard Every example is behind `NEVER_RUN`; no decision is evaluated. +// @detection-target Error paths that grant access when checks fail. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include + +constexpr bool NEVER_RUN = false; +using Authorizer = bool (*)(int, int); + +bool authorizeVulnerable(Authorizer check, int user, int resource) { + if (NEVER_RUN) { + try { + return check(user, resource); + } catch (...) { + return true; // VULNERABLE: CWE-285 + } + } + return false; +} + +bool authorizeSafe(Authorizer check, int user, int resource) { + if (NEVER_RUN) { + try { + return check(user, resource); + } catch (...) { + return false; // SAFE: fail closed + } + } + return false; +} diff --git a/vulns/cpp/format-string-printf.cpp b/vulns/cpp/format-string-printf.cpp new file mode 100644 index 0000000..4ccc383 --- /dev/null +++ b/vulns/cpp/format-string-printf.cpp @@ -0,0 +1,27 @@ +// @id cpp-format-string-printf +// @test-case Uncontrolled format string passed to printf +// @cwe CWE-134 +// @severity high +// @language cpp +// @expected-detection true +// @description Request text is used as the printf format string. +// @safe-guard Every example is behind `NEVER_RUN`; nothing is printed. +// @detection-target Tainted strings used as variadic format arguments. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include + +constexpr bool NEVER_RUN = false; + +void messageVulnerable(const char* message) { + if (NEVER_RUN) { + std::printf(message); // VULNERABLE: CWE-134 + } +} + +void messageSafe(const char* message) { + if (NEVER_RUN) { + std::printf("%s", message); // SAFE: fixed format string + } +} diff --git a/vulns/cpp/hardcoded-crypto-key.cpp b/vulns/cpp/hardcoded-crypto-key.cpp new file mode 100644 index 0000000..5072673 --- /dev/null +++ b/vulns/cpp/hardcoded-crypto-key.cpp @@ -0,0 +1,31 @@ +// @id cpp-hardcoded-crypto-key +// @test-case Hard-coded encryption key embedded in source +// @cwe CWE-321 +// @severity high +// @language cpp +// @expected-detection true +// @description Encryption key material is stored as a source-code literal. +// @safe-guard Every example is behind `NEVER_RUN`; the literal is synthetic. +// @detection-target Static key literals used as cryptographic secrets. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include +#include + +constexpr bool NEVER_RUN = false; + +std::string keyVulnerable() { + if (NEVER_RUN) { + return "TEST_ONLY_AES_KEY_00000000000000"; // VULNERABLE: CWE-321 synthetic fixture + } + return {}; +} + +std::string keySafe() { + if (NEVER_RUN) { + const char* value = std::getenv("APP_ENCRYPTION_KEY"); + return value == nullptr ? std::string{} : std::string{value}; // SAFE: supplied by secret management + } + return {}; +} diff --git a/vulns/cpp/integer-truncation.cpp b/vulns/cpp/integer-truncation.cpp new file mode 100644 index 0000000..ff287d4 --- /dev/null +++ b/vulns/cpp/integer-truncation.cpp @@ -0,0 +1,32 @@ +// @id cpp-integer-truncation +// @test-case Security-sensitive length truncated to a smaller integer +// @cwe CWE-681 +// @severity high +// @language cpp +// @expected-detection true +// @description A request size is narrowed without a range check. +// @safe-guard Every example is behind `NEVER_RUN`; no conversion occurs. +// @detection-target Narrowing conversions of attacker-controlled sizes. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include +#include +#include + +constexpr bool NEVER_RUN = false; + +std::uint16_t lengthVulnerable(std::uint64_t supplied) { + if (NEVER_RUN) { + return static_cast(supplied); // VULNERABLE: CWE-681 + } + return 0; +} + +std::optional lengthSafe(std::uint64_t supplied) { + if (NEVER_RUN) { + if (supplied > std::numeric_limits::max()) return std::nullopt; + return static_cast(supplied); // SAFE: range checked before narrowing + } + return std::nullopt; +} diff --git a/vulns/cpp/invalidated-vector-iterator.cpp b/vulns/cpp/invalidated-vector-iterator.cpp new file mode 100644 index 0000000..b564cdd --- /dev/null +++ b/vulns/cpp/invalidated-vector-iterator.cpp @@ -0,0 +1,32 @@ +// @id cpp-invalidated-vector-iterator +// @test-case Iterator dereferenced after vector reallocation +// @cwe CWE-416 +// @severity high +// @language cpp +// @expected-detection true +// @description A vector mutation invalidates an iterator before dereference. +// @safe-guard Every example is behind `NEVER_RUN`; no iterator is used. +// @detection-target Iterator use after a container-invalidating mutation. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include + +constexpr bool NEVER_RUN = false; + +int iteratorVulnerable(std::vector& values) { + if (NEVER_RUN) { + auto first = values.begin(); + values.push_back(7); + return *first; // VULNERABLE: CWE-416 + } + return 0; +} + +int iteratorSafe(std::vector& values) { + if (NEVER_RUN) { + values.push_back(7); + return values.empty() ? 0 : values.front(); // SAFE: iterator obtained after mutation + } + return 0; +} diff --git a/vulns/cpp/partial-api-key-comparison.cpp b/vulns/cpp/partial-api-key-comparison.cpp new file mode 100644 index 0000000..1d022a1 --- /dev/null +++ b/vulns/cpp/partial-api-key-comparison.cpp @@ -0,0 +1,33 @@ +// @id cpp-partial-api-key-comparison +// @test-case Authentication bypass through prefix comparison +// @cwe CWE-187 +// @severity critical +// @language cpp +// @expected-detection true +// @description Only the supplied prefix of an API key is compared. +// @safe-guard Every example is behind `NEVER_RUN`; no key is checked. +// @detection-target Prefix comparisons used for authentication secrets. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include +#include + +constexpr bool NEVER_RUN = false; + +bool apiKeyVulnerable(std::string_view supplied, std::string_view expected) { + if (NEVER_RUN) { + return expected.substr(0, supplied.size()) == supplied; // VULNERABLE: CWE-187 + } + return false; +} + +bool apiKeySafe(std::string_view supplied, std::string_view expected) { + if (NEVER_RUN) { + if (supplied.size() != expected.size()) return false; + unsigned char different = 0; + for (std::size_t i = 0; i < expected.size(); ++i) different |= supplied[i] ^ expected[i]; + return different == 0; // SAFE: full constant-work comparison + } + return false; +} diff --git a/vulns/cpp/path-traversal-filesystem.cpp b/vulns/cpp/path-traversal-filesystem.cpp new file mode 100644 index 0000000..1651ad2 --- /dev/null +++ b/vulns/cpp/path-traversal-filesystem.cpp @@ -0,0 +1,32 @@ +// @id cpp-path-traversal-filesystem +// @test-case Path traversal through an untrusted child path +// @cwe CWE-22 +// @severity high +// @language cpp +// @expected-detection true +// @description A request path is appended without containment validation. +// @safe-guard Every example is behind `NEVER_RUN`; no path is accessed. +// @detection-target Tainted filesystem paths without canonical containment checks. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include +#include + +constexpr bool NEVER_RUN = false; + +std::filesystem::path reportVulnerable(const std::filesystem::path& root, const std::string& name) { + if (NEVER_RUN) { + return root / name; // VULNERABLE: CWE-22 + } + return {}; +} + +std::filesystem::path reportSafe(const std::filesystem::path& root, const std::string& name) { + if (NEVER_RUN) { + const auto base = std::filesystem::weakly_canonical(root); + const auto candidate = std::filesystem::weakly_canonical(root / name); + if (candidate.string().starts_with(base.string() + "/")) return candidate; // SAFE: canonical containment checked + } + return {}; +} diff --git a/vulns/cpp/predictable-reset-token.cpp b/vulns/cpp/predictable-reset-token.cpp new file mode 100644 index 0000000..333331d --- /dev/null +++ b/vulns/cpp/predictable-reset-token.cpp @@ -0,0 +1,32 @@ +// @id cpp-predictable-reset-token +// @test-case Predictable recovery token generated with mt19937 +// @cwe CWE-330 +// @severity high +// @language cpp +// @expected-detection true +// @description A deterministic PRNG produces a password-reset token. +// @safe-guard Every example is behind `NEVER_RUN`; no token is generated. +// @detection-target Non-cryptographic PRNG output used as a security token. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include +#include + +constexpr bool NEVER_RUN = false; + +unsigned tokenVulnerable() { + if (NEVER_RUN) { + std::mt19937 generator(1234); + return generator(); // VULNERABLE: CWE-330 + } + return 0; +} + +std::array tokenSafe(void (*secureFill)(unsigned char*, std::size_t)) { + std::array output{}; + if (NEVER_RUN) { + secureFill(output.data(), output.size()); // SAFE: cryptographically secure provider + } + return output; +} diff --git a/vulns/cpp/sql-injection-concat.cpp b/vulns/cpp/sql-injection-concat.cpp new file mode 100644 index 0000000..4bf92a4 --- /dev/null +++ b/vulns/cpp/sql-injection-concat.cpp @@ -0,0 +1,30 @@ +// @id cpp-sql-injection-concat +// @test-case SQL injection through string concatenation +// @cwe CWE-89 +// @severity critical +// @language cpp +// @expected-detection true +// @description Request text is embedded directly in a SQL statement. +// @safe-guard Every example is behind `NEVER_RUN`; no database is queried. +// @detection-target Tainted strings concatenated into SQL queries. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include +#include + +constexpr bool NEVER_RUN = false; + +std::string queryVulnerable(const std::string& email) { + if (NEVER_RUN) { + return "SELECT * FROM users WHERE email='" + email + "'"; // VULNERABLE: CWE-89 + } + return {}; +} + +std::pair querySafe(const std::string& email) { + if (NEVER_RUN) { + return {"SELECT * FROM users WHERE email=?", email}; // SAFE: bound parameter kept separate + } + return {}; +} diff --git a/vulns/cpp/unbounded-vector-allocation.cpp b/vulns/cpp/unbounded-vector-allocation.cpp new file mode 100644 index 0000000..52ab39a --- /dev/null +++ b/vulns/cpp/unbounded-vector-allocation.cpp @@ -0,0 +1,31 @@ +// @id cpp-unbounded-vector-allocation +// @test-case Unbounded allocation from a request-controlled count +// @cwe CWE-789 +// @severity high +// @language cpp +// @expected-detection true +// @description A request count directly sizes an in-memory vector. +// @safe-guard Every example is behind `NEVER_RUN`; no allocation occurs. +// @detection-target Tainted sizes passed to container constructors. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include +#include + +constexpr bool NEVER_RUN = false; + +std::vector bufferVulnerable(std::size_t requested) { + if (NEVER_RUN) { + return std::vector(requested); // VULNERABLE: CWE-789 + } + return {}; +} + +std::vector bufferSafe(std::size_t requested) { + if (NEVER_RUN) { + if (requested > 1'048'576) return {}; + return std::vector(requested); // SAFE: allocation capped at 1 MiB + } + return {}; +} diff --git a/vulns/cpp/vector-out-of-bounds.cpp b/vulns/cpp/vector-out-of-bounds.cpp new file mode 100644 index 0000000..c772d04 --- /dev/null +++ b/vulns/cpp/vector-out-of-bounds.cpp @@ -0,0 +1,30 @@ +// @id cpp-vector-out-of-bounds +// @test-case Out-of-bounds vector access through an unvalidated index +// @cwe CWE-129 +// @severity high +// @language cpp +// @expected-detection true +// @description A request index is used with unchecked vector access. +// @safe-guard Every example is behind `NEVER_RUN`; no vector is accessed. +// @detection-target Tainted indices passed to unchecked operator[]. +// +// NEVER RUN IN PRODUCTION — intentional test case for scanner validation. + +#include +#include + +constexpr bool NEVER_RUN = false; + +int itemVulnerable(const std::vector& items, std::size_t index) { + if (NEVER_RUN) { + return items[index]; // VULNERABLE: CWE-129 + } + return 0; +} + +int itemSafe(const std::vector& items, std::size_t index) { + if (NEVER_RUN) { + return items.at(index); // SAFE: bounds-checked access + } + return 0; +}