---
name: resource-review
description: Memory- and resource-safety code review for modern C/C++ (C++17), including Node.js native addons (node-addon-api / Node-API). Use when asked 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. Traces object and resource lifetimes and reports only defects backed by a sanitizer trace, a reproducer, or a fully traced lifetime — never pattern-match guesses.
allowed-tools: Read, Grep, Glob, Bash, WebFetch, WebSearch, Task
license: CC-BY-SA-4.0
---

# C/C++ Resource & Memory Review

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 `/cpp:project-setup`, 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 `/security:web-security-review`),
and pure build-hardening/convention gaps (defer to `/cpp:project-setup`).

### 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 `/cpp:project-setup`) |

Read [`references/proof-and-tooling.md`](./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

- If a path/diff was given, review only that. Otherwise review the working changes,
  including untracked files:
  ```bash
  if git rev-parse --verify --quiet origin/HEAD >/dev/null; then
    git diff --merge-base origin/HEAD
  else
    git diff HEAD
  fi
  git ls-files --others --exclude-standard
  ```
- Establish the toolchain and shape: language standard and defines from `binding.gyp` /
  `CMakeLists.txt`, node-addon-api and `NAPI_VERSION`, whether `NAPI_CPP_EXCEPTIONS` is
  set, target OSes/arches, and any vendored C sources. This tells you which classes are
  reachable and whether you can build a sanitized reproducer.

### 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`](./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`](./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, launch one `Task` subagent per candidate **in parallel**, each
instructed to disprove the defect 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`](./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 <scope>."

### 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 `/cpp:project-setup`'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`](./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`](./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`](./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`](./references/report-format.md)             | step 7        | Output template and finding card                                                                                                          |

## Adapting for your project

Point this skill at your `CLAUDE.md`/`AGENTS.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.
