I Got Hacked Through My Own Workflow (And I Didn't Even Know It)
Admin User
Author
Last year, I built an automated bug-triage system for a client. It was elegant—pull data from Jira, analyze it with an LLM, write structured reports. Simple pipeline. I was proud of it.
Six months later, someone embedded malicious instructions in a ticket description. The payload traveled through three phases of my workflow untouched, sitting quietly inside JSON responses, until it finally reached the execution layer where it executed as intended. I caught it by accident during a code review. The system worked exactly as designed—which was the problem.
That experience completely rewired how I think about security in multi-step systems. I realized I'd been thinking about injection attacks wrong. I was protecting individual steps, not the journey a payload takes between them.
The Real Threat: Lateral Propagation Across Phases
Here's what I missed: a single skill-level input validation doesn't protect you from cross-step injection attacks. When you're building workflows—especially ones involving external data sources, LLM processing, and code execution—you need to think about how a malicious payload travels through your system, not just whether it's valid at each checkpoint.
Imagine a bug-fix workflow. Attacker embeds instructions in a Jira ticket. Phase 1 reads it, Phase 2 analyzes it, Phase 3 writes code based on the analysis. Each phase sees "legitimate" data. But by Phase 3, the attacker's instruction is embedded in the code execution layer. The payload transformed as it moved—maybe wrapped in JSON, then in markdown, then in a prompt—which makes detection harder.
The attack I caught worked exactly like this. Nobody's validation logic was wrong. It just wasn't designed to catch data that had been laundered through legitimate business logic.
Four Defense Principles I Actually Use Now
Sanitization at the boundary. External data enters once, gets structured, and stays structured. Raw text from external systems doesn't propagate downstream. When a later phase needs that text, it comes wrapped with explicit data-boundary markers. I use simple XML-like tags: <external_data> and declare handling rules in the prompt. The tag isn't magic—it's the input/instruction boundary from basic prompt injection defense, applied at every node.
# Bad: raw description bleeds through
jira_data = fetch_jira(ticket_id)
analysis = analyze_bug(jira_data['description']) # ❌
# Good: extract structured fields, isolate raw text
jira_data = fetch_jira(ticket_id)
structured = {
'key': jira_data['key'],
'severity': jira_data['severity'],
'attachments': jira_data['attachment_urls']
}
analysis = analyze_bug(
structured,
external_description=jira_data['description']
)
# The analyze_bug prompt wraps external_description in <external_data> tags
Per-phase permission minimization. Different phases do different things. Phase 1 (read analysis) shouldn't have network access. Phase 3 (code execution) shouldn't write to workflow metadata. I explicitly declare permission scopes in every subagent's task prompt and actually enforce them at the execution layer.
High-impact operation gating. Not every operation needs human approval—that kills automation. But git pushes, external writes, and config modifications need explicit permission declarations and audit trails. Some operations can auto-execute with logging; some need gates.
Real sandboxing, not just promises. This is where I learned a hard lesson. Declaring permissions in a prompt helps, but it's not enforcement. I use E2B or Docker for actual isolation now. If sandboxing isn't available, I treat permission declarations as a fallback, not a solution.
My Take: This Scared Me Into Better Architecture
What hit hardest about revisiting this problem: each individual step in my original workflow was defensible. The vulnerability wasn't in any single component—it was in the absence of a security model that accounted for cross-phase data propagation.
I now design workflows with explicit threat models: where does external data enter, what transformations happen to it, and what's the farthest it can travel if undetected? That's a different security conversation than "is this input validated?"
The audit logging part matters too. After a breach, you need to reconstruct the path a payload took. Structured logs with phase information, operation types, and permission boundaries turned a nightmare investigation into something trackable.
I'm still not paranoid enough, probably. But I'm more honest about it now.
What Would You Do?
If you're building multi-step automated systems—whether with AI agents, webhooks, or microservices—when did you last think about how data travels between steps? Have you caught anything that got past individual validations?
Source: This post was inspired by "Workflow Series (06): Security — Cross-Step Injection Propagation and Four Defense Principles" by Dev.to. Read the original article