attackmap-analyzer-cpp 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. attackmap_analyzer_cpp-0.2.0/LICENSE +21 -0
  2. attackmap_analyzer_cpp-0.2.0/PKG-INFO +170 -0
  3. attackmap_analyzer_cpp-0.2.0/README.md +131 -0
  4. attackmap_analyzer_cpp-0.2.0/pyproject.toml +74 -0
  5. attackmap_analyzer_cpp-0.2.0/setup.cfg +4 -0
  6. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp/__init__.py +3 -0
  7. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp/analyzer.py +601 -0
  8. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp/framework_routes.py +255 -0
  9. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp/native_deps.py +524 -0
  10. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp/native_sinks.py +1737 -0
  11. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp.egg-info/PKG-INFO +170 -0
  12. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp.egg-info/SOURCES.txt +21 -0
  13. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp.egg-info/dependency_links.txt +1 -0
  14. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp.egg-info/entry_points.txt +2 -0
  15. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp.egg-info/requires.txt +9 -0
  16. attackmap_analyzer_cpp-0.2.0/src/attackmap_analyzer_cpp.egg-info/top_level.txt +1 -0
  17. attackmap_analyzer_cpp-0.2.0/tests/test_cpp_analyzer.py +713 -0
  18. attackmap_analyzer_cpp-0.2.0/tests/test_framework_routes.py +220 -0
  19. attackmap_analyzer_cpp-0.2.0/tests/test_native_deps.py +317 -0
  20. attackmap_analyzer_cpp-0.2.0/tests/test_native_deps_regex_linear.py +13 -0
  21. attackmap_analyzer_cpp-0.2.0/tests/test_native_regex_linear.py +24 -0
  22. attackmap_analyzer_cpp-0.2.0/tests/test_native_sinks.py +568 -0
  23. attackmap_analyzer_cpp-0.2.0/tests/test_native_sinks_cpp.py +123 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matthew Davis and AttackMap Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: attackmap-analyzer-cpp
