Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Automated security testing: SAST, DAST, SCA, fuzzing

By updated

Improve this page

Exploiting a vulnerability has become the first intrusion vector, ahead of credential theft. The Verizon DBIR 2026, which covers more than 31,000 incidents and 22,000 confirmed breaches in 145 countries between November 2024 and October 2025, measures it at 31% of breaches against 13% for credential abuse: the first reversal in nineteen editions. On the other side, only 26% of the vulnerabilities in the CISA KEV catalogue were fully fixed in 2025, against 38% the year before, and the median time to full resolution rose from 32 to 43 days. This gap between the speed of exploitation and the speed of fixing is the problem automated security tests try to close.

This page is for teams who want to understand the four families of security tests, SAST, DAST, SCA and fuzzing, and know where each one fits in the development cycle. You will leave with a reading grid to choose the right tools and orchestrate them in a CI/CD pipeline, without setting speed against security.

What you will learn

  • Tell apart the four most common families (SAST, DAST, SCA, fuzzing) and their respective blind spots
  • Identify the class of defects no scanner detects, and what to put against it
  • Choose the right type of test for each phase of the development cycle
  • Prioritise detected vulnerabilities with objective criteria (CVSS, EPSS, KEV)
  • Triage each finding into a traceable decision: fix, false positive, not exploitable or accepted risk
  • Integrate these tests into a CI/CD pipeline without slowing deliveries down
  • Avoid the classic traps (false positives, scans that block everything, a blind spot on the supply chain)

SAST, DAST, SCA and fuzzing: what are the differences?

These four families differ by what they read: the text of the code, the dependency tree, the running service, or crafted inputs. Everything else follows, starting with their blind spots. They are the most widespread, not an exhaustive list: they are often presented as "the four pillars", which suggests complete coverage.

ApproachTargetWhenWhat it detectsLimits
SASTSource codeDevelopmentInjections, secrets, dangerous patternsFalse positives, ignores the runtime context
DASTDeployed applicationStaging/ProdExploitable flaws, misconfigurationsLimited coverage, slow
SCADependenciesBuildCVEs in third-party librariesOnly detects known vulnerabilities
FuzzingInputs/APIsTestsUnexpected behaviour, crashes, edge casesResource hungry

These categories are not fixed: the OWASP Top 10:2025, which replaces the 2021 edition, shows the movement. It introduces two new categories, software supply chain failures and mishandling of exceptional conditions, and merges SSRF into broken access control. See OWASP Top 10 applied to DevSecOps for the ten categories and how they map to your tools.

The families this list leaves out

Presenting SAST, DAST, SCA and fuzzing as "the four pillars" amounts to declaring the subject covered. It is not: the OWASP guides list several other families, from secret scanning to threat modelling, each answering a need the first four leave open. Two references are authoritative on this scope, the OWASP DevSecOps Guideline for pipeline tooling, and the Web Security Testing Guide for the application testing method.

FamilyWhat it adds
Secret scanningReads the repository history, not only the current files: a key removed yesterday is still there
IASTInstruments the application during its functional tests, so it observes the real flow of data instead of inferring it
Container image scanningCovers the base system and the installed binaries, which the application dependency tree ignores
Infrastructure vulnerability scanningSweeps machines and exposed services, including those nobody declared: the forgotten host is in no repository
IaC scanningReads Terraform, Kubernetes and configuration files, where network exposure and permission errors live
Threat modellingReasons about business rules, which the code expresses nowhere, and steers what must be tested

The last row is not a courtesy addition, it is the conclusion of a measurement: on the same application, five scans run with their defaults miss an authorisation defect that a human reviewer spots in thirty seconds. A well-configured scanner finds it too, but you still have to know it must be configured that way.

SAST: static analysis of the source code

SAST (Static Application Security Testing) analyses the code without running it. It parses the source code, builds an abstract model and looks for known vulnerability patterns.

What SAST detects

SAST excels at finding certain categories of problems. What they have in common: they are recognisable patterns directly in the text of the code, with no need to run the application to spot them.

