Sections in this lesson
Direct & Indirect Prompt Injection
Direct input, a retrieved document, a tool result or repository text may contain instructions that conflict with the task.
Reviewed: · Content owner: Interview Preparator
What you will learn
- Untrusted text is not authority
- Constrain effects instead of trusting refusal
- Use controlled evaluations
Before you start
The flow
- 1.Identify scope
- 2.Inspect evidence
- 3.Apply host control
- 4.Run negative cases
- 5.Compare benign behavior
- 6.Review remaining risks
Untrusted text is not authority
Direct input, a retrieved document, a tool result or repository text may contain instructions that conflict with the task. Injection redirects behavior; a jailbreak concerns bypassing a model behavior restriction. Either can affect an application, but they are not synonyms.
Mark each input origin and keep privileged decisions in the host. A system prompt can describe policy, yet cannot authenticate a caller or prevent an already privileged process from sending data.
Constrain effects instead of trusting refusal
The fixture proposes unsupported tool actions such as shell or export. The host rejects the action schema before any effect; it never exposes those capabilities. This demonstrates the application boundary, not a classifier detecting every malicious sentence.
Compare allowed read operations with denied proposals. A defense that rejects all requests can look secure while being unusable. Keep benign task success next to attack outcomes.
Use controlled evaluations
Injected fixtures contain synthetic text and no real secrets. Inspect attempted actions and resource scopes, not just the final response. No external attacker endpoint is contacted by this lab.
Real model resistance requires adversarial natural-language cases, repeated inference and independent annotation. A prompt change that improves one fixture may fail another; version policy, prompt, tools and corpus together.
Try it
import { authorize, payloadDigest } from './policy.mjs';// Teaching host: no shell, network, or arbitrary-file tools are exposed.export class TeachingHost { constructor({ scope, resource, grant, maxCalls = 3, maxTokens = 100, deadline = 1000, epoch = 1 }) { if (!Number.isSafeInteger(maxCalls) || maxCalls < 1 || !Number.isSafeInteger(maxTokens) || maxTokens < 1 || !Number.isFinite(deadline)) throw new Error('Invalid budget'); this.scope = Object.freeze({ ...scope }); this.resource = structuredClone(resource); this.grant = grant ? structuredClone(grant) : null; this.maxCalls = maxCalls; this.maxTokens = maxTokens; this.deadline = deadline; this.epoch = epoch; this.calls = 0; this.tokens = 0; this.mutations = 0; this.receipts = new Map(); this.trace = []; } execute(call, callId, reservedTokens, now) { if (!Number.isSafeInteger(reservedTokens) || reservedTokens < 1 || !Number.isFinite(now) || !/^[a-z0-9-]{1,80}$/.test(callId)) throw new Error('Invalid operation'); if (now >= this.deadline) return this.record('TIMEOUT'); const permission = authorize(this.scope,call,this.resource,this.grant,now,this.epoch); if (permission !== 'ALLOW') return this.record(permission); const digest = payloadDigest(call); const cached = this.receipts.get(callId); if (cached) return this.record(cached.digest === digest ? 'REPLAY' : 'PAYLOAD_MISMATCH'); if (this.calls >= this.maxCalls || reservedTokens > this.maxTokens - this.tokens) return this.record('BUDGET_EXCEEDED'); this.calls++; this.tokens += reservedTokens; if (call.action === 'completePlan') this.mutations++; this.receipts.set(callId,{ digest }); return this.record('EXECUTED'); } record(status) { this.trace.push({ status, calls:this.calls, tokens:this.tokens, mutations:this.mutations }); return status; } stopAndRevoke() { this.epoch++; this.grant = null; }}// Cache keys include authorization scope and corpus version; cache values still// require current authorization at read time (revocation/deletion can change).export function cacheKey(scope, corpusVersion, question) { return JSON.stringify([scope.tenant,scope.user,corpusVersion,question]); }export function memoryRead(record, scope, version) { return record.tenant === scope.tenant && record.user === scope.user && record.conversation === scope.conversation && record.trusted === true && record.version === version ? record.text : null;}Run npm run examples:security and npm run eval:security. This source is a deterministic teaching fixture; it does not prove live model safety, NLI, production auth or OS/network isolation.
Interview questions
Is prompt injection identical to a jailbreak?
Mark each input origin and keep privileged decisions in the host. A system prompt can describe policy, yet cannot authenticate a caller or prevent an already privileged process from sending data.
Why inspect effects instead of refusal text?
Compare allowed read operations with denied proposals. A defense that rejects all requests can look secure while being unusable. Keep benign task success next to attack outcomes.
What must a live injection evaluation add?
Real model resistance requires adversarial natural-language cases, repeated inference and independent annotation. A prompt change that improves one fixture may fail another; version policy, prompt, tools and corpus together.
Practice
Extend one negative scenario and its authorized positive counterexample for Direct & Indirect Prompt Injection.
Check your solution
- State the trusted input and effect boundary.
- Assert the expected status and observed effect.
- Report coverage and limitations without claiming production readiness.
Official references
Continue learning
Category sequence
AI Security
Lesson 2 / 14
Was this lesson useful?
Change notes: 2026-10-31: security/reliability curriculum and source-bound fixture.