RZ-2026-2973
Working noteAutonomous offensive agents: scope guardrails
Allowlists, DNS rebinding, redirects, rate limiting, destructive action classes, kill switch, audit log and the human role for autonomous testing agents.
Abstract
An autonomous testing agent that sends requests to real systems must stay within the authorised scope, including when the model driving it makes a mistake or is manipulated by the target’s content. This note describes an architecture in which guardrails are enforced outside the model, on every request: declarative scope, address resolution and pinning, redirect revalidation, rate limiting, graded action classes, lease-based kill switch, hash-chained audit log and the human role. It lists the known pitfalls and the limits of the approach.
- Reference
- RZ-2026-2973
- Status
- Working note
- Published
- Revised
- Author
- rootzero
- Reading
- 9 min read
Contents 10 sections
- 1. Context and hypothesis
- 2. Architecture
- 3. Scope as a declarative object
- 4. Pitfalls of scope checking
- 4.1 Host name comparison
- 4.2 Discrepancies between URL parsers
- 4.3 DNS rebinding and address notations
- 4.4 Redirects
- 4.5 Target content
- 5. Rate and action classes
- 5.1 Rate limiting
- 5.2 Action classes
- 6. Kill switch and audit log
- 6.1 The lease-based kill switch
- 6.2 The audit log
- 7. Where the human fits
- 8. Testing the guardrails
- 9. Limits
- References
1. Context and hypothesis
An autonomous offensive agent combines a language model, which plans, with tools that act: HTTP client, browser, interpreter. Unlike a scanner, it chooses for itself which paths, parameters and links to explore.
The risk is then that it leaves the authorised scope, through a planning error, an overly broad reading of an instruction or under the influence of the target’s content. The OWASP Top 10 for LLM Applications describes the two mechanisms at play: prompt injection, including indirect injection through content processed by the model (LLM01:2025), and excessive agency, when a system has more capabilities or autonomy than it needs (LLM06:2025). The document Agentic AI, Threats and Mitigations, published by the OWASP GenAI Security Project in February 2025, details threats specific to agents, including tool misuse, repudiation and lack of traceability, and overwhelming of human oversight.
The working hypothesis of this note is that the model is not a security boundary. An instruction written in a prompt (“only test app.example.com”) expresses an intent that nothing forces the model to respect. Guardrails should therefore be enforced by a deterministic component, outside the model, on every action actually executed. This note proposes an architecture; it does not describe a deployed proxy. The task leases in our PentestAI research workspace do not filter network requests.
2. Architecture
The reference architecture separates three roles.
- The agent plans and formulates actions (an HTTP request, a command). It has no direct network access.
- The policy enforcement point, in practice a mandatory outbound proxy, receives each action, checks it against the scope and the rules, executes or refuses it, and logs its decision.
- The controller, out of the agent’s reach, holds the scope, issues short-lived authorisations and carries the kill switch.
Network isolation is the precondition for everything else: the agent can only reach the proxy, otherwise a direct call would bypass every rule. Checks apply to the real request, at the moment it leaves.
3. Scope as a declarative object
The scope is a versioned document derived from the client’s written authorisation. Here is an example structure, with fictional domains and an address range reserved for documentation (RFC 5737):
scope_id: "SC-2026-014"
authorized_by: "client, authorisation letter dated 2026-10-01"
window: { start: "2026-10-12T08:00:00+02:00", end: "2026-10-16T19:00:00+02:00" }
hosts:
exact: ["app.example.com", "api.example.com"]
wildcard: ["preprod.example.com"] # explicitly allows *.preprod.example.com
networks: ["198.51.100.0/24"] # allowed resolution addresses
schemes: ["https"]
ports: [443]
exclude_paths: ["/logout", "/account/delete", "/payment/*"]
rate: { per_host_rps: 5, max_concurrency: 4 }
max_action_class: "C2" # see section 5
This scope is closed: anything not listed is refused. It is also twofold, since an authorised host name must still resolve to an authorised address.
4. Pitfalls of scope checking
The pitfalls below are documented, and testers routinely exploit them in SSRF flaws. The OWASP Server-Side Request Forgery Prevention cheat sheet covers most of them.
4.1 Host name comparison
A naive suffix test, host.endswith("example.com"), accepts malicious-example.com. The comparison must work on complete DNS labels after lower-case and IDN normalisation. This example rejects a trailing dot to avoid a second form of the same name.
from urllib.parse import urlsplit
EXACT = {"app.example.com", "api.example.com"}
WILDCARD = {"preprod.example.com"}
def host_in_scope(url: str) -> bool:
if any(ord(c) < 0x21 for c in url) or "\\" in url:
return False
try:
parts = urlsplit(url)
host, port = parts.hostname, parts.port
except ValueError:
return False
if (parts.scheme != "https" or parts.username or parts.password
or not host or host.endswith(".") or port not in (None, 443)):
return False
try:
host = host.lower().encode("idna").decode("ascii")
except UnicodeError:
return False
if host in EXACT:
return True
return any(host.endswith("." + base) for base in WILDCARD)
This checks only the name and port; it does not replace resolved-address checks or redirect revalidation.
4.2 Discrepancies between URL parsers
Two libraries can read the same URL differently, because of backslashes, user information, encodings or Unicode characters. Orange Tsai gave a systematic demonstration of this at Black Hat USA 2017 in A New Era of SSRF. The resulting rule is to parse the URL once and open the connection from the result of that single parse.
4.3 DNS rebinding and address notations
An authorised name can resolve to an authorised address when it is checked, then to an internal address when the connection is made. This DNS rebinding is a classic time-of-check to time-of-use (TOCTOU) case. The countermeasure is to resolve once, check every address obtained and connect to the checked address, passing the original name in the Host header and in the TLS SNI extension.
import ipaddress
import socket
NETWORKS = [ipaddress.ip_network("198.51.100.0/24")]
class ScopeViolation(Exception):
pass
def pin_address(host: str, port: int = 443) -> str:
infos = socket.getaddrinfo(host, port, proto=socket.IPPROTO_TCP)
addrs = {ipaddress.ip_address(info[4][0]) for info in infos}
if not addrs or not all(any(a in net for net in NETWORKS) for a in addrs):
raise ScopeViolation(f"{host} resolves out of scope: {sorted(map(str, addrs))}")
return str(sorted(addrs, key=lambda a: (a.version, int(a)))[0])
This fragment validates addresses but does not make the pinned connection: the client must use the returned address while retaining the validated name for Host, SNI and certificate verification. The same discipline covers unusual notations (decimal addresses such as 2130706433, octal, IPv4-mapped IPv6), provided the reasoning always applies to the parsed address and never to the string. Some ranges stay forbidden whatever the scope, unless explicitly stated: loopback, link-local addresses (including 169.254.169.254, used by cloud providers’ metadata services) and, more generally, the special-purpose ranges listed in RFC 6890.
4.4 Redirects
An HTTP client that follows redirects automatically can leave the scope in a single response, such as 302 Location: https://other.example/. Automatic following is therefore disabled. Every Location goes through the full set of checks again, and the number of hops is capped. Status codes 307 and 308 preserve the request method and body (RFC 9110, section 15.4): a redirected POST remains a POST.
4.5 Target content
Tested pages contain links to third parties and sometimes text written to manipulate an agent. This content cannot change the scope, which lives in the enforcement point, out of the prompt’s reach. Out-of-band interaction tests, notably for SSRF, use a collection domain controlled by the tester and declared in the scope.
5. Rate and action classes
5.1 Rate limiting
An agent stuck in a loop can generate the load of a denial of service. The proposed proxy should apply a token bucket per host and a cap on concurrent requests. It should back off on 429 Too Many Requests and 503 Service Unavailable, honouring Retry-After when it is provided. It should cut the flow (circuit breaker) if the server error rate or the target’s latency rises markedly compared with the baseline measured at the start of the test.
5.2 Action classes
Requests do not all carry the same potential for damage. The proposed scale has five classes for the enforcement point to apply.
| Class | Nature | Examples | Default policy |
|---|---|---|---|
| C0 | Passive read | Known pages, static files | Allowed |
| C1 | Non-mutating probe | Parameter altered on a read, unusual header | Allowed |
| C2 | Reversible mutation | Creating a test object on a test account | Allowed if the scope provides for it |
| C3 | Irreversible or visible mutation | Deletion, password reset, email sent to real users, payment | Human approval, action by action |
| C4 | Impact on availability | Heavy load, account lockout, intensive fuzzing | Forbidden unless explicitly stated |
The HTTP method is insufficient to classify a request. RFC 9110 defines GET as a safe method, and many applications ignore that semantics (GET /admin/user/42/delete). The class is derived from the method, the path, the scope exclusions and the nature of the payload. When in doubt, the higher class applies.
6. Kill switch and audit log
6.1 The lease-based kill switch
A stop that the agent can reach, ignore or delay guarantees nothing. The proposed mechanism relies on a lease. The proxy would accept a request only if it carries a valid, short-lived authorisation issued by the controller, which renews it for as long as the test is authorised. To stop, the controller would stop renewing and revoke the current lease. New requests would be refused by its expiry, provided the agent has no network path around the proxy.
A controller failure should stop new requests. It cannot undo operations already sent to the target. The expiry interval and this limit need testing before real use.
6.2 The audit log
In this architecture, each proposed action would be logged before the decision, then completed with the decision and the rule applied. A hash chain would make changes to preserved entries detectable, provided a trusted anchor is protected:
{"seq":1842,"ts":"2026-10-13T10:42:07.311Z","scope":"SC-2026-014","agent":"run-7f3a",
"action":{"method":"GET","url":"https://app.example.com/preview?name=%7B%7B7*8%7D%7D","class":"C1"},
"decision":"allow","rule":"hosts.exact","resolved":"198.51.100.24","status":200,
"prev":"sha256:9c1e…","hash":"sha256:4b07…"}
This log would connect requests that passed through the proxy to a decision. It cannot prove that no request bypassed the proxy; that requires correlation with network or target logs. Responses containing personal data are kept in it only as far as necessary.
7. Where the human fits
Human decisions come in at two points: defining the scope and approving class C3 actions. Findings then go through an independent, replayed verification before publication (see note RZ-2026-8084).
A reviewer asked a hundred times an hour ends up approving without reading; OWASP lists this overload among the threats specific to agents. Three rules limit that risk. Each request is presented with its context (exact request, class, reason given by the agent). Similar requests are grouped. And a request left unanswered when its deadline expires is refused.
8. Testing the guardrails
Before deployment, a suite of adversarial cases should be automated and rerun on every change to the enforcement point. All of them must be refused:
- a host outside the list (
other.example) and a fake suffix (malicious-example.com); - a URL with user information, a backslash or a trailing dot;
- an authorised name whose resolution changes between two requests, served by a test DNS server;
- an address in decimal, octal or IPv4-mapped IPv6 notation;
- a 302 redirect, then a 307, to an out-of-scope host;
- an excluded path reached through encoding (
/account/%64elete); - a target page containing instructions asking to test another domain;
- a request sent after the lease has been revoked.
9. Limits
The policy knows nothing of business semantics: an innocuous-looking endpoint can trigger billing. The scope is therefore built with the client, and class C3 acts as a safety net.
The technical scope translates the legal scope without replacing it. Under French law, fraudulently accessing or remaining in an automated data processing system is an offence under article 323-1 of the Code pénal (the French Criminal Code). The client’s written authorisation defines the test, the scope file transcribes it, and any ambiguity is resolved in favour of restriction.
On a CDN or shared hosting, an authorised range may also serve other customers: checking by address complements checking by name without replacing it.
The class scale and the lease mechanism are still being tested, hence the working note status.
In the terms of the NIST AI Risk Management Framework (govern, map, measure, manage), the scope and the classes belong to governance, the adversarial tests to measurement, and the kill switch and the log to management.
References
- OWASP GenAI Security Project, OWASP Top 10 for LLM Applications 2025: LLM01 Prompt Injection, LLM06 Excessive Agency.
- OWASP GenAI Security Project, Agentic AI, Threats and Mitigations, February 2025.
- NIST, AI Risk Management Framework (AI RMF 1.0), NIST AI 100-1, 2023, and Generative AI Profile, NIST AI 600-1, 2024.
- OWASP, Server-Side Request Forgery Prevention Cheat Sheet.
- O. Tsai, A New Era of SSRF: Exploiting URL Parser in Trending Programming Languages, Black Hat USA 2017.
- IETF, RFC 9110, HTTP Semantics; RFC 6890, Special-Purpose IP Address Registries; RFC 5737, IPv4 Address Blocks Reserved for Documentation.
Cite this note
rootzero. “Autonomous offensive agents: scope guardrails”. Research note RZ-2026-2973, rootzero, 8 October 2026. https://rootzero.eu/en/research/offensive-agents-scope-guardrails/
#Method in practice
Every published finding can be replayed.
SpectrAI applies these principles to web and API pentesting: declared scope, demonstrated findings, automatically verified evidence, replayable re-tests.