CategoryExamplesWhy SAST is effective
InjectionsSQL, XSS, command injectionRecognisable patterns: string concatenation with user input
Hardcoded secretsAPI keys, tokens, passwordsRegex patterns: password = "...", known formats (AWS, GitHub)
Weak cryptoMD5, SHA1, DESCalls to functions known to be obsolete
Bad practiceseval(), exec(), unsafe deserialisationSpecific denylisted functions

Limits of SAST

SAST has significant blind spots, and knowing them avoids reading a clean report as a proof of safety:

  • Runtime context: the code user_input = request.get('name') followed by db.query(f"SELECT * FROM users WHERE name = '{user_input}'") is an obvious SQL injection. But if the input is validated three files further, SAST may not make the link.

  • False positives: without context, SAST reports problems that are not. An eval() used on a constant is not dangerous.

  • Business logic: SAST does not understand that "a user should not be able to see another user's orders".

Integration in the cycle

SAST fits at several levels of the development cycle:

Integration pointAdvantageDrawback
IDE (real time)Immediate feedback, fresh contextCan slow the editor down
Pre-commitBlocks before the pushCan frustrate if too strict
CI (pull request)Systematic reviewLater feedback
Scheduled (nightly)In-depth analysisDelayed detection

DAST: dynamic tests at runtime

DAST (Dynamic Application Security Testing) tests the application while it runs. It simulates the actions of an attacker: sends malformed requests, attempts injections, explores the endpoints.

What DAST detects

DAST finds what SAST misses. These flaws have one thing in common: they only exist when the application really runs, with its configuration and runtime environment, which a plain reading of the code cannot reveal.

CategoryExamplesWhy DAST is needed
Exploitable injectionsSQL, XSS that really go throughConfirms the flaw can actually be exploited
MisconfigurationsMissing headers, permissive CORSRuntime configuration, not in the code
Weak authenticationSession fixation, predictable tokensRuntime behaviour
Information leaksStack traces, exposed versionsReal HTTP responses

Limits of DAST

DAST has its own blind spots, and they all come from the fact that it only sees what its requests can reach on a deployed application:

  • Coverage: it only tests what it can reach. An undocumented endpoint or a feature behind complex authentication can be ignored.

  • Slowness: unlike SAST, which analyses text, DAST sends real requests. A full scan can take hours.

  • Environment: it needs a deployed, working application. Code not yet integrated cannot be tested.

When to use DAST

DAST is used later in the cycle than SAST. A recent trend is worth knowing: new-generation DAST tools now analyse an API's OpenAPI specification to infer its intended use, and test business logic instead of a list of guessed endpoints. That reduces manual configuration, but does not replace a human pentest on the most critical scenarios.

EnvironmentUseFrequency
Local developmentTargeted tests on new featuresOn demand
Staging/pre-prodFull scan before releaseEvery release candidate
ProductionContinuous monitoring, pentestWeekly/monthly

SCA: dependency analysis

SCA (Software Composition Analysis) analyses the third-party libraries your application uses. It compares your dependencies with the databases of known vulnerabilities (CVE). It is a critical part of modern DevSecOps, and also the one that changed the most in the last two years under the pressure of supply chain attacks.

Why SCA is critical

The code you write often represents less than 20% of your application. The rest comes from dependencies: frameworks, libraries, utilities. Each dependency is a potential vector of vulnerabilities, and the pace of incidents has clearly accelerated.

Sonatype's 2026 report counts more than 454,000 new malicious open source packages published in 2025, bringing the cumulative total blocked to more than 1.2 million, a 75% rise in one year. A few landmark incidents illustrate the trend:

  • Log4Shell (2021): a vulnerability in Log4j, present in millions of Java applications.
  • Spring4Shell (2022): a flaw in Spring Framework, the most used Java framework.
  • Polyfill.io (2024): a supply chain attack through a widely used JavaScript CDN.
  • Shai-Hulud (September 2025): a self-replicating worm that compromised several hundred npm packages in a few days, stealing publication tokens to spread from repository to repository without even needing a command and control server.
  • axios compromise (March 2026): a single compromised maintainer account turned this very popular HTTP library, more than 100 million downloads a week, into a vector for a remote access Trojan for about three hours.

