CMMC 2.0 SI.L1-3.14.2 (NIST 800-171r2 3.14.2) requires protection against malicious code at appropriate locations in organizational systems. The primary code-level attack vectors are SQL injection via string-concatenated queries, XSS via raw innerHTML insertion of user content, and dependency confusion via uncontrolled registry sources. A single SQL injection point in a route that handles FCI gives an attacker arbitrary read/write access to the database. CWE-20 (Improper Input Validation), CWE-89 (SQL Injection), and OWASP A03 (Injection) all apply. Zod-validated API routes using ORM queries eliminate both injection classes simultaneously.
Critical because SQL injection in an FCI-handling API route gives an unauthenticated or low-privileged attacker direct read/write access to the entire database.
Validate every API input with Zod before it touches the database or the DOM. Use Prisma or parameterized queries — never string-concatenate user input into SQL:
// lib/validation/search.ts
import { z } from 'zod'
export const searchSchema = z.object({
query: z.string().min(1).max(200).trim(),
category: z.enum(['documents', 'users', 'contracts']).optional(),
})
// app/api/search/route.ts
export async function POST(req: Request) {
const result = searchSchema.safeParse(await req.json())
if (!result.success) return Response.json({ error: 'Invalid search parameters' }, { status: 400 })
const { query, category } = result.data
// Prisma parameterizes automatically — no injection possible
const docs = await prisma.document.findMany({
where: { title: { contains: query, mode: 'insensitive' }, ...(category ? { category } : {}) },
})
return Response.json(docs)
}
For React components, prefer {userContent} interpolation — React escapes HTML automatically. When HTML rendering is unavoidable, install dompurify and sanitize before insertion. Never use dangerouslySetInnerHTML={{ __html: userContent }} without prior sanitization.
ID: gov-cmmc-level-1.system-integrity.malicious-code
Severity: critical
CMMC Practice: SI.L1-3.14.2
What to look for: Examine input validation coverage — all user inputs must be validated server-side. Look for schema validation libraries (zod, joi, yup, valibot) used in API route handlers. Check for SQL injection vectors: raw SQL with user input, string-concatenated queries, or ORM usage without parameterization. Look for XSS vectors: raw innerHTML assignments with unsanitized content, or template literals inserting user content into HTML strings. Check for a committed lock file (dependency integrity). Look for dependencies from trusted registries only (no unusual registry overrides in .npmrc). Check for CSP headers that reduce XSS impact.
Pass criteria: Count all API routes and check each for server-side input validation. At least 90% of API routes validate input server-side using a schema library (Zod, Joi, Yup). No more than 0 raw innerHTML assignments with unvalidated user content. Database queries use parameterized statements or ORM. Report: "X of Y API routes have server-side input validation."
Fail criteria: API routes accept user input without server-side validation. SQL queries built with string concatenation from user input. User-provided HTML inserted into the DOM without sanitization. No lock file. Unusual registry configurations in .npmrc.
Skip (N/A) when: Never — malicious code protection is a core CMMC Level 1 requirement.
Detail on fail: Identify the specific injection vector or validation gap. Example: "app/api/search/route.ts: query directly concatenated into SQL string — SQL injection possible. No input validation found in any API route. No lock file committed." Keep under 500 characters.
Remediation: Validate all inputs and use safe query patterns:
// lib/validation/search.ts
import { z } from 'zod'
export const searchSchema = z.object({
query: z.string().min(1).max(200).trim(),
category: z.enum(['documents', 'users', 'contracts']).optional()
})
// app/api/search/route.ts
import { searchSchema } from '@/lib/validation/search'
export async function POST(req: Request) {
const body = await req.json()
const result = searchSchema.safeParse(body)
if (!result.success) {
return Response.json({ error: 'Invalid search parameters' }, { status: 400 })
}
const { query, category } = result.data
// Use ORM (Prisma) — parameterized queries prevent SQL injection automatically
const docs = await prisma.document.findMany({
where: {
title: { contains: query, mode: 'insensitive' },
...(category ? { category } : {})
}
})
return Response.json(docs)
}
For rendering user-provided content in React, prefer plain text interpolation (React escapes automatically) over innerHTML. When HTML rendering is unavoidable, use DOMPurify to sanitize before insertion:
// Install: npm install dompurify @types/dompurify
import DOMPurify from 'dompurify'
// Safe: React escapes this automatically
<div>{userTextContent}</div>
// When HTML rendering is unavoidable, sanitize first:
const cleanHtml = DOMPurify.sanitize(userHtmlContent)
// Then insert the sanitized result — never insert raw user HTML