Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 168 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,66 @@ A GitHub Action that runs **[bandit](https://bandit.readthedocs.io/)** (static c

## When this might be useful

Running bandit and pip-audit directlyor using focused actions like `PyCQA/bandit-action` or `pypa/gh-action-pip-audit`—is a reasonable and common approach. Those tools and actions are fine on their own.
Running bandit and pip-audit directlyor using the official focused actions ([PyCQA/bandit-action](https://github.com/PyCQA/bandit-action) and [pypa/gh-action-pip-audit](https://github.com/pypa/gh-action-pip-audit)) — is a reasonable and common approach. Those tools and actions are fine on their own.

This repo exists for workflows where you want **both** scanners behind **one** job and **one** place to read the outcome. It is a thin wrapper around the same tools, not a different kind of analysis. The things it adds on top of running the tools individually:
This action exists for workflows where you want **both** scanners behind **one** step and **one** place to read the outcome. It is a thin wrapper around the same tools, not a different kind of analysis. The things it adds on top of running the tools individually:

- **One PR comment for both scanners** — created on the first run and updated in place on every subsequent push, so the PR thread stays clean.
- **Single step, unified report** — one action replaces two, with no need to coordinate SARIF uploads or chain step outputs between jobs.
- **One PR comment for both scanners** — created on the first run and updated in place on every subsequent push, so the PR thread stays clean. Neither official action provides this out of the box.
- **Workflow step summary** — the same report is written to the "Summary" tab of the workflow run.
- **Block on fixable-only vulnerabilities** — `pip_audit_block_on: fixable` (the default) fails CI only when a patched version exists, so you can act on it immediately; unfixable CVEs are reported but don't block.
- **Automatic requirements export** — pass `package_manager: uv|poetry|pipenv` and the action runs the appropriate export command before invoking pip-audit, saving you an extra workflow step.
- **Block on fixable-only vulnerabilities** — `pip_audit_block_on: fixable` (the default) fails CI only when a patched version exists, so you can act on it immediately; unfixable CVEs are reported but don't block. The official pip-audit action does not have this mode.
- **Automatic requirements export** — pass `package_manager: uv|poetry|pipenv` and the action runs the appropriate export command before invoking pip-audit. With the official pip-audit action, you must add a separate step to export first.

### Comparison with running the tools separately

**Using the two official actions (uv project, bandit + pip-audit):**

```yaml
jobs:
security:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4

# Static analysis
- uses: PyCQA/bandit-action@v1
with:
targets: src/

# Export dependencies before pip-audit (required for uv projects)
- name: Export requirements
run: uv export --no-dev --format requirements-txt > requirements.txt

# Dependency audit
- uses: pypa/gh-action-pip-audit@v1
with:
inputs: requirements.txt
# Note: no built-in "fixable-only" blocking mode
# Note: findings appear only in the Actions log — no PR comment
```

**Using this action (equivalent result, one step):**

```yaml
jobs:
security:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # for the unified PR comment
security-events: write
steps:
- uses: actions/checkout@v4
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
package_manager: uv # export handled automatically
bandit_scan_dirs: 'src/'
pip_audit_block_on: fixable # only block when a fix exists
# Posts a unified PR comment and step summary automatically
```

## What the PR comment looks like

Expand Down Expand Up @@ -58,6 +110,8 @@ When everything is clean:

The comment is idempotent — it is created once and updated in place on every push, so the PR thread stays clean.

Each section also includes a direct link: the Bandit section links to the repository's GitHub Code Scanning page, and the pip-audit section links to the Dependabot security alerts page. These links appear when GitHub repository context is available (i.e. when running inside a GitHub Actions workflow).

## Quickstart

Add this to your workflow (e.g. `.github/workflows/security.yml`):
Expand All @@ -80,27 +134,78 @@ jobs:

This runs both bandit and pip-audit with sensible defaults: blocks the job on HIGH-severity code issues and on dependency vulnerabilities that have a fix available.

## Required permissions

The action needs these permissions on the job:

```yaml
permissions:
contents: read
pull-requests: write # post/update the PR comment (when post_pr_comment: true)
security-events: write # upload bandit SARIF to GitHub Code Scanning
```

If you only use `post_pr_comment: false` and don't care about Code Scanning integration, `contents: read` alone is sufficient.

## Usage examples

### uv project
### Choosing a package manager

Pass `package_manager` to match how your project manages dependencies. The action exports a requirements list before invoking pip-audit, so no extra step is needed.

**uv:**
```yaml
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
package_manager: uv
bandit_scan_dirs: 'src/'
```

### Poetry project with stricter thresholds
**Poetry:**
```yaml
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
package_manager: poetry
bandit_scan_dirs: 'src/'
```

Block on any bandit finding at MEDIUM or above, and on all known vulnerabilities regardless of whether a fix exists:
**Pipenv:**
```yaml
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
package_manager: pipenv
bandit_scan_dirs: 'src/'
```

**Plain requirements file (default):**
```yaml
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
package_manager: poetry
bandit_severity_threshold: medium
pip_audit_block_on: all
requirements_file: requirements/prod.txt
bandit_scan_dirs: 'src/'
```

### Scanning multiple directories

When your source code spans more than one directory, pass a comma-separated list to `bandit_scan_dirs`:

```yaml
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
package_manager: uv
bandit_scan_dirs: 'src/,scripts/'
```

### Project in a subdirectory (monorepo)

Set `working_directory` to the project root within the repo. All relative paths (scan dirs, requirements file) are resolved from there:

```yaml
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
working_directory: services/api
package_manager: uv
bandit_scan_dirs: 'src/'
```

### Bandit only (skip dependency audit)
Expand All @@ -114,27 +219,72 @@ Useful when you manage dependencies externally or run pip-audit in a separate jo
bandit_scan_dirs: 'src/'
```

### Project in a subdirectory (monorepo)
### Dependency audit only (skip static analysis)

Useful when you already run bandit separately or only care about known CVEs in dependencies:

```yaml
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
working_directory: services/api
tools: pip-audit
package_manager: uv
bandit_scan_dirs: 'src/'
```

### Audit-only mode (never block the job)
### Strict security gate

Block on any bandit finding at MEDIUM or above, and on all known vulnerabilities regardless of whether a fix exists. Suitable for high-assurance services or regulated environments:

```yaml
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
package_manager: poetry
bandit_severity_threshold: medium
pip_audit_block_on: all
```

### Gradual adoption (audit-only, never block)

Run the audit and post the comment for visibility, but don't fail CI:
Add the action first as an observer: it posts findings to the PR comment and step summary without ever failing the job. Tighten the thresholds once your team has addressed the backlog:

```yaml
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
package_manager: uv
bandit_severity_threshold: low # report everything
pip_audit_block_on: none # never block
```

### Scheduled scan on the default branch

Run a weekly audit against `main` in addition to PR checks, so vulnerabilities introduced through dependency updates are caught promptly:

```yaml
name: Weekly Security Audit

on:
schedule:
- cron: '0 8 * * 1' # every Monday at 08:00 UTC
push:
branches: [main]

jobs:
security:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- uses: lhoupert/action-python-security-auditing@12efad3bddc3efd3668cf6ac6799f94837f4fb3d # v0.5.0
with:
package_manager: uv
post_pr_comment: false # no PR to comment on for scheduled runs
```

### Multiple workflows posting separate PR comments

If you run this action from more than one workflow on the same PR (e.g. a general security workflow and a focused API service workflow), each workflow automatically gets its own PR comment. No configuration is needed — the comment is keyed on the workflow name, so the two comments stay independent and update in place separately.

## How blocking works

The job fails (non-zero exit) when **either** tool finds issues above its configured threshold.
Expand Down Expand Up @@ -169,12 +319,13 @@ The job fails (non-zero exit) when **either** tool finds issues above its config
| `post_pr_comment` | `true` | Post/update a PR comment with scan results |
| `github_token` | `${{ github.token }}` | Token used for posting PR comments |
| `artifact_name` | `security-audit-reports` | Name of the uploaded artifact |
| `debug` | `false` | Enable verbose debug logging; also activates automatically when re-running a workflow with "Enable debug logging" |

## Outputs

- **PR comment** — created on first run, updated in place on every subsequent run. The comment is keyed on a hidden `<!-- security-scan-results::{workflow-name} -->` marker, so multiple workflows on the same PR each maintain their own separate comment.
- **Step summary** — the same report is written to the workflow run summary, visible under the "Summary" tab.
- **Artifact** — `pip-audit-report.json` and `results.sarif` uploaded under the name set by `artifact_name` (default: `security-audit-reports`) for download or downstream steps.
- **Artifact** — `pip-audit-report.json` and `results.sarif` uploaded under the name set by `artifact_name` (default: `security-audit-reports`) for download or downstream steps. The `results.sarif` file is the bandit SARIF report; it is also uploaded to GitHub Code Scanning automatically by the underlying `lhoupert/bandit-action` step, making findings visible in the repository's Security tab when the job has `security-events: write` permission.
- **Exit code** — non-zero when blocking issues are found, so the job fails and branch protections can enforce it.

## Development
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading