There is a timing problem at the center of most software security programs. Teams run SAST scans weekly, or nightly if they are disciplined. The scan finds a SQL injection vulnerability in a payment processing function. By the time the finding lands in a ticket and gets assigned to a developer, three weeks have passed. The developer who wrote the vulnerable code has moved on to a different project. The context is gone. Remediation now requires reading unfamiliar code, writing a fix under time pressure, and re-testing without the original author's knowledge of what the function was supposed to do.
The fix exists for that vulnerability, but the process that produced it is expensive and slow. More importantly, it does not prevent the next one. The developer who introduced the SQL injection never got feedback at the moment of writing. They shipped it, it sat in a queue for weeks, and when it came back to them it arrived as a security ticket rather than a learning moment.
The cost gap between catching at PR vs catching in production
There is a well-documented relationship between when a security issue is caught and how much it costs to fix. The figure that comes up most often in software engineering literature is roughly a 30x cost difference between finding a bug during development versus finding it post-deployment. We do not need to debate the exact multiplier; the direction is consistent regardless of methodology.
At the PR gate, the fix is almost always local. The developer who introduced the problem is looking at it. The code is not yet integrated with a dozen downstream components. The fix is a change to the same lines that introduced the issue, often a one-line substitution: a parameterized query instead of string concatenation, a cryptography API call with a secure algorithm instead of a deprecated one, an input validation check before processing user-supplied data.
Post-deployment, the same fix requires a hotfix branch, emergency deployment, coordination with whoever depends on the affected endpoint, regression testing under time pressure, and a post-incident review. The vulnerability might have been exploited in the window between deployment and discovery. Remediation also now involves security incident documentation rather than just a code change.
What PR-level security checks can catch today
We are not claiming that AST-based PR analysis replaces penetration testing, runtime monitoring, or dependency scanning. Those serve different purposes. What PR-level static analysis catches well is a specific category: insecure coding patterns that are deterministic and detectable without execution.
This includes most of the OWASP Top 10 patterns that manifest as code patterns rather than configuration issues. SQL and NoSQL injection when queries are constructed by string concatenation. Command injection when user input flows into subprocess calls without sanitization. Insecure deserialization when untrusted input is passed to deserialization functions without validation. Broken cryptography when code explicitly calls deprecated algorithms (MD5, SHA1, DES) for security-sensitive purposes. Hardcoded secrets in source code. Missing authorization checks on controller endpoints.
These patterns have consistent AST signatures. A function call to cursor.execute() in Python where the argument is a formatted string rather than a parameterized query has a specific AST shape. It does not require dynamic analysis to detect. A call to hashlib.md5() in a password hashing context has a specific shape distinct from using MD5 for a cache key where cryptographic strength is irrelevant.
The false-positive problem in security rules
Security rules have a particular challenge with false positives because the response to a security finding is higher stakes than the response to a style violation. If a style rule fires incorrectly, the developer adds a suppression annotation and moves on. If a security rule fires incorrectly and the developer learns to dismiss security findings as noise, that is a much more serious outcome.
False-positive-heavy security scanning is arguably worse than no scanning, because it actively degrades the signal-to-noise ratio on the checks that matter. We have talked with teams running Semgrep with large default rule sets who had disabled entire rule categories because the noise volume was too high to manage. Those categories included real security findings that were now invisible.
Context-sensitive rules reduce this problem substantially. The MD5 example is a good illustration. A flat rule that flags all uses of MD5 will fire on legitimate uses: verifying file integrity where cryptographic strength is not the goal, generating non-security cache keys, computing checksums for deduplication. A context-sensitive rule that checks whether the MD5 digest is compared against a stored value (suggesting authentication), or whether it is used to hash a value described in surrounding code as a password or secret, produces far fewer false positives.
We are not saying context-sensitive rules are always possible to write, or that every rule can achieve near-zero false-positive rates. Some categories, like taint analysis that tracks user input across multiple function boundaries, have inherent precision limitations in static analysis. But for the class of patterns that PRCheck focuses on, context sensitivity is the difference between a rule that gets used and a rule that gets suppressed.
Integration with existing security workflows
Shifting security checks to the PR gate does not mean eliminating post-merge scanning. It means changing what each layer does.
PR-level checks handle: deterministic code patterns, new code introduced in the diff, issues that are cheapest to fix right now. Weekly or daily SAST scans handle: dependency vulnerabilities in the full dependency tree, configuration file security checks, issues in files that were not touched by recent PRs, and finding regressions from tool version updates that produce new rules. Penetration testing and dynamic analysis handle: business logic flaws, authentication flows, and attack chains that require runtime behavior to detect.
These are complementary, not competing. A team running all three has a layered defense where each layer catches what the others miss. The shift is not "stop scanning" but "catch the things you can catch at PR time, when fixing them is cheapest."
How this changes the developer experience
There is a less-discussed benefit of finding security issues at PR time: the feedback loop. When a developer introduces a command injection vulnerability and a scan finds it three weeks later, the learning value is low. The ticket describes a vulnerability; the developer reads about it; they apply a fix they do not fully understand; they move on. The connection between the code they wrote and the security property they violated is abstract.
When the same finding appears as a PR comment sixty seconds after the developer opens the pull request, the code is still in their working memory. The comment links directly to the specific line, explains why it is a problem, and shows the secure alternative. The developer fixes it, understands why, and is less likely to make the same mistake next week. This is not a side benefit; it is one of the primary arguments for shifting security left.
Security programs that only report findings after shipping create a security-versus-development dynamic where developers experience security as a delayed blocker on their work. Programs that surface findings at the PR gate fold security into the normal development conversation. The framing shifts from "security found a problem with your code" to "here is something to address before merging." That framing change is more durable than any tooling decision.