Bug bounty AI broker: enforce scope outside the model
A scope-gated AI broker for bug bounty should refuse work it cannot bind to a current, human-verified program policy. Keep analysis permission separate from network permission. A model that can discuss an asset should not automatically receive the ability to contact it.
Our earlier broker pattern article explains splitting a planner from a model worker. Its teaching script is not a production enforcement boundary. Regex host extraction, broad suffix checks and an end-date check alone miss too many ways for a task to escape its intended scope.
This application adds a versioned policy envelope, explicit action types, start and end times, deny precedence and fail-closed decisions. It still needs a separate execution boundary. All examples are synthetic and offline; they do not authorize testing any real asset.
turn the policy into a pinned task envelope
Store the policy snapshot separately from the task. A trusted human checks the scope interpretation and approves its revision. The model must not edit its own permissions or select a newer policy just because the old one blocks a request.
{
"program_id": "local-invoice-lab",
"policy_hash": "COMPUTE_SHA256_OF_APPROVED_POLICY",
"valid_from": "2026-11-05T21:00:00-08:00",
"valid_until": "2026-11-05T22:00:00-08:00",
"target_urls": ["https://invoice.example.test:443/api/invoices/lab-A"],
"allowed_actions": ["compare_saved_evidence"],
"exclusions": ["https://payments.example.test"],
"evidence_ids": ["E01", "E02"],
"mode": "analysis_only"
}
The hash placeholder is intentional: calculate it over the exact approved policy bytes. A digest pins a version; it does not prove the policy is authentic or the interpretation is correct. Bind the expected digest through trusted configuration, not a field the model supplies unchecked.
Don't allow a free-text task to smuggle in a second URL outside target_urls. Better, make the worker's interface accept only evidence IDs and a small action enum. Reject additional fields rather than quietly interpreting them as instructions.
analysis permission is not network permission
An analysis-only worker receives sanitized local content. It has no shell, browser, target credentials or network tool. Its output is a proposal or comparison, not an action token.
If a person later approves a specific request, a separate executor checks the policy again at the moment of use. Approval should bind the exact method, origin, path, body hash, actor and budget. A changed request needs a new approval. A queued action can become invalid after a policy change or expiry.
RedRouter's tool-calling documentation, checked November 5, 2026, makes the application boundary explicit: RedRouter returns tool calls; your application executes them. A JSON schema or strict hint is not authorization to run the proposed function.
enforce host rules outside the model
Use a standards-aware URL parser, not regex extraction from prose. For a narrow interface, allow only canonical HTTPS origins and explicit ports. Reject credentials in URLs, malformed ports, ambiguous encodings, fragments and unsupported address forms. Normalize host case and IDNA according to one documented rule before matching.
Exact hosts are the simplest default. invoice.example.test is not invoice.example.test.attacker.test. A wildcard *.example.test means a label-boundary subdomain match if the approved policy says so; it need not include the apex. badexample.test doesn't match. Apply exclusions before inclusions, and reject empty target lists.
Paths, accounts and methods matter too. A permitted hostname isn't blanket approval for payment actions, admin features or third-party account access. For an intentionally narrow broker, reject unrecognized path forms rather than attempting clever normalization of every encoded URL.
a fail-closed decision sketch
The following is design pseudocode, not a complete proxy or runnable security library:
validate(envelope, trusted_policy, current_time):
require exact schema; reject unknown or missing fields
require nonempty explicit targets and known evidence IDs
require envelope.policy_hash == trusted_policy.approved_hash
require trusted_policy.start <= current_time < trusted_policy.end
require envelope window is within trusted policy window
require envelope.mode == analysis_only
require actions are a subset of analysis actions in trusted policy
for each target:
parse and canonicalize using strict supported URL rules
reject ambiguous address, credentials, encoding or port
deny if any trusted exclusion matches
require explicit origin and path inclusion
return analysis permission only; never execute a request
Use parsed, timezone-aware timestamps. Reject missing offsets, inverted windows and times before the start as well as after expiry. Don't let a fresh envelope extend an old policy's validity.
test hostile cases before connecting tools
The companion offline teaching implementation passed 14 local tests on November 5, 2026 (PT), including these cases. It supports only a deliberately narrow DNS/HTTPS input shape and makes no network connections. These results are not a claim that a production broker or redirect handler was tested:
| Input | Expected result |
|---|---|
| Exact approved host and path, valid window | Analysis may proceed |
invoice.example.test.evil.test | Deny |
a.example.test under an explicitly approved wildcard | Match only if label and path rules pass |
| Same host in inclusion and exclusion | Deny |
| Empty target array or malformed timestamp | Deny |
| Request before policy start or at end time | Deny |
| Redirect to payment host | Deny network follow; request separate policy review |
| IPv6 URL when policy supports DNS hosts only | Deny, not a string-match workaround |
| Encoded host or credential-bearing URL | Deny |
| Unknown evidence ID or action | Deny |
If IPv6 is needed, support it deliberately with an IP parser, bracket/port handling and an approved address policy. Don't add it by replacing punctuation until a string happens to match.
a parser still isn't a network sandbox
A worker might mention an alias, a nested URL or an encoded target that a text parser misses. An execution service must validate the actual destination it connects to. Redirects need fresh checks at every hop. DNS resolution and address changes need controls that prevent a permitted name from resolving to an unapproved destination at connection time.
Use a validating egress proxy or equivalent network boundary, block unneeded protocols and credentials, enforce request budgets, and prevent alternate network paths. Keep human approval for active actions. A prompt file cannot supply these controls.
Claude Code's permissions reference distinguishes behavioral guidance from enforced tools and sandboxing. OpenCode's permission reference likewise documents allow/ask/deny settings, while warning through its documented defaults that many operations are permitted unless configured. Neither harness replaces a program-specific target policy.
keep logs useful and small
Log policy digest, task ID, action enum, evidence references, approval decision and stop reason. Avoid credentials and full private bodies. Protect the evidence store separately; hashes of predictable secrets are not a safe way to retain those secrets.
Where external processing is permitted, choose an explicit model from the catalog, send only a bounded redacted task and review API limits. Zero data retention is opt-in, off by default and free on monthly plans; usage metadata remains.
What we still don't know: these offline checks haven't been exercised against a real executor, DNS boundary or proxy. Treat this as a reviewable architecture, not production-ready sandboxing. Keep the worker analysis-only until those boundaries have their own implementation and tests.