That last point deserves a detour: even security tools are not safe. On 19 March 2026, the Trivy scanner was itself compromised, the attackers reusing the access from an earlier incident that had not been fully closed. The official advisory GHSA-69fq-xp46-6x23, published as CVE-2026-33634, describes three affected artefacts: the binary v0.69.4, the Docker images v0.69.5 and v0.69.6, and above all the project's two GitHub actions.

The detail of that last part changes a recommendation you read everywhere. The attackers did not publish one more malicious version: they moved 76 of the 77 version tags of trivy-action to trapped commits, and replaced the 7 tags of setup-trivy. A project that wrote trivy-action@v0.34.2, a reference that looks precise and fixed, ended up running a credential stealer. Only v0.35.0 was spared: published after GitHub's immutable releases were enabled on the repository, its tag could no longer be moved. Worse, the stealer ran before the legitimate scan, so the pipeline stayed green and the output looked normal.

Hence the rule, more demanding than a plain "pin your versions": a tag can be moved, a commit SHA cannot. A GitHub action is pinned to the commit SHA, a container image to its sha256: digest, and that applies first to security tools, which run with the pipeline's rights and access to all its secrets. See Software supply chain security to go further into these defence mechanisms.

What SCA detects

Beyond the plain finding "this library has a known flaw", a good SCA tool reports several complementary pieces of information that are then used to prioritise the patch: the technical severity, the real probability of exploitation, licence compliance and how deep the dependency sits in your tree.

InformationDescriptionUse
CVEPublished vulnerabilities with a unique identifierIdentify known flaws
CVSSSeverity score (0-10)Prioritise by criticality
EPSSProbability of exploitation within 30 days (v4, since March 2025)Prioritise by real risk
LicenceLicence type (MIT, GPL, proprietary)Legal compliance
Transitive dependenciesThe dependencies of your dependenciesVisibility on the supply chain

Prioritisation strategy

Not all CVEs are equal. An effective strategy prioritises along several criteria, so the team does not spend as much time on a theoretical flaw as on an actively exploited flaw. See also Managing CVE vulnerabilities, from triage to patch for a detailed process.

PriorityCriteriaAction
P0 - CriticalCVSS ≥ 9.0 AND (EPSS > 10% OR KEV)Immediate patch, block the deployment
P1 - HighCVSS ≥ 7.0 AND direct dependencyPatch within 7 days
P2 - MediumCVSS ≥ 4.0 OR critical transitive dependencyPatch within 30 days
P3 - LowCVSS < 4.0, no known exploitInclude in regular maintenance

What the KEV catalogue changes in prioritisation

The KEV catalogue (Known Exploited Vulnerabilities) published by CISA does not list serious flaws: it lists those whose exploitation has been observed. That is a difference of nature, not of degree, and it is what makes it the sharpest criterion of the table above. A CVE in the KEV with a moderate CVSS goes ahead of a 9.8 CVE that nobody exploits.

Read from the official feed on 18 September 2026, catalogue version 2026.09.18, it holds 1,716 entries, of which 245 added in 2025 and 232 since January 2026, about twenty a month on average, with bursts during an active campaign. The count is checked in one command, which avoids reusing a stale figure in a dashboard:

Terminal window
curl -s https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json \
| python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["catalogVersion"], d["count"])'
# 2026.09.18 1716

Each entry also carries a dueDate field, filled in on all 1,716: it is the remediation deadline binding on US federal agencies. Since directive BOD 26-04, of 10 June 2026, which replaces and revokes the founding BOD 22-01, this deadline is no longer a single delay but the result of a risk matrix with four factors: exposure of the asset, presence in the KEV, existence of an automatable exploit and technical impact. Deadlines range from 3 days for a publicly exposed asset with an automatable exploit, up to "fix on system upgrade" for the rest. The entry read on 18 September carried precisely a three-day deadline.

