An AppSec SAST Ruleset for AI-Generated Code
Concrete Semgrep rules, regex patterns, and tuning guidance for the AI-specific security baseline. What to flag, what to skip, and how to calibrate FP rates when half your commits are AI-assisted.
Standard SAST rulesets were tuned for code humans write. Humans write insecurely on predictable dimensions — time pressure, copy-paste from Stack Overflow, the occasional junior mistake. AI coding tools fail on a different set of dimensions, because their failure mode is “reproduce what was frequent in training data,” and the training data is disproportionately tutorials, answers, and snippets where clarity outranked safety.
If your AppSec team is running the same ruleset against a codebase where 50% of new commits are AI-assisted as you ran last year, two things are happening. You are missing categories of finding that AI introduces at higher frequency than humans did, and your false-positive rate on low-signal human-era rules is now dominating reviewer attention. Neither serves you.
This post is the ruleset. Ten concrete Semgrep patterns, the reason each fires more on AI output, and how to calibrate severity and FP thresholds when you layer them over a provenance-tagged commit stream (see the provenance post for the trailer spec these rules pair with).
Why a Separate Ruleset
Three structural reasons AI-generated code diverges from the security priors of hand-written code:
Training corpus bias. Public code is full of tutorials, demos, and permissive examples where security was the part that would be added later. A model trained on this corpus treats SELECT * FROM users WHERE id = ' + id + ' as the pattern for simple lookups, because in training it is.
Happy-path optimization. AI tools are rewarded (by RLHF and by developer acceptance) for producing code that compiles and runs on the first attempt. Validation, error handling, and authorization are the parts that don’t affect whether the code works on the happy path. They get skipped.
No live knowledge. The model recommends package-name@2.3.1 because 2.3.1 was popular when the corpus was collected. It does not know 2.3.1 was yanked for a supply-chain compromise eight months ago.
A ruleset tuned for this baseline over-weights the categories AI gets wrong and under-weights the categories humans used to get wrong. That is the adjustment.
The Ruleset
Each rule below includes: an ID, a minimal Semgrep pattern (or sibling rule language equivalent), why it has better signal on AI output than on human output, and the suggested severity when paired with an AI-Assisted: true trailer. The severities assume you have provenance data; if you don’t, treat all of them as a tier higher.
R-001: Inline credential-shaped strings
rules:
- id: ai-inline-credential
pattern-either:
- pattern: $VAR = "sk-..."
- pattern: $VAR = "Bearer ..."
- pattern: $VAR = "xoxb-..."
- pattern-regex: '(api[_-]?key|token|secret|password)\s*[:=]\s*["''][A-Za-z0-9_\-]{16,}["'']'
message: Credential-shaped literal; verify it is not a real secret
severity: ERROR
Why it targets AI: Models frequently reproduce placeholder key shapes from tutorials (sk-abc123..., xoxb-..., JWT-looking strings). The shape is load-bearing because a developer testing locally often pastes a real key over the placeholder and then forgets to move it to env. Rule fires on shape, reviewer confirms intent.
Severity with provenance: ERROR. Even placeholders warrant a reviewer eye on every AI commit.
R-002: String-concatenated SQL
rules:
- id: ai-sql-concat
pattern-either:
- pattern: $DB.query("..." + $X)
- pattern: $DB.query(`...${$X}...`)
- pattern: $CURSOR.execute("..." + $X)
- pattern: $CURSOR.execute(f"...{$X}...")
message: SQL built via string concatenation or interpolation
severity: ERROR
Why it targets AI: The pre-parameterized-query idiom outnumbers the parameterized idiom in public code by roughly an order of magnitude. AI generators fall back to concatenation under any ambiguity. Human engineers who grew up post-2010 rarely produce this pattern unless under pressure; AI produces it at baseline.
Severity with provenance: ERROR, auto-block merge.
R-003: CORS wildcard with credentials
rules:
- id: ai-cors-wildcard-credentials
pattern-either:
- patterns:
- pattern: res.setHeader("Access-Control-Allow-Origin", "*")
- pattern-inside: |
app.use(...);
...
res.setHeader("Access-Control-Allow-Credentials", "true")
- pattern: |
cors({
origin: "*",
credentials: true,
...
})
message: Wildcard origin with credentials is unconditionally unsafe
severity: ERROR
Why it targets AI: Browsers reject this combination at runtime, but AI tools generate it anyway because both patterns appear often in isolation in training data. Pairs cleanly here because the model has no cross-constraint awareness.
Severity with provenance: ERROR. No legitimate production configuration emits this.
R-004: Unrestricted deserialization
rules:
- id: ai-unsafe-deserialization
pattern-either:
- pattern: pickle.loads(...)
- pattern: yaml.load($X) # without SafeLoader
- pattern: unserialize(...)
- pattern: ObjectInputStream($X).readObject()
message: Deserialization primitive that allows arbitrary instantiation
severity: ERROR
Why it targets AI: pickle.loads and yaml.load are the idiomatic first-suggestion for “deserialize this” in every tutorial written before the safe variants became default. The safe alternatives (pickle avoidance, yaml.safe_load, allowlisted deserializers) appear less in training.
Severity with provenance: ERROR on user-controlled data paths, WARNING elsewhere.
R-005: JWT verify skipped or weakened
rules:
- id: ai-jwt-verify-disabled
pattern-either:
- pattern: jwt.decode($T, ..., verify=False)
- pattern: jwt.decode($T, options={"verify_signature": False, ...})
- pattern: jwt.verify($T, "...", { algorithms: ["none"] })
message: JWT decoded without signature verification
severity: ERROR
Why it targets AI: When a developer asks an AI tool to “decode this token and read the claims,” the shortest working completion is jwt.decode(token, verify=False). The model does not distinguish between “inspect a trusted internal token” and “authorize a request from the public internet” — both prompts produce the same snippet.
Severity with provenance: ERROR, no exceptions.
R-006: Error handler leaks stack to response
rules:
- id: ai-error-leak
pattern-either:
- pattern: res.json({ ..., stack: $E.stack, ... })
- pattern: res.send($E.toString())
- pattern: return HttpResponse(str($E), status=500)
- pattern: traceback.format_exc() # in response path
message: Error response exposes internal stack or message
severity: WARNING
Why it targets AI: Dev-tutorial error handlers expose stacks because that is the useful behavior at localhost. The production guard (“only include stack when NODE_ENV !== ‘production’”) is the part that gets skipped under “happy path” pressure.
Severity with provenance: WARNING, HIGH when the file is in the public API path.
R-007: Input-validation absence on request bodies
Pattern requires a cross-file check — look for handler functions (Express, FastAPI, Flask) whose body references req.body/request.json()/body without a preceding schema validator call (zod, pydantic, joi, yup, framework-native).
Why it targets AI: Validation is the “boring part” AI skips. Humans forget it under pressure; AI omits it structurally. When prompted for “an endpoint that accepts X,” the model generates the happy path and moves on.
Severity with provenance: HIGH. Treat this as the single highest-signal AI-specific rule — it maps to a disproportionate share of AI-introduced production bugs.
R-008: Dependency pinned to a version with known CVE
Not a static rule; wire Semgrep to a lockfile scanner (OSV, Snyk, GitHub Advisory Database, Socket) that evaluates the current pinned set. Raise severity on additions that land in AI-assisted commits versus hand-authored ones.
Why it targets AI: The model recommends requests@2.25.1 because it was idiomatic when the corpus was cut, not because it is safe today. Human engineers in 2026 typically get pinned to whatever npm install resolved yesterday; AI pins to what was popular when the model was trained. Those are different distributions.
Severity with provenance: ERROR on known-CVE adds, WARNING on “outdated but not vulnerable” adds.
R-009: Cryptography with bad defaults
rules:
- id: ai-weak-crypto
pattern-either:
- pattern: crypto.createCipher(...) # deprecated, no IV
- pattern: hashlib.md5(...) # not for auth
- pattern: Cipher.getInstance("DES")
- pattern: random.random() # in token generation path
message: Deprecated or cryptographically weak primitive
severity: ERROR
Why it targets AI: Old crypto APIs are overrepresented in training data because they were idiomatic for longer than their replacements. The model’s top completion for “encrypt this” skews historical.
Severity with provenance: ERROR.
R-010: Shell execution with interpolated user input
rules:
- id: ai-shell-injection
pattern-either:
- pattern: subprocess.run($X, shell=True) # with interpolated $X
- pattern: os.system($X)
- pattern: exec($X)
- pattern: child_process.exec(`...${$X}...`)
message: Shell command built with interpolated input
severity: ERROR
Why it targets AI: subprocess.run(..., shell=True) is shorter and reads more naturally than the argv-array alternative. Models prefer shorter. The shorter form is also the unsafe one.
Severity with provenance: ERROR.
Track these metrics automatically with LobsterOne
Get Started FreeCalibrating for Your Baseline
The rules above are the what. The calibration — how aggressively to gate, how to triage findings, when to override — depends on measurements specific to your codebase and tool mix.
Three dials to tune once you have provenance data correlating commits with AI tool and model:
Per-tool severity offsets. Different tools produce different failure distributions. If 60% of your R-004 (unsafe deserialization) findings trace to one specific tool, that tool’s commits deserve a severity bump on that rule, not the others. Aggregate across 4–6 weeks before drawing conclusions; single-week spikes are noise.
Per-reviewer FP rate. Reviewers triaging AI-heavy streams develop pattern fatigue faster than on hand-authored code. If a reviewer’s override rate on a given rule climbs above ~30%, the rule is miscalibrated for their context — either tighten the pattern, add a suppression for the false-trigger idiom, or promote the rule to blocking so overrides require justification.
Merge-gate versus review-gate. Rules R-001 through R-005 and R-009/R-010 are blocking in most contexts. R-006 (error leak) and R-007 (missing validation) are better as review-gates because legitimate exceptions exist and reviewer-judgement is needed. R-008 (CVEs) is blocking on new adds, warning on existing pins — this is the rule most often miscalibrated as “block everything” and most often abandoned as a result.
What Does Not Work
Heuristic “AI-generated code” detectors. Static classifiers that try to identify AI-produced code from the code itself have poor precision and create adversarial dynamics (developers reformat to evade). Provenance tagged at commit time (trailer spec) is the correct input, not inference.
Broad anti-pattern blocklists. Rules that block any use of eval, exec, pickle, subprocess regardless of context generate so many false positives that reviewers disable them within a sprint. Narrow rules that fire on the specific unsafe calling convention are the only ones that stay on.
Style-based proxies. “AI generates more comments / longer function names / different indentation” — these have been investigated as signals and don’t survive a version bump of the tool. Do not build detection on them.
Ruleset freezes. The ruleset above maps to AI failure modes observable in 2025–2026 models. Models change. Revisit the ruleset every 6 months with fresh data from your own codebase before assuming it still fits.
Integration with Quality Gates
Findings from this ruleset feed into your CI/CD quality gates. The gate decides what to do with a finding; this ruleset decides what is findable. Keep the two concerns separate — a gate that embeds its own rule definitions drifts from AppSec’s understanding of the current threat model.
The pairing with provenance metadata is where this ruleset pays back its tuning cost. A finding on a commit labeled AI-Assisted: false from an experienced reviewer deserves different treatment from the same finding on an AI-Assisted: true, AI-Review-Confidence: low commit. Same pattern, different prior, different triage lane. That is the baseline adjustment the title of this post is talking about.
Pierre Sauvignon
Founder
Founder of LobsterOne. Building tools that make AI-assisted development visible, measurable, and fun.
Related Articles

AI Code CI/CD Gating: A Decision Tree for Blocking, Flagging, and Passing
When to block an AI-generated commit at merge, when to flag it for extra review, and when to let it through. A concrete gating tree for staff engineers responsible for production safety.

AI Coding Compliance: A Regulation-by-Regulation Mapping
What SOC 2, HIPAA, PCI-DSS, GDPR, and the EU AI Act actually require when your code is AI-generated — mapped to specific controls, evidence artifacts, and audit-time answers.

How to Build an AI Code Quality Gate for Your CI/CD Pipeline
Linting rules, test coverage thresholds, and automated checks specifically tuned for AI-generated code patterns in your build pipeline.