diff --git a/README.md b/README.md index 0cf1c9d..f46bb76 100644 --- a/README.md +++ b/README.md @@ -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 directly—or 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 directly — or 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 @@ -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`): @@ -80,10 +134,26 @@ 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: @@ -91,16 +161,51 @@ This runs both bandit and pip-audit with sensible defaults: blocks the job on HI 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) @@ -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. @@ -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 `` 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 diff --git a/uv.lock b/uv.lock index b43b044..5d57293 100644 --- a/uv.lock +++ b/uv.lock @@ -566,7 +566,7 @@ wheels = [ [[package]] name = "python-security-auditing" -version = "0.4.3" +version = "0.5.0" source = { editable = "." } dependencies = [ { name = "pip-audit" },