Module tool_scan
ballerina/tool_scan Ballerina Tool
Overview
Static Code Analysis (SCA) uses tools to examine code without executing it. It is used for identifying potential issues like bugs, vulnerabilities, and code smells early, improving software quality, maintainability, and security. Ballerina supports SCA using the Ballerina scan tool.
The scan tool compiles and performs static code analysis, prints results to the console, and reports results. It analyzes the source code defined in each module when compiling a package, analyzes each package in dependency order when compiling a workspace, or analyzes the given source file when compiling a single Ballerina file.
Note: Analyzing individual Ballerina files of a package is not allowed.
Each issue reported by the tool is backed by a rule. Every rule has a severity (BLOCKER, HIGH, MEDIUM, LOW, or INFO) that indicates how important it is to act on it, and where applicable, is mapped to its relevant CWE and OWASP Top 10 coverage. See Severity levels and Rules below for details.
Command
Synopsis:
bal scan [OPTIONS] [<workspace>|<package>|<source-file>]
Options:
All options are optional. Rule filters and platforms can also be configured in a Scan.toml file. See Configuration.
| Option | Description |
|---|---|
--target-dir=<path> | Target directory path for saving analysis reports (only for Ballerina projects). Default is the project's target directory. |
--scan-report | Generate an HTML report containing the analysis results (only for Ballerina projects). Disabled by default. |
--format=<json|sarif> | Specify the format of the report. Default is json. |
--list-rules | List the rules available to the project, along with their kind and severity. Only works inside a Ballerina project. Lists the core rules and the rules contributed by the project's dependencies (library tools and static code analyzer plugins) only. |
--include-rules=<rule1, ...> | Run analysis for a specific set of rules. By default, all available rules are included. A rule that is not available to the project is an error. |
--exclude-rules=<rule1, ...> | Exclude analysis for a specific set of rules. By default, no rules are excluded. A rule that is not available to the project is an error. |
--platforms=<platformName1, ...> | Define platform(s) to report results to. More than one platform can be defined. By default, results are not reported to any platform. Not supported for a single Ballerina file. |
--ignore-suppressions | Disregard @scan:Suppress annotations and report every issue, to audit the unsuppressed results. Rule inclusion and exclusion still apply. Same as enabled = false in Scan.toml. See Suppressing issues. |
--require-suppression-reason | Fail the scan when a @scan:Suppress annotation does not give a reason. Same as requireReason = true in Scan.toml. Has no effect with --ignore-suppressions or enabled = false. |
--require-suppression-rule-id | Fail the scan when a @scan:Suppress annotation does not name the rules it suppresses. Same as requireRuleId = true in Scan.toml. Has no effect with --ignore-suppressions or enabled = false. |
--fail-on-severity=<BLOCKER|HIGH|MEDIUM|LOW|INFO|NONE> | Fail the scan when the number of issues at or above this severity reaches --fail-on-count, or 1 if the count is not set. NONE turns the gate off. Replaces only failOnSeverity in Scan.toml. See Exit codes and the gate. |
--fail-on-count=<n> | Fail the scan when there are n or more issues at or above --fail-on-severity, or of any severity if it is not set. Replaces only failOnCount in Scan.toml, and has no effect when the severity is NONE. |
--exclude-tests[=<true|false>] | Leave the test sources of each package out of the scan. Written alone, it means true. Overrides excludeTests in the [analysis] table of Scan.toml either way. |
Examples
# Run analysis against all Ballerina documents in the current package, print results to the # console, and save results in JSON file format in the target directory. bal scan # Run analysis against a standalone Ballerina file and print results to the console. The # file path of the Ballerina file can be relative or absolute. bal scan main.bal # Run analysis and save analysis results in a specified directory. bal scan --target-dir="results" # Run analysis and generate an HTML report in the target directory. bal scan --scan-report # Run analysis and generate a report in JSON format (default). bal scan --format=json # Run analysis and generate a report in SARIF format. bal scan --format=sarif # View the rules available to the current package. bal scan --list-rules # Run analysis for a specific rule. bal scan --include-rules="ballerina:101" # Run analysis for a specific set of rules. bal scan --include-rules="ballerina:101, ballerina/io:101" # Exclude analysis for a specific rule. bal scan --exclude-rules="ballerina/io:101" # Exclude analysis for a specific set of rules. bal scan --exclude-rules="ballerina:101, ballerina/io:101" # Run analysis and report to sonarqube. bal scan --platforms=sonarqube # Run analysis and report to multiple platforms. bal scan --platforms="sonarqube, semgrep, codeql" # Run analysis reporting every issue, including those suppressed with @scan:Suppress. bal scan --ignore-suppressions # Run analysis and fail if any @scan:Suppress annotation lacks a reason. bal scan --require-suppression-reason # Run analysis and fail on one or more BLOCKER or HIGH issues. bal scan --fail-on-severity=HIGH # Run analysis and fail on 5 or more BLOCKER, HIGH or MEDIUM issues. bal scan --fail-on-severity=MEDIUM --fail-on-count=5
Example output
Running bal scan --list-rules inside a Ballerina project prints a table of the rules available to it with their kind and severity:
RuleID | Rule Kind | Severity | Rule Description -------------|---------------|----------|------------------------------------------------- ballerina:1 | CODE_SMELL | LOW | Avoid checkpanic ballerina:2 | CODE_SMELL | LOW | Unused function parameter ... ballerina:13 | VULNERABILITY | HIGH | Hard-coded secrets are security-sensitive ballerina:14 | VULNERABILITY | MEDIUM | Non configurable secrets are security-sensitive ...
Running bal scan reports each finding as a JSON issue with its location and full rule metadata, including the rule's severity:
[ { "location": { "filePath": "main.bal", "startLine": 22, "endLine": 22, "startColumn": 16, "endColumn": 43, "startOffset": 875, "length": 27, "snippet": "checkpanic parseCount(\"12\")" }, "rule": { "id": "ballerina:1", "numericId": 1, "name": "Avoid checkpanic", "description": "Using `checkpanic` lets an unhandled error panic and crash the program instead of being handled.", "details": "The `checkpanic` expression causes the program to panic and terminate abruptly when the checked expression evaluates to an error, instead of allowing the error to be handled. Prefer `check` with explicit error handling so callers can recover instead of crashing.", "helpUri": "https://ballerina.io/learn/scan-rules/#ballerina-1", "severity": "LOW", "tags": [ "error-handling" ], "standards": { "cwe": [ 248, 636 ], "owasp": [ { "year": 2025, "categories": [ 10 ] } ] }, "ruleKind": "CODE_SMELL" }, "source": "BUILT_IN", "fileName": "main.bal", "filePath": "/home/user/bal-scan-demo/main.bal" } ]
Only the issues are written to standard output, so bal scan > issues.json saves valid JSON, or SARIF with --format=sarif. Progress messages, warnings, the issue summary, such as 3 issue(s), report paths, and the blank lines between sections are written to standard error.
The console output is printed as sections, in this order, with one blank line between them: configuration, warnings, progress, issues, issue summary, report locations, platform output and the gate result. A section that prints nothing is left out, and warnings raised during the scan, such as unmatched suppressions, are printed before the section that follows them.
The scan uses the dependency versions locked in Dependencies.toml, as bal build --locking-mode=hard does. A package without Dependencies.toml is not scanned, in the command line or the editor, and neither are its rules listed; run bal build first to lock its dependencies. An empty package needs none. The scan does not update Dependencies.toml. Only the analyzers declared in Scan.toml are resolved by the scan, since they are not dependencies of the package. In a workspace, each package needs its own Dependencies.toml, and the issues of every package are printed as one document.
Scan report
To generate a detailed HTML report of the analysis results, use the --scan-report option:
bal scan --scan-report
This produces an HTML report and the scan results in JSON format inside the target/report directory (or the directory given via --target-dir). The report is only generated for Ballerina projects, not for standalone Ballerina files. It is generated even when the results are also reported to a platform.
The HTML report includes a summary of the total number of files scanned and the number of code smells, bugs, and vulnerabilities found in each file. You can filter, search, and export the list of files.