3
+ Version: 0.2.0
4
+ Summary: C++ ecosystem analyzer plugin for AttackMap (Crow, Pistache, Drogon, cpprestsdk; libcurl/cpr; OpenSSL/Botan/libsodium/Crypto++; libpqxx/mongocxx/redis-plus-plus).
5
+ Author: AttackMap Contributors
6
+ Author-email: Matthew Davis <mlaify@mlaify.io>
7
+ Maintainer-email: Matthew Davis <mlaify@mlaify.io>
8
+ License-Expression: MIT
9
+ Project-URL: Homepage, https://github.com/mlaify/attackmap-analyzer-cpp
10
+ Project-URL: Repository, https://github.com/mlaify/attackmap-analyzer-cpp
11
+ Project-URL: Issues, https://github.com/mlaify/attackmap-analyzer-cpp/issues
12
+ Project-URL: Changelog, https://github.com/mlaify/attackmap-analyzer-cpp/blob/main/CHANGELOG.md
13
+ Project-URL: AttackMap core, https://github.com/mlaify/AttackMap
14
+ Keywords: security,static-analysis,appsec,defensive-security,attackmap,attackmap-plugin
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Information Technology
18
+ Classifier: Intended Audience :: System Administrators
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3 :: Only
25
+ Classifier: Topic :: Security
26
+ Classifier: Topic :: Software Development :: Quality Assurance
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.11
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENSE
31
+ Requires-Dist: pydantic>=2.7.0
32
+ Provides-Extra: core
33
+ Requires-Dist: attackmap>=0.6.0; extra == "core"
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
36
+ Requires-Dist: build>=1.2.0; extra == "dev"
37
+ Requires-Dist: attackmap>=0.6.0; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # attackmap-analyzer-cpp
41
+
42
+ > [!IMPORTANT]
43
+ > **Active development, slow pace.** AttackMap is under active development, but
44
+ > progress may be slow until more contributors or co-maintainers join. Help is
45
+ > very welcome with the core engine, an analyzer, the macOS app, or the docs —
46
+ > see [CONTRIBUTING.md](CONTRIBUTING.md) or open an issue on
47
+ > [mlaify/AttackMap](https://github.com/mlaify/AttackMap/issues) to say hello.
48
+ > Security reports are still welcome at [security@mlaify.io](mailto:security@mlaify.io).
49
+
50
+ C++ ecosystem analyzer for [AttackMap](https://github.com/mlaify/AttackMap).
51
+
52
+ This analyzer extracts structured signals from C++ source trees (`.cpp`, `.cc`, `.cxx`, `.hpp`, `.hxx`, `.ipp`, `.tpp`):
53
+
54
+ - **Web frameworks** — Crow (`CROW_ROUTE` macro with `.methods("POST"_method, ...)` chain), Pistache (`Routes::Get/Post/...`), Drogon (`registerHandler` with explicit method list, `ADD_METHOD_TO`, `METHOD_ADD` relative to the controller's `/namespace/Class` path, and `PATH_ADD`), cpprestsdk / Casablanca (`http_listener("https://...")` URL extraction), cpp-httplib (`svr.Get/Post/Put/Patch/Delete/Options` on a declared `httplib::Server`/`SSLServer`, regex paths kept as written), oat++ (`ENDPOINT`/`ENDPOINT_ASYNC`; an `AUTHORIZATION(...)` parameter sets `Route.auth = required`), restbed (`set_path`/`set_paths` on a `Resource` joined with its `set_method_handler` methods; `set_authentication_handler` on the resource or service sets `Route.auth = required`)
55
+ - **HTTP clients (external calls)** — libcurl (`CURLOPT_URL` literals), cpr (`cpr::Get(cpr::Url{"..."})`, `cpr::Post(...)`), cpprestsdk (`web::http::client::http_client(URL)`)
56
+ - **Databases** — libpqxx (`pqxx::connection`, `pqxx::work`), MySQL X DevAPI (`mysqlx::Session`), mongocxx (`mongocxx::client` + `mongocxx::uri`), redis-plus-plus (`sw::redis::Redis`), SOCI (`soci::session`), sqlite_orm (`sqlite_orm::make_storage`)
57
+ - **Auth/crypto** — OpenSSL (TLS / EVP / RAND), Botan (`TLS::Server`, `Cipher_Mode`, `PKCS5_PBKDF2`, `Scrypt`), libsodium (`crypto_pwhash` → argon2, `crypto_secretbox`, AEAD primitives), Crypto++ (`CryptoPP::AES`, `CryptoPP::SHA256`, `CryptoPP::Argon2`, `CryptoPP::HMAC`), JWT C++ libraries (`jwt::create`, `jwt::decode`, `jwt::verify`)
58
+ - **Secrets** — `std::getenv`, `getenv` with secret-shaped names
59
+ - **Service hints** — project name from `CMakeLists.txt` `project(NAME ...)` declaration
60
+ - **Dependencies (SBOM)** — vcpkg (`vcpkg.json`), Conan (`conanfile.txt`, `conanfile.py`, `conan.lock`) and CMake (`FetchContent_Declare`, `ExternalProject_Add`, `CPMAddPackage`, `find_package`), so `--cve` reaches native dependencies; unpinned CMake downloads are reported as supply-chain issues (see [Dependencies](#dependencies))
61
+ - **Memory-safety and command-exec sinks** — unbounded copies, non-constant printf formats, `system`/`popen`/`exec*`, input-controlled `memcpy` lengths and allocation-size overflows, tied to request and input sources in the same function (see [Native sinks](#native-sinks))
62
+
63
+ All emissions populate AttackMap's Signal v2 fields (line numbers + evidence snippets + confidence) so downstream insights can cite `path/to/file.cpp:NN`.
64
+
65
+ ## Install
66
+
67
+ ```bash
68
+ uv tool install attackmap --with attackmap-analyzer-cpp \
69
+ --index mlaify=https://pkg.mlaify.io/pypi/simple/ --index-strategy first-index
70
+ # or from source:
71
+ pip install git+https://github.com/mlaify/attackmap-analyzer-cpp.git
72
+ ```
73
+
74
+ The analyzer is registered with AttackMap via the `attackmap.analyzers` entry-point group.
75
+
76
+ ## Usage with AttackMap
77
+
78
+ This analyzer is **experimental and opt-in** (`enabled_by_default=False`): installing it does not make it run on every scan. Select it explicitly:
79
+
80
+ ```bash
81
+ attackmap analyze /path/to/cpp/repo -m cpp
82
+ ```
83
+
84
+ ## Detection
85
+
86
+ `detect()` returns true when any `.cpp`, `.cc`, `.cxx`, `.hpp`, `.hxx`, `.ipp`, or `.tpp` file is present, or `.h` headers in a CMake project that enables CXX (one walk), ignoring `_deps/`, `third_party/`, `external/`, `.cache/`, `Debug/`, `Release/` and AttackMap's shared skip list (`build/`, `out/`, `vendor/`, `node_modules/`, `.git/`, ...). Skip directories are matched only *inside* the repo, so a checkout under e.g. `/build/...` is still scanned. Files are walked with `attackmap.sdk.iter_repo_files`, which does not follow symlinks out of the repo, and read with `read_source`, which falls back to cp1252/latin-1 for legacy-encoded sources.
87
+
88
+ ### `.h` ownership
89
+
90
+ `.h` ownership is decided per repo, by the same rule in both the C and C++ analyzers, so exactly one of them analyzes each header whichever of `-m c` / `-m cpp` is selected:
91
+
92
+ - `.h` belongs to **C++** if the repo has any C++ source or header (`.cpp`, `.cc`, `.cxx`, `.hpp`, `.hxx`, `.ipp`, `.tpp`) or a `CMakeLists.txt` that enables CXX (`project(... CXX ...)`, `enable_language(CXX)` or `CMAKE_CXX_STANDARD`). A bare `project(foo)` doesn't count.
93
+ - Otherwise `.h` belongs to **C**.
94
+ - Files under directories either plugin prunes (`build/`, `third_party/`, `Debug/`, `Release/`, ...) are never markers.
95
+
96
+ In a mixed repo, `.c` files go to C, and `.cpp`/`.hpp`/... plus `.h` go to C++. Running only one of the two analyzers on a mixed repo leaves the other's files unanalyzed. It never double-counts them.
97
+
98
+ A repo with only `.c` and `.h` files (and no C++ CMake) is not picked up here.
99
+
100
+ ## Dependencies
101
+
102
+ `native_deps.py` emits `DependencyHint`s (mlaify/AttackMap#191). The module is shared verbatim with attackmap-analyzer-c. Core dedupes identical records when both analyzers run.
103
+
104
+ | Source | Ecosystem | Version | `dev` |
105
+ |---|---|---|---|
106
+ | `vcpkg.json` `dependencies` | `vcpkg` | `overrides` pin (`resolved=True`), else `>=` from `version>=`, else empty (the `builtin-baseline` decides; it's recorded in the evidence) | `"host": true` |
107
+ | `conanfile.txt` `[requires]` / `[tool_requires]` / `[build_requires]` / `[test_requires]` | `conan` | as written (`1.2.13`, `[>=1.80 <2.0]`) | tool/build/test sections |
108
+ | `conanfile.py` `requires = ...`, `self.requires("x/1.0")` and the tool/build/test forms (parsed with `ast`, never executed; comments and docstrings don't count) | `conan` | as written | tool/build/test |
109
+ | `conan.lock` (Conan 2 lists and Conan 1.x `graph_lock.nodes`) | `conan` | exact (`resolved=True`) | `build_requires`, build-context nodes |
110
+ | CMake `FetchContent_Declare` / `ExternalProject_Add` / `CPMAddPackage` | `cmake` | `GIT_TAG`, a CPM version, or a version in the archive URL | test/example manifests, test frameworks |
111
+ | CMake `find_package(Name [version] [EXACT])` | `cmake` | `>=version` / `==version`, ranges `1.2...<2.0` → `>=1.2,<2.0` | test/example manifests, test frameworks |
112
+
113
+ `cmake` is inventory-only in core: it has no OSV database, so `--cve` reports those dependencies as not CVE-checked. `conan` and `vcpkg` map to OSV ConanCenter and vcpkg. Build tools and CMake modules (`Threads`, `PkgConfig`, `Python3`, `Git`...) aren't dependencies.
114
+
115
+ **Unpinned downloads** are `SupplyChainIssue(kind="mutable_vcs_dependency")` records:
116
+
117
+ | Download | Severity |
118
+ |---|---|
119
+ | git download with no `GIT_TAG`, or a `GIT_TAG` that names a branch (`main`, `master`, `develop`...) | medium |
120
+ | any other tag or a short SHA | low |
121
+ | a full 40-hex commit SHA | not flagged |
122
+ | `URL` download without `URL_HASH`/`URL_MD5` | medium |
123
+
124
+ Values that are CMake variables (`${X}`) are never flagged. Comments, quoted strings and bracket arguments (`[[ ]]`) never contain commands. Malformed or deeply nested manifests are skipped.
125
+
126
+ ## Coverage notes
127
+
128
+ - **Marked experimental**: like the C analyzer, regex coverage of C++ has more false-positive risk than language-specific analyzers with strict imports. Confidence tiering is the primary defense (0.9 for hash-class auth primitives, 0.85 for canonical TLS / cipher / JWT API hits, 0.6 for keyword sweeps).
129
+ - **Crow `.methods("X"_method)` chains**: when present, the route emits one Route per method in the chain. When absent, the route emits with method `ANY`.
130
+ - **Drogon `registerHandler`, `ADD_METHOD_TO`, `METHOD_ADD` and `PATH_ADD`**: all shapes are extracted, including the `{Drogon::Get, Drogon::Post}` initializer-list form and multi-method macros (`Put, Patch, "AuthFilter"`), which produce one Route per method (`ANY` when none is given). `METHOD_ADD` paths are prefixed with the controller's class path, e.g. `api::v1::User` → `/api/v1/User/{id}`.
131
+ - **cpp-httplib / oat++ / restbed routes** (`framework_routes.py`): a `.Get("/x")` call counts only on a receiver declared as a `Server` in the same file, so `httplib::Client` calls aren't routes. Servers from `std::make_unique/make_shared<httplib::Server>` count. Commented-out code and text inside string literals are ignored. A restbed `set_authentication_handler` guards its own `Resource` (even one with a non-literal path), or every resource when called on a declared `Service`; on any other receiver the route's auth stays `unknown`. oat++ and restbed paths are rooted (`users` → `/users`). A router-level prefix (oat++ `addController`, restbed `set_root`) isn't applied. Boost.Beast has no route registry (handlers compare `req.target()` by hand) and isn't extracted yet.
132
+ - **cpprestsdk listener routes**: only the path component of the listener's URL is extracted as a Route. Per-method handlers (`listener.support(methods::GET, ...)`) are not separately emitted — that would require tracking the listener's lifetime.
133
+ - **C++ shares vocabulary with C** (libcurl, OpenSSL, libsodium). Those patterns are duplicated here so a pure-C++ project without the C analyzer installed still gets full coverage. AttackMap's overlay deduplication handles double-firing.
134
+ - **Pure-template / header-only ORM** (sqlite_orm, sqlpp11): only basic detection via headers and `make_storage`; column-level extraction is out of scope.
135
+
136
+ ## Native sinks
137
+
138
+ The analyzer emits function-local C/C++ weaknesses as `CodeWeakness` records (mlaify/AttackMap#189). Core turns them into findings, one per severity tier, with CWE/OWASP/ASVS ids.
139
+
140
+ | Kind (rule id) | Fires on | Not reported |
141
+ |---|---|---|
142
+ | `unbounded_copy` (CWE-120) | `gets`; `scanf`/`fscanf` `%s`/`%[` without a width; `strcpy`/`strcat`/`sprintf` `%s` whose source is traced to an input | a literal or untraced source; a source with an upper-bound check first (`strlen(s) < sizeof dst`, directly or through a length alias; `> 0` / `< 0` checks don't count); a sized destination (`malloc(strlen(s) + 1)`); `%.Ns`; a bounded read (`fgets`, `recv`, `%255s`) copied into an array at least as large |
143
+ | `format_string` (CWE-134) | a printf-family format (`printf`, `fprintf`, `sprintf`, `snprintf`, `syslog`, civetweb/mongoose `mg_printf`, `mg_http_reply`) that is not a constant | literals, `#define` and `static const` strings, literal ternaries, gettext `_()`, variadic and `va_list` wrappers forwarding their own `fmt`, untraced function results |
144
+ | `native_command_exec` (CWE-78) | `system`/`popen` with a non-constant command; `exec*` with an input-derived argument or a non-constant `sh -c` line | constant commands, `NULL`, an untraced `execv(path, args)` without a shell |
145
+ | `tainted_copy_length` (CWE-805) | `memcpy`/`memmove`/`strncpy`/`strncat` length traced to an input | lengths with an upper-bound check (`n > sizeof dst`, `ck_assert_int_lt(n, cap)`) or a clamp (`MIN`, `n > cap ? cap : n`, also inside the call) first, a destination allocated from the same length, a byte count returned by `recv`/`read` |
146
+ | `alloc_size_overflow` (CWE-680) | `malloc`/`realloc`/`alloca`/`new T[]` of a product with an input-derived factor | `calloc`, sums, untraced factors, a factor with an upper-bound check first or a `SIZE_MAX`/`__builtin_mul_overflow` guard |
147
+
148
+ **Sources**, traced per function through assignments, out-parameters and copy functions:
149
+
150
+ - **Request input:** parameters of a framework request type (bare names such as `Request`, `Rest::Request`, `request<...>`, `shared_ptr<Session>` count only in a file that uses that framework; `struct Request` never does) (cpp-httplib `Request`, Crow `crow::request`, Drogon `HttpRequestPtr`, Pistache `Rest::Request`, Boost.Beast `http::request<...>`, restbed `shared_ptr<Session>`/`shared_ptr<const Request>`); oat++ `ENDPOINT` `QUERY`/`PATH`/`HEADER`/`BODY_*` parameters; accessors such as `get_param_value` and `url_params` (other getters count only on a request-typed variable); and the C framework sources (civetweb, mongoose, libmicrohttpd).
151
+ - **Network input:** `recv`/`recvfrom`/`SSL_read`/`BIO_read`/`mbedtls_ssl_read`, and `read` on a socket-named descriptor.
152
+ - **Local input:** stdin (`fgets(..., stdin)`, `scanf`, `std::cin >>`, `std::getline(std::cin, ...)`), `argv`/`optarg` in a function that takes `argv`, `getenv`, and file reads.
153
+
154
+ Taint doesn't flow through calls to unknown functions (`gethostbyname(argv[1])`). A reassignment to a literal (`u = "default"`) clears it. A copy between two declared arrays where the destination is at least as large isn't reported.
155
+
156
+ **Severity and confidence:**
157
+
158
+ | Input traced to the sink | Severity / confidence |
159
+ |---|---|
160
+ | request or network | high / high |
161
+ | stdin, argv, env or file | medium / medium |
162
+ | none (only non-constant formats and commands are reported) | low / low |
163
+
164
+ Code under `examples/`, `demo/`, `samples/`, `contrib/` or `docs/` is capped at low. Test, fuzz and benchmark code (`test/`, `tests/`, `unittest/`, `fuzztest/`, `*_test.c`, ...) is skipped. `ATTACKMAP_INCLUDE_TESTS=1` scans it.
165
+
166
+ `native_sinks.py` is shared verbatim with attackmap-analyzer-c. Each file is analyzed by one of the two plugins (see `.h` ownership), so a weakness is never reported twice. It needs an `attackmap` core that exports `CodeWeakness` from `attackmap.sdk`. With an older core the analyzer still loads and emits no weaknesses.
167
+
168
+ ## License
169
+
170
+ MIT
@@ -0,0 +1,131 @@
1
+ # attackmap-analyzer-cpp
2
+
3
+ > [!IMPORTANT]
4
+ > **Active development, slow pace.** AttackMap is under active development, but
5
+ > progress may be slow until more contributors or co-maintainers join. Help is
6
+ > very welcome with the core engine, an analyzer, the macOS app, or the docs —
7
+ > see [CONTRIBUTING.md](CONTRIBUTING.md) or open an issue on
8
+ > [mlaify/AttackMap](https://github.com/mlaify/AttackMap/issues) to say hello.
9
+ > Security reports are still welcome at [security@mlaify.io](mailto:security@mlaify.io).
10
+
11
+ C++ ecosystem analyzer for [AttackMap](https://github.com/mlaify/AttackMap).
12
+
13
+ This analyzer extracts structured signals from C++ source trees (`.cpp`, `.cc`, `.cxx`, `.hpp`, `.hxx`, `.ipp`, `.tpp`):
14
+
15
+ - **Web frameworks** — Crow (`CROW_ROUTE` macro with `.methods("POST"_method, ...)` chain), Pistache (`Routes::Get/Post/...`), Drogon (`registerHandler` with explicit method list, `ADD_METHOD_TO`, `METHOD_ADD` relative to the controller's `/namespace/Class` path, and `PATH_ADD`), cpprestsdk / Casablanca (`http_listener("https://...")` URL extraction), cpp-httplib (`svr.Get/Post/Put/Patch/Delete/Options` on a declared `httplib::Server`/`SSLServer`, regex paths kept as written), oat++ (`ENDPOINT`/`ENDPOINT_ASYNC`; an `AUTHORIZATION(...)` parameter sets `Route.auth = required`), restbed (`set_path`/`set_paths` on a `Resource` joined with its `set_method_handler` methods; `set_authentication_handler` on the resource or service sets `Route.auth = required`)
16
+ - **HTTP clients (external calls)** — libcurl (`CURLOPT_URL` literals), cpr (`cpr::Get(cpr::Url{"..."})`, `cpr::Post(...)`), cpprestsdk (`web::http::client::http_client(URL)`)
17
+ - **Databases** — libpqxx (`pqxx::connection`, `pqxx::work`), MySQL X DevAPI (`mysqlx::Session`), mongocxx (`mongocxx::client` + `mongocxx::uri`), redis-plus-plus (`sw::redis::Redis`), SOCI (`soci::session`), sqlite_orm (`sqlite_orm::make_storage`)
18
+ - **Auth/crypto** — OpenSSL (TLS / EVP / RAND), Botan (`TLS::Server`, `Cipher_Mode`, `PKCS5_PBKDF2`, `Scrypt`), libsodium (`crypto_pwhash` → argon2, `crypto_secretbox`, AEAD primitives), Crypto++ (`CryptoPP::AES`, `CryptoPP::SHA256`, `CryptoPP::Argon2`, `CryptoPP::HMAC`), JWT C++ libraries (`jwt::create`, `jwt::decode`, `jwt::verify`)
19
+ - **Secrets** — `std::getenv`, `getenv` with secret-shaped names
20
+ - **Service hints** — project name from `CMakeLists.txt` `project(NAME ...)` declaration
21
+ - **Dependencies (SBOM)** — vcpkg (`vcpkg.json`), Conan (`conanfile.txt`, `conanfile.py`, `conan.lock`) and CMake (`FetchContent_Declare`, `ExternalProject_Add`, `CPMAddPackage`, `find_package`), so `--cve` reaches native dependencies; unpinned CMake downloads are reported as supply-chain issues (see [Dependencies](#dependencies))
22
+ - **Memory-safety and command-exec sinks** — unbounded copies, non-constant printf formats, `system`/`popen`/`exec*`, input-controlled `memcpy` lengths and allocation-size overflows, tied to request and input sources in the same function (see [Native sinks](#native-sinks))
23
+
24
+ All emissions populate AttackMap's Signal v2 fields (line numbers + evidence snippets + confidence) so downstream insights can cite `path/to/file.cpp:NN`.
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ uv tool install attackmap --with attackmap-analyzer-cpp \
30
+ --index mlaify=https://pkg.mlaify.io/pypi/simple/ --index-strategy first-index
31
+ # or from source:
32
+ pip install git+https://github.com/mlaify/attackmap-analyzer-cpp.git
33
+ ```
34
+
35
+ The analyzer is registered with AttackMap via the `attackmap.analyzers` entry-point group.
36
+
37
+ ## Usage with AttackMap
38
+
39
+ This analyzer is **experimental and opt-in** (`enabled_by_default=False`): installing it does not make it run on every scan. Select it explicitly:
40
+
41
+ ```bash
42
+ attackmap analyze /path/to/cpp/repo -m cpp
43
+ ```
44
+
45
+ ## Detection
46
+
47
+ `detect()` returns true when any `.cpp`, `.cc`, `.cxx`, `.hpp`, `.hxx`, `.ipp`, or `.tpp` file is present, or `.h` headers in a CMake project that enables CXX (one walk), ignoring `_deps/`, `third_party/`, `external/`, `.cache/`, `Debug/`, `Release/` and AttackMap's shared skip list (`build/`, `out/`, `vendor/`, `node_modules/`, `.git/`, ...). Skip directories are matched only *inside* the repo, so a checkout under e.g. `/build/...` is still scanned. Files are walked with `attackmap.sdk.iter_repo_files`, which does not follow symlinks out of the repo, and read with `read_source`, which falls back to cp1252/latin-1 for legacy-encoded sources.
48
+
49
+ ### `.h` ownership
50
+
51
+ `.h` ownership is decided per repo, by the same rule in both the C and C++ analyzers, so exactly one of them analyzes each header whichever of `-m c` / `-m cpp` is selected:
52
+
53
+ - `.h` belongs to **C++** if the repo has any C++ source or header (`.cpp`, `.cc`, `.cxx`, `.hpp`, `.hxx`, `.ipp`, `.tpp`) or a `CMakeLists.txt` that enables CXX (`project(... CXX ...)`, `enable_language(CXX)` or `CMAKE_CXX_STANDARD`). A bare `project(foo)` doesn't count.
54
+ - Otherwise `.h` belongs to **C**.
55
+ - Files under directories either plugin prunes (`build/`, `third_party/`, `Debug/`, `Release/`, ...) are never markers.
56
+
57
+ In a mixed repo, `.c` files go to C, and `.cpp`/`.hpp`/... plus `.h` go to C++. Running only one of the two analyzers on a mixed repo leaves the other's files unanalyzed. It never double-counts them.
58
+
59
+ A repo with only `.c` and `.h` files (and no C++ CMake) is not picked up here.
60
+
61
+ ## Dependencies
62
+
63
+ `native_deps.py` emits `DependencyHint`s (mlaify/AttackMap#191). The module is shared verbatim with attackmap-analyzer-c. Core dedupes identical records when both analyzers run.
64
+
65
+ | Source | Ecosystem | Version | `dev` |
66
+ |---|---|---|---|
67
+ | `vcpkg.json` `dependencies` | `vcpkg` | `overrides` pin (`resolved=True`), else `>=` from `version>=`, else empty (the `builtin-baseline` decides; it's recorded in the evidence) | `"host": true` |
68
+ | `conanfile.txt` `[requires]` / `[tool_requires]` / `[build_requires]` / `[test_requires]` | `conan` | as written (`1.2.13`, `[>=1.80 <2.0]`) | tool/build/test sections |
69
+ | `conanfile.py` `requires = ...`, `self.requires("x/1.0")` and the tool/build/test forms (parsed with `ast`, never executed; comments and docstrings don't count) | `conan` | as written | tool/build/test |
70
+ | `conan.lock` (Conan 2 lists and Conan 1.x `graph_lock.nodes`) | `conan` | exact (`resolved=True`) | `build_requires`, build-context nodes |
71
+ | CMake `FetchContent_Declare` / `ExternalProject_Add` / `CPMAddPackage` | `cmake` | `GIT_TAG`, a CPM version, or a version in the archive URL | test/example manifests, test frameworks |
72
+ | CMake `find_package(Name [version] [EXACT])` | `cmake` | `>=version` / `==version`, ranges `1.2...<2.0` → `>=1.2,<2.0` | test/example manifests, test frameworks |
73
+
74
+ `cmake` is inventory-only in core: it has no OSV database, so `--cve` reports those dependencies as not CVE-checked. `conan` and `vcpkg` map to OSV ConanCenter and vcpkg. Build tools and CMake modules (`Threads`, `PkgConfig`, `Python3`, `Git`...) aren't dependencies.
75
+
76
+ **Unpinned downloads** are `SupplyChainIssue(kind="mutable_vcs_dependency")` records:
77
+
78
+ | Download | Severity |
79
+ |---|---|
80
+ | git download with no `GIT_TAG`, or a `GIT_TAG` that names a branch (`main`, `master`, `develop`...) | medium |
81
+ | any other tag or a short SHA | low |
82
+ | a full 40-hex commit SHA | not flagged |
83
+ | `URL` download without `URL_HASH`/`URL_MD5` | medium |
84
+
85
+ Values that are CMake variables (`${X}`) are never flagged. Comments, quoted strings and bracket arguments (`[[ ]]`) never contain commands. Malformed or deeply nested manifests are skipped.
86
+
87
+ ## Coverage notes
88
+
89
+ - **Marked experimental**: like the C analyzer, regex coverage of C++ has more false-positive risk than language-specific analyzers with strict imports. Confidence tiering is the primary defense (0.9 for hash-class auth primitives, 0.85 for canonical TLS / cipher / JWT API hits, 0.6 for keyword sweeps).
90
+ - **Crow `.methods("X"_method)` chains**: when present, the route emits one Route per method in the chain. When absent, the route emits with method `ANY`.
91
+ - **Drogon `registerHandler`, `ADD_METHOD_TO`, `METHOD_ADD` and `PATH_ADD`**: all shapes are extracted, including the `{Drogon::Get, Drogon::Post}` initializer-list form and multi-method macros (`Put, Patch, "AuthFilter"`), which produce one Route per method (`ANY` when none is given). `METHOD_ADD` paths are prefixed with the controller's class path, e.g. `api::v1::User` → `/api/v1/User/{id}`.
92
+ - **cpp-httplib / oat++ / restbed routes** (`framework_routes.py`): a `.Get("/x")` call counts only on a receiver declared as a `Server` in the same file, so `httplib::Client` calls aren't routes. Servers from `std::make_unique/make_shared<httplib::Server>` count. Commented-out code and text inside string literals are ignored. A restbed `set_authentication_handler` guards its own `Resource` (even one with a non-literal path), or every resource when called on a declared `Service`; on any other receiver the route's auth stays `unknown`. oat++ and restbed paths are rooted (`users` → `/users`). A router-level prefix (oat++ `addController`, restbed `set_root`) isn't applied. Boost.Beast has no route registry (handlers compare `req.target()` by hand) and isn't extracted yet.
93
+ - **cpprestsdk listener routes**: only the path component of the listener's URL is extracted as a Route. Per-method handlers (`listener.support(methods::GET, ...)`) are not separately emitted — that would require tracking the listener's lifetime.
94
+ - **C++ shares vocabulary with C** (libcurl, OpenSSL, libsodium). Those patterns are duplicated here so a pure-C++ project without the C analyzer installed still gets full coverage. AttackMap's overlay deduplication handles double-firing.
95
+ - **Pure-template / header-only ORM** (sqlite_orm, sqlpp11): only basic detection via headers and `make_storage`; column-level extraction is out of scope.
96
+
97
+ ## Native sinks
98
+
99
+ The analyzer emits function-local C/C++ weaknesses as `CodeWeakness` records (mlaify/AttackMap#189). Core turns them into findings, one per severity tier, with CWE/OWASP/ASVS ids.
100
+
101
+ | Kind (rule id) | Fires on | Not reported |
102
+ |---|---|---|
103
+ | `unbounded_copy` (CWE-120) | `gets`; `scanf`/`fscanf` `%s`/`%[` without a width; `strcpy`/`strcat`/`sprintf` `%s` whose source is traced to an input | a literal or untraced source; a source with an upper-bound check first (`strlen(s) < sizeof dst`, directly or through a length alias; `> 0` / `< 0` checks don't count); a sized destination (`malloc(strlen(s) + 1)`); `%.Ns`; a bounded read (`fgets`, `recv`, `%255s`) copied into an array at least as large |
104
+ | `format_string` (CWE-134) | a printf-family format (`printf`, `fprintf`, `sprintf`, `snprintf`, `syslog`, civetweb/mongoose `mg_printf`, `mg_http_reply`) that is not a constant | literals, `#define` and `static const` strings, literal ternaries, gettext `_()`, variadic and `va_list` wrappers forwarding their own `fmt`, untraced function results |
105
+ | `native_command_exec` (CWE-78) | `system`/`popen` with a non-constant command; `exec*` with an input-derived argument or a non-constant `sh -c` line | constant commands, `NULL`, an untraced `execv(path, args)` without a shell |
106
+ | `tainted_copy_length` (CWE-805) | `memcpy`/`memmove`/`strncpy`/`strncat` length traced to an input | lengths with an upper-bound check (`n > sizeof dst`, `ck_assert_int_lt(n, cap)`) or a clamp (`MIN`, `n > cap ? cap : n`, also inside the call) first, a destination allocated from the same length, a byte count returned by `recv`/`read` |
107
+ | `alloc_size_overflow` (CWE-680) | `malloc`/`realloc`/`alloca`/`new T[]` of a product with an input-derived factor | `calloc`, sums, untraced factors, a factor with an upper-bound check first or a `SIZE_MAX`/`__builtin_mul_overflow` guard |
108
+
109
+ **Sources**, traced per function through assignments, out-parameters and copy functions:
110
+
111
+ - **Request input:** parameters of a framework request type (bare names such as `Request`, `Rest::Request`, `request<...>`, `shared_ptr<Session>` count only in a file that uses that framework; `struct Request` never does) (cpp-httplib `Request`, Crow `crow::request`, Drogon `HttpRequestPtr`, Pistache `Rest::Request`, Boost.Beast `http::request<...>`, restbed `shared_ptr<Session>`/`shared_ptr<const Request>`); oat++ `ENDPOINT` `QUERY`/`PATH`/`HEADER`/`BODY_*` parameters; accessors such as `get_param_value` and `url_params` (other getters count only on a request-typed variable); and the C framework sources (civetweb, mongoose, libmicrohttpd).
112
+ - **Network input:** `recv`/`recvfrom`/`SSL_read`/`BIO_read`/`mbedtls_ssl_read`, and `read` on a socket-named descriptor.
113
+ - **Local input:** stdin (`fgets(..., stdin)`, `scanf`, `std::cin >>`, `std::getline(std::cin, ...)`), `argv`/`optarg` in a function that takes `argv`, `getenv`, and file reads.
114
+
115
+ Taint doesn't flow through calls to unknown functions (`gethostbyname(argv[1])`). A reassignment to a literal (`u = "default"`) clears it. A copy between two declared arrays where the destination is at least as large isn't reported.
116
+
117
+ **Severity and confidence:**
118
+
119
+ | Input traced to the sink | Severity / confidence |
120
+ |---|---|
121
+ | request or network | high / high |
122
+ | stdin, argv, env or file | medium / medium |
123
+ | none (only non-constant formats and commands are reported) | low / low |
124
+
125
+ Code under `examples/`, `demo/`, `samples/`, `contrib/` or `docs/` is capped at low. Test, fuzz and benchmark code (`test/`, `tests/`, `unittest/`, `fuzztest/`, `*_test.c`, ...) is skipped. `ATTACKMAP_INCLUDE_TESTS=1` scans it.
126
+
127
+ `native_sinks.py` is shared verbatim with attackmap-analyzer-c. Each file is analyzed by one of the two plugins (see `.h` ownership), so a weakness is never reported twice. It needs an `attackmap` core that exports `CodeWeakness` from `attackmap.sdk`. With an older core the analyzer still loads and emits no weaknesses.
128
+
129
+ ## License
130
+
131
+ MIT
@@ -0,0 +1,74 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "attackmap-analyzer-cpp"
7
+ version = "0.2.0"
8
+ description = "C++ ecosystem analyzer plugin for AttackMap (Crow, Pistache, Drogon, cpprestsdk; libcurl/cpr; OpenSSL/Botan/libsodium/Crypto++; libpqxx/mongocxx/redis-plus-plus)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ {name = "Matthew Davis", email = "mlaify@mlaify.io"},
15
+ {name = "AttackMap Contributors"},
16
+ ]
17
+ maintainers = [
18
+ {name = "Matthew Davis", email = "mlaify@mlaify.io"},
19
+ ]
20
+ keywords = [
21
+ "security",
22
+ "static-analysis",
23
+ "appsec",
24
+ "defensive-security",
25
+ "attackmap",
26
+ "attackmap-plugin",
27
+ ]
28
+ classifiers = [
29
+ "Development Status :: 4 - Beta",
30
+ "Intended Audience :: Developers",
31
+ "Intended Audience :: Information Technology",
32
+ "Intended Audience :: System Administrators",
33
+ "Operating System :: OS Independent",
34
+ "Programming Language :: Python :: 3",
35
+ "Programming Language :: Python :: 3.11",
36
+ "Programming Language :: Python :: 3.12",
37
+ "Programming Language :: Python :: 3.13",
38
+ "Programming Language :: Python :: 3 :: Only",
39
+ "Topic :: Security",
40
+ "Topic :: Software Development :: Quality Assurance",
41
+ "Typing :: Typed",
42
+ ]
43
+ dependencies = [
44
+ "pydantic>=2.7.0",
45
+ ]
46
+
47
+ [project.optional-dependencies]
48
+ core = [
49
+ "attackmap>=0.6.0",
50
+ ]
51
+ dev = [
52
+ "pytest>=8.0.0",
53
+ "build>=1.2.0",
54
+ "attackmap>=0.6.0",
55
+ ]
56
+
57
+ [project.urls]
58
+ Homepage = "https://github.com/mlaify/attackmap-analyzer-cpp"
59
+ Repository = "https://github.com/mlaify/attackmap-analyzer-cpp"
60
+ Issues = "https://github.com/mlaify/attackmap-analyzer-cpp/issues"
61
+ Changelog = "https://github.com/mlaify/attackmap-analyzer-cpp/blob/main/CHANGELOG.md"
62
+ "AttackMap core" = "https://github.com/mlaify/AttackMap"
63
+
64
+ [project.entry-points."attackmap.analyzers"]
65
+ cpp = "attackmap_analyzer_cpp:CppAnalyzer"
66
+
67
+ [tool.setuptools]
68
+ package-dir = {"" = "src"}
69
+
70
+ [tool.setuptools.packages.find]
71
+ where = ["src"]
72
+
73
+ [tool.pytest.ini_options]
74
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from .analyzer import CppAnalyzer
2
+
3
+ __all__ = ["CppAnalyzer"]