From 271bcf155998d81596dfc97eabcccdf254ffa22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Houpert?= <10154151+lhoupert@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:10:28 +0100 Subject: [PATCH] feat: repurpose the --- .github/scripts/validate_results.py | 4 +- .../workflows/02-requirements-src-bandit.yml | 28 ---- .github/workflows/04-uv-flat.yml | 27 ---- .github/workflows/05-uv-src-vuln.yml | 28 ---- .github/workflows/06-uv-multi-bandit.yml | 28 ---- .github/workflows/07-poetry-flat.yml | 32 ---- .github/workflows/09-pipenv-flat.yml | 35 ---- .github/workflows/10-pipenv-multi-bandit.yml | 27 ---- .github/workflows/11-requirements-root.yml | 28 ---- .github/workflows/12-uv-flat-bandit-only.yml | 26 --- .../workflows/13-requirements-unfixable.yml | 29 ---- .github/workflows/14-uv-low-threshold.yml | 26 --- .github/workflows/integration-tests.yml | 81 +--------- 02-requirements-src-bandit/README.md | 19 --- 02-requirements-src-bandit/requirements.txt | 2 - 02-requirements-src-bandit/src/app.py | 15 -- 04-uv-flat/README.md | 11 -- 04-uv-flat/app.py | 12 -- 04-uv-flat/pyproject.toml | 12 -- 05-uv-src-vuln/README.md | 19 --- 05-uv-src-vuln/pyproject.toml | 15 -- 05-uv-src-vuln/src/fetcher.py | 9 -- 05-uv-src-vuln/src/uv_src_vuln/__init__.py | 0 05-uv-src-vuln/uv.lock | 90 ----------- 06-uv-multi-bandit/README.md | 20 --- 06-uv-multi-bandit/pyproject.toml | 12 -- 06-uv-multi-bandit/scripts/digest.py | 8 - 06-uv-multi-bandit/src/parser.py | 8 - 06-uv-multi-bandit/uv.lock | 150 ------------------ 07-poetry-flat/README.md | 11 -- 07-poetry-flat/app.py | 15 -- 07-poetry-flat/pyproject.toml | 14 -- 09-pipenv-flat/Pipfile | 11 -- 09-pipenv-flat/Pipfile.lock | 87 ---------- 09-pipenv-flat/README.md | 12 -- 09-pipenv-flat/app.py | 16 -- 10-pipenv-multi-bandit/Pipfile | 11 -- 10-pipenv-multi-bandit/README.md | 19 --- 10-pipenv-multi-bandit/scripts/deploy.py | 8 - 10-pipenv-multi-bandit/src/handler.py | 8 - 11-requirements-root/README.md | 21 --- 11-requirements-root/app.py | 8 - 11-requirements-root/requirements.txt | 1 - 12-uv-flat-bandit-only/README.md | 15 -- 12-uv-flat-bandit-only/app.py | 6 - 12-uv-flat-bandit-only/pyproject.toml | 5 - 13-requirements-unfixable/README.md | 18 --- 13-requirements-unfixable/app.py | 4 - 13-requirements-unfixable/requirements.txt | 1 - 14-uv-low-threshold/README.md | 15 -- 14-uv-low-threshold/app.py | 5 - 14-uv-low-threshold/pyproject.toml | 5 - README.md | 29 ++-- expected_results.yml | 92 ----------- 54 files changed, 13 insertions(+), 1225 deletions(-) delete mode 100644 .github/workflows/02-requirements-src-bandit.yml delete mode 100644 .github/workflows/04-uv-flat.yml delete mode 100644 .github/workflows/05-uv-src-vuln.yml delete mode 100644 .github/workflows/06-uv-multi-bandit.yml delete mode 100644 .github/workflows/07-poetry-flat.yml delete mode 100644 .github/workflows/09-pipenv-flat.yml delete mode 100644 .github/workflows/10-pipenv-multi-bandit.yml delete mode 100644 .github/workflows/11-requirements-root.yml delete mode 100644 .github/workflows/12-uv-flat-bandit-only.yml delete mode 100644 .github/workflows/13-requirements-unfixable.yml delete mode 100644 .github/workflows/14-uv-low-threshold.yml delete mode 100644 02-requirements-src-bandit/README.md delete mode 100644 02-requirements-src-bandit/requirements.txt delete mode 100644 02-requirements-src-bandit/src/app.py delete mode 100644 04-uv-flat/README.md delete mode 100644 04-uv-flat/app.py delete mode 100644 04-uv-flat/pyproject.toml delete mode 100644 05-uv-src-vuln/README.md delete mode 100644 05-uv-src-vuln/pyproject.toml delete mode 100644 05-uv-src-vuln/src/fetcher.py delete mode 100644 05-uv-src-vuln/src/uv_src_vuln/__init__.py delete mode 100644 05-uv-src-vuln/uv.lock delete mode 100644 06-uv-multi-bandit/README.md delete mode 100644 06-uv-multi-bandit/pyproject.toml delete mode 100644 06-uv-multi-bandit/scripts/digest.py delete mode 100644 06-uv-multi-bandit/src/parser.py delete mode 100644 06-uv-multi-bandit/uv.lock delete mode 100644 07-poetry-flat/README.md delete mode 100644 07-poetry-flat/app.py delete mode 100644 07-poetry-flat/pyproject.toml delete mode 100644 09-pipenv-flat/Pipfile delete mode 100644 09-pipenv-flat/Pipfile.lock delete mode 100644 09-pipenv-flat/README.md delete mode 100644 09-pipenv-flat/app.py delete mode 100644 10-pipenv-multi-bandit/Pipfile delete mode 100644 10-pipenv-multi-bandit/README.md delete mode 100644 10-pipenv-multi-bandit/scripts/deploy.py delete mode 100644 10-pipenv-multi-bandit/src/handler.py delete mode 100644 11-requirements-root/README.md delete mode 100644 11-requirements-root/app.py delete mode 100644 11-requirements-root/requirements.txt delete mode 100644 12-uv-flat-bandit-only/README.md delete mode 100644 12-uv-flat-bandit-only/app.py delete mode 100644 12-uv-flat-bandit-only/pyproject.toml delete mode 100644 13-requirements-unfixable/README.md delete mode 100644 13-requirements-unfixable/app.py delete mode 100644 13-requirements-unfixable/requirements.txt delete mode 100644 14-uv-low-threshold/README.md delete mode 100644 14-uv-low-threshold/app.py delete mode 100644 14-uv-low-threshold/pyproject.toml diff --git a/.github/scripts/validate_results.py b/.github/scripts/validate_results.py index 634268a..61f2709 100644 --- a/.github/scripts/validate_results.py +++ b/.github/scripts/validate_results.py @@ -1,4 +1,4 @@ -"""Validate that all 14 test workflows produced the expected outcomes. +"""Validate that all 3 smoke-test workflows produced the expected outcomes. Reads job conclusions from the NEEDS_JSON env var (set by integration-tests.yml via ``${{ toJSON(needs) }}``) and parses @@ -22,7 +22,7 @@ # Configuration # --------------------------------------------------------------------------- -EXPECTED_COUNT = 14 +EXPECTED_COUNT = 3 ARTIFACTS_DIR = Path("artifacts") EXPECTED_RESULTS_PATH = Path("expected_results.yml") diff --git a/.github/workflows/02-requirements-src-bandit.yml b/.github/workflows/02-requirements-src-bandit.yml deleted file mode 100644 index 96619f5..0000000 --- a/.github/workflows/02-requirements-src-bandit.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: "02 · requirements · src/ · bandit HIGH" -# Expected: FAIL — B602 (shell=True, HIGH) and B105 (hardcoded pw, MEDIUM) in src/ - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - working_directory: 02-requirements-src-bandit - package_manager: requirements - requirements_file: requirements.txt - bandit_scan_dirs: src/ - bandit_severity_threshold: high - pip_audit_block_on: fixable - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-02 diff --git a/.github/workflows/04-uv-flat.yml b/.github/workflows/04-uv-flat.yml deleted file mode 100644 index c42e245..0000000 --- a/.github/workflows/04-uv-flat.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: "04 · uv · flat · clean" -# Expected: PASS — no bandit issues, no vulnerable deps - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - working_directory: 04-uv-flat - package_manager: uv - bandit_scan_dirs: . - bandit_severity_threshold: high - pip_audit_block_on: fixable - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-04 diff --git a/.github/workflows/05-uv-src-vuln.yml b/.github/workflows/05-uv-src-vuln.yml deleted file mode 100644 index 40836b4..0000000 --- a/.github/workflows/05-uv-src-vuln.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: "05 · uv · src/ · pip-audit vuln" -# Expected: FAIL — pip-audit finds fixable CVE in requests==2.25.0 - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - working_directory: 05-uv-src-vuln - package_manager: uv - bandit_scan_dirs: src/ - bandit_severity_threshold: high - pip_audit_block_on: fixable - debug: 'true' - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-05 diff --git a/.github/workflows/06-uv-multi-bandit.yml b/.github/workflows/06-uv-multi-bandit.yml deleted file mode 100644 index 28a7b6c..0000000 --- a/.github/workflows/06-uv-multi-bandit.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: "06 · uv · src/+scripts/ · bandit MEDIUM" -# Expected: FAIL — B506 (yaml.load, MEDIUM) in src/, B303 (MD5, MEDIUM) in scripts/ - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - working_directory: 06-uv-multi-bandit - package_manager: uv - bandit_scan_dirs: src/,scripts/ - bandit_severity_threshold: medium - pip_audit_block_on: none - debug: 'true' - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-06 diff --git a/.github/workflows/07-poetry-flat.yml b/.github/workflows/07-poetry-flat.yml deleted file mode 100644 index 5e06f94..0000000 --- a/.github/workflows/07-poetry-flat.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: "07 · poetry · flat · clean" -# Expected: PASS — no bandit issues, no vulnerable deps - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Install Poetry - uses: snok/install-poetry@972a0e78ffdebf9e98f6fe404b77831716cdd4aa # v1.4.0 - with: - version: "1.8.5" - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - working_directory: 07-poetry-flat - package_manager: poetry - bandit_scan_dirs: . - bandit_severity_threshold: high - pip_audit_block_on: fixable - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-07 diff --git a/.github/workflows/09-pipenv-flat.yml b/.github/workflows/09-pipenv-flat.yml deleted file mode 100644 index b1b8999..0000000 --- a/.github/workflows/09-pipenv-flat.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: "09 · pipenv · flat · clean" -# Expected: PASS — no bandit issues, no vulnerable deps - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v5 - with: - python-version: '3.12' - - - name: Install pipenv - run: pip install pipenv==2024.4.0 - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - working_directory: 09-pipenv-flat - package_manager: pipenv - bandit_scan_dirs: . - bandit_severity_threshold: high - pip_audit_block_on: fixable - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-09 diff --git a/.github/workflows/10-pipenv-multi-bandit.yml b/.github/workflows/10-pipenv-multi-bandit.yml deleted file mode 100644 index a5b5bee..0000000 --- a/.github/workflows/10-pipenv-multi-bandit.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: "10 · pipenv · src/+scripts/ · bandit HIGH" -# Expected: FAIL — B602 (shell=True, HIGH) in both src/ and scripts/ - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - working_directory: 10-pipenv-multi-bandit - package_manager: pipenv - bandit_scan_dirs: src/,scripts/ - bandit_severity_threshold: high - pip_audit_block_on: fixable - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-10 diff --git a/.github/workflows/11-requirements-root.yml b/.github/workflows/11-requirements-root.yml deleted file mode 100644 index 0a7d78a..0000000 --- a/.github/workflows/11-requirements-root.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: "11 · requirements · flat · clean (root working dir)" -# Expected: PASS — no bandit issues, no vulnerable deps, default working_directory - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - package_manager: requirements - requirements_file: 11-requirements-root/requirements.txt - bandit_scan_dirs: 11-requirements-root - bandit_severity_threshold: high - pip_audit_block_on: fixable - debug: 'true' - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-11 diff --git a/.github/workflows/12-uv-flat-bandit-only.yml b/.github/workflows/12-uv-flat-bandit-only.yml deleted file mode 100644 index 0e97767..0000000 --- a/.github/workflows/12-uv-flat-bandit-only.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: "12 · uv · flat · bandit-only (no pip-audit)" -# Expected: FAIL — B602 subprocess call with shell=True - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - working_directory: 12-uv-flat-bandit-only - tools: bandit - bandit_scan_dirs: . - bandit_severity_threshold: high - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-12 diff --git a/.github/workflows/13-requirements-unfixable.yml b/.github/workflows/13-requirements-unfixable.yml deleted file mode 100644 index 4017374..0000000 --- a/.github/workflows/13-requirements-unfixable.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: "13 · requirements · flat · unfixable vulns (should pass)" -# Expected: PASS — vulns exist but are unfixable, block_on=fixable - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - working_directory: 13-requirements-unfixable - package_manager: requirements - requirements_file: requirements.txt - bandit_scan_dirs: . - bandit_severity_threshold: high - pip_audit_block_on: fixable - debug: 'true' - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-13 diff --git a/.github/workflows/14-uv-low-threshold.yml b/.github/workflows/14-uv-low-threshold.yml deleted file mode 100644 index 0cd6bc4..0000000 --- a/.github/workflows/14-uv-low-threshold.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: "14 · uv · flat · low threshold (B101 assert)" -# Expected: FAIL — B101 assert usage blocked at low threshold - -on: - workflow_call: - -jobs: - security-audit: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - persist-credentials: false - - - name: Run security audit - uses: lhoupert/action-python-security-auditing@1791bce99627a60fd1a0985c0e8a6169ffc19005 # fix--add-additional-tests-pip-audit - with: - working_directory: 14-uv-low-threshold - tools: bandit - bandit_scan_dirs: . - bandit_severity_threshold: low - post_pr_comment: ${{ github.event_name == 'pull_request' }} - artifact_name: security-audit-14 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index e5ec701..24c6dbd 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -15,109 +15,32 @@ concurrency: jobs: - # ── Test workflows (reusable) ────────────────────────────────── + # ── Smoke-test workflows (reusable) ─────────────────────────── test-01: uses: ./.github/workflows/01-requirements-flat.yml permissions: contents: read pull-requests: write - test-02: - uses: ./.github/workflows/02-requirements-src-bandit.yml - permissions: - contents: read - pull-requests: write - test-03: uses: ./.github/workflows/03-requirements-multi-both.yml permissions: contents: read pull-requests: write - test-04: - uses: ./.github/workflows/04-uv-flat.yml - permissions: - contents: read - pull-requests: write - - test-05: - uses: ./.github/workflows/05-uv-src-vuln.yml - permissions: - contents: read - pull-requests: write - - test-06: - uses: ./.github/workflows/06-uv-multi-bandit.yml - permissions: - contents: read - pull-requests: write - - test-07: - uses: ./.github/workflows/07-poetry-flat.yml - permissions: - contents: read - pull-requests: write - test-08: uses: ./.github/workflows/08-poetry-src-both.yml permissions: contents: read pull-requests: write - test-09: - uses: ./.github/workflows/09-pipenv-flat.yml - permissions: - contents: read - pull-requests: write - - test-10: - uses: ./.github/workflows/10-pipenv-multi-bandit.yml - permissions: - contents: read - pull-requests: write - - test-11: - uses: ./.github/workflows/11-requirements-root.yml - permissions: - contents: read - pull-requests: write - - test-12: - uses: ./.github/workflows/12-uv-flat-bandit-only.yml - permissions: - contents: read - pull-requests: write - - test-13: - uses: ./.github/workflows/13-requirements-unfixable.yml - permissions: - contents: read - pull-requests: write - - test-14: - uses: ./.github/workflows/14-uv-low-threshold.yml - permissions: - contents: read - pull-requests: write - # ── Validate results (only on PRs, after all tests) ──────────── validate: if: always() && github.event_name == 'pull_request' needs: - test-01 - - test-02 - test-03 - - test-04 - - test-05 - - test-06 - - test-07 - test-08 - - test-09 - - test-10 - - test-11 - - test-12 - - test-13 - - test-14 runs-on: ubuntu-latest permissions: pull-requests: write @@ -126,7 +49,7 @@ jobs: with: persist-credentials: false - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: pattern: security-audit-* path: artifacts diff --git a/02-requirements-src-bandit/README.md b/02-requirements-src-bandit/README.md deleted file mode 100644 index 9beec43..0000000 --- a/02-requirements-src-bandit/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# 02 · requirements · src/ layout · bandit HIGH - -**Package manager**: `requirements` -**Source layout**: `src/` subdirectory -**Expected outcome**: FAIL (bandit) - -## What this tests - -- Bandit correctly scans `src/` subdirectory only -- B602 (`subprocess.call(shell=True)`) is detected as HIGH severity -- B105 (hardcoded password) is detected as MEDIUM severity -- Workflow fails because B602 meets the HIGH threshold - -## Intentional issues - -| File | Issue | Severity | -|------|-------|---------| -| `src/app.py` | B602: `subprocess.call(cmd, shell=True)` | HIGH | -| `src/app.py` | B105: hardcoded password string | MEDIUM | diff --git a/02-requirements-src-bandit/requirements.txt b/02-requirements-src-bandit/requirements.txt deleted file mode 100644 index 9324823..0000000 --- a/02-requirements-src-bandit/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -httpx==0.27.0 -click==8.1.7 diff --git a/02-requirements-src-bandit/src/app.py b/02-requirements-src-bandit/src/app.py deleted file mode 100644 index ece2f07..0000000 --- a/02-requirements-src-bandit/src/app.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Command runner — intentionally insecure for bandit testing.""" -import subprocess - - -def run_command(cmd: str) -> int: - """Run a shell command.""" - # B602: subprocess call with shell=True — HIGH severity - return subprocess.call(cmd, shell=True) # noqa: S602 - - -def get_config() -> dict: - """Return config dict.""" - # B105: hardcoded password string — MEDIUM severity - password = "supersecret123" # noqa: S105 - return {"password": password} diff --git a/04-uv-flat/README.md b/04-uv-flat/README.md deleted file mode 100644 index a104b82..0000000 --- a/04-uv-flat/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 04 · uv · flat layout · clean - -**Package manager**: `uv` -**Source layout**: flat (Python files at project root) -**Expected outcome**: PASS - -## What this tests - -- Bandit scans the flat project directory -- No bandit issues, no vulnerable deps -- `uv.lock` is **not committed** — tests the action's behaviour when no lockfile is present diff --git a/04-uv-flat/app.py b/04-uv-flat/app.py deleted file mode 100644 index b0e83d8..0000000 --- a/04-uv-flat/app.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Simple app — clean code for uv/flat security audit testing.""" -import httpx -from rich.console import Console - -console = Console() - - -def fetch(url: str) -> None: - """Fetch a URL and display the response status.""" - with httpx.Client() as client: - response = client.get(url) - console.print(f"[green]Status:[/green] {response.status_code}") diff --git a/04-uv-flat/pyproject.toml b/04-uv-flat/pyproject.toml deleted file mode 100644 index d73c075..0000000 --- a/04-uv-flat/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -[project] -name = "uv-flat-clean" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "httpx>=0.27.0", - "rich>=13.9.0", -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" diff --git a/05-uv-src-vuln/README.md b/05-uv-src-vuln/README.md deleted file mode 100644 index 1b4ee25..0000000 --- a/05-uv-src-vuln/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# 05 · uv · src/ layout · pip-audit vuln - -**Package manager**: `uv` -**Source layout**: `src/` subdirectory -**Expected outcome**: FAIL (pip-audit) - -## What this tests - -- `uv export` resolves and exports `requests==2.25.0` from `uv.lock` -- pip-audit detects the fixable CVE-2023-32681 in `requests==2.25.0` -- Bandit finds no issues in clean code -- Workflow fails due to pip-audit finding a fixable vulnerability -- `uv.lock` is **committed** — required so pip-audit reliably resolves the pinned vulnerable version - -## Intentional issues - -| Dependency | Version | CVE | -|-----------|---------|-----| -| `requests` | `2.25.0` | CVE-2023-32681 (fixable — upgrade to ≥2.31.0) | diff --git a/05-uv-src-vuln/pyproject.toml b/05-uv-src-vuln/pyproject.toml deleted file mode 100644 index 95a31eb..0000000 --- a/05-uv-src-vuln/pyproject.toml +++ /dev/null @@ -1,15 +0,0 @@ -[project] -name = "uv-src-vuln" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "requests==2.25.0", - "click>=8.1.7", -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.wheel] -packages = ["src/uv_src_vuln"] diff --git a/05-uv-src-vuln/src/fetcher.py b/05-uv-src-vuln/src/fetcher.py deleted file mode 100644 index 239f8b7..0000000 --- a/05-uv-src-vuln/src/fetcher.py +++ /dev/null @@ -1,9 +0,0 @@ -"""HTTP fetcher — clean code, but depends on vulnerable requests version.""" -import requests - - -def get_resource(url: str) -> bytes: - """Fetch a remote resource and return its content.""" - response = requests.get(url, timeout=30) - response.raise_for_status() - return response.content diff --git a/05-uv-src-vuln/src/uv_src_vuln/__init__.py b/05-uv-src-vuln/src/uv_src_vuln/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/05-uv-src-vuln/uv.lock b/05-uv-src-vuln/uv.lock deleted file mode 100644 index b298e40..0000000 --- a/05-uv-src-vuln/uv.lock +++ /dev/null @@ -1,90 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.11" - -[[package]] -name = "certifi" -version = "2026.2.25" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, -] - -[[package]] -name = "chardet" -version = "3.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fc/bb/a5768c230f9ddb03acc9ef3f0d4a3cf93462473795d18e9535498c8f929d/chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", size = 1868453, upload-time = "2017-06-08T14:34:35.581Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", size = 133356, upload-time = "2017-06-08T14:34:33.552Z" }, -] - -[[package]] -name = "click" -version = "8.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, -] - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, -] - -[[package]] -name = "idna" -version = "2.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/b7/e0e3c1c467636186c39925827be42f16fee389dc404ac29e930e9136be70/idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", size = 175616, upload-time = "2020-06-27T23:45:05.21Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/38/928ddce2273eaa564f6f50de919327bf3a00f091b5baba8dfa9460f3a8a8/idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0", size = 58811, upload-time = "2020-06-27T23:45:03.457Z" }, -] - -[[package]] -name = "requests" -version = "2.25.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "chardet" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/14/4a6542a078773957aa83101336375c9597e6fe5889d20abda9c38f9f3ff2/requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8", size = 101897, upload-time = "2020-11-11T20:05:17.193Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/fc/f91eac5a39a65f75a7adb58eac7fa78871ea9872283fb9c44e6545998134/requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998", size = 61132, upload-time = "2020-11-11T20:05:15.87Z" }, -] - -[[package]] -name = "urllib3" -version = "1.26.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/e8/6ff5e6bc22095cfc59b6ea711b687e2b7ed4bdb373f7eeec370a97d7392f/urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32", size = 307380, upload-time = "2024-08-29T15:43:11.37Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/cf/8435d5a7159e2a9c83a95896ed596f68cf798005fe107cc655b5c5c14704/urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e", size = 144225, upload-time = "2024-08-29T15:43:08.921Z" }, -] - -[[package]] -name = "uv-src-vuln" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "click" }, - { name = "requests" }, -] - -[package.metadata] -requires-dist = [ - { name = "click", specifier = ">=8.1.7" }, - { name = "requests", specifier = "==2.25.0" }, -] diff --git a/06-uv-multi-bandit/README.md b/06-uv-multi-bandit/README.md deleted file mode 100644 index dd0f520..0000000 --- a/06-uv-multi-bandit/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# 06 · uv · src/+scripts/ · bandit MEDIUM - -**Package manager**: `uv` -**Source layout**: `src/` + `scripts/` directories -**Expected outcome**: FAIL (bandit) - -## What this tests - -- Bandit scans two directories via `bandit_scan_dirs: src/,scripts/` -- MEDIUM severity threshold (lower than default HIGH) catches B324 and B506 -- `uv export` produces clean deps — no pip-audit failures -- `pip_audit_block_on: none` ensures only bandit can fail this job -- `uv.lock` is committed — tests the action with a pre-existing lockfile - -## Intentional issues - -| File | Issue | Severity | -|------|-------|----------| -| `src/parser.py` | B506: `yaml.load()` without Loader | MEDIUM | -| `scripts/digest.py` | B324: `hashlib.md5()` | MEDIUM | diff --git a/06-uv-multi-bandit/pyproject.toml b/06-uv-multi-bandit/pyproject.toml deleted file mode 100644 index 8c46392..0000000 --- a/06-uv-multi-bandit/pyproject.toml +++ /dev/null @@ -1,12 +0,0 @@ -[project] -name = "uv-multi-bandit" -version = "0.1.0" -requires-python = ">=3.11" -dependencies = [ - "pyyaml>=6.0.2", - "httpx>=0.27.0", -] - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" diff --git a/06-uv-multi-bandit/scripts/digest.py b/06-uv-multi-bandit/scripts/digest.py deleted file mode 100644 index ef5fd54..0000000 --- a/06-uv-multi-bandit/scripts/digest.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Digest utility — intentionally insecure for bandit testing.""" -import hashlib - - -def compute_hash(data: bytes) -> str: - """Compute a digest of the given data.""" - # B303: use of MD5 — MEDIUM severity - return hashlib.md5(data).hexdigest() # noqa: S324 diff --git a/06-uv-multi-bandit/src/parser.py b/06-uv-multi-bandit/src/parser.py deleted file mode 100644 index bac5f02..0000000 --- a/06-uv-multi-bandit/src/parser.py +++ /dev/null @@ -1,8 +0,0 @@ -"""YAML parser — intentionally insecure for bandit testing.""" -import yaml - - -def parse_config(data: str) -> dict: - """Parse YAML config string.""" - # B506: yaml.load without Loader — MEDIUM severity - return yaml.load(data) # noqa: S506 diff --git a/06-uv-multi-bandit/uv.lock b/06-uv-multi-bandit/uv.lock deleted file mode 100644 index 626775a..0000000 --- a/06-uv-multi-bandit/uv.lock +++ /dev/null @@ -1,150 +0,0 @@ -version = 1 -revision = 3 -requires-python = ">=3.11" - -[[package]] -name = "anyio" -version = "4.13.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "idna" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, -] - -[[package]] -name = "certifi" -version = "2026.2.25" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, -] - -[[package]] -name = "h11" -version = "0.16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, -] - -[[package]] -name = "httpcore" -version = "1.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "h11" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, -] - -[[package]] -name = "httpx" -version = "0.28.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "certifi" }, - { name = "httpcore" }, - { name = "idna" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, -] - -[[package]] -name = "idna" -version = "3.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - -[[package]] -name = "uv-multi-bandit" -version = "0.1.0" -source = { editable = "." } -dependencies = [ - { name = "httpx" }, - { name = "pyyaml" }, -] - -[package.metadata] -requires-dist = [ - { name = "httpx", specifier = ">=0.27.0" }, - { name = "pyyaml", specifier = ">=6.0.2" }, -] diff --git a/07-poetry-flat/README.md b/07-poetry-flat/README.md deleted file mode 100644 index 37049a2..0000000 --- a/07-poetry-flat/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 07 · poetry · flat layout · clean - -**Package manager**: `poetry` -**Source layout**: flat (Python files at project root) -**Expected outcome**: PASS - -## What this tests - -- Bandit scans the flat project directory -- No bandit issues, no vulnerable deps -- `poetry.lock` is **not committed** — tests the action's behaviour when no lockfile is present diff --git a/07-poetry-flat/app.py b/07-poetry-flat/app.py deleted file mode 100644 index df69d2d..0000000 --- a/07-poetry-flat/app.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Simple CLI app — clean code for poetry/flat security audit testing.""" -import click -import httpx - -@click.command() -@click.option("--url", default="https://example.com", help="URL to fetch.") -def fetch(url: str) -> None: - """Fetch a URL and print the HTTP status code.""" - with httpx.Client() as client: - response = client.get(url) - click.echo(f"Status: {response.status_code}") - - -if __name__ == "__main__": - fetch() diff --git a/07-poetry-flat/pyproject.toml b/07-poetry-flat/pyproject.toml deleted file mode 100644 index 03f1cf9..0000000 --- a/07-poetry-flat/pyproject.toml +++ /dev/null @@ -1,14 +0,0 @@ -[tool.poetry] -name = "poetry-flat-clean" -version = "0.1.0" -description = "Clean poetry project for security audit testing" -authors = ["Test "] - -[tool.poetry.dependencies] -python = ">=3.11" -httpx = ">=0.27.0" -click = ">=8.1.7" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/09-pipenv-flat/Pipfile b/09-pipenv-flat/Pipfile deleted file mode 100644 index cc76a60..0000000 --- a/09-pipenv-flat/Pipfile +++ /dev/null @@ -1,11 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -httpx = ">=0.27.0" -click = ">=8.1.7" - -[requires] -python_version = "3.12" diff --git a/09-pipenv-flat/Pipfile.lock b/09-pipenv-flat/Pipfile.lock deleted file mode 100644 index cbb4ce3..0000000 --- a/09-pipenv-flat/Pipfile.lock +++ /dev/null @@ -1,87 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "72739c09fdb86cb470d4a0a6619c85ab1f3deaf06dca30566ff4da6d8a9bd43a" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.12" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "anyio": { - "hashes": [ - "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", - "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc" - ], - "markers": "python_version >= '3.10'", - "version": "==4.13.0" - }, - "certifi": { - "hashes": [ - "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", - "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7" - ], - "markers": "python_version >= '3.7'", - "version": "==2026.2.25" - }, - "click": { - "hashes": [ - "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", - "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6" - ], - "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==8.3.1" - }, - "h11": { - "hashes": [ - "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", - "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86" - ], - "markers": "python_version >= '3.8'", - "version": "==0.16.0" - }, - "httpcore": { - "hashes": [ - "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", - "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8" - ], - "markers": "python_version >= '3.8'", - "version": "==1.0.9" - }, - "httpx": { - "hashes": [ - "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", - "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==0.28.1" - }, - "idna": { - "hashes": [ - "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", - "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" - ], - "markers": "python_version >= '3.8'", - "version": "==3.11" - }, - "typing-extensions": { - "hashes": [ - "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", - "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" - ], - "markers": "python_version >= '3.9'", - "version": "==4.15.0" - } - }, - "develop": {} -} diff --git a/09-pipenv-flat/README.md b/09-pipenv-flat/README.md deleted file mode 100644 index b0de8b4..0000000 --- a/09-pipenv-flat/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# 09 · pipenv · flat layout · clean - -**Package manager**: `pipenv` -**Source layout**: flat (Python files at project root) -**Expected outcome**: PASS - -## What this tests - -- `pipenv requirements` correctly reads `Pipfile.lock` and exports deps -- Bandit scans the flat project directory -- No bandit issues, no vulnerable deps -- `Pipfile.lock` is **committed** — tests the action with a pre-existing lockfile diff --git a/09-pipenv-flat/app.py b/09-pipenv-flat/app.py deleted file mode 100644 index 7f48be7..0000000 --- a/09-pipenv-flat/app.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Simple app — clean code for pipenv/flat security audit testing.""" -import httpx -import click - - -@click.command() -@click.argument("url") -def ping(url: str) -> None: - """Ping a URL and print the status code.""" - with httpx.Client() as client: - response = client.get(url) - click.echo(f"Status: {response.status_code}") - - -if __name__ == "__main__": - ping() diff --git a/10-pipenv-multi-bandit/Pipfile b/10-pipenv-multi-bandit/Pipfile deleted file mode 100644 index 2a416c9..0000000 --- a/10-pipenv-multi-bandit/Pipfile +++ /dev/null @@ -1,11 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -click = ">=8.1.7" -rich = ">=13.9.0" - -[requires] -python_version = "3.12" diff --git a/10-pipenv-multi-bandit/README.md b/10-pipenv-multi-bandit/README.md deleted file mode 100644 index 0912a7b..0000000 --- a/10-pipenv-multi-bandit/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# 10 · pipenv · src/+scripts/ · bandit HIGH - -**Package manager**: `pipenv` -**Source layout**: `src/` + `scripts/` directories -**Expected outcome**: FAIL (bandit) - -## What this tests - -- Bandit scans two directories via `bandit_scan_dirs: src/,scripts/` -- B602 (`subprocess.call(shell=True)`) in both directories triggers HIGH threshold -- Clean deps ensure only bandit fails the job -- `Pipfile.lock` is **not committed** — tests the action's behaviour when no lockfile is present - -## Intentional issues - -| File | Issue | Severity | -|------|-------|---------| -| `src/handler.py` | B602: `subprocess.call(shell=True)` | HIGH | -| `scripts/deploy.py` | B602: `subprocess.call(shell=True)` | HIGH | diff --git a/10-pipenv-multi-bandit/scripts/deploy.py b/10-pipenv-multi-bandit/scripts/deploy.py deleted file mode 100644 index f3653ef..0000000 --- a/10-pipenv-multi-bandit/scripts/deploy.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Deploy script — intentionally insecure for bandit testing.""" -import subprocess - - -def deploy(target: str) -> None: - """Deploy to the given target.""" - # B602: subprocess with shell=True — HIGH severity - subprocess.call(f"deploy.sh {target}", shell=True) # noqa: S602 diff --git a/10-pipenv-multi-bandit/src/handler.py b/10-pipenv-multi-bandit/src/handler.py deleted file mode 100644 index fda4d8e..0000000 --- a/10-pipenv-multi-bandit/src/handler.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Request handler — intentionally insecure for bandit testing.""" -import subprocess - - -def handle_request(cmd: str) -> int: - """Execute a request via shell command.""" - # B602: subprocess with shell=True — HIGH severity - return subprocess.call(cmd, shell=True) # noqa: S602 diff --git a/11-requirements-root/README.md b/11-requirements-root/README.md deleted file mode 100644 index 9f6a1fa..0000000 --- a/11-requirements-root/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# 11 · requirements · flat · clean (root working dir) - -**Package manager**: `requirements` -**Source layout**: flat -**Expected outcome**: PASS — no bandit issues, no vulnerable dependencies - -## What this tests -- Default `working_directory` (`.`) — omitted from action inputs -- `bandit_scan_dirs` pointed at a specific clean folder -- Validates the passthrough path when no working_directory prefix is needed - -## Action settings - -| Setting | Value | -|---------|-------| -| `package_manager` | requirements | -| `requirements_file` | 11-requirements-root/requirements.txt | -| `bandit_scan_dirs` | 11-requirements-root | -| `bandit_severity_threshold` | high | -| `pip_audit_block_on` | fixable | -| `working_directory` | *(default: `.`)* | diff --git a/11-requirements-root/app.py b/11-requirements-root/app.py deleted file mode 100644 index 4ee1ea8..0000000 --- a/11-requirements-root/app.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Clean app — no security issues.""" -from flask import Flask - -app = Flask(__name__) - -@app.route("/") -def hello(): - return "Hello, World!" diff --git a/11-requirements-root/requirements.txt b/11-requirements-root/requirements.txt deleted file mode 100644 index e6365da..0000000 --- a/11-requirements-root/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -flask==3.1.3 diff --git a/12-uv-flat-bandit-only/README.md b/12-uv-flat-bandit-only/README.md deleted file mode 100644 index 4580fe7..0000000 --- a/12-uv-flat-bandit-only/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# 12 · uv · flat · bandit-only (no pip-audit) - -**Package manager**: `uv` -**Source layout**: flat -**Expected outcome**: FAIL — bandit finds B602 (subprocess with shell=True) - -## What this tests -- `tools: bandit` — pip-audit fully disabled, not just `block_on: none` -- Validates that bandit-only mode works correctly - -## Intentional issues - -| File | Issue | Severity | -|------|-------|----------| -| app.py | subprocess call with shell=True (B602) | HIGH | diff --git a/12-uv-flat-bandit-only/app.py b/12-uv-flat-bandit-only/app.py deleted file mode 100644 index aaec571..0000000 --- a/12-uv-flat-bandit-only/app.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Intentional B602 — subprocess call with shell=True.""" -import subprocess - -def run_command(user_input: str) -> str: - result = subprocess.run(user_input, shell=True, capture_output=True, text=True) - return result.stdout diff --git a/12-uv-flat-bandit-only/pyproject.toml b/12-uv-flat-bandit-only/pyproject.toml deleted file mode 100644 index fcca285..0000000 --- a/12-uv-flat-bandit-only/pyproject.toml +++ /dev/null @@ -1,5 +0,0 @@ -[project] -name = "test-12-bandit-only" -version = "0.1.0" -requires-python = ">=3.13" -dependencies = ["click>=8.1.7"] diff --git a/13-requirements-unfixable/README.md b/13-requirements-unfixable/README.md deleted file mode 100644 index bf592e6..0000000 --- a/13-requirements-unfixable/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# 13 · requirements · flat · unfixable vulns (should pass) - -**Package manager**: `requirements` -**Source layout**: flat -**Expected outcome**: PASS — vulnerabilities exist but have no fix versions; `pip_audit_block_on: fixable` should not block - -## What this tests -- `pip_audit_block_on: fixable` only blocks when fix versions are available -- Unfixable vulnerabilities are reported but don't fail the workflow - -## Intentional issues - -| Package | Version | CVE | Fix Available | -|---------|---------|-----|---------------| -| pygments | 2.19.2 | GHSA-5239-wwwm-4pmq (CVE-2026-4539) | No — Patched versions: None | - -ReDoS in `AdlLexer` (archetype.py) via inefficient GUID regex. Low severity. Affects all -versions `<= 2.19.2`. No patched release as of March 2026. diff --git a/13-requirements-unfixable/app.py b/13-requirements-unfixable/app.py deleted file mode 100644 index db51439..0000000 --- a/13-requirements-unfixable/app.py +++ /dev/null @@ -1,4 +0,0 @@ -"""Clean app — no bandit issues.""" - -def process(data: str) -> str: - return data.upper() diff --git a/13-requirements-unfixable/requirements.txt b/13-requirements-unfixable/requirements.txt deleted file mode 100644 index 3b09dd7..0000000 --- a/13-requirements-unfixable/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -pygments==2.19.2 diff --git a/14-uv-low-threshold/README.md b/14-uv-low-threshold/README.md deleted file mode 100644 index 75f4b26..0000000 --- a/14-uv-low-threshold/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# 14 · uv · flat · low threshold (B101 assert) - -**Package manager**: `uv` -**Source layout**: flat -**Expected outcome**: FAIL — bandit B101 (assert usage) blocked at low severity threshold - -## What this tests -- `bandit_severity_threshold: low` — blocks on LOW severity findings -- Completes threshold coverage (high, medium, low) - -## Intentional issues - -| File | Issue | Severity | -|------|-------|----------| -| app.py | Use of assert detected (B101) | LOW | diff --git a/14-uv-low-threshold/app.py b/14-uv-low-threshold/app.py deleted file mode 100644 index a15f4b0..0000000 --- a/14-uv-low-threshold/app.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Intentional B101 — use of assert in non-test code.""" - -def validate(value: int) -> int: - assert value > 0, "Value must be positive" - return value * 2 diff --git a/14-uv-low-threshold/pyproject.toml b/14-uv-low-threshold/pyproject.toml deleted file mode 100644 index e7747d0..0000000 --- a/14-uv-low-threshold/pyproject.toml +++ /dev/null @@ -1,5 +0,0 @@ -[project] -name = "test-14-low-threshold" -version = "0.1.0" -requires-python = ">=3.13" -dependencies = ["click>=8.1.7"] diff --git a/README.md b/README.md index 4886afc..77a62b0 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,21 @@ -# action-python-security-auditing — Test Projects +# action-python-security-auditing — Smoke Tests -Dummy Python projects for testing the reliability of [action-python-security-auditing](https://github.com/developmentseed/action-python-security-auditing) across different package managers and source layouts. +Post-release smoke tests for [action-python-security-auditing](https://github.com/lhoupert/action-python-security-auditing). These 3 scenarios run against the published `@v1` tag after each release to confirm end-to-end behavior is intact. -## Test Matrix +The full integration test suite (14 scenarios) lives in the action repo itself under `integration-tests/` and runs on every PR there. + +## Smoke Test Scenarios | # | Project | Package manager | Source layout | Expected outcome | |---|---------|----------------|---------------|-----------------| | 01 | `01-requirements-flat` | requirements | flat | PASS — no issues | -| 02 | `02-requirements-src-bandit` | requirements | src/ | FAIL — bandit HIGH (B602, B105) | | 03 | `03-requirements-multi-both` | requirements | src/ + scripts/ | FAIL — bandit HIGH + pip-audit | -| 04 | `04-uv-flat` | uv | flat | PASS — no issues | -| 05 | `05-uv-src-vuln` | uv | src/ | FAIL — pip-audit (fixable CVEs) | -| 06 | `06-uv-multi-bandit` | uv | src/ + scripts/ | FAIL — bandit MEDIUM (B324, B506) | -| 07 | `07-poetry-flat` | poetry | flat | PASS — no issues | | 08 | `08-poetry-src-both` | poetry | src/ | FAIL — bandit MEDIUM + pip-audit | -| 09 | `09-pipenv-flat` | pipenv | flat | PASS — no issues | -| 10 | `10-pipenv-multi-bandit` | pipenv | src/ + scripts/ | FAIL — bandit HIGH (B602) | -| 11 | `11-requirements-root` | requirements | flat | PASS — no issues (default working dir `.`) | -| 12 | `12-uv-flat-bandit-only` | uv | flat | FAIL — bandit HIGH (B602), pip-audit disabled | -| 13 | `13-requirements-unfixable` | requirements | flat | PASS — unfixable vulns present, `pip_audit_block_on: fixable` does not block | -| 14 | `14-uv-low-threshold` | uv | flat | FAIL — bandit LOW (B101), pip-audit disabled | -## How it works +These three cover: happy path, both tools firing together, and a lockfile-based package manager (poetry). -Each project has a corresponding workflow file in `.github/workflows/`. Workflows call the action with `working_directory` set to the project subdirectory, so all package manager commands and bandit paths resolve correctly relative to the project root. +## How it works -For uv, poetry, and pipenv projects, lockfiles are **selectively committed** to test both code paths: -- **With lockfile**: tests 05 (`uv.lock`), 06 (`uv.lock`), 08 (`poetry.lock`), 09 (`Pipfile.lock`) — required for reliable pip-audit detection of pinned vulnerable versions, or to verify the action handles pre-existing lockfiles correctly. -- **Without lockfile**: tests 04 (uv), 07 (poetry), 10 (pipenv) — tests the action's behaviour when no lockfile is present in the repository. Bandit-only tests 12 and 14 never need a lockfile. +Each scenario has a workflow file in `.github/workflows/` that calls the action with `working_directory` set to the project subdirectory. The `integration-tests.yml` orchestrator runs all 3 and the `validate` job checks outcomes against `expected_results.yml`. -Requirements-based tests (01, 02, 03, 11, 13) always have `requirements.txt` committed — no lockfile concept applies. +Test 08 (poetry) requires a `poetry.lock` committed in the directory for reliable pip-audit detection of pinned vulnerable versions. diff --git a/expected_results.yml b/expected_results.yml index c40c70f..4e0f7b5 100644 --- a/expected_results.yml +++ b/expected_results.yml @@ -12,18 +12,6 @@ tests: bandit_findings: [] pip_audit_findings: [] - "02": - name: "requirements · src/ · bandit HIGH" - expected_conclusion: failure - bandit_findings: - - rule_id: B602 - level: error - - rule_id: B105 - level: warning - - rule_id: B404 - level: note - pip_audit_findings: [] - "03": name: "requirements · src/+scripts/ · bandit HIGH + pip-audit" expected_conclusion: failure @@ -40,36 +28,6 @@ tests: - package: cryptography has_fix: true - "04": - name: "uv · flat · clean" - expected_conclusion: success - bandit_findings: [] - pip_audit_findings: [] - - "05": - name: "uv · src/ · pip-audit vuln" - expected_conclusion: failure - bandit_findings: [] - pip_audit_findings: - - package: requests - has_fix: true - - "06": - name: "uv · src/+scripts/ · bandit MEDIUM" - expected_conclusion: failure - bandit_findings: - - rule_id: B506 - level: warning - - rule_id: B324 - level: warning - pip_audit_findings: [] - - "07": - name: "poetry · flat · clean" - expected_conclusion: success - bandit_findings: [] - pip_audit_findings: [] - "08": name: "poetry · src/ · bandit MEDIUM + pip-audit" expected_conclusion: failure @@ -83,53 +41,3 @@ tests: has_fix: true - package: requests has_fix: true - - "09": - name: "pipenv · flat · clean" - expected_conclusion: success - bandit_findings: [] - pip_audit_findings: [] - - "10": - name: "pipenv · src/+scripts/ · bandit HIGH" - expected_conclusion: failure - bandit_findings: - - rule_id: B602 - level: error - - rule_id: B404 - level: note - pip_audit_findings: [] - - "11": - name: "requirements · flat · clean (root working dir)" - expected_conclusion: success - bandit_findings: [] - pip_audit_findings: [] - - "12": - name: "uv · flat · bandit-only (no pip-audit)" - expected_conclusion: failure - pip_audit_disabled: true - bandit_findings: - - rule_id: B602 - level: error - - rule_id: B404 - level: note - pip_audit_findings: [] - - "13": - name: "requirements · flat · unfixable vulns (should pass)" - expected_conclusion: success - bandit_findings: [] - pip_audit_findings: - - package: pygments - has_fix: false - - "14": - name: "uv · flat · low threshold (B101 assert)" - expected_conclusion: failure - pip_audit_disabled: true - bandit_findings: - - rule_id: B101 - level: note - pip_audit_findings: []