By clicking “Accept All Cookies”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.
ToolAccuracy of FindingsDetects Non-Pattern-Based Issues?Coverage of SAST FindingsSpeed of ScanningUsability & Dev Experience
DryRun SecurityVery high – caught multiple critical issues missed by othersYes – context-based analysis, logic flaws & SSRFBroad coverage of standard vulns, logic flaws, and extendableNear real-time PR feedback
Snyk CodeHigh on well-known patterns (SQLi, XSS), but misses other categoriesLimited – AI-based, focuses on recognized vulnerabilitiesGood coverage of standard vulns; may miss SSRF or advanced auth logic issuesFast, often near PR speedDecent GitHub integration, but rules are a black box
GitHub Advanced Security (CodeQL)Very high precision for known queries, low false positivesPartial – strong dataflow for known issues, needs custom queriesGood for SQLi and XSS but logic flaws require advanced CodeQL experience.Moderate to slow (GitHub Action based)Requires CodeQL expertise for custom logic
SemgrepMedium, but there is a good community for adding rulesPrimarily pattern-based with limited dataflowDecent coverage with the right rules, can still miss advanced logic or SSRFFast scansHas custom rules, but dev teams must maintain them
SonarQubeLow – misses serious issues in our testingLimited – mostly pattern-based, code quality orientedBasic coverage for standard vulns, many hotspots require manual reviewModerate, usually in CIDashboard-based approach, can pass “quality gate” despite real vulns
Vulnerability ClassSnyk (partial)GitHub (CodeQL) (partial)SemgrepSonarQubeDryRun Security
SQL Injection
*
Cross-Site Scripting (XSS)
SSRF
Auth Flaw / IDOR
User Enumeration
Hardcoded Token
ToolAccuracy of FindingsDetects Non-Pattern-Based Issues?Coverage of C# VulnerabilitiesScan SpeedDeveloper Experience
DryRun Security
Very high – caught all critical flaws missed by others
Yes – context-based analysis finds logic errors, auth flaws, etc.
Broad coverage of OWASP Top 10 vulns plus business logic issuesNear real-time (PR comment within seconds)Clear single PR comment with detailed insights; no config or custom scripts needed
Snyk CodeHigh on known patterns (SQLi, XSS), but misses logic/flow bugsLimited – focuses on recognizable vulnerability patterns
Good for standard vulns; may miss SSRF or auth logic issues 
Fast (integrates into PR checks)Decent GitHub integration, but rules are a black box (no easy customization)
GitHub Advanced Security (CodeQL)Low - missed everything except SQL InjectionMostly pattern-basedLow – only discovered SQL InjectionSlowest of all but finished in 1 minuteConcise annotation with a suggested fix and optional auto-remedation
SemgrepMedium – finds common issues with community rules, some missesPrimarily pattern-based, limited data flow analysis
Decent coverage with the right rules; misses advanced logic flaws 
Very fast (runs as lightweight CI)Custom rules possible, but require maintenance and security expertise
SonarQube
Low – missed serious issues in our testing
Mostly pattern-based (code quality focus)Basic coverage for known vulns; many issues flagged as “hotspots” require manual review Moderate (runs in CI/CD pipeline)Results in dashboard; risk of false sense of security if quality gate passes despite vulnerabilities
Vulnerability ClassSnyk CodeGitHub Advanced Security (CodeQL)SemgrepSonarQubeDryRun Security
SQL Injection (SQLi)
Cross-Site Scripting (XSS)
Server-Side Request Forgery (SSRF)
Auth Logic/IDOR
User Enumeration
Hardcoded Credentials
VulnerabilityDryRun SecuritySemgrepGitHub CodeQLSonarQubeSnyk Code
1. Remote Code Execution via Unsafe Deserialization
2. Code Injection via eval() Usage
3. SQL Injection in a Raw Database Query
4. Weak Encryption (AES ECB Mode)
5. Broken Access Control / Logic Flaw in Authentication
Total Found5/53/51/51/50/5
VulnerabilityDryRun SecuritySnykCodeQLSonarQubeSemgrep
Server-Side Request Forgery (SSRF)
(Hotspot)
Cross-Site Scripting (XSS)
SQL Injection (SQLi)
IDOR / Broken Access Control
Invalid Token Validation Logic
Broken Email Verification Logic
DimensionWhy It Matters
Surface
Entry points & data sources highlight tainted flows early.
Language
Code idioms reveal hidden sinks and framework quirks.
Intent
What is the purpose of the code being changed/added?
Design
Robustness and resilience of changing code.
Environment
Libraries, build flags, and infra metadata flag, infrastructure (IaC) all give clues around the risks in changing code.
KPIPattern-Based SASTDryRun CSA
Mean Time to Regex
3–8 hrs per noisy finding set
Not required
Mean Time to Context
N/A
< 1 min
False-Positive Rate
50–85 %< 5 %
Logic-Flaw Detection
< 5 %
90%+
Severity
CriticalHigh
Location
utils/authorization.py :L118
utils/authorization.py :L49 & L82 & L164
Issue
JWT Algorithm Confusion Attack:
jwt.decode() selects the algorithm from unverified JWT headers.
Insecure OIDC Endpoint Communication:
urllib.request.urlopen called without explicit TLS/CA handling.
Impact
Complete auth bypass (switch RS256→HS256, forge tokens with public key as HMAC secret).
Susceptible to MITM if default SSL behavior is weakened or cert store compromised.
Remediation
Replace the dynamic algorithm selection with a fixed, expected algorithm list. Change line 118 from algorithms=[unverified_header.get('alg', 'RS256')] to algorithms=['RS256'] to only accept RS256 tokens. Add algorithm validation before token verification to ensure the header algorithm matches expected values.
Create a secure SSL context using ssl.create_default_context() with proper certificate verification. Configure explicit timeout values for all HTTP requests to prevent hanging connections. Add explicit SSL/TLS configuration by creating an HTTPSHandler with the secure SSL context. Implement proper error handling specifically for SSL certificate validation failures.
Key Insight
This vulnerability arises from trusting an unverified portion of the JWT to determine the verification method itself
This vulnerability stems from a lack of explicit secure communication practices, leaving the application reliant on potentially weak default behaviors.
Security
July 2, 2026

What Your Git History Knows That Your Wiki Doesn’t

How aging code, concentrated ownership, and departed maintainers quietly erode the security context around software.

In the first two pieces in this series, I wrote about what Git history can reveal that call graphs and threat models miss. A call graph can describe the structure of a system, but it won’t show that two files in different services have moved together for years. A threat model can describe the boundaries a team intends to protect, but it won’t show that the code behind one of those boundaries has been repeatedly rewritten by a rotating cast of contributors. Git history adds something those artifacts don’t: time, people, and interactions.

n.b. if you missed the other pieces in the series:

There’s a third pattern we’ve been studying at DryRun Security that becomes visible only when you look at time, people, and interactions. It appears when important code remains in place while the people who understood it gradually disappear. The service may still have an owner in the wiki or the code owners file, the architecture page may still look current, and the runbook may still explain how to restart it. None of that tells you whether anyone on the current team understands why a particular security decision was made or what would break if it changed.

Call it a wiki, a knowledge base, an internal developer portal, or a collection of architecture documents. Every company has some place where it tries to preserve what the team knows. Those systems are useful, but they preserve what people chose to write down. Git history preserves a different record of what people actually changed, and that difference is where knowledge decay starts to become visible.

Adam Tornhill’s Your Code as a Crime Scene has been influential for me because it treats code history as evidence. The core idea is simple: the way software changes over time often tells us more than the current snapshot of the code. In the first two pieces, that evidence showed up as hidden dependencies and fragmented ownership. Knowledge decay is another expression of the same idea because the evidence isn’t only in what changed. It’s also in what stopped changing, who stopped contributing, and whether anyone inherited the context they left behind.

The Knowledge Decay Pattern

Knowledge decay happens when the source code survives but the chain of understanding around it begins to break. A developer changes teams, a maintainer leaves the company, or a product area gets reorganized. The code continues to compile, the tests continue to pass, and the system continues to run. From the outside, nothing appears to be wrong.

Meanwhile, the world around that code keeps moving. The application adopts a new identity model. A dependency changes its defaults. The company introduces new roles, tenants, service accounts, or integrations. A security standard that was reasonable five years ago becomes obsolete over time. The code may be exactly the same, but the assumptions that once made it correct are no longer guaranteed to be true.

