Safeguard SAST is generally available, and Java is one of its three phase one languages, alongside JavaScript/TypeScript and Python. It reads your Java source without building or running it, follows untrusted input from a source (such as a request parameter) to a dangerous sink (such as a SQL query, a command execution or a file path), and reports each finding with the full dataflow trace, a CWE and OWASP mapping, a severity and the code location. You can run it against a connected repository, from the Safeguard CLI, or on a local runner so your source never leaves your own machines.
This post covers what the engine is documented to do, how to set it up, how to read a Java trace that crosses a controller, a service and a repository class, how to run a pilot, and the limits today.
Key takeaways
- SAST (GA) is Safeguard's first-party static analysis engine. Java is in the first wave of languages. The docs say detection depth "expands over time", so treat coverage as growing, not complete.
- Every SAST finding carries a source to sink dataflow trace, a CWE and OWASP mapping, a severity and a code location. The trace is what makes a Java finding reviewable, because the input and the vulnerable call are usually in different files.
- No build is required. You point the engine at a repository or a source directory. You do not need a working Maven or Gradle build to get results.
- It runs from the Integrations page (add a Safeguard SAST integration), from the Safeguard CLI (GA), or on the local runner (GA), which uploads results and never your source. On-prem and air-gapped deployment (GA) is available for teams that need it.
- Findings land in the project's Findings tab, filterable to SAST, and in the Application Security panel. SAST shares a unified findings model with DAST (GA), so a runtime issue can be linked to the source code sink that caused it. AutoTriage (GA) collapses duplicates across engines.
- Findings can feed policies and gates (GA). For the first few weeks, start with warn rather than block.
- SAST is not part of the free entry tier, which is SCA only. SCA, reachability analysis and autonomous remediation (all GA; remediation ships switched off) cover the third party libraries on your classpath, which SAST does not.
What SAST for Java actually means
Static application security testing reads code without executing it. The useful kind, and the kind Safeguard documents, is taint tracking: the engine marks data that enters from outside the program as untrusted, follows it as it is assigned, passed, concatenated and returned, and raises a finding when it reaches an operation that would be dangerous with attacker controlled data. The entry point is the source. The dangerous operation is the sink. The path between them is the dataflow trace.
Safeguard's application security testing page gives the examples in plain terms. Sources include a request parameter, a command line argument and a file read. Sinks include a SQL query, a command execution and a file path. The engine follows input "across functions and files", which is the part that matters most for Java.
A typical Java web service is layered. A controller method receives the HTTP request. It hands values to a service class, which applies business rules and calls a repository or data access object, which finally talks to the database. The request parameter and the executeQuery call can be four files and three method calls apart. A scanner that only flags the line containing executeQuery leaves the developer to reconstruct that path by hand, and most will not. A scanner that shows each hop lets the developer see in a minute whether the value was validated, bound or concatenated on the way.
For the theory in more depth, our explainer on what taint analysis is covers sources, sinks, sanitisers and propagation in general terms.
The problem: Java injection flaws are old, well understood and still shipping
Injection in Java is not a new problem, and the fixes are not secret. Yet the same classes of flaw keep appearing in new code, in new services and in code written quickly by people and by assistants. The public data is consistent on this.
What the OWASP Top 10 says
The OWASP Top 10:2025 lists A05:2025 Injection. OWASP notes that injection fell two places, from third to fifth, but that 100% of the applications in its data set were tested for some form of injection, that the category maps to 37 CWEs, and that it had the greatest number of CVEs of any category. OWASP singles out cross-site scripting with more than 30,000 CVEs and SQL injection with more than 14,000. In the 2021 edition the same category was A03:2021 Injection, mapped to 33 CWEs, with 94% of applications tested.
The 2025 list also places A03:2025 Software Supply Chain Failures high in the ranking. For a Java team that is a useful reminder that the two halves of the problem are separate: your own code (what SAST reads) and the libraries you pull from Maven Central (what SCA reads). You need both.
What the CWE Top 25 says
MITRE's 2025 CWE Top 25 Most Dangerous Software Weaknesses puts several classes that matter in Java services near the top:
| 2025 rank | CWE | Weakness | Why it matters in Java |
|---|---|---|---|
| 1 | CWE-79 | Cross-site scripting | Servlets, template engines and JSON endpoints that echo input into HTML |
| 2 | CWE-89 | SQL injection | JDBC Statement, string built JPQL or HQL, native queries |
| 6 | CWE-22 | Path traversal | File downloads, uploads, report exports built from user supplied names |
| 9 | CWE-78 | OS command injection | Runtime.exec and ProcessBuilder wrappers around system tools |
| 10 | CWE-94 | Code injection | Scripting engines and expression evaluators fed with user input |
| 15 | CWE-502 | Deserialization of untrusted data | ObjectInputStream, XML and JSON binders that instantiate types |
| 22 | CWE-918 | Server-side request forgery | URL fetchers, webhooks, image and document importers |
The same list puts CWE-862 Missing Authorization at fourth. That is a weakness static taint tracking is poorly suited to, because the problem is a check that is absent rather than data that flows somewhere it should not. We come back to that in the limits section.
What CISA and the FBI said about SQL injection
On 25 March 2024 CISA and the FBI published a joint Secure by Design Alert, Eliminating SQL Injection Vulnerabilities in Software, prompted by a widely exploited SQL injection flaw in a managed file transfer product. The alert notes that SQL injection has been called an "unforgivable" vulnerability since at least 2007, and that CWE-89 remains prevalent despite effective, well documented mitigations. The message to software makers is that this class should be designed out, not patched one instance at a time.
For a Java team, designing it out means parameterised queries everywhere, and some way to notice when someone writes a string concatenated query anyway. Static analysis in the pull request path is that way.
What NIST SSDF expects
NIST SP 800-218, the Secure Software Development Framework, has two practices that SAST serves directly:
- PW.7, Review and/or Analyze Human-Readable Code. PW.7.1 asks the organisation to decide whether code review or code analysis should be used, and PW.7.2 asks it to perform the review or analysis against the organisation's secure coding standards and to record and triage every issue found. One of PW.7.2's examples is a static analysis tool whose results a human reviews.
- PW.8, Test Executable Code. This is the dynamic side. Safeguard's DAST (GA) is the matching engine, and we cover it in a separate post.
RV.1.2 also asks producers to review, analyse or test their software to find previously undetected vulnerabilities, and its first example is configuring the toolchain to run automated code analysis and testing on a regular or continuous basis. NIST maps PW.8.1 and RV.1.2 to section 4(e) of Executive Order 14028. SSDF does not name a tool or require a particular product. It asks you to show that the analysis happens and that the results are triaged.
What the verification standards add
The OWASP Application Security Verification Standard 5.0.0, published on 30 May 2025, lists verifiable requirements for web applications and services. A CWE mapped finding can be traced to the ASVS requirement it breaks, which helps when a customer or auditor asks how you verify a control.
Why Java is hard for static analysis
Four things about Java make static analysis harder, and they are worth knowing before you read your first results.
- Annotations route requests. In most Java web frameworks there is no explicit call from a
mainmethod to your handler. A routing annotation on a method declares it as an entry point, and the parameters bound from the request are the sources. An engine has to understand that a method parameter annotated as a request parameter, header or body is untrusted input. - Dependency injection wires the layers at runtime. A controller calls a service through an interface. The implementation is chosen by the container. A static engine has to resolve that call to the right class or it loses the trail.
- Queries live inside strings. JPQL, HQL and native SQL are often built as strings and passed to an ORM. The ORM call is the sink, and the question is whether user data was concatenated into the string or bound as a parameter.
- Reflection, serialisation and scripting bend control flow. Code that loads a class by name, deserialises an object graph or evaluates an expression can run paths that do not appear in the call graph.
Safeguard's docs do not publish a list of the Java frameworks or APIs the engine models. That is common for SAST engines, and it is why the pilot plan later in this post starts with a seeded test branch built from your own frameworks rather than from a generic benchmark.
The Java weakness classes to put in your evaluation
The table below lists the classes most Java teams should test, the shape of the flaw, the standard fix, and what Safeguard's docs say about each one. Where the docs do not name a sink class, the honest position is "test it on your code during the pilot", not "covered".
| Weakness | Typical Java shape | Standard fix | What Safeguard's docs say |
|---|---|---|---|
| SQL injection (CWE-89) | A request value concatenated into a Statement query or a JPQL string | PreparedStatement with ? placeholders, or named parameters bound through the ORM | SQL queries are a documented sink example |
| OS command injection (CWE-78) | A request value passed into Runtime.exec or a shell string in ProcessBuilder | A fixed executable, arguments passed as a list, no shell, an allow list for any value that varies | Command execution is a documented sink example |
| Path traversal (CWE-22) | A file name from the request joined to a base directory and opened | Resolve the canonical path and reject anything outside the base directory, or map IDs to files server side | File paths are a documented sink example |
| Deserialization (CWE-502) | Request bytes wrapped in ObjectInputStream and passed to readObject | Do not deserialise untrusted data; where unavoidable, harden the stream with an allow list of classes | Not named; test it |
| XML external entities (CWE-611) | An uploaded document parsed with a default DocumentBuilderFactory | Disallow DOCTYPE declarations on the parser factory | Not named; test it |
| Server-side request forgery (CWE-918) | A URL from the request opened with URL.openConnection or an HTTP client | An allow list of destinations, resolved and checked server side | Not named; test it |
| Cross-site scripting (CWE-79) | A request value written to the servlet response or a template without encoding | Context aware output encoding, auto-escaping templates | Not named for Java; test it |
| Code injection (CWE-94) | A request value passed to a scripting or expression engine | Never evaluate user input; use a fixed expression with bound variables | Not named; test it |
The fixes in the third column come from the OWASP Cheat Sheet Series. The SQL Injection Prevention Cheat Sheet puts prepared statements with parameterised queries first. The XML External Entity Prevention Cheat Sheet gives the disallow-doctype-decl feature for Java parsers. The Deserialization Cheat Sheet has a Java section on hardening your own java.io.ObjectInputStream and on not deserialising domain objects from untrusted sources. MITRE's CWE-502 entry includes a short Java example of the readObject pattern.
How Safeguard handles SAST for Java, step by step
Before the steps, here is what Safeguard's application security testing docs say the SAST engine is, in one table.
| Aspect | What the docs say |
|---|---|
| Inputs | A connected repository or a local directory. No build required. |
| Languages (phase 1) | JavaScript/TypeScript, Python and Java, with more to follow |
| What you get | A finding with the source to sink dataflow trace, a CWE and OWASP mapping, a severity and a code location |
| Where it runs | In your pipeline, or on a local runner so source never leaves your perimeter |
| Where results go | Your tenant scoped findings view, in the app |
| Maturity | GA. The docs say SAST and DAST "are rolling out" and detection depth expands over time |
The docs also note that the platform wiring, the unified findings model and the DAST safety controls are in place today. What grows is detection depth. Keep that sentence in mind when you plan rollout.
Step 1: Confirm SAST is switched on for your tenant
SAST is one of the newer first-party engines, and each of them is independently feature flagged. On the project's Findings tab, the Application Security panel has a SAST sub-tab gated by a server enforced flag named scanner:sast. An administrator toggles it. If your tenant does not show the SAST sub-tab, or the SAST option in the Scanner dropdown has nothing behind it, ask your Safeguard contact to enable SAST for your tenant before you troubleshoot anything else.
Two practical notes. The Portal free entry tier is SCA only, so SAST is outside it. And agree up front with your Safeguard contact which Java repositories you will pilot on and how you will report false positives and misses.
Step 2: Decide where the scan runs
You have three routes, and for Java the choice is usually about where the source is allowed to go.
- A connected repository. You connect GitHub, GitLab, Bitbucket or another Git compatible host, select the repository, and the scan runs through the platform. This is the least effort for repositories your security policy already allows a cloud service to read.
- The Safeguard CLI (GA) in your pipeline or on a workstation. The scan runs where the CLI runs.
- The local runner (GA). The runner is part of the CLI. It polls the platform for jobs assigned to that machine over outbound HTTPS, runs each job locally and, in the docs' words, uploads only the resulting SBOM and findings, "never your source code". The application security testing page names the runner as the way to keep source inside your perimeter for SAST.
If your organisation needs everything on its own infrastructure, Safeguard also offers on-prem and air-gapped deployment (GA). Our post on choosing between a repo connection, the CLI and the local runner walks through the trade-offs in detail.
Step 3: Add the Safeguard SAST integration
From the Integrations page, add an AppSec integration and choose Safeguard SAST. The docs give two options: select a repository, or point at a source directory. You add it the same way you add a repository or a registry. Scans then trigger through the standard flow of integration, then pipeline, then CLI, and results appear in your findings views.
For a Java repository, think about scope before you click save:
- Single module services. Point at the repository. The engine reads the source tree.
- Multi-module Maven or Gradle builds. The docs do not say how the engine treats module boundaries. In the pilot, scan the whole repository once, then scan one module on its own, and compare. If traces that cross modules (a controller in
web, a repository class inpersistence) only appear in the whole repository scan, keep scanning at the root. - Generated sources. Code generated from OpenAPI specs, protocol buffers or annotation processors can produce noise or hide the real entry points. Note where generated code lives so you can recognise it in results.
- Test code. Tests often contain deliberately unsafe fixtures. Decide whether you want them in scope and record the decision.
Step 4: Or run it from the CLI
The application security testing docs show this command for static analysis of a source directory:
# Static analysis of a source directory
safeguard appsec sast --dir ./src --tenant-id <tenant> --product-id <product>
If you do not have the CLI yet, the docs give a one line installer:
# macOS and Linux
curl -fsSL https://cli.safeguard.sh/install | bash
Sign in with the device code flow before you scan. On a build agent with no browser, use the no browser variant and approve the session from another device:
# Opens your browser; approve the session there
safeguard login
# On a machine with no browser: prints a URL and a code to enter elsewhere
safeguard login --no-browser
# Who am I signed in as, and when does the token expire
safeguard whoami
The CLI usage docs say a machine is authorised by signing in once, and that there is no API key environment variable for the CLI. Plan your build agents accordingly.
For a Java project, the ./src in the documented example is usually your module's src folder, which holds main/java and often test/java. Point the flag at the folder that matches the scope you chose in Step 3.
Two things the docs are explicit about. First, safeguard --help and safeguard appsec sast --help list the flags in the build you have, and if a docs page and --help disagree, --help is right. Second, there is no CLI command for listing SAST findings yet. You run the scan from the CLI and read the results in the app.
Step 5: Or keep the source on your machines with the local runner
For a private Java codebase that must not be read by a cloud service, start the runner in headless mode on a machine that has the checkout. These are the commands the runner docs show:
# Start without the terminal UI and serve assistant turns for this workspace
safeguard runner start --workspace ./path/to/repo
# Is it running, and what is it doing
safeguard runner status
# Drain in-flight work and stop
safeguard runner stop
The runner sends a heartbeat, claims jobs assigned to that machine, runs them with its own local scan server and uploads result files. All traffic is outbound HTTPS that the runner opens. Nothing connects to it. On networks that forbid long held connections, the docs say to pass a poll wait of zero so the runner polls at a fixed interval instead.
Step 6: Find the results
Open the project in Safeguard and go to the Findings tab. At the top of the tab is a compact Scanner dropdown. The default, All scanners, shows every finding type together. Choose SAST and the table, the summary stats and the underlying queries narrow to SAST findings only.
Further down the same tab is the Application Security panel, with one sub-tab per first-party engine. The SAST sub-tab is the one gated by the scanner:sast flag. The docs describe findings in the app's AppSec views as carrying the severity, the rule, the CWE, the location and, for SAST, the dataflow trace.
Every finding is scoped to your tenant and organisation end to end.
Step 7: Read a Java dataflow trace
This is where most of the value is, so it is worth slowing down. Here is a small, deliberately vulnerable Java example split across three classes, the way real services are. It is an illustration for this post, not output from Safeguard.
The controller receives the request:
@GetMapping("/orders")
public List<Order> search(@RequestParam String customer) {
return orderService.findByCustomer(customer);
}
The service passes the value on, with a check that looks protective but is not:
public List<Order> findByCustomer(String customer) {
if (customer.isBlank()) {
return List.of();
}
return orderRepository.search(customer.trim());
}
The repository builds the query by concatenation:
public List<Order> search(String customer) throws SQLException {
String sql = "SELECT * FROM orders WHERE customer = '" + customer + "'";
try (Statement st = connection.createStatement()) {
return map(st.executeQuery(sql));
}
}
A source to sink trace for this shape has four or five hops. Read conceptually, it says:
- Source. The
customerparameter ofsearchin the controller, bound from the HTTP request. Untrusted. - Call. The value is passed as the argument to
findByCustomerin the service. Still untrusted. - Propagation.
isBlank()is a check that returns early on empty input. It does not change the value or constrain its characters.trim()returns a new string derived from the input. Still untrusted. - Call. The trimmed value is passed to
searchin the repository. - Propagation. The value is concatenated into the
sqlstring. - Sink.
sqlis passed toexecuteQueryon aStatement. This is the SQL query sink. The weakness is CWE-89, under OWASP A05:2025 Injection.
Safeguard's docs describe the trace as "the ordered hops from source to sink", so a developer can see why the code was flagged and not just where. With the trace in hand, triage becomes four questions:
- Is the source really attacker controlled? A request parameter is. A value read from your own configuration file usually is not. A value from another internal service sits in between and depends on your trust model.
- Does any hop actually constrain the value? A length check, a blank check or a
trim()does not. A strict allow list or a conversion to a numeric type does. Look at every hop, not just the last one. - Is the sink really dangerous with this value? A
PreparedStatementwith a bound parameter is not a SQL injection sink for that parameter. AStatementwith a concatenated string is. - Is the code reachable in production? Dead code, feature flagged paths and test utilities change the priority, though they rarely make a true finding false.
Step 8: Triage across engines
SAST findings sit in the same unified findings model as the other engines. The model shares correlation keys across engine types. When a DAST (GA) scan of a verified target confirms a runtime issue, and that issue maps to the SAST source code sink that produced it, the docs say the two are linked and prioritised together, so a confirmed, reachable, exploitable finding rises to the top.
For a Java service with a staging environment, that is the strongest signal you can get. DAST in Safeguard only runs active checks against targets you have verified you own, inside a scope you define. See what Safeguard DAST discovers and what it will not test for how that works.
If you run more than one engine over the same code, AutoTriage (GA) deduplicates across scanners, collapsing the same underlying issue into one correlated finding and reporting how much volume it removed. It is admin toggled through a feature flag.
The docs do not yet describe SAST specific suppression syntax or in-code annotations for marking a finding as accepted. Record triage decisions in the way your process already requires, and ask your Safeguard contact how decisions are kept across rescans.
Step 9: Fix the code
SAST tells you where the flaw is. The fix is still yours to write, and for most Java findings the fix is short. Here is the repository method from Step 7, corrected with a bound parameter:
public List<Order> search(String customer) throws SQLException {
String sql = "SELECT * FROM orders WHERE customer = ?";
try (PreparedStatement ps = connection.prepareStatement(sql)) {
ps.setString(1, customer);
return map(ps.executeQuery());
}
}
The same principle applies through an ORM. Use a named parameter in the JPQL string and bind the value, instead of concatenating it. For the other common classes:
- Command execution. Call a fixed executable with arguments as a list, never through a shell, and check any variable argument against an allow list.
- Path traversal. Build the path, resolve its canonical form, and reject it unless it starts with the canonical base directory. Better still, take an identifier from the user and look the file up server side.
- Deserialization. Do not deserialise untrusted bytes into Java objects. Where you cannot avoid it, the OWASP Deserialization Cheat Sheet describes hardening
ObjectInputStreamso only an allow list of classes can be created. - XML parsing. Set the
disallow-doctype-declfeature on the parser factory, as the OWASP XXE Prevention Cheat Sheet shows for Java.
After the fix, rescan. A finding that clears after the fix is the cheapest confirmation you will get that the engine and your change agree.
Step 10: Gate merges, carefully
Safeguard's policies, gates and guardrails (GA) enforce rules at several points, including CI, where the guardrails docs describe platform policy gates on the scans of connected repositories. Effects are BLOCK, WARN or AUTO_FIX, and every enforcement decision produces an audit record. The application security testing docs point to guardrails as the way to use findings as policy gates in CI/CD.
For a new engine in your pipeline, start with WARN on new SAST findings of high and critical severity, measure the false positive rate on your Java code for a few weeks, and move to BLOCK only for the classes where the engine has proved accurate on your code, typically SQL injection and command injection. The docs do not yet publish the policy fields for SAST findings, so confirm the exact condition you can write with your Safeguard contact. Our post on turning plain English security policies into release gates covers the policy model itself.
Step 11: Keep it running
Safeguard's continuous scanning docs describe connected source repositories being rescanned on every push, with a scheduled pull to catch missed webhooks. That page is about the platform's asset inventory in general. Confirm with Safeguard whether SAST scans run on those same triggers for your tenant or only when the integration or the CLI runs them, and set your pipeline up to match.
Ask about findings from Claude or ChatGPT
If your team triages in an AI assistant, add Safeguard from the Claude or ChatGPT connector directory rather than wiring up a custom server. The connector signs in with your Safeguard account, applies your tenant scope and reads projects, vulnerabilities and findings. Which tools it exposes depends on your plan and your administrator's settings. Our post on Safeguard in your IDE and in the Claude and ChatGPT connector directories shows the install route.
SAST, SCA and DAST on one Java service
A Java service has three different kinds of exposure, and each engine sees one of them. The table below is a decision aid for the questions teams actually ask.
| Question about your Java service | Engine that answers it | Label |
|---|---|---|
| Does any library on our classpath have a known CVE, including transitive dependencies? | SCA, deep dependency scanning | GA |
| Is the vulnerable method in that library actually called by our code? | Reachability analysis | GA |
| Can we get a tested pull request that moves the library to a fixed version? | Autonomous remediation (Griffin fix pull requests), ships switched off with every category on Manual | GA |
| Did one of our developers concatenate a request value into a query? | SAST | GA |
| Does an upload handler pass a user supplied name into a file path? | SAST | GA |
| Is the deployed staging app missing security headers, or does it respond to an injection probe? | DAST, verified targets only | GA |
| Is the SAST finding we are looking at actually exploitable at runtime? | SAST and DAST linked through correlation keys | GA |
| Should this release be blocked? | Policies and gates over the findings | GA |
Two Java specific overlaps are worth calling out. A deserialization flaw often needs both halves: your code calls readObject on untrusted input (a SAST question), and a library on the classpath provides the classes an attacker chains together (an SCA question). And a SQL injection finding in a repository class is still your bug even if the ORM you use has no CVE at all. Neither engine replaces the other. For the broader comparison, see SCA vs SAST vs DAST: which do you actually need first, and for how reachability ranks dependency findings, prioritise vulnerabilities with reachability, EPSS and KEV in Safeguard.
A 30 day SAST pilot for a Java estate
Measure any new scanner before you trust it. Here is a pilot plan that fits most Java teams and produces a clear go or no go decision.
Week 1: choose repositories and seed a test branch. Pick two or three repositories that represent your estate. A good mix is one web service on your main framework, one batch or messaging service, and one shared library. In a private branch that will never ship, add a small number of known bad patterns from the weakness table, written in your own house style and routed through your own layers: a concatenated query in a repository class, a ProcessBuilder built from a request value, a file download built from a request parameter, an ObjectInputStream over a request body. Add a matching set of safe variants (a bound parameter, an allow listed command) so you can measure false positives as well as misses.
Week 2: scan and score. Run SAST on the main branch and the seeded branch. For each seeded case, record whether a finding appeared, whether the CWE was right, and whether the trace showed every hop. For the main branch, sample up to 50 findings and have a developer who knows the code mark each one as true, false or unclear.
Week 3: correlate and fix. If you have a staging environment, verify it as a DAST target and scan it, then look for linked SAST and DAST findings. Fix the true positives on the main branch and rescan to confirm they clear.
Week 4: decide. Set warn or block per weakness class based on what you measured.
| Pilot measure | How to record it | What good looks like |
|---|---|---|
| Seeded cases detected | Found or missed, per CWE | The classes you plan to gate on are found consistently |
| Safe variants flagged | Count of findings on the safe versions | Close to zero for the classes you plan to block |
| Trace completeness | Did the trace show every hop from controller to sink | Developers can triage without opening five files |
| CWE accuracy | Did the mapping match the weakness | Correct for each seeded class |
| Main branch precision | True, false, unclear in your sample of 50 | Enough true findings that developers keep reading them |
| Time to triage | Minutes per finding, median | Falls as the team learns the trace format |
Send the misses and the false positives to Safeguard. That is how the engine improves.
What Safeguard does not do
Honest limits, as of this post:
- Detection depth is still growing. The docs say SAST and DAST are rolling out and that detection depth expands over time. Safeguard does not publish a list of Java rules, modelled frameworks or sink APIs. Do not tell an auditor or a customer that a specific Java weakness class is covered until you have seen it detected on your own code.
- Java is the only JVM language listed for SAST. Phase one is JavaScript/TypeScript, Python and Java. Kotlin, Scala and Groovy are not listed for SAST today. They are covered for dependency analysis by SCA, which is a different question.
- It does not list findings from the CLI yet. The docs are explicit that there is no CLI command for listing SAST findings. Results are read in the app.
- It does not find what is missing. Taint tracking finds data that flows somewhere dangerous. It is weak at finding checks that are absent, such as a missing authorisation check on an admin endpoint (CWE-862, fourth in the 2025 CWE Top 25) or a business rule that can be skipped. Code review and threat modelling still own those.
- It does not prove exploitability. A static finding says a path exists in the code. Whether it is exploitable in production depends on configuration, deployment and runtime controls. DAST against a verified target, and your own testing, answer that.
- It does not scan compiled artifacts. SAST reads source code from a repository or directory. A JAR, WAR or container image you received without source is an SCA and SBOM question, not a SAST one.
- It does not write your code fix for you. The offering files describe Griffin fix pull requests for dependency upgrades and other remediation categories, and remediation ships switched off. They do not describe automatic code fixes for Java SAST findings, so plan to fix these by hand.
- It is not in the free tier. The Portal free entry tier is SCA only.
Common mistakes
Blocking merges on day one. A new scanner on a large Java codebase will produce some findings your developers disagree with. If the first experience is a blocked merge on a false positive, the team will stop reading the results. Warn first, measure, then block selected classes.
Pointing SAST at build output. SAST reads source. Pointing it at a target or build folder full of class files, or at a JAR, gives you nothing useful. Point it at the source tree and use SCA for binaries.
Trusting the last hop. A reviewer sees a validation call in the service class and closes the finding. Read the whole trace. A blank check, a length check or a trim() does not make a value safe for a SQL string. Only binding, strict allow lists or type conversion do.
Escaping instead of binding. Hand written escaping of quotes in a SQL string is fragile and database specific. The OWASP SQL Injection Prevention Cheat Sheet lists prepared statements first for a reason. Fix by binding.
Reading silence as safety. A Java repository with no SAST findings may be clean, or the engine may not yet model the framework it uses. Your seeded test branch is how you tell the difference.
Ignoring test and generated code decisions. Test fixtures and generated clients can fill the findings view with noise, or the team can exclude them and miss a real bug in a generated controller. Decide scope on purpose and write it down.
Running SAST without SCA. A Java service also ships every library on its classpath. A team that rolls out SAST while known exploited CVEs sit unfixed in its dependency tree has its priorities the wrong way round. Run SCA (GA) first or alongside.
FAQ
Does Safeguard SAST support Java?
Yes. Java is one of the three phase one languages listed in Safeguard's application security testing docs, with JavaScript/TypeScript and Python. More languages are planned.
Do I need to build my Maven or Gradle project before scanning?
No. The docs say SAST takes a connected repository or a local directory and that no build is required.
What does a Safeguard SAST finding contain?
A source to sink dataflow trace, a CWE and OWASP mapping, a severity, the rule and a code location. The trace lists the ordered hops from where untrusted input enters to the dangerous operation it reaches.
Which Java vulnerabilities does Safeguard SAST find?
The docs give SQL queries, command execution and file paths as examples of sinks, and request parameters, command line arguments and file reads as examples of sources. They do not publish a full Java rule list. Test the classes you care about, such as deserialization, XXE and SSRF, on your own code during a pilot.
Does my Java source code leave my network?
Not if you use the local runner. It runs the scan on your machine, opens only outbound HTTPS connections and uploads result files, never source. On-prem and air-gapped deployment (GA) is also available. With a connected repository, the platform reads the repository you authorise.
Does Safeguard SAST support Kotlin or Scala?
They are not listed for SAST today. Phase one covers Java, JavaScript/TypeScript and Python. Kotlin and Scala dependencies are covered by SCA (GA).
How is SAST different from SCA for a Java application?
SAST reads the code your team wrote and looks for unsafe data flows. SCA reads your dependency tree, including transitive libraries from Maven Central, and matches components against known vulnerabilities. A Java service needs both.
Can SAST findings block a pull request?
Findings can feed Safeguard's policies and gates (GA), and the docs point to guardrails for using findings as CI gates. Start with warn, measure accuracy on your code, and confirm the exact policy condition with Safeguard before you block.
How does Safeguard link SAST and DAST findings?
Both engines report into one unified findings model with shared correlation keys. When a DAST scan of a verified target confirms a runtime issue that maps to the SAST sink that caused it, the two findings are linked and prioritised together.
The fixes for most Java SAST findings are code changes your team writes. But a Java service's other large exposure, the libraries on its classpath, can often be closed without waiting on anyone, and the section below is the standard way Safeguard does that.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation categories include dependencies, licenses, security posture and findings, and each one is set separately. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds.
3. Gold Registry: start from components that are already fixed
Gold Registry provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs and a completed license check, with signed build provenance (signed with Safeguard's key and verifiable with cosign, SLSA Build Level 3 as the design target) and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. Ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | Safeguard policies and gates in CI |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo is a real Safeguard CLI session. It installs the CLI, opens the interactive terminal with safeguard, and asks it to fix a lodash CVE in a repository. The CLI reads the finding and the advisory, asks before any command that changes something, edits package.json to lodash 4.17.21, runs npm install and npm test with approval, prints a summary and pushes nothing, then shows /permissions, /model and /scan .. The repository is JavaScript, but the terminal, sign in and scan flow are the same CLI you would use to run safeguard appsec sast over a Java source tree.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine, approves it with its device code, and scans from the terminal. This is the route to choose when your Java source must stay on your own hardware: the runner does the work locally and uploads results, not code.
Click through it at your own pace, or open it full screen.
To try it on your own code, install the desktop app or CLI from https://safeguard.sh/download and read the setup guides at https://docs.safeguard.sh/docs . If you want to pilot SAST on Java, ask for it to be enabled when you sign up. For more background on the language itself, see Java security explained, and for how false positives are reduced in static analysis generally, static analysis false positive reduction. For every capability and its label in one place, see what Safeguard does today.
Quick check
A SAST trace shows a request parameter passing through a service method that calls trim() and isBlank() before it reaches Statement.executeQuery in a repository class. Is the finding a false positive?
No. Neither call constrains the characters in the value, so it is still untrusted when it is concatenated into the SQL string. The fix is to use a PreparedStatement and bind the value as a parameter.
Your team wants to scan a vendor's Java JAR file with Safeguard SAST. Is that the right engine?
No. Safeguard SAST reads source code from a repository or directory. For a compiled artifact without source, use SCA and SBOM analysis (GA) to identify its components and their known vulnerabilities.
Sources
- Safeguard docs: Application Security Testing (SAST and DAST)
- Safeguard docs: Unified Findings and Feature Flags
- Safeguard docs: CLI
- Safeguard docs: CLI usage
- Safeguard docs: Local Runner
- Safeguard docs: Guardrails and Enforcement
- Safeguard docs: AutoTriage
- Safeguard docs: Continuous Scanning
- Safeguard docs: Vulnerability Prioritization (Reachability Analysis)
- Safeguard docs: Air-Gapped Deployment
- OWASP Top 10:2025
- OWASP Top 10:2025, A05 Injection
- OWASP Top 10:2021, A03 Injection
- MITRE, 2025 CWE Top 25 Most Dangerous Software Weaknesses
- MITRE, CWE-89: SQL Injection
- MITRE, CWE-502: Deserialization of Untrusted Data
- MITRE, CWE-611: XML External Entity Reference
- MITRE, CWE-918: Server-Side Request Forgery
- NIST SP 800-218, Secure Software Development Framework 1.1
- NIST SP 800-218 full text (PDF)
- CISA and FBI, Secure by Design Alert: Eliminating SQL Injection Vulnerabilities in Software
- CISA, Secure by Design
- OWASP Application Security Verification Standard
- OWASP SQL Injection Prevention Cheat Sheet
- OWASP Deserialization Cheat Sheet
- OWASP XML External Entity Prevention Cheat Sheet
Last reviewed: 24 October 2026. Product capabilities change; check docs.safeguard.sh for the current state.