Runtime Security Testing
Static analysis and dependency scanners check what code says. Some security issues only show up when the code runs: a query that becomes injectable once real input reaches it, an error page that leaks a stack trace, a new dependency that opens a connection to the internet when it's imported. Finding those takes a running target, and before merge there usually isn't one. Teams scan a shared staging environment after merge instead, where findings from many changes are mixed together and it's hard to tell which change caused which finding.
A Signadot sandbox gives every change its own running target. It deploys only the services the change touches, routes requests that carry its routing key to them, and serves everything else from the baseline. Point a security tool at the sandbox and every finding traces back to that one change, while the developer or coding agent that wrote it is still working on it.
This matters more as coding agents write more of the code. An agent can create a sandbox, run a scanner against it, read the report and fix what it finds, in the same loop it already uses for functional tests.
What you can catch before merge
Runtime testing complements the static checks (SAST, dependency and image scanning) you already run in CI. It covers what they can't see:
| Issue class | Examples | How it's detected | Open source tools |
|---|---|---|---|
| Injection | SQL injection, OS command injection, path traversal, cross-site scripting | Active DAST against the sandboxed service | ZAP, Nuclei |
| Security misconfiguration | Missing security headers, insecure cookie flags, verbose error pages and stack traces | Passive DAST | ZAP |
| Exposed endpoints and known CVEs | Debug or admin routes left enabled, exposed config files, vulnerable framework versions | Template-based scanning | Nuclei |
| API input handling | Server errors on malformed input, responses that violate the schema, operations that don't enforce authentication | Schema-driven API fuzzing | Schemathesis |
| Broken authorization between users | One user or tenant reading or changing another's data (IDOR, BOLA) | End-to-end tests that act as two identities | Your testing framework |
| Risky runtime behavior | Unexpected outbound connections, shells spawned by the app, binaries written and run at runtime | Runtime threat detection | Falco, Tetragon |
| LLM and agent vulnerabilities | Prompt injection, jailbreaks, an agent misusing its tools | AI red teaming | promptfoo, garak |
How a security tool reaches a sandbox
Which pattern to use depends on whether the tool sends requests or watches the workload.
Scanners in the cluster: run as a Signadot Job
Package the scanner as a Signadot Job.
The Job runs in a runner pod in your cluster, with the routing key of the
target sandbox in the SIGNADOT_ROUTING_KEY environment variable.
The scanner targets the in-cluster address of the baseline service, such as
http://orders.shop.svc:8080, and adds the routing key to each request:
baggage: sd-routing-key=<routing key>
Requests with this header reach the sandboxed workload, and requests without
it reach the baseline. If a scanner can't set its own headers, set
trafficManager.injectRoutingKey: auto
in the Job spec. A proxy next to the Job then adds the routing headers to all
of its outbound plain HTTP and gRPC traffic. It doesn't touch TLS traffic.
The scanner's report is uploaded as a Job artifact. Nothing is exposed outside the cluster, which makes this the right pattern for active scans.
Scanners outside the cluster: target a preview URL
Hosted scanners, and scanners on a developer's machine, can target a sandbox's
preview URL. The preview URL adds
the routing key itself. The scanner only needs to send a Signadot API key in
the signadot-api-key header. Use a key from a dedicated
service account rather than a personal
key.
Runtime sensors: match the sandbox's pod label
Runtime detection tools watch system calls on each node instead of sending
requests, so they can't see the routing key header. Instead, the Signadot
Operator (v1.4.0 or later) labels every forked pod with
signadot.com/sandbox-routing-key, set to the sandbox's routing key. Scope
rules or alerts to that label. Then drive traffic through the sandbox with
your existing tests, so the changed code paths actually run.
Keep active scans contained
Passive scans and runtime sensors only observe. Active scans and fuzzers send attack payloads and write junk data. A sandbox only deploys the services that changed: the sandboxed workload receives the payloads, but its calls to baseline services and shared databases are real. Before you run an active scan against a sandbox:
- Start passive. A passive scan or a set of runtime rules catches a lot and can't damage anything. Make active scans opt-in.
- Scope the scan to the changed service. Point the scanner at the sandboxed service or its OpenAPI definition, not at a frontend that fans out to every baseline service.
- Isolate the data it writes. Give the sandbox its own database or schema with a resource plugin or database branching, so injected rows never land in shared data.
- Fork what the scan reaches. If the changed service writes to another service the scan will exercise, add that service to the sandbox too. When a change spans several sandboxes, scan them together through a route group.
- Use a dedicated job runner group. A job runner group sets the image, security context and Kubernetes permissions its Jobs run with, and only Signadot admins can create one. Keep scanners in their own runner group, with no more permissions than the scan needs.
Set up this way, an agent or a CI pipeline can run aggressive tests on every change without touching the shared environment other teams depend on.
Run it in the agent loop and in CI
The same Job spec serves coding agents and CI.
Coding agents. An agent creates a sandbox for its change through the
Signadot MCP server or the CLI. Then it submits the
scan Job with the CLI, downloads the report artifact, fixes what the report
shows, and runs the scan again until it's clean. The PR goes up with the clean
report as evidence. To make this routine, add it to the agent's instructions,
for example in AGENTS.md or CLAUDE.md:
## Security checks
Before opening a PR that changes an HTTP handler in services/orders:
1. Create or update a Signadot sandbox for the change.
2. Run `signadot job submit -f .signadot/security/zap-api-scan.yaml --set sandbox=<sandbox> --set target=http://orders.shop.svc:8080 --attach`.
3. If the Job fails, download the report with
`signadot artifact download --job <job-name> /zap/wrk/zap-report.html`,
fix every High-risk finding, and re-run until the Job passes.
The Job spec in this example comes from the ZAP tutorial.
CI. The workflow that creates a sandbox for each pull request (see
CI/CD integrations) submits the same Job with
--attach. The Job fails when the scanner exits with a failure, which fails
the PR check, and the report stays attached to the Job for reviewers.
Integration guides
📄️ ZAP
Run ZAP DAST scans against Signadot sandboxes as Signadot Jobs to catch SQL injection, cross-site scripting and security misconfigurations in each change, during local development and as a pull request check.
📄️ Falco
Run Falco against Signadot sandboxes to catch risky runtime behavior, such as a dependency phoning home or a handler spawning a shell, in each change before merge, with a Signadot Job as the pass/fail check.
Other open source tools
The same patterns work for any tool that can add a request header or filter by pod label:
| Tool | What it tests | How to target a sandbox |
|---|---|---|
| Nuclei | Template-based checks for exposed endpoints, misconfigurations and known CVEs | Run as a Job and pass -H "baggage: sd-routing-key=$SIGNADOT_ROUTING_KEY" |
| Schemathesis | Property-based API fuzzing from an OpenAPI or GraphQL schema | Run as a Job and pass -H "baggage: sd-routing-key=$SIGNADOT_ROUTING_KEY" |
| Tetragon | eBPF runtime observability and enforcement | Select pods with the signadot.com/sandbox-routing-key label using podSelector in a TracingPolicy |
| promptfoo | Red teaming for LLM apps and agents | Point its HTTP target at the preview URL with a signadot-api-key header, or run it as a Job with the routing header |
Commercial DAST, IAST and runtime security products connect the same way: through a request header, a preview URL, or pod labels.