Fuzzing: testing with random data

Fuzzing (or fuzz testing) sends random or semi-random data to an application to trigger unexpected behaviour: crashes, memory leaks, unhandled exceptions.

To go beyond theory, the concept guide on fuzzing (in French) details how a harness works, and two hands-on guides, also in French, put it into practice: in Python with Atheris and in native Go.

Why fuzz

Fuzzing finds what the other methods miss, because it does not look for a known pattern: it feeds the code inputs nobody wrote a test for, and watches for a crash:

  • Edge cases: what happens with a 10-million-character string? A negative integer where a positive one is expected?
  • Unknown vulnerabilities: unlike SAST and SCA, which look for known patterns, fuzzing can discover new vulnerabilities (0-days).
  • Robustness: beyond security, fuzzing improves overall quality by finding stability bugs.

Types of fuzzing

Fuzzing has evolved too: beyond purely random generators, a generation of AI-guided tools now proposes mutations built from a semantic understanding of the target, which finds classes of vulnerabilities that code coverage metrics alone miss.

TypeDescriptionExample
Dumb fuzzingFully random dataRandom bytes sent to a parser
Smart fuzzingRandom data that respects a formatValid JSON with random values
Coverage-guidedAdapts inputs to explore more codeAFL, libFuzzer
API fuzzingTests REST/GraphQL endpoints using OpenAPI specsBurp, RESTler

When to use fuzzing

Fuzzing is resource hungry, so it is reserved for the code where a crash costs the most, and for moments that do not block delivery:

  • Critical code: parsers, cryptography, input validation
  • Shared libraries: code used by many projects
  • Before a major release: in-depth validation
  • Nightly builds: in parallel with development

What does each tool really find on the same application?

None of these tools finds what its neighbour finds, and the way you run them changes the result as much as the choice of tool. The claim is checked rather than told: the figures below come from an expense report service of about fifty lines, carrying six defects planted on purpose, analysed on 20 September 2026 on Debian 13 with bandit 1.9.4 for SAST, osv-scanner 2.6.0 for SCA, gitleaks 8.30.1 for secrets, black-box requests for DAST, and crafted inputs for fuzzing.

CheckWhat it sawWhat it did not seeWhenUsable signal
SASTThe concatenated SQL query (B608) and debug mode on (B201)The 30 advisories on the dependencies, the crash, the authorisation defectAt commit4 findings, including 1 false positive
SCA30 advisories on 5 packages, one of them undeclaredNot one line of the application codeAt buildA list of packages, never a file
Secrets, filesNothingThe token removed from the code in the previous commitPre-commit0 findings
Secrets, historyThe GitHub token, rule github-patEverything elseOn demand1 finding
Anonymous DASTThe injection returning 3 rows instead of 1, missing headers, server versions disclosedThe whole authenticated surface, which answers 401In acceptanceProof of exploitation
Authenticated DAST, 1 accountWhat that account sees, and nothing abnormalThe authorisation defect, invisible from a single point of viewIn acceptanceNo signal
Authenticated DAST, 2 accountsThe authorisation defect, the only automated device of the lab to raise itWhat does not depend on identityIn acceptanceProof of cross access
Fuzzing3 inputs out of 4 take the route down with an error 500Everything its corpus does not reachAt nightCrash trace

Two columns deserve a closer look, because they decide how you use a scanner. The signal does not have the same value from one tool to another: SAST says "this pattern is dangerous", DAST says "this precise request returned the three expense reports". The first opens a discussion, the second closes the debate. And the moment drives the rest: a check that needs a deployed service cannot block a commit.

The same tool, two scopes, two opposite verdicts

The most instructive result of the measurement does not come from comparing tools, but from one tool run twice. On the same repository, with the same configuration, gitleaks dir reports nothing and gitleaks git reports the token. Nothing changed except what it was given to read: the file tree in one case, the history in the other.

Terminal window
gitleaks dir . # 0 findings: the token was removed from the code
gitleaks git . # 1 finding: it is still in the previous commit

