1,200 SAST Findings and One Release Decision
Your team is triaging a Python monorepo of roughly 240k lines for a payments company. SAST returns 1,200 findings; 380 are rated high. Three findings actually bear on Friday's release. First, pickle.loads() is called on values read from a Redis cache in pricing/cache.py. The cache is written only by the pricing service, but the Redis instance is shared with the analytics team's jobs, which execute user-supplied notebooks, and it accepts connections from the whole 10.0.0.0/16 VPC with no AUTH. Second, a helper db.query(sql, params) is used throughout; its implementation runs cursor.execute(sql % params) when params is a tuple of length 1 and otherwise delegates correctly to the driver. Third, yaml.load(request.files['config']) appears in an internal admin upload handler, protected by an @requires_role decorator - but one route is registered before the decorator is applied because of a Blueprint registration-order mistake.
1. For each of the three findings, state realistic exploitability and what evidence you would require before blocking the release.
Read the worked answer
Pickle: exploitable and high. pickle.loads on attacker-influenced bytes is arbitrary code execution. The claim that only the pricing service writes the cache does not hold - Redis is reachable from the entire /16 with no AUTH and is shared with a team running user-supplied notebooks. Any notebook author, or anyone who lands on an analytics host, can write the pricing key and get code execution in the pricing service's context. The trust boundary is the Redis endpoint, not the writer. Evidence to block: which hosts can reach 6379, confirmation that AUTH/TLS is absent, and a one-line proof that a crafted value in that key is deserialized.
db.query: exploitability depends entirely on call sites. The length-1 branch is string interpolation, so any caller passing a single-element tuple where the value is user-controlled - db.query("... WHERE id = %s", (request.args['id'],)) - is injectable, even though everything else is safe. Evidence: enumerate one-argument call sites that use percent-formatted SQL, then trace the value to the request. One reachable instance blocks release; the wrapper fix is a few lines.
yaml.load: read the Blueprint registration before you believe it. If the undecorated route is genuinely registered, it is unauthenticated code execution on an admin upload - block. Modern PyYAML requires a Loader argument, and yaml.load with the unsafe loader constructs arbitrary Python objects. Verify in staging with an unauthenticated request, not by reading the diff.
2. For the pickle finding, argue the fix: replace the serializer, or fix the trust boundary? Which is cheaper and which is durable?
Read the worked answer
The two arguments pull in different directions, and only one of them is about code.
The serializer argument - replace pickle with JSON or msgpack - is correct but expensive, because it means touching every producer and consumer of that cache key, including analytics jobs your team does not own. That coordination cost is what people mean by 'rewrite the serializer'.
The boundary argument says the defect is that a code-execution primitive sits behind something that was never a boundary. Cheapest durable interventions, in order: put AUTH (or token-based auth) on Redis and move it into a private subnet whose security group admits only the pricing service; give analytics its own instance under its own credentials; then, separately, stop using pickle for this key.
The first two are hours of work and remove the attack path for everyone, including notebook users who should never have had network reach to a production cache. Do the serializer migration anyway as tracked debt, because auth alone leaves a latent RCE the moment the cache is re-exposed or a second consumer appears. Note the asymmetry: pickle is unsafe on read from any source you do not fully control, so the code change is the durable part even though the network change is the cheap part. Do both, in that order, and say so explicitly rather than pretending one fix covers the finding.
3. You can block exactly one thing before Friday's release. Which finding do you gate on, and what do you put in CI so this does not recur?
Read the worked answer
Gate on the yaml.load route, but only if the decorator is genuinely missing - verify by reading the registration order in __init__.py and hitting the route unauthenticated in staging. Reasoning: it is the only candidate that is unauthenticated, remotely reachable, and directly code-executing. The pickle path needs network access from inside the VPC first; serious, but a step removed. If the route turns out to be authenticated, it drops to medium - an admin who can upload YAML is already inside the trust boundary, though it still escalates to code execution on the app server and should become yaml.safe_load promptly. That one-line change carries less risk than either the pickle migration or the wrapper rewrite, so there is no real trade-off.
For CI, do not gate on the 1,200 count; it has no signal and will be ignored within a month. Gate on two grep-level checks that fail the build: any occurrence of yaml.load without Loader, and any single-argument db.query call using percent formatting. Add a Semgrep rule for pickle.loads on values whose origin is a network or cache read, and report new findings as a differential against the base branch rather than as an absolute total. That differential is the only number anyone will act on.