Skip to content

Commit b33f42f

Browse files
lhoupertclaude
andcommitted
Initial commit: reusable Python security auditing GitHub Action
Composite action running bandit + pip-audit with PR comment reporting. Includes SHA-pinned dependencies, zizmor CI job, Dependabot config, and pre-commit hooks for zizmor and bandit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8021b55 commit b33f42f

24 files changed

Lines changed: 1305 additions & 1 deletion

.github/dependabot.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: github-actions
4+
directory: /
5+
schedule:
6+
interval: weekly
7+
commit-message:
8+
prefix: "ci"
9+
10+
- package-ecosystem: pip
11+
directory: /
12+
schedule:
13+
interval: weekly
14+
commit-message:
15+
prefix: "deps"

.github/workflows/ci.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
jobs:
9+
pre-commit:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
13+
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
14+
with:
15+
python-version: '3.13'
16+
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
17+
18+
test:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
22+
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
23+
with:
24+
python-version: '3.13'
25+
- name: Install package and dev dependencies
26+
run: pip install -e ".[dev]"
27+
- name: Run tests
28+
run: pytest
29+
30+
zizmor:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
34+
- uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
35+
with:
36+
python-version: '3.13'
37+
- name: Install zizmor
38+
run: pip install zizmor
39+
- name: Run zizmor
40+
run: zizmor --min-severity medium .github/
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Release Please
2+
3+
on:
4+
push:
5+
branches: [main]
6+
7+
permissions:
8+
contents: write
9+
pull-requests: write
10+
11+
jobs:
12+
release-please:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0
16+
id: release
17+
with:
18+
release-type: python
19+
20+
# Move major version tag (e.g. v1) after a release is cut
21+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
22+
if: ${{ steps.release.outputs.release_created }}
23+
- name: Tag major version
24+
if: ${{ steps.release.outputs.release_created }}
25+
run: |
26+
git config user.name "github-actions[bot]"
27+
git config user.email "github-actions[bot]@users.noreply.github.com"
28+
git tag -fa "v${{ steps.release.outputs.major }}" \
29+
-m "Release v${{ steps.release.outputs.tag_name }}"
30+
git push origin "v${{ steps.release.outputs.major }}" --force

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
__pycache__/
2+
*.py[cod]
3+
*.egg-info/
4+
dist/
5+
build/
6+
.venv/
7+
venv/
8+
.mypy_cache/
9+
.ruff_cache/
10+
.pytest_cache/
11+
*.egg
12+
MANIFEST
13+
bandit-report.json
14+
pip-audit-report.json
15+
*-requirements.txt
16+
.claude/

.pre-commit-config.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
repos:
2+
- repo: https://github.com/astral-sh/ruff-pre-commit
3+
rev: v0.5.0
4+
hooks:
5+
- id: ruff
6+
args: [--fix]
7+
- id: ruff-format
8+
9+
- repo: https://github.com/pre-commit/mirrors-mypy
10+
rev: v1.10.0
11+
hooks:
12+
- id: mypy
13+
additional_dependencies:
14+
- pydantic-settings>=2.0
15+
- bandit>=1.8
16+
17+
- repo: https://github.com/zizmorcore/zizmor-pre-commit
18+
rev: v1.23.1
19+
hooks:
20+
- id: zizmor
21+
args: [--min-severity, medium]
22+
23+
- repo: https://github.com/PyCQA/bandit
24+
rev: '1.9.4'
25+
hooks:
26+
- id: bandit
27+
args: [-r, src/]

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2026 Loïc Houpert
3+
Copyright (c) 2024 Development Seed
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# python-security-auditing
2+
3+
Reusable GitHub Action that runs [bandit](https://bandit.readthedocs.io/) and [pip-audit](https://pypi.org/project/pip-audit/) on any Python repository. Posts findings as a PR comment and fails the job when blocking issues are found.
4+
5+
## Usage
6+
7+
### Minimal (requirements.txt project)
8+
9+
```yaml
10+
- uses: developmentseed/python-security-auditing@v1
11+
```
12+
13+
### uv-based project
14+
15+
```yaml
16+
- uses: developmentseed/python-security-auditing@v1
17+
with:
18+
package_manager: uv
19+
bandit_scan_dirs: 'src/,scripts/'
20+
```
21+
22+
### Poetry project, stricter thresholds
23+
24+
```yaml
25+
- uses: developmentseed/python-security-auditing@v1
26+
with:
27+
package_manager: poetry
28+
bandit_severity_threshold: MEDIUM
29+
pip_audit_block_on: all
30+
```
31+
32+
### Bandit only (no dependency audit)
33+
34+
```yaml
35+
- uses: developmentseed/python-security-auditing@v1
36+
with:
37+
tools: bandit
38+
bandit_scan_dirs: 'src/'
39+
```
40+
41+
## Inputs
42+
43+
| Input | Default | Description |
44+
|---|---|---|
45+
| `tools` | `bandit,pip-audit` | Comma-separated tools to run |
46+
| `bandit_scan_dirs` | `.` | Comma-separated directories for bandit to scan |
47+
| `bandit_severity_threshold` | `HIGH` | Minimum severity that blocks the job: `HIGH`, `MEDIUM`, or `LOW` |
48+
| `pip_audit_block_on` | `fixable` | Block on: `fixable` (has a fix), `all`, or `none` |
49+
| `package_manager` | `requirements` | How to resolve deps: `uv`, `pip`, `poetry`, `pipenv`, `requirements` |
50+
| `requirements_file` | `requirements.txt` | Path when `package_manager=requirements` |
51+
| `post_pr_comment` | `true` | Post/update a PR comment with scan results |
52+
| `github_token` | `${{ github.token }}` | Token for PR comments |
53+
54+
## Outputs
55+
56+
- **Step summary** — written to the workflow run summary.
57+
- **PR comment** — upserted on every run (idempotent via `<!-- security-scan-results -->` marker).
58+
- **Artifacts** — `bandit-report.json` and `pip-audit-report.json` uploaded as `security-audit-reports`.
59+
- **Exit code** — non-zero when blocking issues are found.
60+
61+
## Development
62+
63+
```bash
64+
pip install -e ".[dev]"
65+
pytest
66+
pre-commit run --all-files
67+
```

action.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Python Security Auditing
2+
description: Run bandit and pip-audit security scans on Python code and report findings
3+
author: Development Seed
4+
5+
inputs:
6+
tools:
7+
description: Comma-separated tools to run (bandit, pip-audit)
8+
default: bandit,pip-audit
9+
bandit_scan_dirs:
10+
description: Comma-separated directories for bandit to scan
11+
default: .
12+
bandit_severity_threshold:
13+
description: Minimum bandit severity that blocks the job (HIGH, MEDIUM, LOW)
14+
default: HIGH
15+
pip_audit_block_on:
16+
description: When to block on pip-audit findings — fixable, all, or none
17+
default: fixable
18+
package_manager:
19+
description: How to resolve dependencies for pip-audit (uv, pip, poetry, pipenv, requirements)
20+
default: requirements
21+
requirements_file:
22+
description: Path to requirements file when package_manager=requirements
23+
default: requirements.txt
24+
post_pr_comment:
25+
description: Post or update a PR comment with the scan results
26+
default: 'true'
27+
github_token:
28+
description: GitHub token used for posting PR comments
29+
default: ${{ github.token }}
30+
working_directory:
31+
description: Directory to run the audit from (useful when the project is in a subdirectory)
32+
default: '.'
33+
34+
runs:
35+
using: composite
36+
steps:
37+
- name: Set up Python
38+
uses: actions/setup-python@8d9ed9ac5c53483de85588cdf95a591a75ab9f55 # v5.5.0
39+
with:
40+
python-version: '3.13'
41+
42+
- name: Install python-security-auditing
43+
shell: bash
44+
run: pip install "${{ github.action_path }}"
45+
46+
- name: Run security audit
47+
id: audit
48+
shell: bash
49+
continue-on-error: true
50+
working-directory: ${{ inputs.working_directory }}
51+
env:
52+
TOOLS: ${{ inputs.tools }}
53+
BANDIT_SCAN_DIRS: ${{ inputs.bandit_scan_dirs }}
54+
BANDIT_SEVERITY_THRESHOLD: ${{ inputs.bandit_severity_threshold }}
55+
PIP_AUDIT_BLOCK_ON: ${{ inputs.pip_audit_block_on }}
56+
PACKAGE_MANAGER: ${{ inputs.package_manager }}
57+
REQUIREMENTS_FILE: ${{ inputs.requirements_file }}
58+
POST_PR_COMMENT: ${{ inputs.post_pr_comment }}
59+
GITHUB_TOKEN: ${{ inputs.github_token }}
60+
PR_NUMBER: ${{ github.event.pull_request.number }}
61+
run: python -m python_security_auditing
62+
63+
- name: Upload audit reports
64+
if: always()
65+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
66+
with:
67+
name: security-audit-reports
68+
path: |
69+
${{ inputs.working_directory }}/bandit-report.json
70+
${{ inputs.working_directory }}/pip-audit-report.json
71+
if-no-files-found: ignore
72+
73+
- name: Fail if blocking issues found
74+
if: steps.audit.outcome == 'failure'
75+
shell: bash
76+
run: exit 1

pyproject.toml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "python-security-auditing"
7+
version = "0.1.0"
8+
description = "Reusable GitHub Action for Python security auditing with bandit and pip-audit"
9+
license = { text = "MIT" }
10+
requires-python = ">=3.11"
11+
dependencies = [
12+
"pydantic-settings>=2.0",
13+
"bandit>=1.8",
14+
"pip-audit>=2.7",
15+
]
16+
17+
[project.optional-dependencies]
18+
dev = [
19+
"pytest>=8.0",
20+
"pytest-mock>=3.14",
21+
"mypy>=1.0",
22+
"ruff>=0.5",
23+
"types-requests",
24+
]
25+
26+
[tool.hatch.build.targets.wheel]
27+
packages = ["src/python_security_auditing"]
28+
29+
[tool.ruff]
30+
line-length = 100
31+
32+
[tool.ruff.lint]
33+
select = ["E", "F", "I", "UP"]
34+
35+
[tool.mypy]
36+
python_version = "3.11"
37+
strict = true
38+
39+
[tool.pytest.ini_options]
40+
testpaths = ["tests"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Python Security Auditing — bandit + pip-audit GitHub Action."""

0 commit comments

Comments
 (0)