Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
SAST (Static Application Security Testing), DAST (Dynamic Application Security Testing), and IAST (Interactive Application Security Testing) are the three core automated approaches to finding vulnerabilities in application code, and they find fundamentally different classes of bugs because they look at the application from different vantage points. SAST reads source or bytecode without running it; DAST attacks a running application from the outside, as a black-box HTTP client; IAST instruments the running application from the inside and correlates traffic with the code paths it actually exercises.
Treating these as interchangeable — “we run a scanner, we’re covered” — is one of the most common AppSec program mistakes. Each tool class has a structurally different blind spot: SAST cannot see runtime configuration or environment-specific issues, DAST cannot see code that its crawler never reaches, and IAST only reports on code paths that were actually exercised during a test run. A mature program uses at least SAST and DAST together, gated at different points in the pipeline, and adds IAST or DAST-in-CI where the cost is justified by the application’s risk.
Attack Prerequisites
What each tool class needs to operate is itself a useful way to understand their scope:
- SAST needs source code or compiled artifacts (bytecode/IL) and a build graph; it does not need a running application at all, which is why it runs in pre-merge CI.
- DAST needs a deployed, reachable application (usually staging) with enough seeded data and authenticated sessions to crawl meaningfully — a DAST scan against an empty database with no test accounts finds almost nothing.
- IAST needs an instrumented runtime — an agent or bytecode hook injected into the application server (Java, .NET, Node) — plus real or synthetic traffic (usually driven by functional or DAST tests) flowing through it during the scan window.
- All three need tuned rulesets/policies for the target stack, or they either flood reviewers with false positives or say nothing at all.
How It Works
SAST parses source into an abstract syntax tree, builds data-flow and control-flow graphs, and traces taint from untrusted sources (request parameters, file reads, deserialization) to dangerous sinks (SQL execution, shell exec, eval, HTML output) across function and sometimes file boundaries. Because it never executes the code, it can theoretically see every path, including rarely-hit error handlers and admin-only code — but it has no idea what the runtime configuration, framework auto-escaping, or actual network exposure looks like, so a real bug can be reported as a false positive if a downstream sanitizer the tool didn’t model actually neutralizes it, and conversely a theoretically reachable sink that is never actually callable in production still gets flagged.
DAST works like an automated pentester: it crawls the application’s exposed surface (following links, parsing forms/OpenAPI specs, driving an authenticated session), then fuzzes each parameter with attack payloads (SQLi, XSS, path traversal, XXE) and inspects the *response* for evidence of a successful attack — an error signature, a reflected payload, a timing difference for blind injection. Because it observes the real, deployed system, it is largely language-agnostic and it naturally covers runtime/config issues (missing security headers, verbose error pages, misconfigured TLS) that SAST cannot see. Its blind spot is coverage: anything the crawler cannot reach — a multi-step workflow, a state machine that needs a valid prior step, an API only reachable with a specific custom header — is invisible to it.
IAST splits the difference by placing sensors inside the running application (via bytecode instrumentation, similar in mechanism to an APM agent) so it can see real taint flow through real, executed code, while traffic is driven by a functional test suite, a DAST scan, or manual QA. Because it only reports on code that was actually exercised and only flags flows it observed at runtime, its false-positive rate is typically much lower than pure SAST, and its coverage is bounded by whatever traffic drove the application during the test window — untested code paths simply never get analyzed. IAST is usually the most expensive to operate because it requires an instrumented environment as a permanent fixture, not a scheduled scan.
Practical Example / Configuration
A Semgrep SAST rule targeting a classic taint-flow bug — untrusted request data reaching subprocess with shell=True in Python — shown as a real, runnable rule file (shell-injection.yaml):
rules:
- id: flask-request-to-shell-injection
languages: [python]
severity: ERROR
message: >-
Untrusted Flask request data flows into subprocess with shell=True.
Use shell=False with an argument list, or shlex.quote() if a shell
is unavoidable.
metadata:
cwe: 'CWE-78: OS Command Injection'
owasp: 'A03:2021 - Injection'
mode: taint
pattern-sources:
- pattern: request.args.get(...)
- pattern: request.form.get(...)
- pattern: request.json.get(...)
pattern-sinks:
- patterns:
- pattern: subprocess.$FUNC(..., shell=True, ...)
YAMLRun it: semgrep --config shell-injection.yaml src/ — this flags a call like subprocess.run(f"convert {request.args.get('file')}", shell=True) before it ever ships. Contrast with what a DAST scan of the *same bug* looks like once deployed: it never sees the source, it sends ; id in the file parameter and infers command execution from response timing or content, which only works if that endpoint is in the crawl scope and actually reachable without extra setup.
A comparison of what each tool class would report for this one bug:
- SAST (Semgrep): flags
subprocess.run(..., shell=True)at build time, before deploy, with exact file/line — zero traffic required. - DAST (e.g. OWASP ZAP active scan): only fires if the crawler reaches the
/convertendpoint with valid auth and the payload actually executes observably in the response. - IAST agent: confirms the exact same taint flow SAST theorized, but only if a test (functional, DAST, or manual) actually sent a request through that endpoint during the instrumented run — otherwise it reports nothing at all.
Walkthrough / Implementation
A practical pipeline places each tool where its cost/latency profile fits:
1. Pre-commit / pre-merge (fast, source-only):
semgrep --config p/owasp-top-ten --error --baseline-commit=origin/main
Blocks the PR only on NEW findings vs. the baseline (avoids a wall of
legacy findings blocking day-one adoption).
2. Nightly / on-merge-to-main (slower, deeper):
Full-repo SAST scan (all rulesets) + dependency SCA scan, results filed
as tickets rather than blocking, triaged by AppSec weekly.
3. Staging, post-deploy (DAST):
zap-baseline.py -t https://staging.internal -r report.html
(or an authenticated zap-full-scan.py run with a seeded session)
4. Staging, continuous (IAST, high-risk apps only):
Java agent attached via -javaagent:iast-agent.jar, correlates the same
QA/functional traffic already running through the app.
TEXTThe key operational discipline is triage routing: SAST and SCA findings go to the owning engineering team as PR comments or backlog tickets; DAST findings on staging usually go to a security queue for confirmation before filing, since black-box confirmation of exploitability still needs a human for anything beyond the clearest signatures.
Note: None of the three replaces manual penetration testing or code review. Business-logic flaws — a workflow that lets a discount be applied twice, an authorization check that is correct per-endpoint but wrong across a multi-step transaction — are essentially invisible to all three tool classes, which only reason about known vulnerability *patterns*, not intended behavior.
Note: SAST tool quality varies enormously by ruleset maturity per language. A generic “OWASP Top Ten” pack tuned for Java Spring will produce very different signal-to-noise on a Go or Rust codebase; budget time to write or tune custom rules (as above) for your actual stack rather than trusting default packs alone.
Detection and Defense
“Defense” here means building a program where each tool’s blind spot is covered by another control:
- Layer, don’t pick one — SAST in CI for fast source-level feedback, DAST against staging for runtime/config issues SAST cannot see, IAST or targeted pentesting for the highest-risk applications where both are worth the cost.
- Gate on diff, not absolute count — block PRs on new findings versus a committed baseline; blocking on total findings from day one kills adoption.
- Feed SCA (software composition analysis) in parallel — none of SAST, DAST, or IAST reliably find vulnerable third-party dependencies; that is a distinct scan class (Grype, Snyk, Dependabot) run against the SBOM.
- Route findings to owners with context — a SAST finding with file/line and a proposed fix gets resolved; an unowned PDF report does not.
- Track escape rate — vulnerabilities found in pentest or production that none of the automated tools caught are the signal to invest in coverage, not raw finding counts.
Real-World Impact
OWASP’s Application Security Verification Standard and Testing Guide both treat SAST, DAST, and IAST as complementary rather than substitutable, and most compliance frameworks that mandate application testing (PCI DSS Requirement 6, for instance) explicitly expect both static and dynamic testing rather than either alone. In practice, security teams that rely on a single tool class consistently report a class of production incidents the tool structurally could not have caught — config-only issues missed by SAST, or unreachable-by-crawler endpoints missed by DAST — which is the strongest real-world argument for layering.
Conclusion
SAST, DAST, and IAST are not competing products but three different vantage points on the same application, each with a coverage model shaped by what it can see: source without execution, execution without source, or instrumented execution bounded by test traffic. Build a pipeline that places each where its cost and latency fit, tune rulesets for your actual stack, and remember that all three are pattern-matchers — business logic still needs a human.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments