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.
| Approach | Target | When | What it detects | Limits |
|---|---|---|---|---|
| SAST | Source code | Development | Injections, secrets, dangerous patterns | False positives, ignores the runtime context |
| DAST | Deployed application | Staging/Prod | Exploitable flaws, misconfigurations | Limited coverage, slow |
| SCA | Dependencies | Build | CVEs in third-party libraries | Only detects known vulnerabilities |
| Fuzzing | Inputs/APIs | Tests | Unexpected behaviour, crashes, edge cases | Resource 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.
| Family | What it adds |
|---|---|
| Secret scanning | Reads the repository history, not only the current files: a key removed yesterday is still there |
| IAST | Instruments the application during its functional tests, so it observes the real flow of data instead of inferring it |
| Container image scanning | Covers the base system and the installed binaries, which the application dependency tree ignores |
| Infrastructure vulnerability scanning | Sweeps machines and exposed services, including those nobody declared: the forgotten host is in no repository |
| IaC scanning | Reads Terraform, Kubernetes and configuration files, where network exposure and permission errors live |
| Threat modelling | Reasons 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.
| Category | Examples | Why SAST is effective |
|---|---|---|
| Injections | SQL, XSS, command injection | Recognisable patterns: string concatenation with user input |
| Hardcoded secrets | API keys, tokens, passwords | Regex patterns: password = "...", known formats (AWS, GitHub) |
| Weak crypto | MD5, SHA1, DES | Calls to functions known to be obsolete |
| Bad practices | eval(), exec(), unsafe deserialisation | Specific 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 bydb.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 point | Advantage | Drawback |
|---|---|---|
| IDE (real time) | Immediate feedback, fresh context | Can slow the editor down |
| Pre-commit | Blocks before the push | Can frustrate if too strict |
| CI (pull request) | Systematic review | Later feedback |
| Scheduled (nightly) | In-depth analysis | Delayed 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.
| Category | Examples | Why DAST is needed |
|---|---|---|
| Exploitable injections | SQL, XSS that really go through | Confirms the flaw can actually be exploited |
| Misconfigurations | Missing headers, permissive CORS | Runtime configuration, not in the code |
| Weak authentication | Session fixation, predictable tokens | Runtime behaviour |
| Information leaks | Stack traces, exposed versions | Real 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.
| Environment | Use | Frequency |
|---|---|---|
| Local development | Targeted tests on new features | On demand |
| Staging/pre-prod | Full scan before release | Every release candidate |
| Production | Continuous monitoring, pentest | Weekly/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.
| Information | Description | Use |
|---|---|---|
| CVE | Published vulnerabilities with a unique identifier | Identify known flaws |
| CVSS | Severity score (0-10) | Prioritise by criticality |
| EPSS | Probability of exploitation within 30 days (v4, since March 2025) | Prioritise by real risk |
| Licence | Licence type (MIT, GPL, proprietary) | Legal compliance |
| Transitive dependencies | The dependencies of your dependencies | Visibility 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.
| Priority | Criteria | Action |
|---|---|---|
| P0 - Critical | CVSS ≥ 9.0 AND (EPSS > 10% OR KEV) | Immediate patch, block the deployment |
| P1 - High | CVSS ≥ 7.0 AND direct dependency | Patch within 7 days |
| P2 - Medium | CVSS ≥ 4.0 OR critical transitive dependency | Patch within 30 days |
| P3 - Low | CVSS < 4.0, no known exploit | Include 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:
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 1716Each 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.
| Type | Description | Example |
|---|---|---|
| Dumb fuzzing | Fully random data | Random bytes sent to a parser |
| Smart fuzzing | Random data that respects a format | Valid JSON with random values |
| Coverage-guided | Adapts inputs to explore more code | AFL, libFuzzer |
| API fuzzing | Tests REST/GraphQL endpoints using OpenAPI specs | Burp, 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.
| Check | What it saw | What it did not see | When | Usable signal |
|---|---|---|---|---|
| SAST | The concatenated SQL query (B608) and debug mode on (B201) | The 30 advisories on the dependencies, the crash, the authorisation defect | At commit | 4 findings, including 1 false positive |
| SCA | 30 advisories on 5 packages, one of them undeclared | Not one line of the application code | At build | A list of packages, never a file |
| Secrets, files | Nothing | The token removed from the code in the previous commit | Pre-commit | 0 findings |
| Secrets, history | The GitHub token, rule github-pat | Everything else | On demand | 1 finding |
| Anonymous DAST | The injection returning 3 rows instead of 1, missing headers, server versions disclosed | The whole authenticated surface, which answers 401 | In acceptance | Proof of exploitation |
| Authenticated DAST, 1 account | What that account sees, and nothing abnormal | The authorisation defect, invisible from a single point of view | In acceptance | No signal |
| Authenticated DAST, 2 accounts | The authorisation defect, the only automated device of the lab to raise it | What does not depend on identity | In acceptance | Proof of cross access |
| Fuzzing | 3 inputs out of 4 take the route down with an error 500 | Everything its corpus does not reach | At night | Crash 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.
gitleaks dir . # 0 findings: the token was removed from the codegitleaks git . # 1 finding: it is still in the previous commitThis 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 run | Request on someone else's expense report | What the scanner can conclude |
|---|---|---|
| Without credentials | 401 | Nothing, the surface is out of reach |
| With one account | 200, its own data | Nothing, the answer is legitimate |
| With two accounts compared | 200, the other account's data | Cross 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:
| Corpus | Inputs | Crashes |
|---|---|---|
| Numeric | 0, 1, 2, 99, -1, 2147483647 | 0 |
| Textual | abc, 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.
| Decision | When to take it | Where it is recorded |
|---|---|---|
| Fix | the defect exists and is exploitable | in the fix: the finding disappears on the next scan |
| False positive | the finding is accurate but the defect does not exist, like the path under /var/tmp chosen on purpose | an annotated suppression in the code, targeted at the rule, with its reason |
| Not exploitable here | the dependency is vulnerable but the faulty code is never called | a VEX document, readable by scanners |
| Accepted risk | the fix is impossible for now | a 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:
semgrep scan --config p/python --error --metrics=off --baseline-commit HEAD~1 .| Scan | Findings reported | Exit code |
|---|---|---|
| Full | 4: the existing SQL injection and 3 on the new subprocess command | 1 |
Baseline on HEAD~1 | 3: only those of the new commit | 1 |
| Baseline, commit with no new finding | 0 | 0 |
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 0700Finding 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:
-
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/gitleaksrev: v8.30.1hooks:- id: gitleaksrevalso accepts a commit SHA, and that is the robust form: a tag can be moved, as thetrivy-actioncompromise showed. The trade-off is thatpre-commit autoupdatethen writes a tag back over it. -
CI (pull request): SAST + SCA
Every PR is analysed automatically, and a finding blocks the merge:
--errormakes 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 Actionssecurity-scan:runs-on: ubuntu-24.04permissions:contents: readsteps:- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1with:persist-credentials: false# SAST with Semgrep- name: Semgreprun: >-docker run --rm -v "$PWD:/src" -w /srcsemgrep/semgrep:1.178.0@sha256:32e459968daabe7ab86968184a29109b9564aa00392401156f9788452b42786bsemgrep scan --config p/python --config p/owasp-top-ten --error --metrics=off .# SCA with osv-scanner- name: osv-scannerrun: >-docker run --rm -v "$PWD:/src"ghcr.io/google/osv-scanner:v2.6.0@sha256:afd838850ac1a0fcc15ff4a041dc9ba11123c3f0d2666217a5f0fcf9222b55fascan source -r /srcTwo choices in this example come from a measurement, not a habit. The
semgrep/semgrep-actionaction, 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-auditreturns no finding (code 0), whilep/pythonandp/owasp-top-tenraisetainted-sql-string(code 1). A green SAST with the wrong ruleset proves nothing. On the SCA side, osv-scanner returns 4 advisories onflask==0.12.2and exits with 1. -
Staging: DAST
Once the application is deployed to staging, the dynamic tests can run.
dast-scan:needs: deploy-stagingruns-on: ubuntu-24.04permissions: {}env:CIBLE: ${{ vars.STAGING_URL }}steps:- name: DAST with nucleirun: >-docker run --rmprojectdiscovery/nuclei:v3.11.1@sha256:582d5546902e67052097cb2d07296c642d50a1afc5e44623cb038845df9a32eb-u "$CIBLE" -tags cve -severity critical,highThe staging address comes from a repository variable, passed through
env:: interpolating it straight intorun:would open a command injection.-tags cveselects nuclei 3's CVE templates by their tag, without depending on how they are laid out in the templates repository. -
Nightly: fuzzing and in-depth scans
Long analyses run at night, without blocking development.
on:schedule:- cron: '0 2 * * *' # Every day at 2amjobs: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.
| Metric | Description | Target |
|---|---|---|
| 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 rate | Irrelevant alerts | < 10% |
| Coverage | % of the code analysed by SAST | > 90% |
| Vulnerability debt | Known unfixed vulnerabilities | Downward 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
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
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: Secrets in code and Git history
A new full quiz with random questions
Work again only on the questions you missed
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.