DEV Community

Cover image for Prompt Injection Is a Laravel Problem Now

Prompt Injection Is a Laravel Problem Now

Nazar Boyko on August 10, 2026

Here is a Laravel feature that takes five lines: use Prism\Prism\Facades\Tool; $lookupOrder = Tool::as('lookup_order') ->for('Fetch an o...
Collapse
 
nazar-boyko profile image
Nazar Boyko • Edited

@mark_boyko_1a6cae69fd43d7 that read versus write split is the right instinct and there is one edge case that breaks it which I wish I had put in the article. A read only agent can still write outbound if you render its answer as markdown. Injected text tells the model to end its reply with an image pointing at an attacker domain and to put the order data in the query string. The victim browser fetches it the moment the reply renders. No tool call, no side effect, nothing for a policy to intercept and your logs show a perfectly well behaved read only agent. So the output side is an attack surface too, not just the tools. What helps is rendering agent replies as plain text or allowlisting the hosts for any links and images before you render them, plus a strict img-src in your CSP so the browser refuses the request even if something slips through. This is also the other half of what @sika_vasia_c04f8b19da4964 asked about multi tenant because the transcript itself is the leak, not only the tools attached to it.

Collapse
 
sika_vasia_c04f8b19da4964 profile image
Vasia

That is a nasty edge case 😁 I hadn't considered the browser rendering itself as an outbound channel. It really shows that securing the tools is only half the problem. 🙃

Collapse
 
mark_boyko_1a6cae69fd43d7 profile image
Mark

That is a really good point. I was thinking about side effects mainly in terms of tools but the rendered output itself can become a side channel. The markdown example makes it very clear why output sanitization and CSP need to be part of the agent security model too!

Collapse
 
saturn01n profile image
Olivia

This is a great reminder that prompt injection isn't just about tricking the model. Once the model has access to real tools and data, the security of the whole application becomes part of the problem. Very practical perspective!

Collapse
 
nazar-boyko profile image
Nazar Boyko

Thanks Olivia, that is exactly the shift I hoped would land. The model being fooled is survivable, the damage comes when the deputy acts on it with real permissions. Which is why the fix lives in the application layer, scoping what the agent can touch, not in writing better prompts.

Collapse
 
saturn01n profile image
Olivia

Totally agree. Once the agent has real permissions, prompt quality alone can only get you so far. I’m curious though: in a Laravel app, what do you think is the most practical way to scope an agent’s permissions without making the implementation too complex for developers?

Thread Thread
 
nazar-boyko profile image
Nazar Boyko

Good question, and the nice part is that Laravel already ships the answer. Capture which user the agent is serving, then inside every tool run your existing policies as that user, the Gate facade has a forUser method exactly for checking permissions as someone other than the current session, and scope queries through the user relationship instead of querying models directly. The complexity cost is about one line per tool, because you reuse the policies you already wrote instead of building a new permission system. For anything that writes or sends I would add one more step and treat the model output as a proposal that a person or a policy confirms before it runs.

Thread Thread
 
saturn01n profile image
Olivia

That makes a lot of sense, especially if you can reuse the policies that are already in place instead of creating a separate permission layer for the agent. I also like the idea of treating writes and sends as proposals first. How would you decide which actions should always require human confirmation and which ones are safe to let the agent handle automatically?

Thread Thread
 
nazar-boyko profile image
Nazar Boyko

My rule of thumb is two questions: can you undo it, and does it leave the building. Scoped reads and internal drafts are safe to automate, because the worst case is a wrong answer you can correct. Anything irreversible or outward facing, refunds, customer emails, deletions, external API calls, starts behind confirmation, since one bad trigger there is a real incident instead of a bad draft. And I would start with everything confirmed and relax it action by action as the logs show the agent behaving, it is much easier to remove a confirmation step than to explain why it was missing.

Collapse
 
bb-33023 profile image
BB 33

Thanks for sharing this great article! Really enjoyed reading it and learned a lot. I have also seen how important prompt validation and security checks are when working with AI features in real projects. Keep up the good work!

Collapse
 
nazar-boyko profile image
Nazar Boyko

Thanks, glad it was useful. Validation definitely helps, though the bigger win in my experience is keeping the tool surface small, so there is simply less for a bad prompt to reach.

Collapse
 
bb-33023 profile image
BB 33

Thanks!

Collapse
 
sika_vasia_c04f8b19da4964 profile image
Vasia

Nice work! Really liked the confused deputy comparison here. It makes the problem much easier to reason about than treating prompt injection as just a prompt engineering issue. I especially agree with the idea that every tool should act with the users authority rather than the applications authority. I wonder how you would handle this in a multi tenant Laravel app where the agent can potentially switch between different tenant contexts during the same conversation.

Collapse
 
nazar-boyko profile image
Nazar Boyko

Thanks, glad the framing helped. For multi tenant the first rule I would set is that the tenant is never something the model can pass in, because the moment tenant id becomes a tool argument it is just another injectable value. I bind the tenant and the acting user in the agent constructor, resolve them from the session on every tool call, and scope every query through the tenant relation instead of a global query. The harder half is the conversation itself. If the acting user or the tenant changes mid conversation, I start a fresh conversation rather than continuing, because the old transcript already holds the previous tenant data and the model will happily quote it back without calling a single tool. So the rule ends up being one conversation, one tenant, one acting user.

Collapse
 
jeremy_6a02b3 profile image
Jeremy II

I can be wrong but the point about Laravel policies not automatically protecting AI tools is probably the part many Laravel developers will miss. We are so used to putting authorization in controllers and middleware that it is easy to forget that an agent tool is effectively creating a new execution path. I would be really interested to see a follow up article showing a reusable Laravel pattern for passing the acting user into every tool without having to implement the same authorization plumbing over and over

Collapse
 
nazar-boyko profile image
Nazar Boyko

Yes, and you put it better than I did: a tool is a new execution path with no route, no middleware and no resolved user in front of it. The pattern that works for me is a small abstract base tool that takes the acting user in the constructor and runs the Gate::forUser check inside handle, then delegates to an abstract method the concrete tool implements. The agent constructor becomes the only place identity enters the system, tools() just passes it down, and no individual tool can forget the check because it never writes it. One trap worth knowing about: agent middleware in the Laravel AI SDK intercepts prompts, not tool calls, so it looks like the natural hook for this and it is not. Good idea for a follow up, it is on my list now.

Collapse
 
jeremy_6a02b3 profile image
Jeremy II

That makes a lot of sense. I like the idea of keeping the identity and authorization flow in one place so individual tools cannot accidentally skip it.

Collapse
 
mark_boyko_1a6cae69fd43d7 profile image
Mark

One thing I have started thinking about with AI agents is that read access and write access should probably be treated very differently. A scoped database lookup is one thing, but sending emails, deleting records or making external requests can have much bigger consequences. I like your proposal of making the model produce a proposed action first and putting policy or human approval between the model and the side effect. That feels much more robust than trying to make the prompt smarter

Collapse
 
nazar-boyko profile image
Nazar Boyko

@mark_boyko_1a6cae69fd43d7 Agreed, and read is the tier people underestimate. A read only agent can still write outbound if you render its reply as markdown. Injected text asks the model to end with an image pointing at an attacker domain and to put the data in the query string, then the victim browser fetches it the moment the reply renders. No tool call, no side effect, nothing for a policy to catch. Rendering replies as plain text and locking img-src in the CSP closes that one.

Collapse
 
__catisback profile image
Cat is Back

Loved the comparison with the confused deputy problem. It makes the risks of AI agents much easier to grasp, especially for developers who are new to AI security. Definitely something worth keeping in mind when building agent-based features.

Collapse
 
nazar-boyko profile image
Nazar Boyko

Thanks! The confused deputy framing does the heavy lifting because the pattern is older than AI, and security folks already solved it once with scoped tokens and capabilities. Once you see the agent as a deputy holding your permissions, the right defenses become much easier to reason about.

Collapse
 
__catisback profile image
Cat is Back

Thanks for sharing!