Office Hours — Should you use regex or other methods to enforce security constraints on LLM agent outputs?
A daily developer question about AI/LLMs, answered with a direct, opinionated take.
Should you use regex or other methods to enforce security constraints on LLM agent outputs?
No, not as your primary defense. Regex and simple string-matching guards are useful for catching obvious mistakes, but they create a false sense of security and miss the actual vulnerability surface. The real problem is that agents output semantically plausible violations—things that pass syntactic checks but do the wrong thing.
The Regex Trap
You can write a regex that blocks DROP TABLE or rm -rf, and it will catch the dumb cases. But an agent asked to “optimize the database” might generate ALTER TABLE users MODIFY password_hash VARCHAR(10) instead. Syntactically clean. Semantically catastrophic. Your regex won’t touch it.
Or worse: an agent given API access might generate a request that looks structurally valid but includes credentials in the query string instead of headers, or requests a permission it shouldn’t have. The string passes validation. The semantic intent is wrong.
The Daily Signal flagged this directly. On May 22, researchers documented that sharing .env files with LLMs for agent execution creates an entirely different vulnerability surface than training-time data exposure. Agents don’t just repeat training data—they reason about credentials and take actions with them. A regex blocking cat .env won’t stop an agent from passing secrets in an API call or writing them to logs.
What Actually Works: Capability-Level Controls
Effective constraint enforcement happens at the capability level, not the output level.
Instead of parsing agent outputs and hoping your regex catches violations, restrict what the agent can do:
- Don’t give agents raw database connections. Give them a restricted query builder that only allows
SELECTon certain tables, with a schema layer that prevents column access. The agent can’t violate what it can’t express. - Don’t give agents credential files. Inject secrets via environment variables or credential managers with fine-grained access control. The agent has no path to exfiltrate what it can’t read.
- Don’t give agents shell access. Give them a sandboxed execution environment (like Daytona or similar bare-metal sandbox products seeing 74% month-on-month growth) where dangerous commands fail at the kernel level, not because you regex’d them away.
The architectural pattern emerging in production is clear: pair LLM reasoning with deterministic guardrails at the infrastructure boundary, not the output boundary. This is the “hybrid AI” approach mentioned in the May 23 Signal. The guard doesn’t parse what the agent outputs; it controls what execution paths are available.
When Output-Level Checks Do Help
Output validation isn’t useless, but it’s a last resort, not a primary defense.
Use regex or structured output schemas (like JSON Schema in the API) for:
- Catching hallucinations that don’t match expected format. If you ask an agent to return a JSON object with specific fields and it returns garbage, reject it and retry.
- Sanitizing obvious noise before it hits critical systems. If an agent outputs a string meant to be a database identifier and it contains
; DROP TABLE, block it. But know this is catching mistakes, not threats. - Enforcing that outputs match a controlled vocabulary. If an agent must choose from a list of actions, use structured outputs to force that choice at generation time, not parse time.
The key is understanding what you’re actually defending against. Regex catches sloppy outputs. It doesn’t catch reasoning errors that are syntactically valid.
Real Example: Database Query Agent
Imagine an agent that generates SQL. You could write:
import re
def is_safe_query(sql: str) -> bool:
dangerous = ["DROP", "DELETE", "TRUNCATE", "ALTER"]
for word in dangerous:
if re.search(rf"\b{word}\b", sql, re.IGNORECASE):
return False
return True
This will catch DROP TABLE users. It won’t catch UPDATE users SET admin=1 (a valid vulnerability). It won’t catch SELECT * FROM users WHERE id = 1 OR 1=1 (SQL injection that passes regex). It won’t catch SELECT password_hash FROM users LIMIT 1000000 (permission violation that’s syntactically fine).
Better approach:
from sqlalchemy import create_engine, MetaData, Table, select
# Agent only gets this, not raw SQL
class QueryBuilder:
def __init__(self, engine, allowed_tables):
self.engine = engine
self.metadata = MetaData()
self.allowed_tables = {
name: Table(name, self.metadata, autoload_with=engine)
for name in allowed_tables
}
def select_from(self, table_name):
if table_name not in self.allowed_tables:
raise ValueError(f"Table {table_name} not allowed")
return select(self.allowed_tables[table_name])
The agent can’t generate DROP because the API doesn’t have a drop method. It can’t access tables it shouldn’t because they’re not in allowed_tables. The constraint is structural, not syntactic.
Bottom line:
Regex and string-matching guards are hygiene, not security. They catch typos and test failures, not adversarial or reasoning-driven violations. Build constraints into your agent’s execution environment by restricting capabilities at the infrastructure boundary, not by parsing outputs. Use output validation as a second layer to catch hallucinations, not as your primary defense.
Question via Hacker News