Skip to main content

Bitwarden Workflow Linter

Bitwarden's Workflow Linter is an extensible linter to apply opinionated organization-specific GitHub Action standards. It was designed to be used alongside yamllint to enforce specific YAML standards.

To see an example of Workflow Linter in practice in GitHub Actions, see the composite Action.

Prerequisites

  • Python 3.13
  • pipenv
  • Windows systems: Chocolatey package manager
  • Mac OS systems: Homebrew package manager
  • pipx

Setup

  1. Create the virtual environment:

    python3.13 -m venv /Users/$USER/bitwarden_workflow_linter_venv
    
  2. Activate the virtual environment:

    source /Users/$USER/bitwarden_workflow_linter_venv/bin/activate
    

Installation

From PyPI

This is the recommended method for most users. Installing from PyPI ensures you get the latest stable release and is the easiest way to install and update the package.

  1. Install Bitwarden Workflow Linter:

    pip install --upgrade bitwarden_workflow_linter
    
  2. Deactivate the virtual environment (optional):

    deactivate
    

Using pipx

Alternatively, you can install bwwl globally using pipx to keep it isolated:

  1. Install Bitwarden Workflow Linter:
    pipx install bitwarden_workflow_linter --python python3.13
    

This method is ideal for running bwwl as a standalone CLI tool without managing a virtual environment manually.

From GitHub Release

Use this method if you need a specific version of the package that is not yet available on PyPI, or if you want to access pre-release versions.

  1. Download the release tarball or zip file from GitHub:

    wget https://github.com/bitwarden/workflow-linter/archive/refs/tags/vX.Y.Z.tar.gz
    tar -xzf vX.Y.Z.tar.gz
    cd workflow-linter-X.Y.Z
    
  2. Install the package:

    pip install .
    
  3. Deactivate the virtual environment (optional):

    deactivate
    

Locally

This method is useful for developers who want to contribute to the project or need to make local modifications to the source code. Make sure to follow the virtual environment prerequisite setup

  1. Clone the repository:

    git clone git@github.com:bitwarden/workflow-linter.git
    cd workflow-linter
    
  2. Install the package:

    pip install -e .
    
  3. Deactivate the virtual environment (optional):

    deactivate
    

Usage

Setup settings.yaml

If a non-default configuration is desired (different than src/bitwarden_workflow_linter/default_settings.yaml), copy the below and create a settings.yaml in the directory that bwwl will be running from ( generally will be the root directory in CI).

enabled_rules:
    - id: bitwarden_workflow_linter.rules.name_exists.RuleNameExists
      level: error
    - id: bitwarden_workflow_linter.rules.name_capitalized.RuleNameCapitalized
      level: error
    - id: bitwarden_workflow_linter.rules.pinned_job_runner.RuleJobRunnerVersionPinned
      level: error
    - id: bitwarden_workflow_linter.rules.job_environment_prefix.RuleJobEnvironmentPrefix
      level: error
    - id: bitwarden_workflow_linter.rules.step_approved.RuleStepUsesApproved
      level: error
    - id: bitwarden_workflow_linter.rules.step_pinned.RuleStepUsesPinned
      level: error
    - id: bitwarden_workflow_linter.rules.underscore_outputs.RuleUnderscoreOutputs
      level: warning
    - id: bitwarden_workflow_linter.rules.run_actionlint.RunActionlint
      level: warning
    - id: bitwarden_workflow_linter.rules.check_pr_target.RuleCheckPrTarget
      level: error
    - id: bitwarden_workflow_linter.rules.permissions_exist.RulePermissionsExist
      level: warning

approved_actions_path: default_actions.json
default_branch: main

Command Line Usage

usage: bwwl [-h] [-v] {lint,actions} ...

positional arguments:
  {lint,actions}
    lint          Verify that a GitHub Action Workflow follows all of the Rules.
    actions       Add or Update Actions in the pre-approved list.

options:
  -h, --help      show this help message and exit
  -v, --verbose

lint subcommand

usage: bwwl lint [-h] [-s | -e] -f FILES [FILES ...] [-o OUTPUT]

options:
  -h, --help            show this help message and exit
  -s, --strict          return non-zero exit code on warnings as well as errors
  -e, --errors-only     only show and fail on errors; warnings are suppressed
                        from output and do not affect the exit code
  -f, --files FILES     files or directories to lint
  -o, --output OUTPUT   output format: [stdout|json|md] (default: stdout)

Note: --strict and --errors-only are mutually exclusive.

Pre-commit Hook Setup

Navigate to the .git/hooks directory in the repository you wish to lint:

cd .git/hooks

Create the pre-commit file (if it does not already exist):

touch pre-commit

Make the script executable:

chmod +x pre-commit

Edit the pre-commit script:

Open the pre-commit file with your favorite text editor and add the following content, replacing /Users/$USER/bitwarden_workflow_linter_venv/bin/activate with the actual path to your virtual environment:

