Every CodePeel review — whether it runs on a GitHub pull request, in the VS Code extension, or through the MCP server — passes changed code through a security-focused analysis before anything reaches your diff. This article explains what that pass actually does: the deterministic checks that run on the raw diff, the OWASP/CWE-focused static analysis, the custom rules you can inject, and the gates that decide which findings are worth your attention.
The goal is not a full audit of your repository. It is a fast, repeatable check of the changes in front of you for the vulnerability classes that matter most in review: exposed secrets, injection, missing authorization, and insecure handling of untrusted input.
Two layers: deterministic checks and model analysis
The security pass has two complementary layers.
The first layer is deterministic: plain pattern checks executed against the diff text before any model is involved. They are cheap, they never hallucinate, and they produce the same result for the same input on every run.
The second layer is a deep static analysis (SAST) pass driven by a language model with a security-only instruction set. It looks for the OWASP Top 10 classes, injection flaws, missing authentication and authorization, exposed secrets, insecure deserialization, and related resource-exhaustion anti-patterns.
Both layers emit findings in the same shape — file, line, severity, explanation, problematic code, and a suggested fix — so downstream consumers (inline PR comments, the VS Code findings tree, MCP tool responses, pre-merge checks) treat them identically.
Deterministic checks on the raw diff
Two patterns are checked line by line on every diff, because both appear constantly in real pull requests and both are reliably detectable without a model.
Hardcoded secret fallbacks
The first check looks for credentials that fall back to a hardcoded literal when an environment variable is missing:
// Flagged: predictable fallback for a credential
const token = process.env.AUTH_TOKEN || "dev-token-12345";
The matcher targets environment variables whose names contain SECRET, API_KEY, ACCESS_KEY, PRIVATE_KEY, or AUTH_TOKEN combined with a || fallback to a string literal. When it matches, the finding is emitted at high severity with a concrete replacement that fails fast instead of silently using a predictable value:
// Suggested fix: fail closed when configuration is missing
const rawToken = process.env.AUTH_TOKEN;
if (!rawToken) throw new Error('AUTH_TOKEN is required');
const token = rawToken;
This class of bug is a favorite of quick local workarounds: the fallback string lands in git history, and suddenly your staging token is one git log away from anyone with repository access.
Webhook signatures over reconstructed bodies
The second check catches a subtle webhook-verification bug: computing an HMAC over a re-serialized request body instead of the original bytes.
// Flagged: signature computed over reserialized JSON
const isValid = verifyHmac(JSON.stringify(req.body), req.headers['x-signature']);
Signature verification must run over the exact bytes the sender signed. Most frameworks parse the body into an object; serializing that object back to a string can reorder keys, change whitespace, or alter number formatting. The reconstructed payload may still look identical, but its bytes differ — so verification either always fails or, worse, can be bypassed by a crafted payload. The deterministic check detects the JSON.stringify(req.body) pattern in a diff that touches signature, HMAC, or webhook verification logic, and points the fix at the framework's raw-body hook:
// Suggested fix: verify against the original request bytes
const rawBody = req.rawBody;
if (!rawBody) throw new Error('Raw request body is required');
The SAST pass: OWASP Top 10 and CWE classes
The model-driven SAST pass receives the diff (capped so oversized changes remain reviewable) with a tightly scoped instruction set:
- Security only. The pass is explicitly told not to comment on style, formatting, general bugs, or performance — that is the other review engines' job. Every finding it returns must be security-related.
- OWASP Top 10 and CWE. Findings must cite the relevant Common Weakness Enumeration identifier where one applies, so an explanation like
CWE-89: SQL Injection risk — the input parameter is concatenated directly into the queryis the expected shape, not a bonus. - Exploitability, not theory. The pass is instructed to flag items that are realistically exploitable or represent severe architectural risk — not every conceivable weakness in every line.
- Strict output contract. The response must be a single JSON object: an array of vulnerabilities, each with file, line, severity (
criticalorwarning), the problematic code, and — where one exists — a copy-paste-ready fix. Invalid or unparseable output is discarded rather than guessed at. - Deterministic settings. The pass runs at temperature zero, so the same diff produces the same findings across runs. Requests are time-boxed; a timeout yields zero findings rather than a partial review.
Line numbers matter downstream — inline PR comments anchor to them — so the pass is required to report the line in the new file (the right side of the diff), counted from the hunk header, not the offset within the diff text.
Resource-exhaustion anti-patterns
Beyond the OWASP classes, the SAST pass also looks for two performance-adjacent security problems that show up in application code: unbounded in-memory growth (caches, maps, or lists that accumulate without a TTL or eviction policy) and N+1 query patterns. Both are noted explicitly so the rest of the review does not duplicate them.
Secret redaction in findings
When a finding quotes code that contains a credential, the analysis is instructed to wrap the secret in [SECRET]...[/SECRET] markers in the problemCode field. The server strips the markers and replaces the enclosed content before anything is stored or displayed. Leaking a real credential into a stored review result would itself be a data breach, so this is enforced at the prompt level for every security finding that quotes code.
Redaction is a safety net, not an invitation: rotate any credential that reached a diff, and see the security handling page for how submitted code and findings are processed.
Custom rules: your patterns, enforced on every diff
Repositories can define compliance rules in Repository Config (via the YAML tab in the web dashboard) that run as an additional deterministic layer on every review:
rules:
- id: no-direct-db-calls
message: "Use the repository layer instead of calling the DB client directly."
pattern: "\\.query\\("
paths: ["src/api/**"]
severity: high
category: architecture
Each rule is a regular expression evaluated against added lines only (+ lines in the diff), with optional path scoping so a rule that matters in src/api/ does not fire in test fixtures. Two details make the enforcement practical rather than noisy:
- Context awareness. A line that matches a pattern inside an obvious transaction or batch call can be skipped, so a rule targeting bare database calls does not fire on legitimate batched writes.
- Deduplication. Matches are collapsed per file and line, so one offending line produces one comment even if several rules overlap.
Custom rules also participate in the model-driven review: they are injected into the analysis instructions as rules the reviewer must enforce, so violations can be explained in context, not just pattern-matched.
Noise gates: what decides what you actually see
A security pass that flagged everything would train you to ignore it. After analysis, findings pass through quality gates before they become comments:
- Architecture and best-practice opinions are suppressed on small files (under roughly 120 changed lines) when they match known noise patterns — a five-file PR does not need a service-boundary lecture.
- Findings are capped per file, keeping the highest-severity items, so one rough file cannot bury the rest of the review in comments.
- Vague recommendations are dropped: a finding whose entire suggestion is "consider adding validation" and which carries no concrete fix does not survive the gate.
The result is a finding list biased toward items that are specific, severe, and fixable — which is also exactly the subset the auto-fix pipeline can act on.
From findings to merge enforcement
Security findings do not stop at comments. The aggregate result feeds the pre-merge quality gates: a security_blocker check can turn any security finding into a failed commit status, and critical_findings can block merges on critical-severity results — enforced through GitHub branch protection. See A Guide to CodePeel's Pre-Merge Quality Gates for how that enforcement works.
What this scanning cannot do
An honest description of the pass includes its limits:
- It reviews changes, not repositories. Behavior in other services, runtime configuration, dependencies, or unchanged code is out of scope.
- Coverage is not a guarantee. The deterministic checks are narrow by design; the model-driven pass can miss issues and can produce false positives. Treat each finding as a pointer to investigate, not a verdict.
- It is not a secret scanner for history. A fallback secret removed in the latest commit still exists in prior commits. Use a dedicated secret-scanning tool for repository history.
- Suggested fixes need review. A generated patch is a starting point; confirm it preserves intended behavior before applying it.
Used that way — as a fast, consistent first pass over every change, feeding gates you control — the security scan earns its place in the review workflow without pretending to replace the tools around it.
Try it on your own diff
The fastest way to see the pass work is to hand it something vulnerable: add a process.env.API_KEY || "..." fallback to a scratch branch and open a PR, or run the same diff through the VS Code extension or the MCP server before you commit. For the operational details of findings and severity, see the security review guide and understanding review findings.