Application security is the practice of finding and fixing weaknesses in software - in first-party code, in the open-source dependencies that code pulls in, and in how the application is built, configured and deployed. Treated as part of exposure management, it adds a second question to every finding: whether the weak code is reachable and exploitable in the application as it actually runs.
Application security has a credibility problem earned by arithmetic. Static analysis and dependency scanning produce finding counts in the thousands, of which a small minority are exploitable as deployed. Developers learn that the queue is mostly noise and discount all of it, including the findings that would have mattered. Adding scanners makes that worse; precision is the only intervention that changes the dynamic.
The second problem is the shape of the request. A finding hands an engineer an investigation rather than a change: which version resolves it, whether that version breaks anything else, and when there is a window to test it. Answering those is the real cost, and since discovery capacity has risen faster than engineering capacity, filtering by exploitability is what keeps the actionable set finite.
The practice spans four surfaces. First-party code carries logic flaws, injection paths and broken authorisation. Dependencies carry vulnerabilities disclosed after the code was written, which is why dependency risk changes without anyone touching the repository. The build pipeline carries build credentials, package registries and signing keys. Runtime configuration - permissions, exposed ports, default credentials, debug endpoints left enabled - is where an otherwise sound application becomes reachable.
Every route has a different fix mechanic. A manifest edit resolves a direct dependency, a rebuild resolves a base image, and vendored source has to be patched by hand because no version number points at a replacement.
Composition analysis answers a presence question: is a vulnerable version of this package in the dependency tree. Function-level reachability asks whether the vulnerable function is called at all, directly or transitively. Data-flow or taint analysis asks whether data an attacker can influence arrives at the vulnerable parameter. A function invoked only with trusted internal constants is a materially different risk from the same function at the end of a path beginning with an untrusted request.
The technique has real limits. Reflection, dynamic dispatch and configuration-driven loading degrade call-graph fidelity, forcing an analysis either to over-approximate, which reintroduces noise, or to under-approximate, which hides exposure. Runtime-observed reachability inherits the opposite limit: a path production traffic has never exercised looks unreachable until the day it is exercised.
CVE-2021-44228 in Apache Log4j 2, disclosed in December 2021, is the clearest illustration available. Enormous numbers of Java applications had a vulnerable Log4j version in the build manifest, usually pulled in transitively rather than chosen. Presence was near-universal and presence was the wrong measure. An application that bound a different logging implementation, or never routed untrusted input into a logged message, contained the vulnerable class without entering the lookup path that made it exploitable: loaded, zero exposure. An application logging a request header on an internet-facing endpoint was fully exposed by the identical version, and the manifest cannot tell the two apart.
The fix side has the same shape. Upgrading NumPy to 2.0 is an ABI break that fails builds of pandas, SciPy and scikit-learn compiled against 1.x: a bot proposes the bump, package-level checks pass, and the application dies at import.
An engineer commits a configuration file containing a long-lived cloud access key, notices within a day, and removes it in a follow-up commit. The file is gone from the working tree, the removal is merged, and the finding is marked resolved.
The credential is still present and still valid. The earlier blob remains in the object database, and every clone taken before the removal keeps a copy: forks, continuous-integration caches, build artefacts, developer working copies. Rewriting history removes the object from the canonical repository and does nothing about the copies already distributed.
The change that closes the exposure is rotation: issue a replacement credential, update the systems that consume it, revoke the original, and read the provider's access logs for use of the old key between commit and revocation. That last step is the one most often skipped, and it separates a rotated secret from one that had already been used. Deletion edits the code; rotation moves the risk.
Vulnerability identifiers come from the CVE Program, operated by MITRE with CISA sponsorship. Severity scoring uses CVSS and exploitation likelihood uses EPSS, both maintained by FIRST, and CISA publishes the Known Exploited Vulnerabilities catalogue of confirmed in-the-wild exploitation. Dependency inventories are expressed as an SBOM in CycloneDX or SPDX, the latter standardised as ISO/IEC 5962. OWASP publishes the Top 10 and the Application Security Verification Standard, and NIST SP 800-218 describes secure development practices.
Does reachability analysis replace static and composition analysis? No. It is a filter applied to their output. Scanners establish that a weakness exists in code or in a dependency; reachability establishes whether that code can be invoked and whether untrusted data can arrive at it.
How does function-level reachability differ from what composition analysis reports? Composition analysis reports that a vulnerable package version is in the dependency tree. Function-level reachability reports whether the vulnerable function is invoked, and data-flow analysis whether untrusted input can reach it. The first is a presence check; the others are exploitability checks.
Will an automated dependency upgrade break the build? It can, which is why the safe unit of change is often a coordinated upgrade set. Impact analysis across the dependency graph identifies consumers that would break, and where no safe path exists the finding is better staged with the analysis attached.
Where does secrets scanning fit? It is a separate surface covering credentials in source, history and build output, where the fix is rotation rather than deletion.
Why are so many application security findings not exploitable? Because the common scanners answer a presence question rather than an exploitability one. Composition analysis reports that a vulnerable package version resolved into the build; static analysis reports a pattern in source it cannot observe running. Whether the code is invoked, whether attacker-influenced data reaches it, and whether the configuration enables the affected feature are three further conditions, each of which removes findings from the set.