scc, Semgrep, Gitleaks, Trivy: What Each One Catches
Every Phase 3 audit pulls in four separate static analysis tools, not because more tools looks more thorough on a slide, but because each one is built to catch exactly one class of problem and is structurally blind to the other three. Run only one and you're not getting a smaller version of the full picture. You're getting a complete picture of a quarter of the risk surface, and no visibility into the rest.
scc: Complexity
scc (Sloc Cloc and Code) counts lines of code and computes complexity metrics per file and per language: cyclomatic complexity, comment-to-code ratio, and where logic is concentrated. It's fast, it doesn't need a build, and it tells you which files are structurally hardest to change safely.
What it doesn't do is evaluate whether any of that code is correct, secure, or safe. A perfectly simple, low-complexity file can still ship a SQL injection vulnerability or a hardcoded API key. scc has no concept of either. It answers "how hard is this to maintain," full stop, and nothing about that answer says anything about security or dependency risk.
semgrep: Security Patterns
Semgrep is a rule-based static analysis tool that matches code structure against known-dangerous patterns: unsanitized input reaching a database query, unsafe deserialization, missing authentication checks, and similar OWASP-class issues. It's fast enough for real-time, incremental scanning inside a CI pipeline rather than needing a slow, separate audit pass, which is a big part of why it's become a standard part of modern AppSec pipelines.
Its blind spot is scope, not accuracy. By design, it evaluates source code patterns, not committed secrets and not third-party dependency risk. A hardcoded API key sitting in a config file isn't a dangerous code pattern in the way semgrep looks for one, it's an exposed credential, which is a different detection problem entirely. Semgrep also has no visibility into vulnerabilities that live inside a dependency's compiled code rather than in the source it's scanning.
gitleaks: Secrets
Gitleaks exists to catch exactly what semgrep doesn't: hardcoded API keys, tokens, and credentials, scanning both current code and git history so a secret committed and later deleted doesn't stay silently exposed in an old commit. It works by matching known secret formats and flagging high-entropy strings that look credential-shaped.
That entropy-based approach cuts both ways. It catches secrets in formats it recognizes, but a custom internal token format it has no pattern for can slip through, and a secret that only exists as a runtime environment variable, never committed to the repository at all, is invisible to a tool that only scans what's actually in the code. Gitleaks also has no opinion on whether a piece of code is complex, insecure in its logic, or built on a vulnerable dependency. It only answers one question: is there a secret sitting in this repository that shouldn't be.
trivy: Dependency CVEs
Trivy scans package manifests and lockfiles against known vulnerability databases, flagging third-party dependencies with disclosed CVEs, along with container and infrastructure-as-code configuration issues. It answers a specific, valuable question: are we running a version of something with a publicly known security problem.
What it doesn't do is tell you whether your code actually calls the vulnerable part of that dependency. Open-source SCA scanners, Trivy included, generally don't perform function-level reachability analysis, which means a flagged CVE in a rarely-used corner of a large dependency shows up with the same urgency as one in a function your application calls on every request. It's a real gap, and it'swhy a trivy report needs a human, or a second layer of analysis, to separate what's urgent from what's technically present but practically irrelevant.
What Each One Misses, Side by Side
scc catches complexity, misses everything about correctness, security, secrets, and dependencies. It's a structural map, not a risk assessment.
semgrep catches known-dangerous code patterns, misses secrets and dependency risk entirely. Its blind spot is scope: it only ever looks at the code you wrote, not what's committed as a credential or what's pulled in as a package.
gitleaks catches committed secrets, misses runtime-only secrets and unrecognized custom formats. It also has zero opinion on code quality or dependency risk, secrets detection is its entire job.
trivy catches known CVEs in dependencies, misses whether your code actually exercises the vulnerable part. A flagged package isn't automatically an urgent one without reachabilitycontext.
Four tools, four non-overlapping blind spots. Running all four doesn't just add coverage, it removes the false confidence that comes from one clean report meaning the codebase is actually fine.
A Worked Example
Take a payment processing file that's been touched by three engineers over a year. scc flags it at high complexity, a 300-line handler with deep nested conditionals. semgrep separately flags a pattern match on that same file, unvalidated input reaching a downstream API call. gitleaks flags nothing in that file, but does flag a high-entropy string in a config file two directories over. trivy reports the payment SDK the handler depends on is two major versions behind, with a disclosed CVE in the version currently pinned.
Looked at individually, each finding is a data point. Looked at together, they describe one coherent risk: the file most likely to contain a real defect is also the one that's structurally hardest to review carefully, running the least-trusted input path, sitting downstream of a known-vulnerable dependency, in a part of the codebase where a secret was also recently exposed. No single tool produces that sentence. Each one hands back one piece of it, and reconciling four separate reports into that picture by hand is exactly the kind of correlation work that's slow and easy to get wrong under deadline pressure.
Why an LLM Interpretation Layer Sits on Top
Four separate tool reports don't automatically add up to a usable answer. A gitleaks flag on a high-entropy string might be a real secret or might be a hash that happens to look like one. A semgrepfinding and an scc complexity flag on the same file are more urgent together than either is alone, since a risky pattern in a hard-to-change file is a worse combination than either fact by itself. Sorting through four raw tool outputs by hand, correlating what overlaps and what's actually urgent, is its own significant chunk of engineering time.
This is the part an LLM interpretation layer is actually good at: reading all four reports together, weighting overlapping findings appropriately, and turning four disconnected outputs into one prioritized narrative instead of four separate lists someone has to reconcile manually.
How This Runs as Phase 3
This is what Phase 3 of gap analysis actually is: all four tools running against every user story's code, scored from 0 to 100 across complexity, pattern adherence, and anti-patterns, with architectural gaps, security vulnerabilities, missing unit tests, and undocumented code surfaced on top. The output isn't four raw scan results dumped in a folder, it's a PDF report with annotated snippets and prioritized fixes, ready for a sprint or a leadership review, because the interpretation work has already been done.
This also means the four tools aren't running as four separate CI steps someone has to configure, maintain, and reconcile independently. They're one input into a single scoring pipeline per story, which is the practical difference between a DevOps team owning four separate tool integrations and a QA or engineering lead reading one report that already accounts for what all four found.
Run Phase 3 on your codebase: start a gap analysis audit, or read the full breakdown of what the resulting quality score measures.
