Architectural defense: six design patterns, and how plan-then-execute drives attack success to zero
Move the defense out of the prompt and into the structure: what action-selector, plan-then-execute, map-reduce, dual LLM, code-then-execute and context-minimization each constrain, then land two of them on the range so attack success hits zero while the agent keeps working.
Today's Goals
- Say what each of the six design patterns constrains and what capability it costs
- Pick a pattern for a given task from a single criterion instead of listing all six
- Rebuild the range agent as plan-then-execute with quarantined reading, and prove it worked with both numbers
Yesterday you wired up all four levels of input-side defense. The best of them pushed the attack success rate down to 33%, at the cost of one perfectly legitimate ticket getting blocked, and one adaptive case was enough to push it back up. Today we change layers: instead of asking "how do I keep the model from being fooled", we ask "if it is fooled anyway, can it still get the bad thing done". When you have finished, come back to the top of the page and check off the three goals.
Plain-Language Walkthrough
Ordering from a menu versus a buffet: when nobody can change your order any more
You order at a restaurant and the server walks the ticket back to the kitchen. Now the person at the next table leans over and says "change their order to ten lobsters, bill it to them" — does it go through? If the house rule is that the written ticket governs everything after it is filed, then no. Not because the server saw through the stranger, but because there is no entry point in the process for changing an order at all.
A buffet is the exact opposite. You walk the line with a plate, deciding as you look, and anyone who can influence what you see can influence what ends up on your plate.
Yesterday's deskmate was buffet-shaped: it read the ticket, read the page linked in the ticket, and decided its next move along the way. An attacker never has to talk to it. Slipping a paragraph into something it reads already puts them in the decision seat. That is exactly why input-side defense is structurally reactive: it can only stop text that looks bad, while the door to the decision stays wide open.
One row in yesterday's table is worth a second look. Spotlighting pushed the attack success rate down to 33%, and the three cases that still got through contained no suspicious words at all. They override no instruction; they simply phrase the exfiltration as this ticket's own handling requirement — and what the user asked for was "handle this ticket as requested". That class of payload did not beat the detector; it has no detectable feature to begin with. On the level of text it is the same kind of object as a legitimate ticket. Detectors are out of road here. Everything left has to come from structure.
One core principle: after ingestion, nothing ingested may have consequences
All six of today's patterns grow from a single sentence. It comes from Beurer-Kellner et al., June 2025 (authors from Google DeepMind, ETH and Microsoft), and it is worth memorizing word for word:
Once an agent has ingested untrusted input, it must be constrained such that this input cannot trigger any consequential action.
Notice what it does not say. It does not say "keep the model from being deceived", it says "being deceived must not matter". That is a constraint on capability, not a hope about judgment. Judgment is probabilistic; capability can be nailed down in code.
Translated into something you can build, it becomes two sentences: either the actions are fixed before ingestion, or the call that ingests holds no tools. All six patterns are combinations of those two.
Start from the counter-example, which is the shape of yesterday's code:
// Yesterday's flow: whatever it reads is what decides the next move
const blocks = [{ label: 'Ticket body (from an external submitter)', content: ticketBody }]
const link = /https?:\/\/\S+/.exec(ticketBody)?.[0]
if (link) blocks.push({ label: 'Fetched web page', content: await fetchPage(link) })
// One call holds both the untrusted content and the full tool list
const actions = await model.decide({ prompt: buildPrompt(level, request, blocks), tools: ALL })
for (const a of actions) await tools[a.tool](a) // whatever the ticket says, it may do# Yesterday's flow: whatever it reads is what decides the next move
blocks = [{"label": "Ticket body (from an external submitter)", "content": ticket_body}]
link = re.search(r"https?://\S+", ticket_body)
if link:
blocks.append({"label": "Fetched web page", "content": fetch_page(link.group(0))})
# One call holds both the untrusted content and the full tool list
actions = model.decide(prompt=build_prompt(level, request, blocks), tools=ALL)
for a in actions:
TOOLS[a.tool](a) # whatever the ticket says, it may doThe bug is in no single line. It is in the parameter list of decide: untrusted content and the tool list show up in the same call. The engineering cost lives there too — fixing it means changing the control flow, not adding a filter function. Which is why this kind of rework belongs on an iteration plan rather than in a drive-by patch.
Action-selector and plan-then-execute: results that never flow back, and actions fixed up front
Action-selector is the most conservative of the six. The model only translates one sentence of natural language into one predefined action, and once that action runs, its result never flows back to the model. Think of a self-service ordering kiosk: you say "cancel this order", it becomes one cancellation request, and whatever text the cancellation endpoint returns is never handed to the model to read. Because nothing flows back, indirect injection has no route in at all. The price is blunt: it can do one-shot actions only, never multi-step work, which amputates the most valuable part of an agent.
Plan-then-execute loosens that slightly: multiple steps are allowed, but the list of actions is fixed before any untrusted data is read. After that, whatever it reads can change the contents of each step's parameters, but cannot add, remove or alter the steps themselves.
// The planner's parameters do not include ticketBody. That omission IS the pattern.
function plan(request, recipients) {
const steps = [{ tool: 'search_customers' }]
if (request.includes('write back to the ticket')) steps.push({ tool: 'update_ticket' })
// Recipients come from the user's pre-authorization, not from the ticket
for (const to of recipients) steps.push({ tool: 'send_email', target: to })
return steps
}
// The executor only turns planned steps into actions; what was read is data, nothing more
function runPlanned(input, recipients = []) {
const steps = plan(input.request, recipients)
const read = quarantinedRead(input.request, input.ticketBody)
return steps.map((s) => ({ ...s, note: `read ${read.summary.length} chars, used as data only` }))
}# The planner's parameters do not include ticket_body. That omission IS the pattern.
def plan(request: str, recipients: list[str]) -> list[dict]:
steps = [{"tool": "search_customers"}]
if "write back to the ticket" in request:
steps.append({"tool": "update_ticket"})
# Recipients come from the user's pre-authorization, not from the ticket
steps.extend({"tool": "send_email", "target": to} for to in recipients)
return steps
# The executor only turns planned steps into actions; what was read is data, nothing more
def run_planned(inp: dict, recipients: list[str] | None = None) -> list[dict]:
steps = plan(inp["request"], recipients or [])
read = quarantined_read(inp["request"], inp["ticket_body"])
return [{**s, "note": f"read {len(read.summary)} chars, used as data only"} for s in steps]The signature of plan is the wall. Today's lab includes a mutation check where you hand ticketBody to the planner yourself — the attack success rate climbs straight back, and you get a very concrete look at where the wall was standing. The price is that once the plan is fixed, it cannot change its mind based on what it reads: if the ticket says "legal needs to see this first", the agent will not improvise an extra step.
LLM map-reduce and the dual LLM pattern: the privileged model never sees the raw text
LLM map-reduce targets the case where a lot of untrusted material has to be read. Each shard goes to an isolated sub-agent that holds no tools and may only return structured fields — a boolean plus a one-line summary, say — and the main agent sees only those fields. It is peer review: a hundred submissions go to a hundred reviewers, each returns one fixed-format score sheet, and the editor never reads a manuscript. The price is weaker cross-shard reasoning: a conclusion that requires holding shard 3 and shard 87 side by side is out of reach in this shape.
The dual LLM pattern generalizes the same idea, and it is the half that actually lands in today's lab: the privileged model never sees the raw text. It receives a symbolic reference instead ("variable a holds the page we just fetched"), while the model that does see the raw text holds no tools whatsoever.
// This call can see the raw text, but its tool list is empty. No tools, no consequences.
function quarantinedRead(request, ticketBody) {
const blocks = collectUntrusted(ticketBody)
const wanted = model.decide({ prompt: buildPrompt(3, request, blocks), tools: [] })
// Record what it WANTED to do, purely so you can see that it really was hijacked
const hijackAttempts = wanted.filter((a) => a.tool === 'send_email' || a.tool === 'post_webhook')
return { summary: summarize(blocks), hijackAttempts } // only strings leave this function
}# This call can see the raw text, but its tool list is empty. No tools, no consequences.
def quarantined_read(request: str, ticket_body: str) -> QuarantinedRead:
blocks = collect_untrusted(ticket_body)
wanted = model.decide(prompt=build_prompt(3, request, blocks), tools=[])
# Record what it WANTED to do, purely so you can see that it really was hijacked
hijack_attempts = [a for a in wanted if a.tool in ("send_email", "post_webhook")]
return QuarantinedRead(summary=summarize(blocks), hijack_attempts=hijack_attempts) # only strings leaveThe price is orchestration complexity: you now maintain a symbol table, a cross-model data flow, and a set of rules for when a reference may be dereferenced. If you want to see what the industrial version looks like, read CaMeL (arXiv 2503.18813) — dual LLM plus data provenance tracking as a complete system, where every value carries a label saying where it came from. You are not expected to reproduce it, but one read gives you an accurate sense of how much engineering "doing it completely right" actually costs.
Code-then-execute and context-minimization: a plan you can check statically, and context deleted after use
Code-then-execute is plan-then-execute with the volume up: instead of emitting a list of steps, the model writes a small program that your own interpreter runs. The win is that the program can be statically checked before it runs — the data flow is explicit, and "where does the value returned by the customer database eventually end up" has a definite answer rather than a guess based on the model's own prose explanation. The price is very real: you have to build the interpreter and the policy engine, making this the heaviest of the six.
Context-minimization is the lightest: once a piece of untrusted text has done its job, delete it from the context so it never enters later turns. Use it to produce a database query, then drop the original and let subsequent turns see only the query result. Its security motivation is plain: every piece of untrusted text still sitting in the context is another dice roll on every later turn — surviving turn one says nothing about turn seven.
The selection criterion: does this task actually need to change its actions based on untrusted content
With all six laid out, the real question is not "which one do I pick" but do not reach for all of them at once. There is exactly one criterion, and it is a question you can answer on the spot:
Does this task actually need to change its actions based on untrusted content?
For the large majority the answer is no. Summarize a ticket, classify a ticket, write the key points of a linked page back into the ticket — in all of those, untrusted content shapes the text produced, not which things get done. Reach straight for plan-then-execute: small engineering cost, almost no capability lost.
Only a "yes" is worth paying for the dual LLM pattern. The typical shape is "read this document, and if it mentions a refund, start the refund process" — the action itself depends on the content. There, what you want is for the privileged side to see one controlled symbol (an enum value, say) rather than the raw text.
There is also a third answer: "yes, but it can be turned into a no." That is the best-value route, and it is the last line of today's lab. With pure plan-then-execute, the legitimate copy request in case N-03 got dropped along with everything else — the ticket asked to copy the risk-control team, and the plan had no such step. Move the recipient allowlist to user pre-authorization, and the capability comes back while attack success stays at 0. A lot of requirements that "must depend on the content" simply evaporate when you change the source of trust.
Put the bill on the table: every pattern buys safety with capability
Finally, the six patterns next to their invoices. Any security proposal that lists only benefits is incomplete — including in interviews, where being unable to name the cost tells the interviewer you have never shipped one.
| Pattern | What it constrains | Price |
|---|---|---|
| Action-selector | Results never flow back to the model | One-shot actions only, no multi-step work |
| Plan-then-execute | Action list fixed before ingestion | Cannot change its mind based on what it reads |
| LLM map-reduce | Each shard goes to an isolated sub-agent returning structured fields | Weaker cross-shard reasoning |
| Dual LLM | The privileged model sees only symbolic references | High orchestration complexity |
| Code-then-execute | Emits a program you can check statically | You must build the interpreter and policy engine |
| Context-minimization | Deleted once used | Reduces exposure, is not isolation |
The table today's lab prints is that invoice made physical:
D2 level 3: spotlighting 40% 100% kept A-07 A-08 A-09 A-10
D2 level 4: markers plus detector 10% 83% dropped A-10
plan-then-execute 0% 100% dropped (none)
plan-then-execute + pre-authorized 0% 100% kept (none)Three things to say out loud. First, attack success went from 40% to 0 and it was not smarter detection that did it, it was structure. Second, the third row shows task completion at 100% while the legitimate-copy column says dropped — safety really is paid for in capability, and that column is the bill. Third, the last row buys that capability back with attack success still at 0. That is what a competent rework looks like: both numbers, every time, neither one alone.
Source Reading
Hands-On Lab
Before you start, remember one thing: the first run of the starter reports 0% attack success and 0% task completion. Do not celebrate — that is exactly the anti-pattern called out yesterday, an agent rebuilt to do nothing at all. The two numbers only mean something together. If you get stuck, read the three TODO comments in starter/src/agent/planned.ts; they point at the traps rather than at the answers.
- Run the solution first and read the whole four-row table plus the A-07 walkthrough at the bottom, until you can point at which row was yesterday's best result and which row is today's output.
- In the starter, fill in the planner plan: its parameters are the user request and the pre-authorized recipients only, and it cannot see the ticket body — do not add the ticket body, because adding it voids the entire pattern.
- Fill in the quarantined read quarantinedRead: call the model once, record the outbound sends it wanted, then throw every action away and return a string. Running it should show a non-zero hijack count.
- Fill in the executor runPlanned so it only turns planned steps into actions. Once that line is written, attack success on the last two rows should hit zero together.
- Run two mutation checks: pass the ticket body into the planner and let it append sends as the ticket asks, then change the executor to run the actions the quarantined read returned. Both must push attack success back up; if either does not, your control arm is not wired in and the 0% in the table is fake.
Interview Questions
Today's 3 questions sit in the bank below, weighted towards what each of the six patterns constrains and costs, the selection criterion, and the essential difference between architectural and prompt-level defense. Expand each one and read the analysis before the key points — question 3 asks when you would refuse to use these patterns, which is where "has read the paper" separates from "has shipped it", so do not skip it. The "common in China / common globally" tags let you triage by target market.
Checklist and Tomorrow
- I can say what each of the six design patterns constrains and what capability it costs
- I can pick a pattern for a given task from a single criterion instead of listing all six
- I can rebuild the range agent as plan-then-execute with quarantined reading and prove it with both numbers
- I can recite the core principle word for word and explain that it constrains capability, not judgment
- I can explain the difference between "hijacked but with no consequences" and "resisted the attack"
- All 5 acceptance criteria pass, and both mutation checks pushed attack success back up
Tomorrow (D4) we loosen the assumption one more notch: assume the injection already happened and the architecture has a seam, then use the runtime to contain the loss. You will declare a capability and a risk tier for every tool, put an approval gate in front of irreversible actions, put allowlists on the filesystem and the network, split "sandbox" into the policy layer you write yourself and the isolation layer the kernel provides, and finally handle secrets, per-user credentials and tenant isolation. The order is deliberate: today's layer keeps the bad action from ever being proposed, tomorrow's keeps a proposed one from executing — you need both, so that no single oversight in either takes the whole line down.
Interview questions
Why does plan-then-execute stop indirect prompt injection, and what does it fail to stop?先定计划后执行为什么能挡住间接注入?它挡不住什么?
Common in ChinaCommon overseasIntermediate#prompt-injection#architecture#plan-then-executeHow to reason about it · think before answering
- The real question is the second half. Answering only the first half reads as paper-deep: anyone who has shipped this pattern has been bitten by the capability it removes.
- First half, one step of reasoning: injection works because untrusted content participates in deciding which actions to take. Plan-then-execute moves the action list before ingestion, and the planner's signature simply does not include the ticket body, so the text can only change the content of each step, never the steps themselves.
- So what it blocks is the whole class of 'add a new action', such as an outbound send that was never planned.
- The second half has two layers. The capability cost: once the plan is fixed, a legitimate 'please cc risk control' in the ticket gets dropped too. That is the definition of the pattern, not a bug. More importantly, it does not stop the content of planned actions from being poisoned: if the plan already writes a conclusion back to the ticket, injection can make that conclusion wrong or misleading.
- Land it in one sentence: risk shrinks from 'any action' to 'parameters of planned actions'. That is containment, not elimination, and the remainder is covered by runtime egress allowlists and confirmation gates.
- Expected follow-up: how do you buy the lost capability back? Not by loosening the plan, but by changing the source of trust — let the user pre-authorize the recipient list so the ticket has no say. Capability returns, attack success rate stays at zero.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。只答前半句的人一律被判成「读过论文没落地过」——因为任何一个真上过线的人,都被它砍掉的能力咬过一次。
- 前半句的推导只有一步:注入之所以能得手,是因为不可信内容参与了「做哪几件事」的决策;先定计划后执行把动作清单挪到了摄入之前,计划器的参数表里根本没有工单正文,那段文字再巧妙也只能改变每一步的参数内容,改不了步骤本身。
- 所以它挡住的是「新增一个动作」这一类,比如凭空多出一次对外发送。
- 后半句同样有两层。第一层是能力代价:计划定死之后,工单里那句合理的「请抄送风控」也会被一起丢掉——这不是 bug,是这个模式的定义。第二层更要紧:它挡不住计划内动作的**内容**被污染,比如计划里本来就有一步写回工单,注入就可以让写回去的那段结论是错的、误导人的。
- 结论给成一句话:它把风险从「任意动作」压缩到「计划内动作的参数」,是收缩不是消除。剩下的那部分要靠运行时的出口白名单与确认门去兜。
- 可预期的追问是「那怎么把被砍掉的能力买回来」。答案不是放宽计划,而是换信任来源:把收件人白名单改成由用户预授权,工单说了不算——能力回来了,攻击成功率仍然是 0。
Key points
- The action list is fixed before any untrusted content is ingested; the planner never sees the ticket body, so injection cannot add actions.
- It does not stop the parameters or output of planned actions from being poisoned, such as the conclusion written back to the ticket.
- The cost is that it cannot change its mind based on what it reads, so legitimate ad-hoc requests get dropped too.
- Frame it as containment, not elimination; residual risk is handled at runtime by egress allowlists and confirmation gates.
- Buy the lost capability back by changing the source of trust: let the user pre-authorize the allowlist instead of the untrusted content.
答题要点
- 它把动作清单定死在摄入不可信内容之前,计划器看不到工单正文,注入因此无法新增动作。
- 它挡不住计划内动作的参数与产出被污染,比如写回工单的那段结论本身被带偏。
- 它的代价是无法根据读到的内容改主意,合理的临时请求也会被一起丢掉。
- 正确的定位是风险收缩而不是消除,剩余风险交给运行时的出口白名单与确认门。
- 被砍掉的能力靠换信任来源买回来:白名单由用户预授权,而不是由不可信内容指定。
What is the fundamental difference between dual-LLM isolation and adding a detector?双模型隔离和加一个检测器,本质区别在哪?
Common in ChinaCommon overseasDeep dive#dual-llm#detector#threat-modelingHow to reason about it · think before answering
- This probes whether you separate probabilistic defenses from structural ones. Answering 'detectors are not accurate enough, dual-LLM is more thorough' turns it into a question of degree, but the difference is one of kind.
- Unpack it by asking: does this defense still hold after the attacker rewrites the payload? A detector depends on bad input having a detectable signature, and once the signature is public, attacks grow around it — adaptive attacks have done this systematically. Dual-LLM isolation depends on no signature at all: the model that reads the raw text holds no tools, so what it wants is irrelevant.
- A reusable formulation: a detector guesses whether the input is good or bad; isolation limits what can happen after you are fooled. The former fails by false negatives, the latter by orchestration bugs such as dereferencing a symbol too early.
- Volunteer the counterintuitive observation: under isolation the model still gets fooled. On the range, the quarantined model was hijacked five times and genuinely intended to send the customer list out — it simply had no key in its hand. 'Our model resisted the attack' is a different kind of safety, and teams that conflate the two get burned on the first model upgrade.
- Conclusion: keep the detector as a noise filter and an alerting signal, but it is not a boundary. Only structure can be a boundary.
- Expected follow-up: what does dual-LLM cost? Orchestration complexity — the symbol reference table, cross-model data flow, and the rules for when a reference may be dereferenced are all yours to maintain; a full implementation like CaMeL additionally tags every value with its provenance.
分析过程 · 先想清楚再作答
- 这题在考你区不区分「概率性防御」和「结构性防御」。答成「检测器准确率不够高、双模型更彻底」就落进了程度之争,而这两者的差别不是程度,是种类。
- 拆的方式是问一句:攻击者换一种写法之后,这道防御还成立吗?检测器的有效性建立在「坏输入有可被识别的特征」上,特征一旦公开,攻击就会绕着它长——自适应攻击已经系统性地做过这件事。双模型隔离不依赖任何特征:看原文的那个模型手上没有工具,它想做什么都不重要。
- 所以一个可复用的判断句是:**检测器是在猜输入是好是坏,隔离是在限制被骗之后能干什么。** 前者的失败模式是漏报,后者的失败模式是编排写错了、把引用解开了。
- 这里要主动讲一个反直觉的实测现象:隔离结构下的模型**照样会上当**。在靶场里隔离模型被劫持了五次,它真的打算把客户名单发出去,只是它手上没有那把钥匙。「我们的模型抗住了攻击」和这是两种完全不同的安全性,把它们混为一谈的团队会在第一次模型换版时翻车。
- 结论:检测器可以留着当降噪层与告警源,但它不是边界;边界只能由结构给。
- 可预期的追问是「双模型隔离的代价」。答:编排复杂度——符号引用表、跨模型数据流、以及什么时候允许解开引用的规则,都要你自己维护;CaMeL 那套完整实现还要给每个值挂来源标签。
Key points
- A detector is probabilistic and assumes bad input has a detectable signature; once published, adaptive attacks grow around it.
- Dual-LLM isolation is structural: the model reading raw text has no tools, so being fooled does not change what it can cause.
- Different failure modes: detectors fail by false negatives, isolation fails by orchestration bugs or dereferencing a symbol too early.
- Under isolation the model still gets fooled; 'no consequence' and 'not fooled' are different kinds of safety.
- Keep detectors for noise reduction and alerting, never as the boundary; the boundary is structural, and it costs orchestration complexity.
答题要点
- 检测器是概率性的,依赖坏输入有可识别特征;特征公开后自适应攻击会绕着它长。
- 双模型隔离是结构性的,看原文的模型没有工具,被骗与否不改变它能造成的后果。
- 两者失败模式不同:检测器失败于漏报,隔离失败于编排写错或过早解开符号引用。
- 隔离下模型照样会上当,「没造成后果」和「没上当」是两种完全不同的安全性。
- 检测器可以留作降噪与告警,但不能当边界;边界只能由结构给,代价是编排复杂度。
When would you decline to apply these patterns and accept the risk instead?什么情况下你会拒绝使用这些模式,宁可接受风险?
Common in ChinaCommon overseasBasic#risk-tradeoff#lethal-trifecta#architectureHow to reason about it · think before answering
- This is asked in reverse, and it tests whether you treat security as engineering. Anyone who immediately says 'security first, apply them all' fails: every one of the six patterns trades capability for safety, and applying all of them ships a useless product.
- Unpack it from the lethal trifecta: remove any one of the three edges and the chain is broken. So the first case for declining is an incomplete trifecta — the agent touches no private data, or has no outbound channel at all. Adding dual-LLM isolation there is pure cost.
- Second case: consequences are reversible and auditable. If every action is something like writing a conclusion back to a ticket, you can roll back and trace it, so spend the budget on audit logs and rollback rather than isolation orchestration.
- Third case: the value structure does not hold. An internal tool where the operator is the only data subject and the untrusted content comes from that same person — attacker and user are the same party, so the threat model does not apply.
- Be precise about the shape of the refusal: you decline a specific pattern, not defense itself. Still run the selection rule — does this task need to change actions based on untrusted content? Usually no, and then plan-then-execute costs almost nothing. There is no reason to decline a defense that cheap.
- Expected follow-up: how do you report this decision upward? Write it as a conditional record: the justification is an incomplete trifecta or reversible consequences, and the moment someone adds an outbound tool to this agent the record expires and must be re-evaluated. A security decision has to hang on a condition that will actually fire, not on a one-time verbal judgment.
分析过程 · 先想清楚再作答
- 这题是反着问的,考的是你有没有真的把安全当工程做。张口就说「安全无小事、必须全上」的人会被直接判掉——六个模式每一个都在用能力换安全,全上等于把产品做废。
- 拆的方式是先回到致命三件套:三条边缺任意一条,这条攻击链就断了。所以第一类可以拒绝的场景是**三件套不全**——Agent 压根碰不到私有数据,或者它没有任何对外通信能力,那么为它引入双模型隔离就是纯成本。
- 第二类是**后果可逆且可审计**:动作全是写回工单这种可逆操作,出事能回滚、日志能定位,那么把预算花在审计与回滚上比花在隔离编排上更划算。
- 第三类是**收益结构不成立**:内部工具、使用者本人就是唯一的数据主体、并且不可信内容只来自他自己。这时「攻击者」和「用户」是同一个人,威胁模型不成立。
- 但要说清拒绝的正确形式:拒绝的不是「防」,是「这一个模式」。选型判据仍然要走一遍——先问这个任务需不需要根据不可信内容改变动作,多数答案是不需要,那用先定计划后执行几乎没有能力损失,这种便宜的防御没有理由拒绝。
- 可预期的追问是「那你怎么向上汇报这个决定」。答:写成一条带前提的记录——拒绝的依据是三件套不全或后果可逆,一旦哪天给这个 Agent 加了对外发送工具,这条记录就自动失效、必须重评。**安全决定必须挂在一个会被触发的条件上,不能只是一次口头判断。**
Key points
- All six patterns trade capability for safety, so declining one is a legitimate engineering choice rather than negligence.
- Decline when the lethal trifecta is incomplete: no private data or no outbound channel means the chain is already broken.
- Decline when consequences are reversible and auditable; rollback and audit logging are the better use of budget.
- You decline a specific pattern, not defense in general — plan-then-execute is cheap enough that refusing it is rarely justified.
- Record the decision with an expiry condition: the moment an outbound capability is added, the assessment must be redone.
答题要点
- 六个模式都在用能力换安全,全上等于把产品做废,所以拒绝本身是合法的工程选择。
- 三件套不全时可以拒绝:没有私有数据或没有对外通信,攻击链本来就断了。
- 后果可逆且可审计时可以拒绝,把预算花在回滚与审计日志上更划算。
- 拒绝的是某一个模式而不是防御本身,便宜的先定计划后执行几乎没有理由拒绝。
- 决定要写成带失效条件的记录:一旦给这个 Agent 加了对外发送能力,必须重新评估。