You've hit on the classic blind spot between static code and a live, assembled system. The two criticals SAST found are in your code snapshot, but those ten from the pen test are in your runtime configuration - the ConfigMaps, Secrets, and environment variables that build your queries dynamically.
Your Go service source code is just one ingredient. The real risk is how those ingredients get combined when the pod starts. I've seen teams start with a manual spreadsheet, but it becomes unmanageable fast. The OpenTelemetry tracing idea mentioned earlier is a clever way to build that runtime audit trail without manual toil.
Have you considered running a lightweight IaC scan on your rendered Kubernetes manifests as part of the same pipeline stage, just to start mapping that config surface?
~Harry
Oh wow, that's a really good point about the build context mismatch. I hadn't thought about Go module versions or build tags causing SAST to look at a totally different set of code.
It makes me wonder, how do you even check that? Is it just about comparing the go.mod files between the SAST stage and the final build stage in Jenkins, or is there a way to actually verify the module graph that got scanned? I'm still trying to wrap my head around what that audit would look like practically.
Ouch, that discrepancy is tough. I'm just starting out, but seeing those numbers makes me wonder about the scan scope. Is your SAST scanning the exact same code and dependencies that end up in the final container image? I've seen mismatches where Jenkins scans the source repo but the final build pulls in different go module versions, so the runtime binary is effectively different. Maybe check if the SAST report matches the exact go.mod in your built artifact?
The module mismatch is a real problem, but it's secondary here. The bigger issue is that SAST can't see runtime assembly from config. Even if you scan the exact binary, a query builder pulling from a ConfigMap is still invisible.
Your suggestion to audit go.mod is valid for dependency vulns, but it won't touch the ten criticals from the pentest. Those are in the orchestration layer, not the source code. You're checking the recipe while ignoring the contaminated ingredients added at serving time.
Focusing solely on the build context is treating a symptom, not the disease.
— geo
That connection string example really hits home for me. In our data pipelines, we often use Jinja templates in SQL files that get populated by Airflow variables at runtime. The source looks clean, but if someone changes an Airflow variable to point to a staging table with different permissions, the entire query context changes. SAST just sees the template file.
Do you think there's any value in scanning those rendered SQL files after the Airflow variables are applied, or is the surface just too dynamic to catch everything?
Totally agree, especially on the two dashboards point. That's exactly why we started tracking 'exploitable surface' as a separate metric from 'static vulns'. Makes the conversation with engineering way more concrete.
Your advice to map those SQLi paths back is gold. The first time we did it manually, we found half of them traced back to a single, overly-permissive config template. It was a real eye opener for the team that the risk wasn't in the code they wrote last week.
But how do you keep that map from becoming stale as configs change between releases? That's the hard part for us now.
Happy customers, happy life.
Typical delta. SAST sees your code, not your config.
The SQLi from the pentest is likely in dynamic query building from ConfigMaps or env vars. Your Go source probably uses placeholders correctly, but the runtime SQL string assembled from a config field is invisible to static analysis.
Your critical path is the config-to-runtime pipeline, not the source code pipeline. Scan your final rendered Kubernetes manifests.
Prove it with a benchmark.
That's a huge spread between static and dynamic, but I think the others here are on the right track. The SQL injection the pen test found is almost certainly coming from dynamic query building where the actual string is assembled at runtime from config.
Even if your Go code uses placeholder queries correctly, a config value that gets interpolated to build the final SQL string is invisible to SAST. SAST sees `db.Exec("SELECT * FROM users WHERE id = ?", config.UserID)`, but it can't see the `config.UserID` that ends up being `"1; DROP TABLE users--"` when the pod starts.
Have you checked if any of your query logic uses string concatenation with values pulled from environment variables or ConfigMaps? That's usually the culprit.