Application Security and DevSecOps
How application security controls actually work inside a delivery pipeline: SAST, DAST, SCA, secret scanning, supply-chain provenance, triage by reachability, and the failure modes that turn a security program into noise.
Application security (AppSec) is the practice of finding and preventing the defects in software that an attacker can convert into access, and DevSecOps is the delivery discipline that puts those checks where engineers already work — in the pull request, the build, and the deploy — instead of in a review that arrives after the code has shipped.
The hard part is not choosing scanners. It is deciding which findings stop a release, who owns each fix, and how a control behaves when it is wrong. A program that flags everything and blocks nothing is operationally indistinguishable from having no program at all, and it is more expensive.
What AppSec Covers
Four distinct surfaces get lumped under one word, and they fail for different reasons:
- Code you wrote — injection into an interpreter or query, broken authorization logic, unsafe deserialization, server-side request forgery, credentials committed to the repository.
- Code you imported — direct and transitive dependencies, base images, build plugins. You did not write it, you did not review it, and it runs with your process privileges.
- The way it is built and delivered — the build system, its credentials, the artifact registry, and the deploy path. This is production infrastructure regardless of what the org chart says.
- The runtime configuration — the container, the cloud role attached to it, what is exposed to the internet, and the response headers the application actually sends.
Tools are strong on the second and fourth surfaces and weak on the first, because business-logic authorization flaws depend on intent that a scanner cannot read. A missing ownership check between two tenants looks exactly like correct code. That gap is why threat modeling and design review stay necessary no matter how many scanners run.
The Scanner Toolchain
| Control | What it inspects | What it is good at | Where it is blind |
|---|---|---|---|
| SAST | Source, AST, data flow | Tainted input reaching a dangerous sink; unsafe API use | Intent and authorization; anything crossing a process boundary; frameworks it does not model, which produce noise |
| SCA | Manifests and lockfiles | Known-vulnerable dependency versions, licence obligations | Vendored or unpinned code; whether the vulnerable function is reachable from your code at all |
| DAST | A running instance over HTTP | Misconfiguration, missing headers, behaviour that only appears at runtime | Anything behind an authentication flow it cannot complete; deep state; it also mutates data, so it needs a disposable environment |
| IAST | Instrumented runtime | Confirming exploitability along requests actually exercised | Paths your tests never hit; runtime overhead |
| Secret scanning | Diffs and full history | Credentials committed to source | Secrets leaked outside the repository; and detection does not rotate anything |
| IaC / manifest policy | Terraform, Kubernetes, cloud templates | Public storage, permissive network rules, privileged containers | Drift introduced after apply, or resources created outside the pipeline |
| Image scanning | OS and language packages in a built image | Inherited base-image vulnerabilities | Your own application logic |
Each control has a characteristic wrong answer. SAST over-reports, DAST under-reports, SCA reports accurately about a package and says nothing about your exposure. Choose the blocking gate based on which wrong answer you can live with.
Software Supply Chain
Pin dependencies and commit a lockfile. Without one, the same source builds differently on different days, and no statement about what you shipped can be verified afterwards. Most of the dependency risk lives in the transitive graph, not the handful of packages a developer typed.
Beyond pinning, three mechanisms matter. Provenance links a deployed artifact back to the commit and the builder that produced it, so “is this the code we reviewed?” is a verifiable question rather than an assumption. Signing and verification at admission gives that link teeth: if the deploy path refuses unsigned artifacts, an attacker who can push an image still cannot get it run. An inventory — an SBOM or an equivalent queryable index — is a response tool, not a prevention tool; its value is answering “where do we run this package?” in minutes instead of by grepping every repository in the middle of an incident.
Treat the build system as production. CI holds deploy credentials and signing keys, which makes a build step the highest-privilege code in the estate. Do not run untrusted pull-request code in a job that has access to those secrets; split the untrusted test job from the trusted build job, and scope build credentials to one repository and one destination.
Placing Controls in the Pipeline
Placement decides whether a control is useful or merely present. Early checks must be fast and mostly advisory; late checks must be few and genuinely blocking.
developer workstation
└── pre-commit ......... secret scan, formatter [fast, advisory]
│
▼
pull request
├── SAST (changed files) ............................. [blocking on NEW findings]
├── SCA on the lockfile .............................. [blocking on NEW findings]
├── IaC / manifest policy ............................ [blocking]
└── secret scan on the full diff ..................... [blocking, always]
│
▼
build
├── produce ONE artifact (image / bundle / package)
├── scan THAT artifact, not the repo ................. [blocking on base-image CVEs]
└── sign it + emit provenance (artifact ← commit ← builder)
│
▼
pre-deploy
├── verify signature + provenance ⇒ refuse unsigned ... [blocking]
└── DAST against a deployed staging copy ............. [advisory, scheduled]
│
▼
runtime
├── config + exposure checks (headers, auth, network)
├── dependency inventory query when a new CVE lands
└── logs and alerts routed to the OWNING team
The single most common structural mistake is scanning the repository and deploying something else — a different tag, a rebuilt image, a branch that drifted. Scan the artifact you deploy, and make the deploy refuse artifacts that were not scanned.
Triage and Reachability
A severity score describes a vulnerability in the abstract. It does not describe your exposure. Convert a finding into a decision with four questions, in this order:
- Is the vulnerable code reachable from any of our entry points? A vulnerability in a code path we never call is inventory, not risk.
- Does untrusted input reach it? Reachable from an authenticated admin console is a different problem from reachable from an unauthenticated endpoint.
- Is there a compensating control already in the path — a network boundary, an authorization check, a parser that rejects the shape required to exploit it?
- Is there a fixed version, and what does upgrading break? If the answer is a major version bump across a framework, that is a project, and it needs to be scheduled as one rather than left open as a ticket.
Set remediation windows per severity that your team can actually meet with its real capacity. A deadline nobody meets teaches everyone that deadlines here are fictional, which is worse than a longer window that holds. Every exception needs a named owner and an expiry date; an exception with neither is a silent decision to accept the risk forever.
Failure Modes
| Anti-pattern | What it produces | Fix |
|---|---|---|
| Turn a scanner on and block on day one | A wall of pre-existing findings, then mass bypass and a disabled gate | Run advisory first, then block only on findings new to the change — a ratchet, not a wall |
| Baseline the entire backlog as accepted | The backlog never shrinks and nobody remembers why | Block net-new, and burn the baseline down on an owned schedule |
| Findings routed to a security inbox | The people who can fix the code never see it | Route to the owning team in the tracker they already use, with the file and the line |
| Secret detected, access revoked later | The credential is already out; removing the commit changes nothing | Treat detection as a confirmed leak: rotate first, then clean history |
| No in-tool suppression path | Suppression happens in people’s heads and the tool is ignored | Make suppression explicit, reviewable, and expiring |
| Scanning source but shipping a different artifact | Clean reports that do not describe production | Scan and sign one artifact and verify it at admission |
| Coverage measured by number of tools | Overlapping tools, no owner, no reduction in risk | Measure by surface covered and by time-to-fix, not by tool count |
When Not to Add a Control
Adding a gate is not free: it costs build minutes, review attention, and credibility every time it is wrong. Skip a control when nobody will own its output, when an existing blocking gate already catches the same class, or when the component has no untrusted input path and the budget would do more good on the one that does. If you decline a control deliberately, write down why — an undocumented decision is indistinguishable from an oversight when someone audits it later, and compliance work will ask.
The inverse also holds. Controls that are cheap, quiet, and almost never wrong — secret scanning, dependency pinning, signature verification at deploy — should simply be on everywhere, with no exception process worth the name.
Checklist
- Every repository has a named owning team, reachable from a finding
- Secret scanning runs on every diff, and detection triggers rotation, not just removal
- Dependencies pinned, lockfiles committed, transitive graph visible
- The artifact that is scanned is the artifact that is deployed
- Deploy path verifies signature and provenance, and refuses what it cannot verify
- Build jobs holding credentials never execute untrusted contributor code
- Gates block on new findings; the legacy baseline has a burn-down owner
- Exceptions carry an owner and an expiry date
- A dependency inventory can answer “where do we run X?” without a code search
- Authorization logic is reviewed by humans, because no scanner reads intent
Related reading in this section: identity and access management decides who the request belongs to, zero trust architecture decides whether it is allowed to reach the service at all, threat modeling finds the design flaws scanners cannot, and compliance engineering turns these controls into evidence. For credential handling in the delivery path see secrets management, and for the section index see Security & Compliance.