Platform Runtime Detection SBOM & SCA Container Scanning Supply Chain Solutions Fintech Engineering SaaS Engineering Regulated SaaS Integrations Pricing Docs Blog
Sign in Start Free Trial
DevSecOps 8 min read

Shift Left Doesn't Mean Shift All the Way Left: The Runtime Gap in DevSecOps

DevSecOps shift-left with eBPF runtime security and SBOM coverage

The engineering industry spent most of the 2010s convincing security teams to shift left — integrate static analysis into the IDE, run dependency scans in CI, add SAST gates before merge. That argument was correct and the progress was real. The problem is that shift-left became a destination rather than a layer, and somewhere along the way teams started treating "we run security scans in CI" as equivalent to "we have security coverage." Those two things are not the same.

Static analysis gates catch a meaningful class of vulnerabilities — injection risks in application code, obviously vulnerable dependency versions, known-bad base image CVEs. They are genuinely useful and worth having. But they operate on a model of the software, not the running software. The model is always incomplete. What executes at runtime is shaped by environment variables, configuration drift, operator behavior, and the behavioral interaction of components that static analysis can't observe.

What Shift-Left Actually Covers — and What It Doesn't

A typical shift-left toolchain in a K8s-native SaaS team looks like this: a pre-commit hook running a linter and a secrets scanner, a CI gate running SAST (e.g., Semgrep or CodeQL) and SCA (dependency CVE scanning), a container image scanner before push to the registry, and maybe an OPA or Kyverno admission policy that blocks images without a clean scan result from reaching production.

This is a solid pipeline. It catches hardcoded credentials before they're committed, known-vulnerable packages before they're deployed, and critically-CVE'd base images before they reach production. None of this is trivial to build or maintain.

What it doesn't catch: behavior that emerges after deploy. Process executions that weren't in the code path analyzed by SAST. A container that was clean at scan time and is exploited a week later via a vulnerability disclosed after the image was pushed. Configuration changes made by an operator that widen the attack surface without touching source code. Lateral movement from a compromised workload to adjacent services on the same namespace. A dependency that passes CVE scanning because the vulnerability hasn't been published yet.

These aren't exotic edge cases. They're the attack patterns that show up in incident retrospectives. The incident that costs engineering teams weeks of response time isn't usually "we had a critical CVE and our scanner missed it." It's "the vulnerability was unknown or context-dependent and we had no runtime visibility to detect the exploit when it happened."

The Developer Loop vs. The Runtime Gap

Shift-left tooling is most effective when it's woven into the developer loop: IDE plugins that surface SCA findings as you type, pre-commit hooks that catch obvious issues before a context switch to "security review mode," CI gates that provide actionable fix suggestions rather than just CVSS scores. The closer security feedback is to the moment of code authorship, the lower the context-switching cost and the higher the fix rate.

This is genuine ergonomic progress. A developer who sees "this package has a known RCE vulnerability, consider version X.Y.Z" while writing a requirements.txt update is more likely to act than a developer who gets a security team ticket three days later pointing at a CI log.

But developer loop integration has a boundary: it ends at the merge gate. Once code is in production, the developer loop model has no observability into what the code is actually doing. Runtime is a different environment — different load, different configuration, different interaction patterns between services, different operating system and kernel behavior than the developer's local machine or the CI runner.

We're not saying shift-left tooling is insufficient or that teams should abandon their CI security gates — the opposite is true. A mature AppSec program needs both layers. The point is that treating the CI gate as the terminal control is a miscalibration. The threat model doesn't end at deploy.

Where Runtime Context Changes the Prioritization Equation

Consider a growing platform team with 80+ microservices on EKS. Their SCA pipeline surfaces an average of 340 CVE findings per sprint cycle across the service fleet. The engineering team is eight people. There is no world where eight engineers remediate 340 findings per sprint; triage is the real job.

Static SCA triage is based on CVSS score, package version, and whether a patch is available. This is a reasonable starting point. But CVSS score doesn't tell you whether the vulnerable code path is executed in your service. A CVSS 9.8 RCE in a crypto library used only for one legacy API endpoint that handles 0.1% of traffic is a different risk than the same CVSS score in a library called on every authenticated request.

Runtime SCA correlation uses live execution traces — in the eBPF case, syscall-level process ancestry and shared library load events — to determine which packages are actually loaded and executing in each workload. A CVE in a package that is present in the SBOM but never loaded at runtime can be deprioritized with high confidence. A CVE in a package that is loaded on the hot execution path for your payment processing service cannot.

In practice, runtime reachability correlation reduces the actionable CVE count by 60-80% in typical K8s microservice deployments. The finding rate from static SCA doesn't change — what changes is the filter applied before a finding becomes a developer task. That filter is what turns an unworkable backlog into a manageable sprint commitment.

The Shift-Everywhere Model

The frame that works better than "shift left" is "coverage at every layer" — or, more practically, shift-everywhere. Pre-commit hooks catch secrets and obvious misconfigurations. CI SAST catches code-level vulnerability patterns. SCA scanning catches known-vulnerable dependencies. Container scanning catches image-layer CVEs. Admission policies enforce deployment-time supply chain requirements. Runtime detection covers behavior after deploy.

Each layer has a different cost, a different detection latency, and a different false positive profile. Pre-commit is cheap, fast, and high-false-positive-rate. Runtime detection is more expensive operationally, has higher latency (detection happens at execution, not authorship), and has a lower false positive rate because it's observing actual behavior rather than modeling it.

The mistake that leads to gaps isn't choosing the wrong layer — it's treating any single layer as complete. Teams that run excellent SAST and SCA but have no runtime visibility have a gap. Teams that have runtime detection but no supply chain attestation have a different gap. The mature model closes both.

DevSecOps as Continuous Coverage, Not a Pipeline Stage

Shift-left as a practice improvement was a correction to a real problem: security as an afterthought bolted on at release time. That correction produced genuine gains in fix-earlier, fix-cheaper metrics and in developer security awareness. The next correction isn't to shift right — it's to stop thinking about "left" and "right" as the axis entirely.

Security coverage is a function that needs to be non-zero across the full software lifecycle: from the developer typing a dependency name, through the CI build, through the admission gate, through the running workload. The shape of coverage at each stage looks different — IDE plugin vs. CI job vs. admission controller vs. eBPF probe — but the goal is the same: no layer of the stack is an observation blind spot that an attacker can exploit without detection.

The teams that close incidents in hours rather than weeks aren't the teams with the most extensive CI gate. They're the teams that knew something anomalous was happening at runtime — a process executing that shouldn't exist, a network connection to an unexpected destination, a syscall pattern matching a known lateral movement signature — within minutes of exploitation. That signal doesn't come from the pre-commit hook. It comes from the runtime layer that shift-left alone doesn't reach.