This is the real scenario of the "fixed" secret: someone took the key out of the file, pushed the fix, and the pre-commit scan has been green ever since. The token, for its part, stays readable by anyone who clones the repository. Removing a secret from a file does not revoke it: only rotation does.

A finding is not a defect

Of the 4 SAST findings, one targets a file path under /var/tmp deemed unsafe (B108). In this application, that path is a deliberate choice of the test environment: the finding is accurate, the defect does not exist. That is the very definition of a false positive, and here it represents a quarter of what was reported.

This ratio explains why a blocking threshold set without triage paralyses a team within days. The answer takes two moves: establish a baseline of existing findings so only new ones block, and only raise to a quality gate the rules you have checked produce no noise on your code.

The defect no default scan found

The service authenticates its caller, then returns any expense report, without ever checking that the caller is its owner. It is a direct data leak, and yet the scans, run the way they usually are, all missed it:

  • SAST sees nothing suspicious, since the query is correctly parameterised and no dangerous function is called;
  • SCA finds no dependency at fault, since there is none;
  • secret scanning has nothing to report, no secret being exposed;
  • anonymous DAST gets an HTTP 401 on the whole surface concerned: it does not conclude that it is safe, it does not reach it;
  • fuzzing causes no crash, the route being perfectly robust.

The code is technically flawless. It breaks a business rule, and a business rule cannot be inferred from the text of the program: nothing in the code says that an expense report belongs to someone. That is category A01, Broken Access Control, still at the top of the OWASP Top 10:2025.

The device that finds it: authenticated DAST

Concluding that "tooling can do nothing" would be wrong, and it is the mistake not to make. An authenticated DAST with two distinct accounts detects this defect, provided it is configured for it, as the measurement shows. It reads in three steps:

How the scan is runRequest on someone else's expense reportWhat the scanner can conclude
Without credentials401Nothing, the surface is out of reach
With one account200, its own dataNothing, the answer is legitimate
With two accounts compared200, the other account's dataCross access, hence an authorisation defect

The detection comes from no request taken in isolation: the three answers are each valid. It comes from the comparison between what an account should see and what it actually gets. That is exactly what a scanner does when given two sets of credentials and the list of each one's resources.

Hence two practical consequences. First, a DAST run without credentials on an application whose essentials live behind authentication measures almost nothing, and its empty report is misleading. Second, the configuration that matters is not the number of enabled rules but giving it several roles to compare.

Fuzzing coverage, or why zero crashes proves nothing

The crash on malformed input shows the same dependence on scope, on the fuzzing side. Two corpora of six inputs were sent to the same route:

CorpusInputsCrashes
Numeric0, 1, 2, 99, -1, 21474836470
Textualabc, 0x10, 1e999, %00, null, []3 of 6

The same code, the same defect, and two opposite verdicts depending on what you send it. That is fuzzing coverage: a zero crash rate does not say "the code is sound", it says "my corpus does not reach this path". Modern fuzzers address this by being guided by code coverage, that is by keeping the inputs that take branches never visited before, rather than drawing at random. Without that loop, the question to ask in front of an empty report is not "is it sound?" but "what fraction of the code was even executed?".

This defect falls under A10, Mishandling of Exceptional Conditions, the category the 2025 edition introduced precisely for this kind of failure.

How do you triage a scanner's findings?

Triaging means giving each finding an explicit and traceable decision, instead of letting it age in a report. An untriaged finding does not go away: it adds to the noise, and that noise is what pushes a team to switch the control off. Four decisions cover every case, and each one is recorded in a different place.

DecisionWhen to take itWhere it is recorded
Fixthe defect exists and is exploitablein the fix: the finding disappears on the next scan
False positivethe finding is accurate but the defect does not exist, like the path under /var/tmp chosen on purposean annotated suppression in the code, targeted at the rule, with its reason
Not exploitable herethe dependency is vulnerable but the faulty code is never calleda VEX document, readable by scanners
Accepted riskthe fix is impossible for nowa dated exception, with an owner and a deadline, as described in vulnerability management

