Guide 061 pillar

What is SAST and what can it actually tell you?

Understand what SAST examines, what its alerts can establish, and how source scope, configuration and review turn scan output into useful work.

Static application security testing (SAST) examines code or compiled artifacts without running the application. It can point reviewers toward risky paths, such as untrusted input reaching a database query, and often names the relevant file and line. It cannot, by itself, establish that a route is deployed, that an attacker can reach it, that an unseen control fails, or that no vulnerabilities exist elsewhere. The answer changes with the tool's language and framework support, the source actually included, its configuration, and the claim you are trying to verify. Treat a SAST result as evidence with a scope. Confirm the path before reporting a vulnerability; a developer can still make a straightforward protective correction while exposure is being checked.

OWASP describes SAST as analysis of source or compiled code that helps find security flaws; its source analysis overview lists both developer-friendly location information and limits such as false positives and difficulty proving a vulnerability. That combination explains why a scanner is useful even when its report is not a verdict.

What happens during a static scan?

The input is a particular repository revision or build artifact. Static analysis does not exercise the application's runtime behavior, but preparing that input may execute build scripts or dependency tooling. Check the tool's build mode and environment requirements before treating a scan as a read-only operation. Depending on the product, the engine parses supported languages, identifies patterns or data flows, applies configured rules, and emits alerts. A data-flow result may trace a value from an entry point through several functions to a security-sensitive operation. A pattern result may identify a dangerous API call or configuration value. OWASP's static code analysis description explains that such tools often aid an analyst by directing attention to security-relevant code rather than automatically settling every claim.

The most important word in that description is input. If the scan received only a service directory, it did not assess a different service, a generated route absent from that directory, or production settings outside the repository. A result may also be conditional: the risky function exists in the code, but a gateway, feature flag or downstream authorization check could change whether the proposed action is possible. Conversely, an omitted file or unsupported language can hide an issue from the scan. “No alerts” says nothing about those parts until the coverage record explains them.

SAST is one activity in a development and verification process. The OWASP Developer Guide distinguishes static analysis from dynamic testing, which applies input to a running application. Manual review adds knowledge of the intended business rule and system design. These methods answer different questions, so purchasing one does not silently perform the others.

Read an alert as three different statements

Synthetic example; no scan or request was performed: Larkspur Support's export service has two query branches. At revision LS-214, a fictional scanner emits:

Rule: request value reaches SQL execution
Location: exports/query.py:18
Source: request.query["status"]
Path: export_route → build_query → cursor.execute
Severity: high

The location is a source fact if the file and line exist. The path is the analyzer's model. “A customer can extract another account's tickets” is a further claim requiring authority, query behavior and deployment evidence. The alert bundles useful navigation with a hypothesis; the reviewer unbundles them.

Here is the relevant illustrative Python-style code. It is not an executable application:

if config.use_legacy_export:
    sql = "SELECT id FROM tickets WHERE status = '" + status + "'"
    cursor.execute(sql)
else:
    cursor.execute(
        "SELECT id FROM tickets WHERE tenant_id = ? AND status = ?",
        (verified_tenant, status),
    )

The legacy branch puts the value into statement text. The other branch passes values separately using SQLite-style placeholders; a real review checks the actual driver contract. The modern branch also includes tenant scope. Those are two different controls: binding protects SQL structure, while tenant scope restricts the selected records. Fixing either one does not automatically fix the other.

Now supply the missing configuration: the synthetic release manifest for LS-214 sets use_legacy_export=false, and route registration shows no other caller of the legacy branch in that build. The immediate production-path claim is defeated for this configuration, assuming the manifest accurately binds to the deployment. The legacy branch still merits removal or a guarded fix before someone enables it. That engineering task can be justified without calling the current release exploited.

Change one fact: a scheduled export worker has its own configuration with the flag enabled. The handler's safe branch no longer settles the worker. The useful outcome is a new worker-specific investigation, not a global “false positive” suppression on every cursor.execute call.

Evidence What the CTO can conclude Next decision
Exact unsafe legacy construction Source contains a risky query branch Assign an owner to remove or repair it
Matched release configuration disables that branch Current route claim is defeated at this scope Record the defeating configuration; avoid an unsupported emergency declaration
Worker has a separate enabled flag A different path may be applicable Trace worker inputs, authority and query call
No runtime validation No executed disclosure demonstrated Request a narrow authorized check only if needed to settle behavior

This is the practical value of SAST: the alert finds the place to investigate; the review turns it into the right work. Counting the alert as a confirmed vulnerability would overstate it. Ignoring it because one route is protected would miss the worker question.

What SAST can tell you well

For supported languages and visible code, a scanner can repeatedly highlight particular coding patterns, data flows and suspicious operations. It can draw attention to code a reviewer might not otherwise inspect during a busy release, and it can often provide the starting location for a developer. OWASP lists repeated use at scale and file-level output among SAST's strengths in its source analysis overview. A good alert saves search time when it preserves the path and explains the rule.