This is why age is a useful signal, although it needs to be handled carefully. Huge caveat here: old code isn’t automatically bad code. Some modules are old because they’re stable, well tested, and mostly finished. The age of a file should change our curiosity, not determine our verdict.

File age is also a fairly blunt measurement. A file can look current because someone updated an import, added telemetry, changed a type, or ran a formatter across the repository. None of those changes means the security-sensitive logic inside it was reconsidered. A token validation function may have survived untouched through several identity migrations, even though the file containing it was modified last week.

For that reason, the age of a code region can be more revealing than the age of the file. Looking at the surviving lines and hunks lets us distinguish between a module that is actively evolving and a busy file with an ancient security decision buried inside it. The important question isn’t simply when the file was last touched. It’s when the assumption enforced by that piece of code was last examined in the context of the system that exists today.

Ownership and Surviving Code

Ownership concentration gives us the other half of the picture. In DryRun Security Git Behavioral Graph, we look at how much of a file’s surviving code is attributed to its primary contributor. When one developer owns more than 90 percent of a heavily modified file, that concentration can indicate a knowledge silo. It doesn’t mean the developer did anything wrong, nor that the code is insecure. Deep ownership can be a major strength while that person is actively maintaining the module and sharing its context with the team.

The signal changes when the contributor is no longer active. Their account may have been deactivated, they may have left the organization, or they may simply have stopped working in that part of the codebase. Whatever the reason, the source code still reflects their decisions, while the person who could explain them is no longer part of the normal review path. The wiki may say that the Identity Platform team owns the service, but Git may show that 94 percent of the surviving authorization logic was written by someone who hasn’t committed to the repository in three years.

That gap between assigned ownership and inherited understanding is where documentation can give an organization more confidence than it has earned.

The strongest signal isn’t age or ownership concentration by itself. It appears when old code, concentrated ownership, and maintainer inactivity converge, especially when someone begins changing the code again. Dormant code can sit quietly for years without attracting much attention. The first material modification pulls all of its old assumptions back into the present, often after the people who understood them have gone.

Example Scenario

Imagine a token validation module that was largely written by one engineer five years ago. The file appears active because another developer recently added logging, but the branch that handles legacy refresh tokens hasn’t materially changed since the original implementation. The primary contributor has left, and a new pull request now changes how that branch handles a missing claim.

A traditional scanner may not find a dangerous API in the diff. The call graph can correctly show that the function is reachable from an authentication endpoint, and the threat model can correctly identify the trust boundary. The wiki can even point to the team that owns the service. Git history contributes the missing context: this is the first meaningful change to old security logic after the person who carried most of its history stopped participating.

That doesn’t mean the pull request should be rejected. It means the review should account for the loss of context. The team may need to reconstruct the original security contract, write characterization tests before changing behavior, or bring in someone who understands an adjacent system. In some cases, the right answer may be to remove the legacy path instead of extending it. The graph isn’t making that decision for the team. It’s showing why a three-line diff may deserve more than a three-minute review.

This becomes even more important as AI makes it easier to modify unfamiliar code. An agent can explain the current control flow and produce a plausible patch, but it doesn’t automatically know about the production incident that led to an awkward conditional four years ago. It can’t recover undocumented intent from the code alone, and it won’t know that the original maintainer was the only person who understood a customer-specific exception unless we give it the historical evidence.

Security and Verification in Practice

This is one of the reasons we added Git Behavioral Graphs into the DryRun Security product. Before an agent begins reading code, we can use deterministic signals such as code age, ownership concentration, and maintainer activity to decide where deeper analysis should begin. Instead of treating every reachable function as equally understood, we can steer attention toward security-sensitive code that may have outlived the people and assumptions that shaped it.

Knowledge decay isn’t a vulnerability, and it shouldn’t become another simplistic rule that labels every old file as dangerous. It’s a reason to adjust confidence. A quiet module can look safe because it hasn’t generated an alert, even though nobody currently working on the system can explain why its security decisions are correct. When that module starts changing again, the missing understanding becomes part of the risk of the change.

A wiki can tell you who owns a system today. Git history can tell you whether that ownership was ever transferred. It's a very useful signal in AI coding and one of many reasons that teams are using DryRun Security to get AI Code Verification at scale.