To investigate further, click on a file name to open the file view. This view shows the source of the file and highlights the exact lines where problems were detected. Hover over a highlight to see a summary of the issue, or click it to see the full details.

The issues found in the file are listed in a table with the line, rule ID, name, kind, severity, CWE, and OWASP Top 10 category of each issue. Expand an issue to see its description and the full rule details, such as its location, source, and tags. From there, you can jump to the issue in the source using Show in code, or open the rule's documentation using Rule documentation.

Configuration
Rule filters, platform plugins, and static code analyzer plugins can also be configured in a Scan.toml file (only for Ballerina projects). The tool picks up a Scan.toml in the package root, or the file (local path or URL) specified in Ballerina.toml:
[scan] configPath = "path/to/Scan.toml"
A sample Scan.toml:
# Rules to include or exclude in the analysis (same as --include-rules and --exclude-rules) [rule] include = ["ballerina:1", "ballerina/io:101"] # exclude = ["ballerina:1"] # Platform plugins that --platforms can report results to [[platform]] name = "sonarqube" path = "path/to/sonar_platform_plugin.jar" # How @scan:Suppress annotations are applied [suppression] enabled = true # false reports every issue, same as --ignore-suppressions requireReason = true # same as --require-suppression-reason requireRuleId = true # same as --require-suppression-rule-id # When the scan fails on its findings (same as --fail-on-severity and --fail-on-count) [gate] failOnSeverity = "MEDIUM" failOnCount = 5 # Leave the test sources out of the scan (same as --exclude-tests) [analysis] excludeTests = true
The rules given with --include-rules are added to include, and those given with --exclude-rules are added to exclude, for that run. For example, with exclude = ["ballerina:1"] in Scan.toml, --exclude-rules=ballerina:2 excludes both rules. Including and excluding rules at the same time is not allowed, so an option that conflicts with the other setting in Scan.toml is a usage error: with exclude = ["ballerina:1"] in Scan.toml, --include-rules=ballerina:2 exits with code 2.
Each [[platform]] entry requires both a name and a path. The path must point to the platform plugin JAR, either as a local file path (resolved relative to the folder of the Scan.toml file) or as a URL to download it from. A declared platform is reported to only when it is named in --platforms, and --platforms can only name platforms declared there. The plugin JAR is checked, or downloaded, only when its platform is named; a path that is then neither an existing file nor a URL stops the scan with an error. The results are always printed and saved first, and the named platforms then run one after another. If any platform fails, the scan lists every failure and exits with code 1.
See Scan file configurations for all available options.
Exit codes and the gate
bal scan exits with the standard exit codes of Ballerina CLI tools, so scripts, Git hooks and CI/CD pipelines can act on the result:
| Code | Meaning |
|---|---|
0 | The scan completed, and no gate is set or the gate was not tripped. |
1 | The gate was tripped, or the scan could not be completed, for example because of an invalid Scan.toml, a package that cannot be loaded, such as one whose Ballerina.toml has an error, a compilation error, an empty package (an empty workspace member is skipped with a warning), a plugin failure, an invalid suppression or a report that cannot be written. |
2 | Invalid command-line input, such as an invalid option value, an unknown option, both --include-rules and --exclude-rules, a rule option that conflicts with the [rule] table of Scan.toml, --platforms with a single Ballerina file, or an unknown rule ID. |
3 | The path is not a Ballerina package, workspace or standalone file, or is a .bal file inside a package. |
130 | The scan was interrupted. |
Every failure other than an interrupt is reported on standard error as bal scan: error: <message>, followed by the problems that caused it, such as Scan.toml:2:18: error: <message>. Warnings are reported as bal scan: warning: <message> and never change the exit code.
By default no gate is set, so findings never fail the scan. The gate is set with --fail-on-severity and --fail-on-count, or with the [gate] table of Scan.toml, and fails the scan when the number of issues at or above the severity reaches the count:
| --fail-on-severity | --fail-on-count | Fails when |
|---|---|---|
| Not set | Not set | Never. The gate is off. |
| Not set | n | There are n or more issues of any severity. |
X | Not set | There is at least one issue at or above X. |
X | n | There are n or more issues at or above X. |
NONE | Any | Never. The gate is off. |
Each command-line option replaces only its own setting in Scan.toml for that run, so a setting left off the command line is still read from Scan.toml. For example, --fail-on-count=2 with failOnSeverity = "HIGH" in Scan.toml fails on 2 or more issues at or above HIGH. --fail-on-count does not turn on a gate that failOnSeverity = "NONE" turns off; the scan warns that the count has no effect.
The gate counts the issues left after rule selection and suppressions, and runs after the reports are written, so a failing scan still leaves a full report. An error always wins over a tripped gate. In a workspace, each package is checked against its own gate with the issues of that package only, whether the gate comes from the command line, the workspace Scan.toml or the package Scan.toml. The gate is not checked when a platform fails, since an error wins over the gate.
Suppressing issues
An individual issue can be accepted in the source with the @scan:Suppress annotation from the ballerina/scan package. Unlike excluding a rule, which turns it off for the whole project, a suppression applies only to the construct it is attached to and records why the issue is accepted:
import ballerina/crypto; import ballerina/scan; function hashPassword(string password) returns string|error { @scan:Suppress {rules: ["ballerina/crypto:2"], reason: "Legacy hash kept for the migration window, SEC-1042"} string hashed = check crypto:hashBcrypt(password, 4); return hashed; }
- Placement. The annotation applies to issues reported within the construct it is attached to: a variable or constant declaration, a function, method or resource, a parameter, a return type, a field, a type, a class, a service, a listener, a worker or an
externalfunction body. A suppression on a service, class, object type or object constructor does not reach issues inside its members. An issue in a statement that is not a declaration can be suppressed by turning the statement into one, for examplevar _ = check f();instead ofcheck f();. A suppression the scan cannot apply, such as one on a destructuring declaration or on the return type of a function type, is reported as a warning and ignored. - Fields. Both fields are optional. Without
rules, every issue in the construct is suppressed; withoutreason, the issue is suppressed without a justification. The annotation can be attached more than once to give each rule its own reason. See theballerina/scanpackage documentation for examples of each option, the checks done duringbal buildand the editor's code actions. - Rule ID validation. The compiler plugin of
ballerina/scanreports a malformed rule ID or an empty rule list as a compile error, andbal scanreports them as errors too, so that such a suppression never passes as one of no rule.bal scanchecks that each rule exists, among the core rules and the rules of every analyzer it engages, and fails before reporting anything when one does not. A rule of an analyzer the scan does not engage cannot be verified and is reported as a warning. - Results. Suppressed issues are not printed to the console or sent to platforms, and the issue summary counts them, such as
3 issue(s), 2 suppressed. They remain in the saved JSON and SARIF reports with asuppressionsentry of kindinSourcecarrying the reason, so every accepted issue stays reviewable. The HTML report counts them separately from the active issues and lists them, with their reason and the rules the annotation names, under theSuppressedorAllstatus filter. - Unmatched suppressions. A suppression that matches no issue is reported as a warning, for example when the issue was fixed. A suppression is not reported for a rule left out by
--include-rules,--exclude-rulesorScan.toml, and one withoutrulesis only reported when every rule is scanned, since whether it is still needed cannot be told from a narrowed scan. An unknown rule still fails the scan, before anything is reported. A rule that an earlier annotation on the same construct already names is reported as already suppressed instead, since that annotation is the one that applies. Likewise, a second annotation withoutruleson a construct is reported as suppressing rules that are already suppressed. - Requirements. A project can require every suppression to give a reason or name its rules, with
requireReasonandrequireRuleIdin the[suppression]table ofScan.toml, or with the matching command-line options. The requirements have no effect when suppressions are disabled, since every issue is then reported. A suppression that does not meet a requirement fails the scan before anything is reported: the errors are printed, and no issues are printed, saved to the target directory or sent to platforms. In a workspace, every package is checked first, so no package is reported when one of them fails. - Audit.
--ignore-suppressionsreports every issue as though no suppression existed. To disregard suppressions for every run of a project, for example from a sharedScan.toml, setenabled = falsein the[suppression]table.
Rules
The full list of rules, with detailed explanations and noncompliant/compliant code examples, is documented at ballerina.io/learn/scan-rules.
Severity levels
The severity of a rule indicates how urgently a reported issue should be addressed, and how it can affect development and deployment if left unresolved:
| Severity | Meaning | Impact |
|---|---|---|
BLOCKER | A critical issue that is very likely to cause application failure or a security breach. | Should block merging or deployment until fixed. |
HIGH | A serious issue likely to cause incorrect behavior or expose the application to exploitation. | Should be fixed before deployment. |
MEDIUM | An issue that affects reliability, maintainability, or security to a moderate degree. | Should be scheduled and fixed soon, does not need to block deployment on its own. |
LOW | A minor issue such as a code smell that affects readability or maintainability. | Safe to defer, but worth cleaning up over time. |
INFO | An informational finding with no material severity. | Does not require action; useful for awareness. |
Core rules
The language-level rules below are defined within this tool.
| Rule ID | Name | Kind | Severity | Description |
|---|---|---|---|---|
ballerina:1 | Avoid checkpanic | Code Smell | Low | Using checkpanic lets an unhandled error panic and crash the program instead of being handled. |
ballerina:2 | Unused function parameter | Code Smell | Low | A function parameter is declared but never used within the function body. |
ballerina:3 | Non isolated public function | Code Smell | Low | A public function is not marked isolated, so it cannot be safely called from concurrently executing code. |
ballerina:4 | Non isolated public method | Code Smell | Low | A public method is not marked isolated, so it cannot be safely called from concurrently executing code. |
ballerina:5 | Non isolated public class | Code Smell | Low | A public class is not marked isolated, so its instances cannot be safely shared across concurrently executing code. |
ballerina:6 | Non isolated public object | Code Smell | Low | A public object is not marked isolated, so its instances cannot be safely shared across concurrently executing code. |
ballerina:7 | This operation always evaluates to true | Code Smell | Low | An operation always evaluates to true regardless of its operands' runtime values. |
ballerina:8 | This operation always evaluates to false | Code Smell | Low | An operation always evaluates to false regardless of its operands' runtime values. |
ballerina:9 | This operation always evaluates to the same value | Code Smell | Low | An operation always reduces to one of its own operands, making the operation itself redundant. |
ballerina:10 | This variable is assigned to itself | Code Smell | Low | A variable is assigned to itself, which has no effect and usually signals a mistake. |
ballerina:11 | Unused class private fields | Code Smell | Low | A private class field is declared but never used. |
ballerina:12 | Invalid range expression | Code Smell | Low | A range expression's bounds never produce any elements, making it dead code. |
ballerina:13 | Hard-coded secrets are security-sensitive | Vulnerability | High | A secret such as a password, API key, or token is written in source code as a literal or a constant. |
ballerina:14 | Non configurable secrets are security-sensitive | Vulnerability | Medium | A secret is taken from a non-configurable variable that holds a fixed value. |
Library tools
Rules for other Ballerina libraries (ballerina/file, ballerina/http, ballerina/io, ballerina/log, ballerina/os, etc.) are specified separately by each library's own compiler plugin and are reported alongside the core rules when that library is used in the project being scanned. Refer to ballerina.io/learn/scan-rules for the full list of rules contributed by each library tool.