SKILL.md
SOTA C & C++ (2026)
Expert-level rules for producing and auditing production C and C++. C and C++ are memory-unsafe by default: the compiler will not stop you from reading freed memory, overrunning a buffer, or invoking undefined behavior (UB) that the optimizer then weaponizes. These rules exist to claw back the safety the language doesn't give you — through RAII, the type system, sanitizers, hardened build flags, and disciplined review. Baseline: C++23 (ISO/IEC 14882:2024) and C17/C23; flag where a control needs a newer toolchain. C++26 is feature-complete (DIS ballot 2026) — contracts, reflection, erroneous behavior for uninitialized reads, and a standardized hardened standard library; the last is usable today via libc++/libstdc++ hardening flags (rules/02). Every rule states the why; every rules file ends with an audit checklist of grep/clang-tidy/ sanitizer patterns.
Purpose
Two consumers, one source of truth:
- BUILD mode — generating new C/C++: follow the rules as defaults, not
suggestions. Prefer C++ with RAII over raw C idioms unless the target is C. Deviate only with a comment justifying it.
- AUDIT mode — reviewing existing C/C++: hunt violations using the audit
checklists, classify by severity, report in the finding format below. Memory- safety and UB findings are presumed exploitable until proven otherwise.
BUILD mode
- Before writing, read the rules files relevant to the task (see index). A
parser handling untrusted bytes needs 02, 03, 04; a threaded service needs 05.
- Apply the top-10 non-negotiables (below) unconditionally.
- New projects: CMake (≥3.20) with
-Wall -Wextra -Wpedantic -Werror, the
OpenSSF hardening flags (rules/04), a debug build wired to ASan+UBSan, clang-tidy + clang-format configs, and CI running all of it from day one (rules/06).
- Prefer the standard library and RAII types over hand-rolled
allocation/ownership. Every new/malloc/fopen/mutex.lock() should be owned by a destructor (uniqueptr, container, lockguard), not a manual matching call you can forget on an early return or exception.
- Treat warnings as errors. A clean
-Wall -Wextrabuild is the floor, not
the goal — also run a static analyzer and the sanitizers (rules/06).
- When you must use a sharp tool (raw pointer arithmetic,
reinterpret_cast,
unsafe C interop, manual lifetime), leave a // NOTE(sota): comment explaining the invariant you're upholding so auditors don't flag it blind.
AUDIT mode
Work through each relevant rules file's audit checklist against the target. Run the listed grep/clang-tidy/sanitizer commands; confirm each hit manually (greps are recall-oriented). Where feasible, build with -fsanitize=address, undefined and run the test suite — a sanitizer abort is ground truth.
Severity conventions
| Severity | Meaning | Examples |
|---|---|---|
| CRITICAL | Exploitable memory corruption or guaranteed UB on reachable input | Heap/stack buffer overflow on attacker data, use-after-free, double-free, OOB write, format-string with user-controlled fmt, system() with interpolated input, data race on a pointer |
| HIGH | Likely corruption, crash, or security weakness | Unchecked malloc/new size from input, integer overflow feeding an allocation or index, missing bounds check, strcpy/sprintf/gets, TOCTOU on a path, missing RAII so a leak/UB occurs on the exception path |
| MEDIUM | Correctness/maintainability hazard, latent bug | Raw owning pointers, manual new/delete pairs, C-style casts, narrowing conversions, memcpy where a typed copy fits, missing override/= delete, signed/unsigned comparison |
| LOW | Idiom/perf debt, works but wrong shape | Pass-by-value of large objects, needless copies instead of std::move, using namespace std in headers, macros where constexpr/inline fits |
| INFO | Style/doc/hygiene | clang-format drift, naming, missing [[nodiscard]], include hygiene |
Finding format
[SEVERITY] file.cpp:LINE — short title
Rule: rules/NN-name.md § section
Evidence: the offending line(s), verbatim
Impact: one sentence — what corrupts/leaks/races, under what input
Fix: concrete replacement code or action
Effort: trivial | small | medium | large
Group findings by severity, CRITICAL first. End with: counts per severity, the three highest-leverage fixes, and which checklists/sanitizers were run.
Rules index
| File | Read this when... |
|---|---|
rules/01-idioms.md |
Writing/reviewing any C++: RAII and the rule of zero/five, ownership with uniqueptr/sharedptr, value semantics and move, const/constexpr, references vs pointers, casts, enum class, error handling (exceptions vs std::expected vs error codes), in-band sentinels (absence encoded as -1/0/"") incl. EOF in a char — broken only where char is unsigned, C-vs-C++ idiom choices |
rules/02-memory-safety.md |
Anything touching pointers, buffers, lifetimes, or allocation: bounds, use-after-free/return, dangling references and views (stringview/span), iterator invalidation, ownership discipline, sanitizers (ASan/MSan), FORTIFYSOURCE/GLIBCXX_ASSERTIONS |
rules/03-undefined-behavior.md |
Reasoning about UB and the optimizer: integer overflow, strict aliasing, uninitialized reads, null/misaligned access, signed shifts, data races as UB, unsigned arithmetic, UBSan, why "it worked in debug" proves nothing |
rules/04-security.md |
Any input crossing a trust boundary: CERT C/C++ + MISRA, banned functions (gets/strcpy/sprintf/system), integer-overflow-to-allocation, format strings, path traversal/TOCTOU, command injection, deserialization/parsers, CSPRNG, the OpenSSF hardening flag set |
rules/05-concurrency.md |
Anything with threads, atomics, or shared state: the C++ memory model, data races, std::atomic and memory orders, mutex/lockguard/scopedlock, deadlock ordering, condition variables, std::jthread/stop tokens, TSan |
rules/06-build-tooling-ci.md |
Setting up or auditing builds/CI: CMake hygiene, warning flags, clang-tidy/clang-format, static analysis (clang-analyzer, cppcheck, Coverity), sanitizer CI matrix, fuzzing (libFuzzer/OSS-Fuzz), dependencies and supply chain (vcpkg/Conan, pinning, SBOM). **Test strategy lives in sota-testing; this file owns C/C++ build/test mechanics.** |
rules/07-performance.md |
Latency/throughput/memory work: profiling (perf, VTune, Callgrind), allocation reduction and custom allocators, move/copy elision, cache locality and data-oriented layout, <algorithm> over hand loops, LTO/PGO, micro-benchmarking pitfalls |
Top-10 non-negotiables
- Every resource is owned by a destructor (RAII). No
new/deleteor
malloc/free pairs you have to match by hand; no bare owning pointers. Use uniqueptr, containers, lockguard/scoped_lock, RAII wrappers. A leak or UB on the exception/early-return path is the default failure mode of manual cleanup. (rules/01, rules/02)
- No buffer touches memory it doesn't own. Bounds-check every index/
length derived from input; use std::span/std::string/containers and .at() or explicit checks, never raw pointer + length you assume. Overflow on attacker input is CRITICAL. (rules/02)
- No use-after-free / dangling. A pointer, reference, iterator,
string_view, or span must not outlive its storage. Never return a reference/view to a local or to a temporary. (rules/02)
- Undefined behavior is a bug even if it "works". Signed integer
overflow, strict-aliasing violations, uninitialized reads, OOB, data races are UB the optimizer may exploit. Build with UBSan; treat any UBSan diagnostic as CRITICAL/HIGH. (rules/03)
- **Integers feeding an allocation, index, or
memcpysize are
overflow-checked and the right signedness.** Validate ranges before use; prefer unsigned for sizes, check for wrap. Overflow-to-undersize-alloc is a classic RCE primitive. (rules/03, rules/04)
- Banned functions are banned. No
gets,strcpy/strcat/sprintf
(use bounded forms or std::string/std::format), no system() with interpolated input (use posix_spawn/exec* with an argv array). (rules/04)
- Build hardened, by default.
-Wall -Wextra -Werrorplus the OpenSSF
set (-DFORTIFYSOURCE=3 -DGLIBCXXASSERTIONS -fstack-protector-strong -fstack-clash-protection -fcf-protection -Wl,-z,relro,-z,now). Missing hardening on a network-facing binary is a HIGH finding. (rules/04, rules/06)
- Shared mutable state is synchronized; data races are CRITICAL. Guard
with a mutex/scoped_lock or use std::atomic with a justified memory order. A -fsanitize=thread failure is not flaky noise. (rules/05)
- Sanitizers and a static analyzer gate CI. A debug/test job runs
ASan+UBSan (and TSan for threaded code); clang-tidy + cppcheck run on every PR. Untrusted-input parsers get a fuzz target. (rules/06)
- Prefer the type system to convention.
enum classover macros,
constexpr/inline over #define, gsl::span/std::span over pointer+ length, [[nodiscard]] on must-check returns, explicit on single-arg constructors, override/final. Make misuse fail to compile. (rules/01)