Static analysis also fits early feedback. Developers may run it while changing a function, and a team may run it against a pinned revision before a release decision. The gain is earlier investigation of a candidate, not a guarantee that the candidate is true. A tool may flag code that never runs or miss a framework-specific sanitizer; a reviewer must still understand what the application actually does.

The method is especially helpful when the question is local enough to express in code terms: Does untrusted input reach this operation? Is this unsafe function called? Is a check present on the traced path? Even then, the interpretation depends on framework behavior, callers, alternate routes, input constraints and deployment. The alert should expose those assumptions so a developer can challenge it.

What it cannot tell you alone

It cannot prove live exploitability from a line number. The line may be in a disabled route or a build artifact that is not deployed. A later service, policy or storage layer may reject the action. A source-supported actor-to-effect path is stronger than a raw alert, but it remains distinct from a request executed in an authorized environment. Do not imply a penetration test occurred because a static tool drew an attack path.

It cannot understand every business rule by default. “A customer may issue one refund per paid order” is an organization-specific rule. A scanner can inspect the refund code, but without that policy and the complete workflow it may have no basis to decide whether a second refund is improper. OWASP's secure code review guidance places business logic, architecture, authorization and deployment review alongside automated tools.

It cannot attest to everything outside the analyzed input. Infrastructure policy, deployed secrets, CDN behavior, identity-provider settings, feature flags and third-party processing may affect risk. A repository can include some configuration as code, but that does not make all runtime configuration visible or current. A dependency advisory check, or software composition analysis, is another activity with its own inventory and reachability questions. The label “code scanning” on a dashboard does not prove that a SAST run assessed dependencies, containers, running services or cloud configuration.

It cannot turn a standard into a pass badge. OWASP ASVS provides requirements for verifying application security controls. To say a requirement was satisfied, a team needs the evidence appropriate to that requirement and its scope. A scanner rule mapped to an ASVS identifier may point to relevant code; the mapping alone does not establish that the control works across the deployed application.

Turn a scanner run into a useful decision

Ask for the pinned input, analyzed-scope record and the disposition of a representative alert. For Larkspur, a useful update is: “The public export route uses the bound, account-scoped branch in the mapped release. The legacy worker configuration needs review. Engineering will remove the obsolete branch; AppSec owns the worker applicability decision.” It names a control, an uncertainty and an owner without inflating the result.

Keep the run separate from the findings. Tool/rule versions, skipped files and completion status explain what work happened. The supported path, counterevidence and remaining questions explain which repair or investigation deserves a developer task. Independent review is especially useful for consequential or disputed conclusions. After a fix, compare the actual changed control; a missing alert could also mean the rule stopped running.

If the report is empty, partial or noisy

Before making the scan a release requirement, ask one engineer to use an alert without the vendor present. In the Larkspur example, can that engineer open LS-214, locate the two adapters, identify the worker configuration and explain what would close the worker question? If the answer requires reconstructing missing paths from screenshots, the first improvement is the handoff, even if the engine found an interesting lead. If the artifact is clear but the team lacks access to deployment records, assign that dependency explicitly. Buying more scans does not supply the missing permission or owner.

Keep the first operational commitment narrow: run the selected checks on an agreed service, route candidates to an owner, and record the time required to reach a useful disposition. Include unsuccessful setup and unresolved alerts in that record. A fast successful scan can still leave a slow investigation; a longer run can be worthwhile if its evidence reduces reconstruction work. These are separate observations to measure locally, not a promised return from using SAST.

That first cycle also identifies who maintains the configuration. A language upgrade, new query wrapper or moved service can invalidate assumptions in a previously useful result. The responsible team should know which change calls for reviewing the scan setup, rather than treating last month's green badge as a permanent property of the repository.

An empty result should trigger a coverage check: did the tool successfully parse the intended languages, analyze the selected services, apply the expected rules and complete the run? If any of those are unknown, the useful output is “assessment incomplete” with a named next check. If all are known, report “no findings from these configured checks in this assessed scope,” then review high-risk business rules and deployment-dependent controls separately.

For a noisy result, do not bulk-close alerts by severity or bulk-forward them to developers. Select a sample that spans rules and components, verify each claim and counterexample, and change the configuration only after recording what the change may suppress. If a rule catches one true path and many false ones, the choice may be to tune its source/sink model, route its output to security review, or retire that rule with an explicit coverage loss. The decision belongs to the security owner, with engineering feedback about whether tickets are usable.

For the next run, ask for a pinned revision, analyzed-scope ledger and one independently reviewed alert before asking for a vulnerability count. Then use the SAST, DAST and penetration-testing comparison to choose the next method, or the real-repository evaluation guide if you are selecting a tool.