Negative Constraints in Prompts: Banned APIs That Safer Codegen Respects

Negative Constraints in Prompts: Banned APIs That Safer Codegen Respects
9 views
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.yaml sourced 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.

Negative Constraints in Prompts: production guidance

yaml
# 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:
text
## 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

typescript
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}`);
 }
 }
}
typescript
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

python
# 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 Related: Code Generation with LLMs: Structured Output, AST Manipulation, and Avoiding Hallucinated APIs; Claude as a Code Generation Engine: System Prompts and Patterns That Ship Production Code; REST API Design Best Practices: The Patterns That Make APIs a Joy to Use.

Watch: 100 Days of AI on YouTube

Short videos from CheatCoders, one AI engineering topic per day.

Open the playlist on YouTube · CheatCoders on YouTube

Deep-dive PDF

Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.

Already subscribed? or open the subscribe page.


Discover more from CheatCoders

Subscribe to get the latest posts sent to your email.

Comments

No comments yet. Why don’t you start the discussion?

Leave a comment

No account needed. Name and email are optional.