Semgrep ruleset for C++ market-data / FIX feed handlers.
The rules encode 15 Cycode SAST findings as reusable static-analysis patterns so the same classes of defect are caught in CI rather than in the next scan cycle. They cover three themes: unchecked lengths on the wire-decode path, missing TLS peer and hostname verification, and unsynchronised ring-buffer state.
One rule per file, 17 files at the repository root. The filename prefix is the Cycode severity tier and the finding number, so files sort into tier order and any single rule can be enabled, pinned, or disabled on its own.
| Prefix | Files | Findings | Semgrep severity | Remediation window |
|---|---|---|---|---|
critical- |
5 | 1-4 | ERROR |
Before next deployment |
high- |
9 | 5-12 | WARNING |
Within current sprint |
medium- |
3 | 13-15 | INFO |
Medium-term |
All rules are languages: [cpp] and match .cpp, .cc, .cxx, .h, .hpp, and .hxx.
Findings 3 and 5 each have two rules (03/03b and 05/05b) because they need distinct patterns for the call site and the definition, which is why 15 findings produce 17 rules.
Each file opens with a three-line header naming the finding, both severities, and the command to run that rule alone.
Run every rule by pointing at the directory:
semgrep scan --config path/to/SemgrepRules/ path/to/srcRun one rule:
semgrep scan --config critical-01-venue-code-overflow.yaml path/to/srcRun one tier.
Semgrep takes --config once per file rather than a glob, so expand the prefix in the shell:
semgrep scan $(printf -- '--config %s ' critical-*.yaml) path/to/srcBlock a build on the critical tier while leaving the rest advisory:
semgrep scan $(printf -- '--config %s ' critical-*.yaml) --error path/to/src
semgrep scan $(printf -- '--config %s ' high-*.yaml medium-*.yaml) path/to/srcConsume a rule straight from GitHub without cloning:
semgrep scan --config https://raw.githubusercontent.com/dmancloud/SemgrepRules/main/critical-01-venue-code-overflow.yaml path/to/srcRule IDs keep the cycode-NN- naming independently of the filename, so existing baselines, nosemgrep comments, and triage history stay valid.
| File | Rule ID | Finding | What it catches |
|---|---|---|---|
critical-01-venue-code-overflow.yaml |
cycode-01 |
1 | Unchecked wire length from readRaw<> passed to readBytesRaw() in pullVenueCode |
critical-02-instr-code-overflow.yaml |
cycode-02 |
2 | Unclamped codeWidth in assignCode, memcpy() into a fixed-size struct field |
critical-03-tls-permissive-verify-cb.yaml |
cycode-03 |
3 | SSL_CTX_set_verify() installed with a permissive verify callback |
critical-03b-tls-verify-cb-returns-1.yaml |
cycode-03b |
3 | Verify callback that ignores preverify_ok and is hardcoded to return 1 |
critical-04-tls-verify-none.yaml |
cycode-04 |
4 | TLS peer verification disabled via SSL_VERIFY_NONE |
| File | Rule ID | Finding | What it catches |
|---|---|---|---|
high-05-readraw-callsites.yaml |
cycode-05 |
5 | Every readRaw<T>() call site (audit rule, see below) |
high-05b-readraw-no-bounds-check.yaml |
cycode-05b |
5 | readRaw<T>() definition with no bounds check against length_ |
high-06-sbe-header-null-deref.yaml |
cycode-06 |
6 | decodeSbeHeader() missing null and minimum-length checks |
high-07-ring-non-atomic-pos.yaml |
cycode-07 |
7 | Ring-buffer positions declared as plain size_t instead of std::atomic |
high-08-tls-no-hostname-check.yaml |
cycode-08 |
8 | SSL_connect() without SSL_set1_host() or SNI, so the hostname is never checked |
high-09-ssl-ctx-no-verify.yaml |
cycode-09 |
9 | SSL_CTX factory that never sets verification or loads a trust store |
high-10-framelen-not-validated.yaml |
cycode-10 |
10 | SBE payload offsets used without validating frameLen |
high-11-cursor-memcpy-oob.yaml |
cycode-11 |
11 | Successive cursor memcpy() reads with no cumulative bounds guard |
high-12-readbytesraw-struct-oob.yaml |
cycode-12 |
12 | readBytesRaw(&struct, sizeof(T)) without a remaining() check |
| File | Rule ID | Finding | What it catches |
|---|---|---|---|
medium-13-ring-push-toctou.yaml |
cycode-13 |
13 | Ring::push TOCTOU in the gap between the atomic load and store |
medium-14-buffer-ctor-null.yaml |
cycode-14 |
14 | ByteBuffer constructor that stores the data pointer without a null check |
medium-15-tls-min-version.yaml |
cycode-15 |
15 | SSL_CTX created without SSL_CTX_set_min_proto_version() |
Each rule carries metadata with the originating cycode-finding number, CWE identifiers, OWASP Top 10 mapping where one applies, and confidence / likelihood / impact ratings, so results can be routed by CWE or filtered by confidence in a pipeline.
The ruleset is deliberately strict and low-noise.
Patterns are tied to the specific APIs and identifiers named in the findings (readRaw, readBytesRaw, decodeSbeHeader, SSL_CTX_set_verify, and so on), which means they will not fire on unrelated code and will correspondingly miss renamed variants.
If your codebase uses different names, widen the relevant pattern or metavariable-regex rather than loosening the guard clauses.
Two rules need particular attention:
cycode-05is an audit rule and flags everyreadRaw<T>()call site by design, since the finding calls for auditing roughly 15 of them. Once the bounds check is added insidereadRaw()(cycode-05b), drophigh-05-readraw-callsites.yamlfrom the config list.cycode-14enumerates constructor initialiser-list shapes with zero to four initialisers, because Semgrep's C++ parser rejects...inside an initialiser list. Add anotherpattern-eitherbranch if a buffer class initialises more than four members.
Every rule was validated against synthetic vulnerable and remediated C++ fixtures: it fires on the vulnerable form and stays silent on the remediated form of the same code. The split into one file per rule was a repackaging only, and the rule bodies are unchanged from the validated versions.