ballerina/tool_scan Ballerina Tool

0.13.0

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:

Copy
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.

OptionDescription
--target-dir=<path>Target directory path for saving analysis reports (only for Ballerina projects). Default is the project's target directory.
--scan-reportGenerate 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-rulesList 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-suppressionsDisregard @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-reasonFail 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-idFail 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

Copy
# 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:

Copy
[
  {
    "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:

Copy
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.

scan-report-summary-view

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.

scan-report-file-view

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.

scan-report-issue-view

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:

Copy
[scan]
configPath = "path/to/Scan.toml"

A sample Scan.toml:

Copy
# 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:

CodeMeaning
0The scan completed, and no gate is set or the gate was not tripped.
1The 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.
2Invalid 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.
3The path is not a Ballerina package, workspace or standalone file, or is a .bal file inside a package.
130The 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-countFails when
Not setNot setNever. The gate is off.
Not setnThere are n or more issues of any severity.
XNot setThere is at least one issue at or above X.
XnThere are n or more issues at or above X.
NONEAnyNever. 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:

Copy
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 external function 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 example var _ = check f(); instead of check 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; without reason, the issue is suppressed without a justification. The annotation can be attached more than once to give each rule its own reason. See the ballerina/scan package documentation for examples of each option, the checks done during bal build and the editor's code actions.
  • Rule ID validation. The compiler plugin of ballerina/scan reports a malformed rule ID or an empty rule list as a compile error, and bal scan reports them as errors too, so that such a suppression never passes as one of no rule. bal scan checks 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 a suppressions entry of kind inSource carrying 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 the Suppressed or All status 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-rules or Scan.toml, and one without rules is 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 without rules on 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 requireReason and requireRuleId in the [suppression] table of Scan.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-suppressions reports every issue as though no suppression existed. To disregard suppressions for every run of a project, for example from a shared Scan.toml, set enabled = false in 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:

SeverityMeaningImpact
BLOCKERA critical issue that is very likely to cause application failure or a security breach.Should block merging or deployment until fixed.
HIGHA serious issue likely to cause incorrect behavior or expose the application to exploitation.Should be fixed before deployment.
MEDIUMAn 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.
LOWA minor issue such as a code smell that affects readability or maintainability.Safe to defer, but worth cleaning up over time.
INFOAn 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 IDNameKindSeverityDescription
ballerina:1Avoid checkpanicCode SmellLowUsing checkpanic lets an unhandled error panic and crash the program instead of being handled.
ballerina:2Unused function parameterCode SmellLowA function parameter is declared but never used within the function body.
ballerina:3Non isolated public functionCode SmellLowA public function is not marked isolated, so it cannot be safely called from concurrently executing code.
ballerina:4Non isolated public methodCode SmellLowA public method is not marked isolated, so it cannot be safely called from concurrently executing code.
ballerina:5Non isolated public classCode SmellLowA public class is not marked isolated, so its instances cannot be safely shared across concurrently executing code.
ballerina:6Non isolated public objectCode SmellLowA public object is not marked isolated, so its instances cannot be safely shared across concurrently executing code.
ballerina:7This operation always evaluates to trueCode SmellLowAn operation always evaluates to true regardless of its operands' runtime values.
ballerina:8This operation always evaluates to falseCode SmellLowAn operation always evaluates to false regardless of its operands' runtime values.
ballerina:9This operation always evaluates to the same valueCode SmellLowAn operation always reduces to one of its own operands, making the operation itself redundant.
ballerina:10This variable is assigned to itselfCode SmellLowA variable is assigned to itself, which has no effect and usually signals a mistake.
ballerina:11Unused class private fieldsCode SmellLowA private class field is declared but never used.
ballerina:12Invalid range expressionCode SmellLowA range expression's bounds never produce any elements, making it dead code.
ballerina:13Hard-coded secrets are security-sensitiveVulnerabilityHighA secret such as a password, API key, or token is written in source code as a literal or a constant.
ballerina:14Non configurable secrets are security-sensitiveVulnerabilityMediumA 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.

Other versions

See more...

Metadata

Released date: 4 days ago

Version: 0.13.0

License: Apache-2.0


Security

No known vulnerabilities in libraries

Checked on 2026-11-08 via OSV.dev


Compatibility

Platform: java21

Ballerina version: 2201.13.2

GraalVM compatible: Yes


Pull count

Total: 7171

Current verison: 29


Weekly downloads


Source repository


Keywords

scan

static code analysis


Contributors