Hysn Logo
Back to Blogs
Practical DevSecOps Resources

SAST vs DAST vs IAST: 3 Testing Methods Compared — Which Belongs in Your Pipeline First

Varun KumarVarun Kumar 8 min read 12 May 2026
SAST vs DAST vs IAST

Every team knows these three acronyms. Far fewer deploy them correctly. SAST runs on code that can't actually execute, so it can't see runtime behavior. DAST needs a live application and has no visibility into your source code. IAST sits in the middle but comes with trade-offs most teams underestimate. Use the wrong one at the wrong stage and you're generating noise instead of signal.

Here's the short version before the detail: run SAST on every pull request to catch code-level issues early, run DAST against a deployed staging environment to find runtime vulnerabilities, and only invest in IAST if you have a mature QA pipeline and can absorb the operational overhead. Most teams need the first two. Very few actually need all three from day one.

What SAST Actually Tests

SAST (Static Application Security Testing) analyzes source code, bytecode, or binaries without executing the program, looking for patterns that indicate security vulnerabilities.

SAST tools parse your code into an abstract syntax tree or control flow graph and apply rules. Semgrep matches patterns directly against the AST. Checkmarx and SonarQube do data flow analysis, tracking tainted input from sources (HTTP request parameters) to sinks (SQL queries, file writes). This catches SQL injection, command injection, and insecure deserialization in a way that pure pattern matching misses.

What SAST misses: anything that only manifests at runtime. Broken authentication, SSRF to an internal metadata endpoint, insecure direct object references, race conditions, and configuration-level issues are invisible to static analysis. A misconfigured CORS header in your nginx config won't show up in a SAST scan of your Python code.

SAST also has a false positive problem that most vendors won't tell you about upfront. Out-of-the-box Checkmarx runs against a Java codebase can produce thousands of findings. Most are noise. Without tuning, developers learn to ignore the tool entirely. I've seen this happen at multiple companies. The scanner becomes a compliance artifact, not a security control.

The tools worth knowing: Semgrep is my go-to for CI integration. It's fast, the rules are readable, and you can write custom rules without a PhD. SonarQube Community covers OWASP Top 10 for most languages. Checkmarx SAST has better data flow analysis but costs significantly more and needs tuning to be useful.

What DAST Actually Does

DAST (Dynamic Application Security Testing) sends real HTTP requests to a running application and analyzes responses to identify vulnerabilities.

Because DAST actually talks to a live app, it can find things SAST can't: authentication bypasses, session fixation, CORS misconfigurations, server-side request forgery, and insecure API endpoints that SAST never sees because they're assembled at runtime. It's also language-agnostic. DAST doesn't care whether your API is Go, Ruby, or PHP.

What DAST misses: anything in code paths the scanner doesn't reach. Authenticated areas behind complex login flows are often under-crawled. Business logic flaws are invisible. And DAST is slow. A full ZAP scan of a complex API can take hours. That's fine for a nightly pipeline against staging. It's not fine for a 10-minute PR gate.

DAST is also useless without a running app. I've watched teams try to configure DAST in their pipeline before they had a staging environment. It ran against localhost and found nothing, because there was nothing to find.

Setting Up Authenticated DAST with OWASP ZAP

The biggest gap in most DAST setups is that the scanner never gets past the login page. Unauthenticated scans miss everything that requires a session: user-specific endpoints, admin panels, account management flows, and any API that checks a JWT before responding. Here's how to configure ZAP for authenticated scanning against a JWT-protected API.

First, generate an auth token outside ZAP and inject it as a header. The simplest approach that works in CI:

yaml
# stackhawk.yml (StackHawk wraps ZAP with better CI config)app: applicationId: your-app-id env: staging host: https://staging.yourapp.comhawk: spider: base: true ajax: false authentication: loggedInIndicator: "\"authenticated\":true" loggedOutIndicator: "\"error\":\"Unauthorized\"" usernameParameter: email passwordParameter: password loginPath: /api/auth/login type: TOKEN_INJECTION tokenInjection: - location: header key: Authorization value: "Bearer ${AUTH_TOKEN}"

In your CI pipeline, you generate AUTH_TOKEN by calling your login endpoint before ZAP runs:

bash
AUTH_TOKEN=$(curl -s -X POST https://staging.yourapp.com/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"[email protected]","password":"'$ZAP_TEST_PASSWORD'"}' \ | jq -r '.token')export AUTH_TOKEN

Use a dedicated test account for DAST. Never use a real user account or an admin account. The DAST scanner will submit forms, click buttons, and hit destructive endpoints. Scope the test account's permissions to exactly what a normal user has. Destructive actions (delete, bulk export) should be excluded from the scan path or the test account should lack permission to execute them.

Tools: OWASP ZAP is free and handles authenticated scanning with its API. StackHawk wraps ZAP into a more CI-friendly package with OpenAPI spec support. Burp Suite Professional is the gold standard for manual testing and has a solid CI scanner (Burp Suite Enterprise) but the price reflects that. For API-heavy teams, 42Crunch does DAST specifically for REST APIs validated against OpenAPI specs.

IAST: Why Most Teams Don't Use It

IAST (Interactive Application Security Testing) instruments your application at runtime, using agents or sensors to observe code execution during testing and flag security vulnerabilities as they're triggered.

The appeal is obvious: IAST gets the accuracy of SAST (it knows which code path executed) with the real-execution context of DAST. False positive rates are dramatically lower. Contrast Security's agent, for example, can identify a SQL injection in the exact function call that's vulnerable, not just flag every place where a query is built.

The reality is harder. IAST requires a language-specific agent running in your application process. That agent adds memory overhead and CPU overhead. I've seen 15-25% performance degradation in JVM apps with Contrast Security enabled. That's acceptable in a dedicated QA environment, but it means you can't run the same artifact in QA that you ship to production. That breaks artifact immutability, which is a problem if you care about supply chain integrity.

Cost is the other issue. Contrast Security, Hdiv (now Sqreen), and Seeker from Synopsys aren't cheap. The ROI calculation only works if you have a QA team running meaningful functional tests that the IAST agent can observe. If your QA pipeline is thin, IAST generates fewer findings than you expect and you've paid a lot for that.

Tuning SAST to Cut False Positives

Most tutorials stop here, which is exactly where things go wrong. A security tool that produces 300 findings per scan and 280 of them are noise teaches developers to click through the scanner output like a cookie banner. The tool is worse than useless.

For Semgrep specifically, false positive management works at three levels.

First, the .semgrepignore file tells Semgrep to skip certain files and directories entirely. It follows the same syntax as .gitignore. Use it to exclude generated code, vendored libraries, test fixtures, and any path you know contains intentional "insecure" patterns (like security test suites that contain SQL injection examples on purpose):

yaml
#.semgrepignorevendor/node_modules/**/generated/tests/fixtures/*_test.go 
# Go test files if you're not scanning test code

Second, inline suppression comments disable a specific rule for a specific line without affecting the rest of the scan:

yaml
# This query is safe: input is an integer cast from a validated form fieldresult = db.execute(f"SELECT * FROM products WHERE id = {product_id}") # nosemgrep: python.lang.security.audit.formatted-sql-query

Use this sparingly. Every suppression comment is a claim that the finding is a false positive. Audit them in code review.

Third, rule severity overrides in your semgrep.yaml let you downgrade noisy rules from error to warning without disabling them. Warnings don't fail the build. They go into a report for later review:

bash
# semgrep.yamlrules: - id: python.django.security.audit.xss.template-autoescape-off severity: WARNING # override from ERROR; legacy templates, tracked in SEC-200 ...

For incremental adoption, combine --baseline-commit with severity filtering. New code gets strict rules. Existing code gets reported but doesn't block. This is the only realistic way to introduce SAST into a codebase that already has 10,000 existing findings from a rule that was never tuned.

For DAST, tune your ZAP config to exclude known false positives. Use .zap/rules.tsv to disable alert IDs that don't apply to your stack. If you're running a React SPA, ZAP's alerts about missing X-Frame-Options on JSON API responses are noise, not findings.

Triage ruthlessly. Set a threshold: block the build on critical and high findings only. Medium and low go to a tracking board, not the build gate. This keeps CI feedback loops tight and prevents the "everything is blocked" paralysis that kills adoption.

Where Each Tool Belongs in Your Pipeline

This is the decision that matters most. Wrong tool placement = wasted time.

StageMethodToolTrigger
Pre-commitSAST (fast rules)Semgrep local scanGit hook
PR openedSASTSemgrep, SonarQubePR event
PR openedSecretsgitleaksPR event
Post-merge to mainFull SASTSonarQube, CheckmarxMerge event
Staging deployDASTOWASP ZAP, StackHawkDeployment hook
QA test runIASTContrast SecurityTest execution
NightlyDAST (full)Burp Suite EnterpriseSchedule

DAST against production is rarely a good idea. You're sending attack traffic to live users. If you do it, use a dedicated test account and scope the scan carefully.

Decision Flowchart: Which Method to Add First

If you're not sure where to start, work through this sequence:

Do you have source code access? If yes, start with SAST. If no (third-party app, compiled binary), go straight to DAST.

Do you have a deployed staging environment? If yes, DAST is available to you. If not, SAST is your only option until one exists.

Is your team already failing CI builds on SAST findings? If yes and the findings are accurate, DAST is the next layer. If yes and the findings are mostly noise, tune SAST before adding another tool.

Does your team run meaningful automated QA tests? If yes and the stack is Java, .NET, or Python, IAST is worth evaluating. If your test coverage is thin, IAST will find little and cost a lot.

Do you have compliance requirements (PCI DSS, SOC 2, FedRAMP)? PCI DSS 6.3.2 specifically requires automated security testing. SonarQube's structured reports satisfy auditors better than raw Semgrep output. Plan accordingly.

The trap most teams fall into is deploying all three at once, getting overwhelmed by findings from different tools with overlapping reports, and abandoning all of them. One tool done well beats three tools done poorly.

Decision Table: What to Use When

SituationRecommendation
Small team, early-stage productSemgrep (SAST) + ZAP (DAST) on staging
Regulated environment (PCI, SOC 2)Add SonarQube for audit trail, structured reports
Microservices with OpenAPI specsStackHawk or 42Crunch for API DAST
Java/Python QA with thick test suiteConsider Contrast Security IAST
Need fast PR gatesSemgrep only, incremental mode
Binary/compiled software, no source accessDAST only; SAST requires source

If you want hands-on practice integrating these into real pipelines, the Certified DevSecOps Professional program covers tool integration in actual CI environments, not just theory.

FAQ

Should I run security scans on every PR or only on main branch merges?

Run fast, targeted scans (secrets detection, SAST on changed files) on every PR. Full scans on merge. Scanning everything on every PR adds latency without proportional benefit. Tune your tooling to fail fast on high-severity findings only.

How do I handle secrets that are already in git history?

Use git filter-repo to remove them, rotate the credentials immediately regardless, and treat the history as compromised. Removal from history doesn't mean no one has the secret. Run truffleHog against the full history to identify everything that leaked before you start rotating.

Is GITHUB_TOKEN enough for CI/CD or do I need a separate service account?

For most pipeline operations GITHUB_TOKEN is sufficient. Use it with minimal permissions. A separate service account (PAT or machine user) is only justified if you need cross-repo access or actions the GITHUB_TOKEN can't perform, like triggering workflows in other repos.

What's the minimum security I should add to a CI/CD pipeline today?

Pin all third-party actions by SHA, add detect-secrets as a pre-commit hook, set permissions: read-all at the workflow level and grant specific write permissions per job, and configure OIDC-based cloud auth to replace stored credentials. Those four changes eliminate the most common vectors.

Varun Kumar

Varun Kumar

Security Research Writer

Varun is a Security Research Writer specializing in DevSecOps, AI Security, and cloud-native security. He takes complex security topics and makes them straightforward. His articles provide security professionals with practical, research-backed insights they can actually use.

Related articles

All blogs →