C/C++ Resource & Memory Review
Leaf-mode guard
If the task identifies your role as leaf-reviewer or sets
delegation-budget: 0, read and follow
references/validation-pass.md, validate only
the supplied candidates, return the verdicts to the caller, and stop before the
full workflow below.
Identify provable memory- and resource-safety defects in modern C/C++ (C++17),
including Node.js native addons built with node-gyp and node-addon-api. Reason about
object lifetimes and resource ownership the way an AddressSanitizer report would — then
report only what you can back with concrete proof. Signal over noise.
This skill finds defects (a leak, a use-after-free, a race that actually exists). For
"is this project set up with the right hardening flags, sanitizers, CI, and conventions?"
use the project-setup skill, which assesses preventive controls, not exploitable bugs. A
missing hardening flag is not a defect; a heap-buffer-overflow is.
Scope
In scope: C and C++ translation units (.c/.cc/.cpp/.cxx/.h/.hpp), especially
Node.js native addons (Napi::ObjectWrap, AsyncWorker, ThreadSafeFunction,
finalizers), the C ABI boundary between C++ and C libraries, and the binding.gyp
defines that change runtime behavior.
Out of scope: the surrounding JavaScript/TypeScript (defer to
the web-security-review skill), and pure build-hardening/convention gaps (defer to
the project-setup skill).
Report vs. research — the prime directive
- Report on: only the file, diff, or path the user asked about.
- Research: the entire module to establish the facts — who allocates, who frees,
which thread touches what, when a destructor or finalizer actually runs, and what the
C library's ownership contract says.
A native defect is almost never visible in one function. Trace the allocation to its
free, the handle to its close, the pointer to the object's real lifetime, and the
napi_value to the call that created it. Never report on pattern-match alone.
Reporting gate: proof, not probability
Report a finding only when you can construct concrete proof. If you cannot describe
how the defect actually manifests at runtime, it is not a finding. Use one of these proof
shapes:
- Dynamic proof (strongest): a sanitizer or Valgrind report with a stack trace —
ASan (use-after-free, heap/stack-buffer-overflow, double-free), LSan (leak), UBSan
(signed overflow, misaligned access, invalid cast), TSan (data race), or Valgrind
memcheck/helgrind. Reproduce it when a toolchain is available.
- Reproducer proof: a concrete, deterministic sequence of calls (or inputs from JS)
that drives the code to the defect, with the exact lifetime step that goes wrong.
- Traced-lifetime proof: the full ownership path in code — the allocation/acquisition
site, every branch to release, and the specific path that double-frees, leaks, frees
early, or reads freed/uninitialized memory — with
file:line for each step.
Every shape must establish the trigger (what sequence or input reaches it), the
mechanism (the exact lifetime error), and the impact (crash, corruption, leak,
disclosure). Prefer to miss a theoretical issue than to flood the report.
Do not assign confidence percentages or 1–10 scores — the complete proof is the
evidence. Every candidate is in exactly one state:
| State |
You have… |
Action |
| Proven |
one complete proof shape above, with the lifetime error described concretely |
Report, with that proof in the finding |
| Lead |
a suspicious lifetime, ownership ambiguity, or static-analyzer hit with a missing proof element |
List under "Needs verification" as a question |
| Theoretical |
a pattern match, style nit, or missing hardening control with no demonstrable error |
Drop (hardening belongs to the project-setup skill) |
Read references/proof-and-tooling.md before
reporting anything — it defines how to obtain each proof and the false-positive
discipline (a static-analyzer warning is a Lead, not a finding; a sanitizer suppression
may be hiding the very bug you are looking for).
Review workflow
Run these steps in order. Load reference files as each step needs them.
1. Scope resolution
2. Map the defect surface
For each file, decide which defect classes are even reachable, then load the matching
sections of references/defect-classes.md:
| Code under review |
Primary classes to check |
Manual new/delete, malloc/free, owning raw pointers |
leaks, use-after-free, double/mismatched free |
| OS handles / fds / sockets / library handles |
resource/handle/fd leaks, use-after-close |
Buffers, arrays, pointer arithmetic, memcpy/strcpy |
heap/stack overflow, out-of-bounds read/write |
| Size/length math, casts between int widths/signs |
integer overflow/truncation → under-allocation |
| Uninitialized locals/members, partial construction |
uninitialized reads |
| Filesystem path checks then use |
TOCTOU |
Threads, AsyncWorker::Execute, shared mutable state |
data races, lock-ordering, atomics misuse |
Napi::ObjectWrap, Reference, ThreadSafeFunction, finalizers |
Node-API lifetime — see napi-resource-model.md |
For any addon code, load
references/napi-resource-model.md: the
addon-specific hazards a general C++ reviewer misses (call-scoped napi_value stored
too long, reference/handle-scope leaks, finalizers that run late or under restricted API
rules, threadsafe-function acquire/release imbalance, exceptions crossing the C ABI).
3. Deep lifetime scan
The core pass. Reason about ownership; do not grep-and-report. For each allocation or
acquisition, answer: who owns it, is release guaranteed on every path (including
exceptions and early returns), can it be released twice, and can anyone use it after
release? For each buffer, answer: is every index and length bound-checked against the
real allocation size, computed without overflow? Apply the detection signals and safe
patterns in defect-classes.md.
4. Cross-boundary lifetime analysis
Step back and trace lifetimes across function, thread, and language boundaries — where
the real defects hide:
- C++ ↔ C library: does the code honor the library's ownership contract (which
allocator frees it, required teardown order, when error accessors are still valid)?
- Native ↔ JS: does a JS object outlive the native resource it wraps, or vice versa?
Is a
napi_value, Reference, or ObjectWrap pointer used after the object is gone?
- Thread ↔ thread: is every field touched from a worker thread and the main thread
synchronized or atomic? Does
Execute() touch napi_env/napi_value (forbidden)?
- Teardown: what runs during environment/process teardown, and in what order — can a
finalizer, cleanup hook, or destructor touch something already freed?
5. Obtain proof
Promote each surviving candidate to Proven or drop it. When a toolchain is available,
build a sanitized target and drive the path (see proof-and-tooling.md) — an ASan/UBSan/
TSan/LSan or Valgrind trace is the gold standard. When you cannot build, construct a
complete traced-lifetime proof with file:line for every step, or downgrade it to a Lead.
6. Adversarial self-verification
For each surviving candidate, try to refute it before it makes the report.
For a non-trivial finding set, use at most two leaf validation tasks total.
Partition or batch the candidates between them; never launch one task per
candidate or a second validation round.
Prefer the tool-restricted cpp:reviewer agent when the host exposes it;
otherwise use a general task-local subagent. Start every prompt with
role: leaf-reviewer and delegation-budget: 0, omit workflow skill names, and
point it at the resolved path of
<plugin-root>/skills/resource-review/references/validation-pass.md. When
context inheritance is configurable, do not pass the surrounding conversation.
Ask each reviewer to disprove its assigned candidates using
references/proof-and-tooling.md:
- Re-read the lifetime with fresh eyes. Is release actually missing, or is there an
RAII guard,
unique_ptr, tracking set, or cleanup hook that handles it?
- Is the pointer really used after free, or was ownership transferred/nulled first?
- Is the buffer access truly unbounded, or is there an upstream length/
SafeCast check?
- Is the "race" real, or is the state single-threaded by construction / mutex-protected?
- Is the "leak" first-party, or a one-time library init that a suppression legitimately
covers?
Drop anything without a complete applicable proof after refutation.
7. Report
Emit the report in the structure defined by
references/report-format.md: a severity summary table,
then findings grouped by defect class, each with location, proof (sanitizer trace /
reproducer / traced lifetime), a plain-English trigger, impact, and a minimal fix. If
nothing survives, say so explicitly and state what was scanned — "No proven memory/resource
defects identified in ."
8. Propose fixes (do not auto-apply)
For each Critical/High finding, propose a concrete, minimal patch (vulnerable → fixed),
preserving surrounding style and names, and prefer the RAII/ownership fix that removes the
class of bug (see the project-setup skill's modern-cpp-conventions.md) over a one-off
patch. State plainly: "Review each patch before applying — nothing has been changed."
Never edit files as part of the review unless the user explicitly asks.
Severity
| Severity |
Impact |
Native examples |
| Critical |
Memory corruption reachable from untrusted input |
Heap-buffer-overflow or use-after-free driven by attacker-influenced JS input; write past a buffer |
| High |
Corruption/crash under realistic conditions |
Use-after-free on a teardown/close race; double-free; unbounded write with a plausible trigger |
| Medium |
Real defect, bounded or conditional impact |
Out-of-bounds read; integer truncation feeding a size; leak on a hot path; data race on non-critical state |
| Low |
Demonstrable but limited |
One-time leak only at process exit; missing close on a rare error path with small impact |
Rate by demonstrated impact and trigger reachability, not by how alarming the class
sounds. A theoretical overflow with no reachable trigger is not a finding at all.
Output rules
- Lead with a findings summary table (counts by severity).
- Group by defect class, not by file.
- Every finding:
file:line, an evidence snippet, its proof (the applicable shape), a
plain-English trigger scenario, impact, and a fix.
- Never auto-apply patches — present them for human review.
- A clean result is a valid result: say what was scanned and that nothing was proven.
Reference files
Load on demand — keep SKILL.md context lean.
| File |
Load during |
Covers |
references/defect-classes.md |
steps 2, 3 |
Per-class detection signals, safe patterns, CWE/CERT mapping, and which sanitizer proves each: UAF, double/mismatched free, leaks, OOB, uninitialized reads, integer overflow/truncation, TOCTOU, data races |
references/napi-resource-model.md |
steps 2, 4 |
Node-API / node-addon-api lifetime hazards: call-scoped handles, handle scopes, reference RAII, ObjectWrap finalizer timing and API restrictions, ThreadSafeFunction/AsyncWorker ownership, exceptions across the C ABI, context-aware instance data |
references/proof-and-tooling.md |
steps 5, 6 |
The proof hierarchy; running ASan/UBSan/TSan/LSan/Valgrind against a Node addon; what each tool misses; legitimate vs. bug-masking suppressions; the Proven/Lead/Theoretical gate |
references/report-format.md |
step 7 |
Output template and finding card |
Adapting for your project
Point this skill at AGENTS.md and optional CLAUDE.md for the module's threading model,
ownership conventions, and known-safe patterns. Record the project's real invariants (for
example "statements are single-threaded by construction", "explicit close orders native
teardown before finalization") in references/napi-resource-model.md so the review credits them
instead of re-flagging them, and add any project-specific RAII wrappers to
references/defect-classes.md so their release contracts are understood.
1---2name: resource-review3description: Top-level memory- and resource-safety code review for modern C/C++ (C++17), including Node.js native addons (node-addon-api / Node-API). Use when the user asks to "review C++/C for memory safety", "find a memory leak", "why does this segfault", "check for use-after-free / double-free / buffer overflow / data race", "resource/handle/fd leak", "review this native addon", "N-API / node-addon-api review", or to check native code with AddressSanitizer/UBSan/TSan/Valgrind. Reports only defects backed by a sanitizer trace, reproducer, or fully traced lifetime. Do not restart the full workflow for a delegated leaf validation task.4---56# C/C++ Resource & Memory Review78## Leaf-mode guard910If the task identifies your role as `leaf-reviewer` or sets11`delegation-budget: 0`, read and follow12[`references/validation-pass.md`](./references/validation-pass.md), validate only13the supplied candidates, return the verdicts to the caller, and stop before the14full workflow below.1516Identify **provable** memory- and resource-safety defects in modern C/C++ (C++17),17including Node.js native addons built with node-gyp and node-addon-api. Reason about18object lifetimes and resource ownership the way an AddressSanitizer report would — then19report only what you can back with concrete proof. Signal over noise.2021This skill finds *defects* (a leak, a use-after-free, a race that actually exists). For22"is this project set up with the right hardening flags, sanitizers, CI, and conventions?"23use the `project-setup` skill, which assesses preventive controls, not exploitable bugs. A24missing hardening flag is not a defect; a heap-buffer-overflow is.2526## Scope2728**In scope:** C and C++ translation units (`.c/.cc/.cpp/.cxx/.h/.hpp`), especially29Node.js native addons (`Napi::ObjectWrap`, `AsyncWorker`, `ThreadSafeFunction`,30finalizers), the C ABI boundary between C++ and C libraries, and the `binding.gyp`31defines that change runtime behavior.3233**Out of scope:** the surrounding JavaScript/TypeScript (defer to34the `web-security-review` skill), and pure build-hardening/convention gaps (defer to35the `project-setup` skill).3637### Report vs. research — the prime directive3839- **Report on:** only the file, diff, or path the user asked about.40- **Research:** the *entire* module to establish the facts — who allocates, who frees,41 which thread touches what, when a destructor or finalizer actually runs, and what the42 C library's ownership contract says.4344A native defect is almost never visible in one function. Trace the allocation to its45free, the handle to its `close`, the pointer to the object's real lifetime, and the46`napi_value` to the call that created it. **Never report on pattern-match alone.**4748## Reporting gate: proof, not probability4950Report a finding only when you can **construct concrete proof**. If you cannot describe51how the defect actually manifests at runtime, it is not a finding. Use one of these proof52shapes:5354- **Dynamic proof (strongest):** a sanitizer or Valgrind report with a stack trace —55 ASan (use-after-free, heap/stack-buffer-overflow, double-free), LSan (leak), UBSan56 (signed overflow, misaligned access, invalid cast), TSan (data race), or Valgrind57 memcheck/helgrind. Reproduce it when a toolchain is available.58- **Reproducer proof:** a concrete, deterministic sequence of calls (or inputs from JS)59 that drives the code to the defect, with the exact lifetime step that goes wrong.60- **Traced-lifetime proof:** the full ownership path in code — the allocation/acquisition61 site, every branch to release, and the specific path that double-frees, leaks, frees62 early, or reads freed/uninitialized memory — with `file:line` for each step.6364Every shape must establish the **trigger** (what sequence or input reaches it), the65**mechanism** (the exact lifetime error), and the **impact** (crash, corruption, leak,66disclosure). Prefer to miss a theoretical issue than to flood the report.6768Do **not** assign confidence percentages or 1–10 scores — the complete proof is the69evidence. Every candidate is in exactly one state:7071| State | You have… | Action |72| --------------- | ------------------------------------------------------------------------- | ----------------------------------------------- |73| **Proven** | one complete proof shape above, with the lifetime error described concretely | **Report**, with that proof in the finding |74| **Lead** | a suspicious lifetime, ownership ambiguity, or static-analyzer hit with a missing proof element | List under **"Needs verification"** as a question |75| **Theoretical** | a pattern match, style nit, or missing hardening control with no demonstrable error | **Drop** (hardening belongs to the `project-setup` skill) |7677Read [`references/proof-and-tooling.md`](./references/proof-and-tooling.md) before78reporting anything — it defines how to obtain each proof and the false-positive79discipline (a static-analyzer warning is a Lead, not a finding; a sanitizer suppression80may be hiding the very bug you are looking for).8182## Review workflow8384Run these steps in order. Load reference files as each step needs them.8586### 1. Scope resolution8788- If a path/diff was given, review only that. Otherwise review the working changes,89 including untracked files:90 ```bash91 if git rev-parse --verify --quiet origin/HEAD >/dev/null; then92 git diff --merge-base origin/HEAD93 else94 git diff HEAD95 fi96 git ls-files --others --exclude-standard97 ```98- Establish the toolchain and shape: language standard and defines from `binding.gyp` /99 `CMakeLists.txt`, node-addon-api and `NAPI_VERSION`, whether `NAPI_CPP_EXCEPTIONS` is100 set, target OSes/arches, and any vendored C sources. This tells you which classes are101 reachable and whether you can build a sanitized reproducer.102103### 2. Map the defect surface104105For each file, decide which defect classes are even reachable, then load the matching106sections of [`references/defect-classes.md`](./references/defect-classes.md):107108| Code under review | Primary classes to check |109| --------------------------------------------------- | --------------------------------------------------------------------- |110| Manual `new`/`delete`, `malloc`/`free`, owning raw pointers | leaks, use-after-free, double/mismatched free |111| OS handles / fds / sockets / library handles | resource/handle/fd leaks, use-after-close |112| Buffers, arrays, pointer arithmetic, `memcpy`/`strcpy` | heap/stack overflow, out-of-bounds read/write |113| Size/length math, casts between int widths/signs | integer overflow/truncation → under-allocation |114| Uninitialized locals/members, partial construction | uninitialized reads |115| Filesystem path checks then use | TOCTOU |116| Threads, `AsyncWorker::Execute`, shared mutable state | data races, lock-ordering, atomics misuse |117| `Napi::ObjectWrap`, `Reference`, `ThreadSafeFunction`, finalizers | Node-API lifetime — see `napi-resource-model.md` |118119For any addon code, load120[`references/napi-resource-model.md`](./references/napi-resource-model.md): the121addon-specific hazards a general C++ reviewer misses (call-scoped `napi_value` stored122too long, reference/handle-scope leaks, finalizers that run late or under restricted API123rules, threadsafe-function acquire/release imbalance, exceptions crossing the C ABI).124125### 3. Deep lifetime scan126127The core pass. **Reason about ownership; do not grep-and-report.** For each allocation or128acquisition, answer: who owns it, is release guaranteed on every path (including129exceptions and early returns), can it be released twice, and can anyone use it after130release? For each buffer, answer: is every index and length bound-checked against the131real allocation size, computed without overflow? Apply the detection signals and safe132patterns in `defect-classes.md`.133134### 4. Cross-boundary lifetime analysis135136Step back and trace lifetimes *across* function, thread, and language boundaries — where137the real defects hide:138139- **C++ ↔ C library:** does the code honor the library's ownership contract (which140 allocator frees it, required teardown order, when error accessors are still valid)?141- **Native ↔ JS:** does a JS object outlive the native resource it wraps, or vice versa?142 Is a `napi_value`, `Reference`, or `ObjectWrap` pointer used after the object is gone?143- **Thread ↔ thread:** is every field touched from a worker thread and the main thread144 synchronized or atomic? Does `Execute()` touch `napi_env`/`napi_value` (forbidden)?145- **Teardown:** what runs during environment/process teardown, and in what order — can a146 finalizer, cleanup hook, or destructor touch something already freed?147148### 5. Obtain proof149150Promote each surviving candidate to Proven or drop it. When a toolchain is available,151build a sanitized target and drive the path (see `proof-and-tooling.md`) — an ASan/UBSan/152TSan/LSan or Valgrind trace is the gold standard. When you cannot build, construct a153complete traced-lifetime proof with `file:line` for every step, or downgrade it to a Lead.154155### 6. Adversarial self-verification156157For each surviving candidate, **try to refute it** before it makes the report.158For a non-trivial finding set, use at most **two** leaf validation tasks total.159Partition or batch the candidates between them; never launch one task per160candidate or a second validation round.161162Prefer the tool-restricted `cpp:reviewer` agent when the host exposes it;163otherwise use a general task-local subagent. Start every prompt with164`role: leaf-reviewer` and `delegation-budget: 0`, omit workflow skill names, and165point it at the resolved path of166`<plugin-root>/skills/resource-review/references/validation-pass.md`. When167context inheritance is configurable, do not pass the surrounding conversation.168Ask each reviewer to disprove its assigned candidates using169`references/proof-and-tooling.md`:170171- Re-read the lifetime with fresh eyes. Is release *actually* missing, or is there an172 RAII guard, `unique_ptr`, tracking set, or cleanup hook that handles it?173- Is the pointer really used after free, or was ownership transferred/nulled first?174- Is the buffer access truly unbounded, or is there an upstream length/`SafeCast` check?175- Is the "race" real, or is the state single-threaded by construction / mutex-protected?176- Is the "leak" first-party, or a one-time library init that a suppression legitimately177 covers?178179Drop anything without a complete applicable proof after refutation.180181### 7. Report182183Emit the report in the structure defined by184[`references/report-format.md`](./references/report-format.md): a severity summary table,185then findings grouped by defect class, each with location, proof (sanitizer trace /186reproducer / traced lifetime), a plain-English trigger, impact, and a minimal fix. If187nothing survives, say so explicitly and state what was scanned — "No proven memory/resource188defects identified in <scope>."189190### 8. Propose fixes (do not auto-apply)191192For each Critical/High finding, propose a concrete, minimal patch (vulnerable → fixed),193preserving surrounding style and names, and prefer the RAII/ownership fix that removes the194class of bug (see the `project-setup` skill's `modern-cpp-conventions.md`) over a one-off195patch. State plainly: **"Review each patch before applying — nothing has been changed."**196Never edit files as part of the review unless the user explicitly asks.197198## Severity199200| Severity | Impact | Native examples |201| ------------ | ----------------------------------------------- | ------------------------------------------------------------------------------------------------- |202| **Critical** | Memory corruption reachable from untrusted input | Heap-buffer-overflow or use-after-free driven by attacker-influenced JS input; write past a buffer |203| **High** | Corruption/crash under realistic conditions | Use-after-free on a teardown/close race; double-free; unbounded write with a plausible trigger |204| **Medium** | Real defect, bounded or conditional impact | Out-of-bounds *read*; integer truncation feeding a size; leak on a hot path; data race on non-critical state |205| **Low** | Demonstrable but limited | One-time leak only at process exit; missing `close` on a rare error path with small impact |206207Rate by demonstrated impact and trigger reachability, not by how alarming the class208sounds. A theoretical overflow with no reachable trigger is not a finding at all.209210## Output rules211212- Lead with a findings **summary table** (counts by severity).213- **Group by defect class**, not by file.214- Every finding: `file:line`, an evidence snippet, its **proof** (the applicable shape), a215 plain-English trigger scenario, impact, and a fix.216- Never auto-apply patches — present them for human review.217- A clean result is a valid result: say what was scanned and that nothing was proven.218219## Reference files220221Load on demand — keep SKILL.md context lean.222223| File | Load during | Covers |224| -------------------------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |225| [`references/defect-classes.md`](./references/defect-classes.md) | steps 2, 3 | Per-class detection signals, safe patterns, CWE/CERT mapping, and which sanitizer proves each: UAF, double/mismatched free, leaks, OOB, uninitialized reads, integer overflow/truncation, TOCTOU, data races |226| [`references/napi-resource-model.md`](./references/napi-resource-model.md) | steps 2, 4 | Node-API / node-addon-api lifetime hazards: call-scoped handles, handle scopes, reference RAII, ObjectWrap finalizer timing and API restrictions, ThreadSafeFunction/AsyncWorker ownership, exceptions across the C ABI, context-aware instance data |227| [`references/proof-and-tooling.md`](./references/proof-and-tooling.md) | steps 5, 6 | The proof hierarchy; running ASan/UBSan/TSan/LSan/Valgrind against a Node addon; what each tool misses; legitimate vs. bug-masking suppressions; the Proven/Lead/Theoretical gate |228| [`references/report-format.md`](./references/report-format.md) | step 7 | Output template and finding card |229230## Adapting for your project231232Point this skill at `AGENTS.md` and optional `CLAUDE.md` for the module's threading model,233ownership conventions, and known-safe patterns. Record the project's real invariants (for234example "statements are single-threaded by construction", "explicit close orders native235teardown before finalization") in `references/napi-resource-model.md` so the review credits them236instead of re-flagging them, and add any project-specific RAII wrappers to237`references/defect-classes.md` so their release contracts are understood.