The rule that keeps this triage sustainable: no decision without a trace. A suppression without a written reason, an exception without a date, and the finding resurfaces in six months in front of someone who no longer knows why it had been set aside.

Block only new findings: the baseline

Putting a quality gate on a repository that already carries findings blocks every merge from day one. The baseline solves this: the scanner compares against a reference commit and only reports what appeared since. The existing debt stays visible, it simply stops blocking the work in progress. Measured with Semgrep 1.178.0 on a repository whose previous commit already carried a SQL injection:

Terminal window
semgrep scan --config p/python --error --metrics=off --baseline-commit HEAD~1 .
ScanFindings reportedExit code
Full4: the existing SQL injection and 3 on the new subprocess command1
Baseline on HEAD~13: only those of the new commit1
Baseline, commit with no new finding00

The last row is the one that matters in CI: a commit that adds no defect passes, even on an indebted repository. The baseline does not excuse you from dealing with the debt; it only prevents it from growing while you pay it off.

Suppress a finding without losing sight of it

A false positive is marked in the code, on the line concerned, naming the rule and writing the reason. Naming the rule is not a detail: on a line that triggers three Semgrep rules, # nosemgrep: followed by the identifier of subprocess-shell-true masks only one, and the other two stay raised. A bare # nosemgrep would have silenced them all. With bandit 1.9.2, replayed on 3 October 2026, same principle:

EXPORT_DIR = "/var/tmp/exports" # nosec B108 - dedicated directory, created by the service as 0700

Finding B108 disappears, the other three of the same analysis remain. These suppressions are reread periodically, with the option that ignores them: semgrep scan --disable-nosem and bandit --ignore-nosec bring back everything that was masked. Measured: 4 findings with the latter, 3 without. A suppression whose reason is no longer true then becomes a finding to deal with again.

Integration in the CI/CD pipeline

The question is not whether to integrate these tests, but how to orchestrate them efficiently. A poorly thought-out integration creates more friction than security: blocking every commit with a full scan slows the team down, while too light a scan lets the essentials through. Here is a proven strategy, detailed in Secure CI/CD pipeline:

  1. Pre-commit: secret detection

    The first barrier, furthest to the left. It blocks commits containing secrets before they reach the repository.

    .pre-commit-config.yaml
    repos:
    - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1
    hooks:
    - id: gitleaks

    rev also accepts a commit SHA, and that is the robust form: a tag can be moved, as the trivy-action compromise showed. The trade-off is that pre-commit autoupdate then writes a tag back over it.

  2. CI (pull request): SAST + SCA

    Every PR is analysed automatically, and a finding blocks the merge: --error makes Semgrep exit with 1, and osv-scanner exits with 1 as soon as an advisory targets a dependency. Both tools run in their official image, pinned by digest.

    # GitHub Actions
    security-scan:
    runs-on: ubuntu-24.04
    permissions:
    contents: read
    steps:
    - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
    with:
    persist-credentials: false
    # SAST with Semgrep
    - name: Semgrep
    run: >-
    docker run --rm -v "$PWD:/src" -w /src
    semgrep/semgrep:1.178.0@sha256:32e459968daabe7ab86968184a29109b9564aa00392401156f9788452b42786b
    semgrep scan --config p/python --config p/owasp-top-ten --error --metrics=off .
    # SCA with osv-scanner
    - name: osv-scanner
    run: >-
    docker run --rm -v "$PWD:/src"
    ghcr.io/google/osv-scanner:v2.6.0@sha256:afd838850ac1a0fcc15ff4a041dc9ba11123c3f0d2666217a5f0fcf9222b55fa
    scan source -r /src

    Two choices in this example come from a measurement, not a habit. The semgrep/semgrep-action action, still quoted everywhere, is archived by its publisher: the official image replaces it. And the ruleset matters as much as the tool: on an obvious Flask SQL injection, p/security-audit returns no finding (code 0), while p/python and p/owasp-top-ten raise tainted-sql-string (code 1). A green SAST with the wrong ruleset proves nothing. On the SCA side, osv-scanner returns 4 advisories on flask==0.12.2 and exits with 1.

  3. Staging: DAST

    Once the application is deployed to staging, the dynamic tests can run.

    dast-scan:
    needs: deploy-staging
    runs-on: ubuntu-24.04
    permissions: {}
    env:
    CIBLE: ${{ vars.STAGING_URL }}
    steps:
    - name: DAST with nuclei
    run: >-
    docker run --rm
    projectdiscovery/nuclei:v3.11.1@sha256:582d5546902e67052097cb2d07296c642d50a1afc5e44623cb038845df9a32eb
    -u "$CIBLE" -tags cve -severity critical,high

    The staging address comes from a repository variable, passed through env:: interpolating it straight into run: would open a command injection. -tags cve selects nuclei 3's CVE templates by their tag, without depending on how they are laid out in the templates repository.

  4. Nightly: fuzzing and in-depth scans

    Long analyses run at night, without blocking development.

    on:
    schedule:
    - cron: '0 2 * * *' # Every day at 2am
    jobs:
    deep-scan:
    # Full analysis, all thresholds

