This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Bazel

Learn how to implement Endor Labs in monorepos using Bazel

This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Learn how to implement Endor Labs in monorepos using Bazel

Bazel is an open-source build and test tool commonly used in monorepos to quickly build software across multiple languages.

You can use Endor Labs and Bazel to scan software for potential security issues and policy violations, prioritize vulnerabilities in the context of your applications, and understand relationships between software components.

Endor Labs also supports Bazel aspects to augment the build dependency graphs with additional information and actions. If you use custom rules to build your software, you can create your own custom Bazel aspects and integrate them with Endor Labs. See Bazel Aspects for more information.

Ensure that the following prerequisites are in place for a successful scan:

  • WORKSPACE file exists in your repository
  • bazel command installed and available
  • Bazel version 5.x.x, 6.x.x, or 7.x.x
  • Supported target types in your project

Before you proceed to run a deep scan, ensure that your system meets the following specification.

Project Size Processor Memory
Small projects 4-core processor 16 GB
Mid-size projects 8-core processor 32 GB
Large projects 16-core processor 64 GB

You can choose to build the targets before running the scan. Use the bazel build commands to do this by passing a comma-separated list of targets. For example, for targets //:test and //:test2, run bazel build //:test,//:test2.

endorctl will automatically build targets if they are not already built. endorctl uses bazel build //:target and bazel query 'deps(//:target)' --output graph to build each target and analyze its dependency tree.

The following table lists the supported Bazel rules and Endor Labs features for each language.

Language Supported Rules Version Requirements
Java java_library, java_binary

๐Ÿ“ While dependency scanning is supported for java_binary targets, call graph generation requires an uber jar containing all dependencies. The java_binary rule itself does not produce an uber jar, but its deploy.jar output provides the necessary consolidated dependencies for call graph analysis.
4.1+
Python py_binary, py_library, py_image

๐Ÿ›‘ py_image only supports PY3 toolchain (py3_image)
0.9.0+
Go go_binary, go_library, go_image 0.40.1+ (Bazel 5.x-6.x), 0.42.0+ (Bazel 7.x)

๐Ÿ“ For Bazel with Gazelle in vendored mode, see Go with Gazelle.
Scala scala_binary, scala_library 5.0.0 - 6.6.0
Rust (Beta) rust_binary, rust_library 0.40.0+

Use the following commands to find scannable targets in your repository.

bazel query 'kind(java_binary, //...)'
bazel query 'kind(py_binary, //...)'
bazel query 'kind(go_binary, //...)'
bazel query 'kind(scala_binary, //...)'
bazel query 'kind(rust_binary, //...)'
bazel query 'kind(".*_binary", //...)'

Use these common query patterns to find targets.

Run the following command to find all targets in a specific package.

bazel query '//your-package:*'

Run the following command to find all binary targets across languages.

bazel query 'kind(".*_binary", //...)'

Run the following command to find targets with specific attributes.

bazel query 'attr(visibility, "//visibility:public", //...)'

Run the following command to find dependencies of a target.

bazel query 'deps(//your-target:name)'

Run the following command to find reverse dependencies of a target.

bazel query 'rdeps(//..., //your-target:name)'

The following table lists the common flags and options to scan Bazel projects.

