Contents7 sections
Forty-three findings nobody introduced
The first full scan of this project's tree came back with forty-three findings. Not one arrived that week. They had been sitting in the repository for as long as the repository had existed, and the only thing that changed on the day the number appeared was that a tool with no opinion about any of it finally read the files end to end. Nobody had introduced anything.
That number is the useful part of the exercise, and it is useful in a way nobody expects going in. Forty-three measures elapsed neglect. It says how long the code went unread by a machine that reads for a living, and it says almost nothing about how exposed anybody was. Turn the same scanner on any tree of comparable age and the number lands in the same neighbourhood, which is why a first scan is a baseline rather than a verdict.
The mistake worth naming runs the other way. A clean report gets read as a statement about the software; what it actually states is something about the scanner. Nothing matched the patterns we know about, in the code we could see. That sentence is real and useful, and it is a great deal smaller than the sentence people hear when somebody says the scan came back clean.
Getting that one sentence written down, in the place where release decisions actually get made, is most of what a scanning programme is for. The rest is plumbing.
Two acronyms, two different corpora
SAST and SCA get used as though they were one purchase. They read different material and answer different questions. That difference decides who fixes a finding.
Static Application Security Testing, SAST from here on, reads the source your team wrote without running it, hunting for shapes that stay dangerous whatever the input reaching them turns out to be. A shell command assembled by string concatenation is one. A hash chosen for speed where resistance was the point is another. Static is the operative word: no test data, no deployment, no traffic.
Software Composition Analysis, SCA, reads the dependency tree instead. It takes everything you imported plus everything those imports pulled in, resolves the lot to exact versions, and checks the result against public advisory databases. That is a lookup rather than an analysis. SCA never reads the dependency's code.
Secret scanning is the third discipline, routinely folded into the other two and covered by neither. It reads the diff and the history for credentials. SAST hunts unsafe patterns and sails straight past a pasted API key. Worse, once a key reaches the history, deleting the line leaves it exactly where it was.
Ownership follows the corpus. A SAST finding is your code and your patch. An SCA finding is usually a version bump, and occasionally a long, unrewarding conversation with an upstream maintainer who stopped caring about that package two years ago and has no plans to start again.
Two properties are worth stating before anybody buys anything, because most disappointment with these tools traces back to one of them.
Neither runs your program. Both are blind to runtime configuration; both are silent on whether a finding is reachable in production. Penetration testing is a different discipline and these are not it.
Neither proves absence either, which is the sentence from the opening.
What each one sees, and the list nobody owns
Coverage reads best as three lists. What SAST sees, what SCA sees, and the one everybody forgets to write down.
| Finding class | What it looks like | Who sees it |
|---|---|---|
| Command or SQL injection | A shell string built by concatenation | SAST |
| Weak or misused crypto | MD5 for integrity, verify=False | SAST |
| Unsafe deserialization | pickle.loads, yaml.load with no SafeLoader | SAST |
| Dangerous defaults | Bound to 0.0.0.0, debug on in production | SAST |
| Known CVE, direct dependency | The version you pinned is listed | SCA |
| Known CVE, transitive | Four levels down, nobody chose it | SCA |
| Typosquats and malicious packages | Exists only to be installed | SCA |
| Licence incompatibility | GPL in a product meant to stay closed | SCA |
| A credential in a diff or in history | Neither SAST nor SCA reads for this | Secret scanning |
| Broken authorization | The check is present, and wrong | Nobody |
| Business-logic flaws | Refund twice, negative quantity | Nobody |
| A vulnerability nobody has published | SCA knows advisories, not code | Nobody |
Table 1. The last three rows are why a green scan is not a safe release.
SAST is strong on local, syntactic danger: the pattern sits in a few lines and never depends on program state. Context is where it weakens. A false positive rate that feels intolerable is usually the tool telling the truth about a construct that is dangerous in general and safe right here, with no way on earth to know which of the two it is looking at. It cannot see that the string reaching your SQL is a UUID your framework validated four layers up. That is the trade. It errs toward flagging, and a team that never triages ends up ignoring the whole thing.
SCA is close to unarguable on facts: this version, this advisory, published on this date. Its weaknesses are the shape of its data rather than a defect in the tool. It knows only what has been published, so a vulnerability discovered and not yet disclosed is invisible to it; SCA is a memory rather than a detector. Most CVEs in a large tree also sit in code paths your application never calls, and that gap between present and reachable is what VEX statements exist to record.
Then comes the list nobody owns: broken authorization, business-logic flaws, and anything not yet published. No scanner finds these. Review, tests and threat modelling do, which is an argument for scanners rather than against them, since they clear the mechanical findings so that human attention reaches the ones only a human was ever going to catch.
The family these two belong to
A programme that adopts SAST and SCA and stops has done real work. It has also covered less of the surface than it feels like. These two are the cheapest members of a larger family; cheap is why they get adopted first. Seeing the whole set once is worth the ten minutes, if only to know what you are choosing to skip.
| Discipline | What it reads | What it sees that the others do not | In this piece |
|---|---|---|---|
| SAST | Your source, unexecuted | Injection, weak crypto, unsafe deserialization | In scope |
| SCA | Your resolved dependency tree | Published advisories against the versions you pinned | In scope |
| Secret scanning | The diff, and the whole history | Credentials, which neither of the two above read for | In scope |
| Infrastructure as code | Terraform, Helm, CloudFormation | A public bucket, an open security group, a role granted everything | Not covered |
| Container scanning | The built image, layer by layer | The operating system packages your lockfile never mentions | Not covered |
| DAST | The running application, over HTTP | What is actually exposed once configuration and routing are real | Not covered |
| IAST | The application from inside, under test traffic | Reachability, which SAST and SCA both have to guess at | Not covered |
| Fuzzing | The application under hostile generated input | Crashes and memory faults nobody wrote a test case for | Not covered |
Table 2. Ordered by how much has to exist before the scanner can run. SAST needs a file; DAST needs a deployment.
Two of those deserve singling out, because they are commonly the highest-yield thing a team is not doing. Infrastructure-as-code scanning reads Terraform, Helm and CloudFormation. In a cloud-hosted product, the misconfiguration it finds is routinely a shorter path to your data than any code defect in the application, for the unglamorous reason that a storage bucket readable by the world needs no injection flaw standing in front of it to be useful to somebody. That scanner is cheap and static, and it fits the same slot in continuous integration.
Container scanning reads the built image, which carries an operating system your lockfile has never mentioned. A Python dependency audit says nothing whatever about the base image's OpenSSL. That base image ships to production either way.
Neither is covered here. Both sit closer to SCA in cost than to DAST. If you are choosing where the second increment of effort goes, they are the honest answer more often than configuring SAST more thoroughly is.
Why the arithmetic changed
None of the above is new. What changed is the ratio between how fast code is produced and how fast it gets examined, and every argument for automating security review is really an argument about that ratio.
Review capacity stayed where it was. Generation did not. A reviewer reads roughly the same number of lines per hour as five years ago, so when a session produces a day of code in an hour the constraint moves to review, and the only checks that moved with it are the mechanical ones.
Generated code is idiomatic-looking, which is exactly the failure mode reviewers handle worst. Insecure code written by a person in a hurry tends to look hurried. Model-written insecure code looks like the rest of the file, because plausibility is the thing the model optimises for. Nothing about it trips the instinct that makes a reviewer slow down and read twice.
Training data has a half-life. A model suggests the API it saw most often, and the most-seen API is frequently the older one. yaml.load over safe_load. MD5, from a decade when nobody minded. verify=False, from ten years of tutorials working around a certificate error. Those are precisely SAST's strongest category, which is a piece of luck worth taking.
Dependencies now get suggested rather than chosen. An import can arrive on plausibility alone, with nobody having evaluated anything; models also suggest packages that have never existed. Attackers register those names. Vigilance makes a poor defence here. A lockfile plus SCA makes a good one, because SCA asks about the package you actually resolved rather than the one you meant to.
One mistake also replicates now. A pattern applied by hand appears once. Applied by an agent across a refactor it appears in thirty files in a single commit, and what the reviewer opens is a large diff of consistent-looking changes: the hardest kind to read carefully, and the easiest kind to approve.
The conclusion concerns pace rather than danger. That cheap mechanical layer of review has to be genuinely automatic now, because it is the only layer keeping up. The same velocity cuts the other way, and that half rarely gets said: a finding a model can fix in seconds is a finding worth surfacing, and backlogs that were previously untouchable become tractable. Scanners produce exactly the kind of work these tools are unusually good at doing.
What you are actually deciding
Reduced to the decision rather than the tooling, five things.
Adopt both, early. They are cheap, they run on a laptop, and their strongest category is precisely where generated code most often lands. The cost is triage rather than licence fees.
Budget for triage, not just for scanning. An unfiltered report is not a work queue. Programmes do not fail at installation; they fail when a queue nobody can finish gets ignored, then switched off.
Baseline the existing debt. A gate that is red on arrival gets bypassed, and a bypassed gate is worse than an absent one because it still reports. Record what is already there. Fail on additions.
Do not let green mean safe. Write the sentence down once, in the place where release decisions get made. A clean scan means nothing matched the patterns we know about.
Cost the next increment honestly. The second-best spend after these two is usually infrastructure-as-code or container scanning. Both are static, both are cheap, and both cover ground neither of these tools can reach.
SAST reads what you wrote. SCA reads what you imported. Secret scanning reads what should be in neither. None of the three sees your logic, your authorization, or tomorrow's disclosure, and in a system with a model inside it none of them sees the model at all. Adopt them anyway, because in a codebase where most lines are now drafted faster than anyone can read them, that mechanical layer is the part of review still keeping pace. Then go and look at the forty-three findings that were always there.
Glossary
| Term | Definition |
|---|---|
| Advisory database | A public record of disclosed vulnerabilities, each tied to a package and a version range. SCA is a lookup against one of these, which is why it knows only what somebody has already published. |
| Baseline | A recorded set of findings that already existed when scanning started, held so that a gate can fail on additions rather than on history. Without one, the first scan turns every pull request red. |
| CVE | Common Vulnerabilities and Exposures, the identifier scheme that gives a disclosed vulnerability a stable name. It says a flaw exists in a version; it says nothing about whether your application reaches it. |
| DAST | Dynamic Application Security Testing. It exercises the running application over HTTP, so it sees what configuration and routing actually expose, and it needs a deployment before it can run at all. |
| Fuzzing | Feeding an application generated hostile input to find crashes and memory faults. It finds defects nobody wrote a test case for, at the cost of a rig and a great deal of machine time. |
| IAST | Interactive Application Security Testing. It instruments the application from inside while test traffic runs, which lets it answer the reachability question that SAST and SCA both have to guess at. |
| Infrastructure as code | Deployment configuration kept in files such as Terraform, Helm or CloudFormation. Scanning it is cheap and static, and in a cloud product it often finds a shorter path to the data than any application defect. |
| Lockfile | A file recording the exact resolved version of every dependency, direct and transitive. It is what turns a loose version range into a fact that SCA can check. |
| Reachability | Whether your code can actually call the vulnerable path in a dependency. Most CVEs in a large tree sit in paths the application never touches, which is the difference between present and exploitable. |
| SAST | Static Application Security Testing. It reads the source your team wrote without running it, looking for shapes that are dangerous whatever the input. |
| SCA. Software Composition Analysis | It resolves the dependency tree to exact versions and asks whether anyone has published a vulnerability against them. |
| Secret scanning | Reading a diff and a repository's history for credentials. It is a separate discipline from the other two, and it is the only one that treats history as part of the corpus. |
| Transitive dependency | A package you did not import, pulled in by something you did. Most of a large tree is transitive, and most CVEs land there. |
| Triage | The work of turning a raw scan report into a queue somebody can finish. It is the real cost of a scanning programme, and the place programmes die. |
| VEX | Vulnerability Exploitability eXchange, a format for recording that a known vulnerability is present in a product but not exploitable in it. It exists so that a reachability judgment survives the analyst who made it. |
Part 1 of 3 · Security Scanning and the AI Codebase
The rest of the set
Three pieces that each stand on their own and read end to end: what the scanners read and cannot prove, where to run them so the gate survives, and the four layers of an AI product they never see.
Keep reading
More field notes
This piece is part of the MAX Research Collective library. Browse the rest, or connect on LinkedIn.