On March 29, 2024, PostgreSQL developer Andres Freund noticed SSH logins on a Debian sid box were consuming slightly too much CPU and throwing errors under valgrind. That anomaly led him to a backdoor planted in xz-utils 5.6.0 and 5.6.1 by a contributor known as "Jia Tan," the product of a social-engineering campaign that had been building maintainer trust for more than two years. The flaw, tracked as CVE-2024-3094, scored a 10.0 on CVSS and could have compromised sshd on a meaningful fraction of the internet's Linux servers had it shipped in a stable distribution release. It is one of the starkest recent reminders that AppSec risk doesn't only arrive as a new CVE — it accumulates quietly, in dependencies nobody re-reviewed, in tooling nobody re-validated, and in credentials nobody rotated. Just as IT teams do seasonal hardware audits, AppSec programs need a recurring, deliberate pass over four categories of drift: stale dependencies, dead security tooling, expired secrets, and policy rules that no longer match how the team actually ships code. This piece lays out what each of those checks should actually look like, grounded in real incidents rather than generic advice.
Why do stale dependencies deserve a dedicated audit, not just a CVE feed?
A CVE feed only tells you about vulnerabilities that have been disclosed — it says nothing about depth, abandonment, or maintainer risk. Log4Shell (CVE-2021-44228) is the clearest illustration: the vulnerable JNDI lookup behavior existed in Log4j since 2013, unnoticed for eight years, until Chen Zhaojun of Alibaba Cloud's security team privately disclosed it to Apache on November 24, 2021, with public disclosure following on December 9, 2021. No feed flagged it before that date because there was nothing to flag — the risk was latent in code nobody had re-examined since it shipped. A dependency audit needs to go past "any open CVEs?" and ask "when did we last actually look at this package, how deep in our tree does it sit, and who maintains it now?" Real dependency trees commonly reach tens of thousands of transitive packages by level four to six, and most SCA tools stop scanning around depth 50–60 — well short of where incidents like SolarWinds SUNBURST and the xz backdoor actually sat.
What does a "dead security tooling" audit actually look for?
It looks for scanners, gates, and integrations that are still installed but have quietly stopped producing signal — a webhook pointing at a decommissioned SCM app, a SAST job that's been red for so long it's allowlisted at the pipeline level, or a secrets scanner whose service account token expired eight months ago and now fails silently instead of blocking. The check isn't "is the tool configured" — it's "did this tool produce a finding, block a build, or get acknowledged by a human in the last quarter." Tooling death is rarely announced; a scanner that throws a 401 on every run and gets treated as a flaky CI step is functionally the same as having no scanner at all, except it still shows up on the compliance slide as "SAST: enabled." An audit should pull actual invocation logs and finding counts per tool, per quarter, and flag anything with zero real output.
How should teams find secrets that are still live, not just still committed?
The event-stream incident from 2018 is a useful case study here: a new maintainer using the handle "right9ctrl" added a dependency called flatmap-stream to the popular event-stream package on September 9, 2018, which was engineered to steal private keys specifically from the Copay bitcoin wallet's high-balance users before npm pulled it on November 26, 2018. The lesson for a secrets audit isn't just "grep git history for keys" — it's that a credential's exposure and its exploitability are different questions, and only live verification answers the second one. A pattern match against a regex tells you a string looks like an AWS key; calling sts:GetCallerIdentity against it tells you whether that key still authenticates. Safeguard's secrets scanning does this verification step against issuers including AWS, GitHub, Stripe, Slack, and OpenAI, and separates unverified pattern matches from confirmed-live credentials — a distinction that matters when you're triaging a backlog of "secret found" tickets and need to know which ones are actually urgent versus which are already-rotated noise sitting in old commits.
Why does policy drift accumulate even when the rules never change?
Policy drift happens because the codebase, team, and tooling around a static policy keep moving even when the YAML doesn't. A "block on any critical CVE" rule written two years ago for a monolith doesn't account for the microservices split that happened since, the new build system that doesn't emit the SBOM format the rule expects, or the exception list that's grown from three approved suppressions to forty because nobody re-reviewed them. Gates that once blocked real risk quietly become checkboxes once enough exceptions pile up next to them, and nobody notices until an incident forces a retrospective. An audit here means re-reading every active gate against the current architecture and asking whether it still evaluates the thing it was written to evaluate, and pruning exceptions that were meant to be temporary but have outlived the reason they were granted.
What does a practical seasonal audit checklist look like?
Run it quarterly, not annually — annual audits let a full year of drift accumulate before anyone looks. For dependencies: re-scan full transitive trees past the depth most tools default to, and flag packages with no commits or maintainer activity in the past 12 months regardless of CVE status. For tooling: pull real finding counts and last-successful-run timestamps for every integration, not just its "connected" status. For secrets: re-verify every previously-flagged credential against its issuer, since a secret marked "unverified" six months ago may be live today. For policy: diff every active gate's assumptions against the current build pipeline and prune exceptions older than one review cycle. None of these four checks is expensive individually — the value is in running all four together, on a calendar, before drift compounds the way it did in the two years before the xz backdoor was finally noticed.