#!/bin/bash
set -e
# Activate the virtual environment
source "/Users/$USER/bitwarden_workflow_linter_venv/bin/activate"
# Get the repository root directory
repo_root=$(git rev-parse --show-toplevel)
# Run your Python script
bwwl lint -f "$repo_root/.github/workflows"
# Deactivate the virtual environment (optional)
deactivate

Test the Hook:

Try committing a change to the repository. The pre-commit hook should run the workflow linter.

Development

Setup

Refer to the Locally instructions above to clone the repository and install the package.

Testing

All built-in src/bitwarden_workflow_linter/rules should have 100% code coverage and we should shoot for an overall coverage of 80%+. We are lax on the imperative shell (code interacting with other systems; ie. disk, network, etc), but we strive to maintain a high coverage over the functional core (objects and models).

pipenv shell
pytest tests --cov=src

Code Reformatting

We adhere to PEP8 and use black to maintain this adherence. black should be run on any change being merged to main.

pipenv shell
black .

Linting

We loosely use Google's Python style guide, but yield to black when there is a conflict.

pipenv shell
pylint --rcfile pylintrc src/ tests/

Add a new Rule

A new Rule is created by extending the Rule base class and overriding the fn(obj: Union[Workflow, Job, Step]) method. Available attributes of Workflows, Jobs and Steps can be found in their definitions under src/models.

For a simple example, we'll take a look at enforcing the existence of the name key in a Job. This is already done by default with the src.rules.name_exists.RuleNameExists, but provides a simple enough example to walk through.

from typing import Union, Tuple

from ..rule import Rule
from ..models.job import Job
from ..models.workflow import Workflow
from ..models.step import Step
from ..utils import LintLevels, Settings


class RuleJobNameExists(Rule):
    def __init__(self, settings: Settings = None, lint_level: Optional[LintLevels] = LintLevels.ERROR) -> None:
        self.message = "name must exist"
        self.on_fail: LintLevels = lint_level
        self.compatibility: List[Union[Workflow, Job, Step]] = [Job]
        self.settings: Settings = settings

    def fn(self, obj: Job) -> Tuple[bool, str]:
        """<doc block goes here> """
        if obj.name is not None:
            return True, ""
        return False, self.message

By default, a new Rule needs five things:

  • self.message: The message to return to the user on a lint failure
  • self.on_fail: The level of failure on a lint failure (NONE, WARNING, ERROR). NONE and WARNING will exit with a code of 0 (unless using strict mode for WARNING). ERROR will exit with a non-zero exit code
  • self.compatibility: The list of objects this rule is compatible with. This is used to create separate instances of the Rule for each object in the Rules collection.
  • self.settings: In general, this should default to what is shown here, but allows for overrides
  • self.fn: The function doing the actual work to check the object and enforce the standard.

fn can be as simple or as complex as it needs to be to run a check on a single object. This linter currently does not support Rules that check against multiple objects at a time OR file level formatting (one empty between each step or two empty lines between each job).

IMPORTANT: A rule must be implemented and tested then merged into main before it can be activated. This is because the released version of bwwl will use the current settings.yaml file, but it will not have the new rule functionality yet and cause an error in the workflow linting of this repository.

To activate a rule after implementing and releasing it, add it to settings.yaml in the project's base folder and src/bitwarden_workflow_linter/default_settings.yaml to make the rule default.

Before creating a new rule please read the Workflow linter rule rollout process document in which you'll find the process for rolling out new workflow linter rules.

Metadata

Release files for bitwarden-workflow-linter 3.5.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bitwarden-workflow-linter 3.5.9
File Size Uploaded Provenance
bitwarden_workflow_linter-3.5.9.tar.gz 120.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for bitwarden-workflow-linter 3.5.9
File Interpreter ABI Platform
bitwarden_workflow_linter-3.5.9-py3-none-any.whl Python 3 none any Details

Total release size: 177.3 kB

Release files / bitwarden_workflow_linter-3.5.9.tar.gz

Download URL bitwarden_workflow_linter-3.5.9.tar.gz
Size 120.0 kB
Tags Source
SHA-256 checksum
How to use checksums
df2d12a8d97e8434c2cae88efb285e89a48a87261a7f389eb264c3235dfa9605
BLAKE2b-256 checksum
How to use checksums
803cd977de2cdbc549cc8aecb3fdfc35871c9279e6efca0d2f7ec2a1719d86c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / bitwarden_workflow_linter-3.5.9-py3-none-any.whl

Download URL bitwarden_workflow_linter-3.5.9-py3-none-any.whl
Size 57.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
120ea8a2a9884f523cffd0c3018728d05349406b9fe0808eda829221ee200cd8
BLAKE2b-256 checksum
How to use checksums
fc4ed16e4e28f248363065d5b5b459d3c75c6aa0f150d6837d7efbea1df6d692
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Security for bitwarden-workflow-linter 3.5.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for bitwarden_workflow_linter-3.5.9.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

3.5.9 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page