Flag Purpose Example
--bazel-include-targets Specify targets to scan --bazel-include-targets=//app:main
--bazel-exclude-targets Exclude specific targets --bazel-exclude-targets=//test:*
--bazel-targets-query Use Bazel query to select targets --bazel-targets-query='kind(java_binary, //...)'
--bazel-workspace-path Non-root workspace location --bazel-workspace-path=./src/java
--bazel-vendor-manifest-path Go vendored mode go.mod path --bazel-vendor-manifest-path=./go.mod
--disable-private-package-analysis Skip private package analysis --disable-private-package-analysis
--quick-scan Fast scan mode --quick-scan
--bazel-rc-path Specify custom paths for Bazel configuration files --bazel-rc-path=.custom.bazelrc.user
โ€”-bazel-flags Specify additional command-line flags that should be passed to Bazel when running a scan -โ€”bazel-flags="config=ci, config=dev, remote_retries=5"
--use-bazel-aspects Enable Bazel aspect framework for dependency resolution --use-bazel-aspects
--bazel-aspect-package Override base aspect package (defaults to @//.endorctl/aspects) --bazel-aspect-package=@//endor_aspects
-o json Output format -o json | tee results.json

To scan with Endor Labs, you need to specify which targets to analyze using one of two approaches:

  • Specific target list: Provide a comma-separated list of exact targets using --bazel-include-targets.
  • Query-based selection: Use the Bazel query language to select all targets matching your criteria with --bazel-targets-query.

Run a fast scan for software composition visibility without reachability analysis.

endorctl scan --use-bazel --bazel-include-targets=//your-target-name --quick-scan

Perform a full analysis with dependency resolution, reachability analysis, and call graphs.

endorctl scan --use-bazel --bazel-include-targets=//your-target-name
Private Package Analysis
When a deep scan is performed, all private software dependencies are completely analyzed by default if they have not been previously scanned. This is a one-time operation and will slow down initial scans, but won’t impact subsequent scans.

You can scan specific targets in your Bazel project using the --bazel-include-targets flag.

Run the following command to scan a single target.

endorctl scan --use-bazel --bazel-include-targets=//your-target-name

To scan multiple targets, provide a comma-separated list.

endorctl scan --use-bazel --bazel-include-targets=//target1,//target2,//target3

Use these commands to scan targets based on queries.

endorctl scan --use-bazel --bazel-targets-query='kind(java_binary, //...)'
endorctl scan --use-bazel --bazel-targets-query='kind(py_binary, //...)'
endorctl scan --use-bazel --bazel-targets-query='kind(go_binary, //...)'
endorctl scan --use-bazel --bazel-targets-query='kind(scala_binary, //...)'
endorctl scan --use-bazel --bazel-targets-query='kind(rust_binary, //...)'
endorctl scan --use-bazel --bazel-targets-query='attr(visibility, "//visibility:public", //...)'

If your WORKSPACE file isn’t at the repository root.

endorctl scan --use-bazel \
  --bazel-targets-query='kind(java_binary, //...)' \
  --bazel-workspace-path=./src/java

For Go projects using Bazel with Gazelle in vendored mode.

endorctl scan --use-bazel \
  --bazel-include-targets=//your-go-target \
  --bazel-vendor-manifest-path=./go.mod

For large codebases, disable private package analysis.

endorctl scan --use-bazel \
  --bazel-include-targets=//your-target-name \
  --disable-private-package-analysis

For detailed information about scanning specific languages:

You can save the findings of your scans to a local file or view the findings in the Endor Labs user interface.

Run the following command to save the results of a quick scan to a local file.

endorctl scan --use-bazel --bazel-include-targets=//your-target-name --quick-scan -o json | tee results.json

Run the following command to save the results of a deep scan to a local file.

endorctl scan --use-bazel --bazel-include-targets=//your-target-name -o json | tee results.json

To view your scan results in the Endor Labs user interface:

  1. Sign in to Endor Labs user interface and select Projects from the left sidebar.
  2. Select the project you want to view and click Findings to view your scan results.

For more information, see Viewing findings in the Endor Labs user interface.

Check the following common issues and solutions for Bazel projects scans.

No targets found
Check your query syntax and target types.
Workspace not found
Use --bazel-workspace-path flag.
Build failures
Pre-build targets with bazel build.
Slow scans
Use --disable-private-package-analysis
Go vendored issues
Specify --bazel-vendor-manifest-path.

Bazel Aspects

In Bazel, a rule defines how a target is built. An aspect is a reusable extension that Bazel can apply to that rule and its dependencies during analysis. Refer to the Bazel documentation for more information.

Endor Labs uses aspects to perform software composition analysis on your software packages and extract dependency information in a structured and repeatable manner.

Endor Labs provides built-in Bazel aspects that automatically enhance dependency resolution when scanning Bazel workspaces. You can run scans with aspects enabled so that Endor Labs can automatically discover and use the appropriate aspect rules for your project. If you have custom rules to build your software, you can create your own custom Bazel aspects and integrate them with Endor Labs.

The following table lists the Bazel aspect command reference.

Flag Description
--use-bazel-aspects Enable the Bazel aspect framework. You need to use this flag along with --use-bazel.
--bazel-aspect-package By default, endorctl reads the contents of the @//.endorctl/aspects package for the available aspects. To override the base aspect package, use the --bazel-aspect-package flag. For example, --bazel-aspect-package=@//endor_aspects.

Endor Labs supports Bazel aspects for the following open-source rulesets:

Ruleset Minimum Version Supported Languages
rules_go 0.42.0 Go
rules_rust 0.40.0 Rust
rules_js 2.0.0 JavaScript
Version support
Endor Labs automatically selects the appropriate aspect rule version based on the ruleset version detected in your workspace.

Run the following command to scan the workspace using Bazel aspects.

endorctl scan --use-bazel --use-bazel-aspects

Aspect rules are located under the .endorctl/aspects directory in the workspace.

For example, if your workspace is located at ~/my-workspace, the aspect rules will be located at ~/my-workspace/.endorctl/aspects.

Place your custom aspects in the .endorctl/aspects/custom directory.

When Endor Labs scans a Bazel workspace with aspects enabled, it performs the following steps:

  1. Set up Aspects: Initializes and extracts the Bazel aspects plugin to the workspace.
  2. Query the workspace: Runs bazel query to get information about the rules versions used in the workspace.
  3. Query the target: Runs bazel query to query the target being scanned and get information about the external dependencies used by it.
  4. Execute the aspect rule: Runs bazel build to execute the aspect rule.
  5. Read the aspect output: Reads the aspect output to get the dependency information.

Bazel aspects output data in JSON format, which Endor Labs uses to populate the dependency graph.

When executing aspects, Endor Labs runs bazel build with specific flags and configuration.

Endor Labs creates a temporary .bazelrc configuration that includes:

Flag Purpose
--aspects=<aspect_reference> Specifies the aspect to execute.
--output_groups=endor_sca_info Requests only the endor_sca_info output group.
--aspects_parameters=external_target_json='<json>' Passes external dependency information to the aspect.
--aspects_parameters=ref='<target_ref>' Passes the target reference (for example, git commit SHA) to the aspect.
--build_event_json_file=<bep_file> Specifies the Build Event Protocol (BEP) output file. endorctl always uses BEP to read build events and retrieve aspect-generated files.
--aspects_parameters=json_go_mod='<go_mod_json>' Passes Go module dependency information. (Go targets only)

When using remote executors or remote caching, aspect-generated files may be stored remotely, making them inaccessible to endorctl for processing.

To ensure all Bazel aspect outputs are available locally, endorctl automatically sets the following flags:

  • --remote_download_outputs=all: Forces all aspect outputs to be downloaded locally when using remote executors (for example, Build without Bytes). This is required because endorctl needs to read the json files generated by aspects to populate the dependency graph.
  • --remote_download_toplevel_outputs=all: Ensures top-level outputs are also downloaded locally, which is necessary for accessing aspect-generated files.

For more information about these Bazel flags, refer to the Bazel command-line reference.

You can extend Bazel with custom rules to support proprietary toolchains, internal build workflows or enterprise-specific requirements that are not covered by Bazel’s built-in rules. While powerful, these custom rules can obscure dependency information from standard analysis tools.

Endor Labs can automatically analyze dependencies for open-source rule sets. However, custom rules often define dependencies in a non-standard way, such as:

  • Generated targets
  • Internal dependency resolution logic

Since Bazel considers custom rules as first-class citizens, dependency information inside them is not automatically visible unless explicitly surfaced. Without an aspect, Endor Labs cannot reliably determine:

  • What dependencies the rule introduces
  • Whether those dependencies are internal or third-party
  • How they relate to the rest of the build graph

Custom aspects solve this by explicitly exposing dependency metadata in a format Endor Labs understands.

Before you can get started with developing your own aspects, ensure you have the following set up.

Your machine must have the relevant permissions to access the git repository regardless of where it is hosted, be it GitHub, GitLab, or self-hosted.

Bazel should be installed in the machine you are going to build custom aspects. If you don’t have it installed already, follow the Bazel installation instructions.

Run the following command to check your Bazel installation.

bazel version

You also need the endorctl CLI available in your path. See endorctl CLI documentation for more information.

Beta
Custom aspects support is currently in beta. The API and behavior may change in future releases as we continue to improve the framework based on feedback.

The following sections provide information to help you build your custom Bazel aspects.

To help engineers get started, we have open-sourced an example for JavaScript rules. You can find the complete codebase in the example repository.

You need a custom Bazel aspect if:

  • Your dependency graph flows through a custom Bazel rule kind (rule class) that Endor Labs does not support out of the box, such as my_company_js_binary.
  • The rule declares dependencies in non-standard locations, including custom attribute names, generated targets, or internal dependency resolution logic.

Custom aspects must be available in the repository that you want to scan.

Ensure that you organize them as shown in the following directory structure for endorctl to recognize them. Use –bazel-aspect-package to configure the base package (defaults to @//.endorctl/aspects).

.endorctl/aspects/
โ””โ”€โ”€ custom/                           # User-defined custom aspects
   โ””โ”€โ”€ {ecosystem}/
       โ””โ”€โ”€ {rule_class}/             # Directory named after rule class
           โ””โ”€โ”€ {rule_class}.bzl      # Custom aspect file

Use the following path pattern to create your custom aspect.

{baseAspectPackage}/custom/{ecosystem}/{rule_class}/{rule_class}.bzl
Component Example
Ecosystem Go, Rust, JavaScript
Rule Class go_binary, my_custom_rule

Your custom aspect must be named endor_resolve_dependencies. endorctl discovers it by looking for this symbol in a .bzl file at the path described above.

The aspect definition must declare attr_aspects to tell Bazel which rule attributes to traverse (for example, deps, data, srcs). It must also declare the following mandatory attributes. The scan fails if any are excluded.

Attribute Type Required Description
ref attr.string() Yes Git reference (branch/tag) for the scan. Passed by endorctl via --aspects_parameters.
log_level attr.string(default = "DEBUG") Yes Logging verbosity. Used internally by the aspect for debug output.
external_target_json attr.string(default = "{}") Yes JSON output of external dependency query. Passed by endorctl via --aspects_parameters.

The following attribute is language-specific and optional.

Attribute Type Required Description
json_go_mod attr.string(default = "{}") Go only Go module dependency information. Passed by endorctl via --aspects_parameters for Go targets.

The output files must be JSON. Serialize your provider (for example, EndorDependencyInfo) to JSON with json.encode_indent(). The following table lists the fields Endor Labs expects.

Field Type Required Description
original_label string Yes Canonical Bazel label (must use @@// prefix)
purl string Yes Package URL (PURL) for the dependency, for example pkg:npm/package-name@version
internal boolean Yes true for first-party code, false for third-party
dependencies string[] No List of direct dependency labels
vendored boolean No true if vendored dependency
hide boolean No true to hide the node from the Endor Labs dependency graph
depset requirement
The output file must be returned in a depset from the endor_sca_info output group. endorctl reads these depsets through BEP to construct the complete dependency tree.

The Endor Labs aspects example repository provides a complete custom aspect for JavaScript rules.

The example defines an EndorDependencyInfo provider that carries the metadata Endor Labs needs for each target: original_label, purl, dependencies, internal, vendored, and hide.

After defining the provider, it defines helper functions. _get_dependency_list() goes through each dependency attribute, and collects labels of targets that have an endor_sca_info output group. _get_dependency_files() collects the output files from those targets. _get_sca_information() resolves the package name and version from the rule context, and falls back to the target label and ref attribute when explicit metadata is not available.

The aspect implementation (_impl) extracts deps, data, src, and srcs from the rule attributes. It calls the helpers to build a list of dependency labels and collect transitive dependency files. It then constructs a PURL (for example, pkg:npm/package-name@version), populates the EndorDependencyInfo provider, and writes it to a JSON file using json.encode_indent(). Finally, it returns OutputGroupInfo(endor_sca_info = depset([output_file] + dependency_files)), combining the current target’s output with all files from its transitive dependencies.

The aspect itself is defined as endor_resolve_dependencies with the mandatory attributes described in Aspect attributes.

endorctl reads the resulting depsets through the Build Event Protocol (BEP) to construct the complete dependency graph. These files must be available locally. endorctl ensures downloads when using remote execution or caching (see Bazel aspect remote execution and caching).