Metrics to track

Measuring the effectiveness of your security tests avoids confusing activity (number of scans run) with results (vulnerabilities actually fixed). The DBIR 2026 gives the orders of magnitude to beat: 43 days median for full resolution, 26% of the CISA KEV catalogue's vulnerabilities actually dealt with, and, in the median case, 50% more critical vulnerabilities to fix than the previous year. In other words, the load rises faster than the capacity to handle it, which makes triage more decisive than the volume of scans. See also DevSecOps security KPIs for a complete dashboard.

MetricDescriptionTarget
MTTD (Mean Time To Detect)Time between introduction and detection< 24h for critical
MTTR (Mean Time To Remediate)Time between detection and fix< 7 days for critical
False positive rateIrrelevant alerts< 10%
Coverage% of the code analysed by SAST> 90%
Vulnerability debtKnown unfixed vulnerabilitiesDownward trend

Key points

  • SAST: analyses the source code, fast, many false positives, suited to development.
  • DAST: tests the real application, slow, few false positives, suited to staging/prod.
  • SCA: analyses the dependencies, critical for the supply chain, suited to CI/CD.
  • Fuzzing: finds edge cases and 0-days, suited to the nightly/release phases.
  • Combination: no approach is enough on its own, and their sum is not either. Measured on the same application, the five families run with their defaults missed an authorisation defect.
  • How you run the tool matters as much as the tool. That same defect is detected by an authenticated DAST with two accounts, and by it alone: the detection comes from the comparison between roles, not from an isolated request.
  • An empty report reads as a question, not a verdict. Zero crashes in fuzzing means "my corpus does not reach this path", hence coverage-guided fuzzing. Zero findings in anonymous DAST means "I did not reach the authenticated surface", which here answered 401.
  • Triaging means deciding and tracing: fix, annotated suppression targeted at one rule, VEX or dated exception. The baseline only blocks new findings.
  • A green pipeline does not say "no vulnerability", it says "no pattern my tools recognise, in the scope where I ran them".
  • The reading scope decides the result: the same secret scanner returns 0 findings on the files and 1 on the history. Removing a secret from a file does not revoke it.
  • OWASP Top 10:2025 introduced two new categories, supply chain failures and mishandling of exceptional conditions: your SAST/DAST rules must follow this change.
  • Even security scanners can be compromised. The Trivy compromise of 19 March 2026 moved 76 of the 77 tags of its GitHub action: a tag moves, a commit SHA does not. Pin actions to the SHA and images to their digest, starting with your security tools.

Knowledge check

Check that you have the essentials of this lesson: what each family of tests reads, the prioritisation of CVEs and the blind spots measured on the same application. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

8 questions
8 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

FAQ: frequently asked questions

These short answers cover the questions that come up most often about automated security testing: the difference between SAST and DAST, the place of SCA and the choice of tools. Each one stands on its own.

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn