Seniors already ban child_process.exec with unsanitized input, aws-sdk v2 in new code, and hard-coded credentials—then the coding agent cheerfully regenerates them. Negative constraints encode those bans in versioned system prompts and in machine-checkable validators so codegen fails closed when it ignores the list.
⚡ TL;DR: Maintain a
banned_apis.yamlsourced from lint rules; inject a compressed NEGATIVES block into system prompts; validate model patches with AST/grep gates before apply; version with Prompt Management. Pair with LLM output validators and Bedrock Guardrails.
Single source of truth
# banned_apis.yaml — generated from eslint/deny + security ADRs
version: 17
banned:
- id: no-exec-string
match: { module: "child_process", exports: ["exec", "execSync"] }
reason: "Use execFile with arg arrays; see ADR-0142"
- id: no-aws-sdk-v2
match: { module: "aws-sdk" }
reason: "Use @aws-sdk/* v3 clients"
- id: no-hardcoded-secrets
match: { regex: "(AKIA[0-9A-Z]{16}|secretAccessKey\\s*[:=])" }
reason: "Secrets belong in SSM/Secrets Manager"
- id: no-verifalse-tls
match: { regex: "rejectUnauthorized\\s*:\\s*false" }
reason: "Never disable TLS verification"
deprecated_modules:
- "request"
- "moment"
CI publishes a compact prompt fragment:
## NEGATIVES (must not generate)
- Do NOT import aws-sdk (v2). Use @aws-sdk/* v3.
- Do NOT call child_process.exec/execSync with string commands.
- Do NOT disable TLS (rejectUnauthorized:false).
- Do NOT embed AKIA… keys or secretAccessKey literals.
If a task seems to require a banned API, explain and propose the approved alternative.
Prompt + validator sandwich
import banned from "./banned_apis.json";
function systemPrompt(task: string): string {
return [
BASE_CODER_PROMPT,
renderNegatives(banned), // versioned fragment
`Task:\n${task}`,
].join("\n\n");
}
function assertNoBanned(patch: string): void {
for (const rule of banned.banned) {
if (rule.match.module && importUses(patch, rule.match)) {
throw new Error(`banned:${rule.id}:${rule.reason}`);
}
if (rule.match.regex && new RegExp(rule.match.regex).test(patch)) {
throw new Error(`banned:${rule.id}:${rule.reason}`);
}
}
}
async function safeApply(task: string) {
const patch = await generatePatch(systemPrompt(task));
try {
assertNoBanned(patch);
} catch (e) {
// One repair loop with the violation named
const repaired = await generatePatch(
systemPrompt(task) + `\nREPAIR: ${String(e)}. Use approved alternatives only.`,
);
assertNoBanned(repaired); // fail closed on second violation
return applyPatch(repaired);
}
return applyPatch(patch);
}
Do not rely on prompt-only bans without AST/grep gates — models drift; lint is the backstop.
Keep negatives short and current
| Practice | Why |
|---|---|
| Cap NEGATIVES block ~40–60 lines | Long lists get ignored |
| Generate from lint deny lists | Avoid prompt/lint drift |
| Version + changelog | Know what prod agents saw |
| Prefer “use X instead” | Models need alternatives |
Rotate deprecated items into the list when eslint bans land; remove entries once the codebase cannot import them anymore (noise reduction).
Eval the negatives
# canary: agent must refuse or rewrite banned patterns
cases = load_yaml("evals/banned_api_tasks.yaml")
for c in cases:
patch = run_agent(c["task"])
assert not violates(patch, banned), c["id"]
assert c["approved_alternative_marker"] in patch
Wire into CI canaries so prompt edits that drop NEGATIVES fail loud.
Closing checklist
Dos
– Generate NEGATIVES from the same source as lint deny rules
– Inject a short versioned fragment into system prompts
– Validate patches with AST/regex gates; one repair then fail closed
– Offer approved alternatives in the prompt text
– Canary banned-API tasks on every prompt release
Donts
– Do not rely on prose bans alone
– Do not paste a 500-line policy into every prompt
– Do not let prompt and eslint diverge
– Do not silently strip banned lines without telling the user
– Do not forget secret regexes (keys, tokens) in the same list
Related reading
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
