## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## AI-Assisted

## Reusable workflow: bandit security-focused Python static analysis.
##
## Free open-source local-runnable alternative to the Python side
## of Coverity Scan. Complements pyflakes / pylint (those focus on
## correctness / style) by surfacing security-relevant patterns:
## hardcoded passwords, shell=True subprocess, weak crypto, eval(),
## etc.
##
## ## Trust footprint
##
## bandit is published by PyCQA and packaged in the Debian / Ubuntu
## archive; apt-installed by name. No GitHub Marketplace action
## used.
##
## ## Python discovery
##
## bandit's stock discovery is by '.py' extension. This org names
## many Python sources without a '.py' extension (Debian-libexec
## convention, genmkfile package-tag naming '*.py#tag'); a stock
## bandit invocation would silently miss them. The reusable
## therefore discovers Python files by `.py` extension PLUS first-
## line shebang `^#!.*python` via
## ci/bandit-discover-python.sh, and feeds the explicit file list
## to bandit. See agents/github-actions-consumer-templates.md
## "Parameter ownership across reusable inputs" for the broader
## bandit-as-universal-template rationale.
##
## ## No per-repo configuration
##
## bandit is a universal consumer template - no
## .github/dm-consumer.yml section needed. severity-level,
## confidence-level, and timeout-minutes are hardcoded here at
## the previous defaults (medium / medium / 10 min). Per-repo
## suppressions use bandit's `# nosec` inline comment, not a
## workflow-side `skips:` list. If a real use case for per-repo
## skip lists appears during migration, promote `bandit.skips`
## to dm-consumer.yml at that point (see spec parameter table).
##
## ## Caller responsibilities
##
## - Define the trigger schedule. Schedule cannot live in
##   workflow_call per G-A-002.
## - Hardcode the owner in 'jobs.<id>.uses:' (G-A-001).
##
## ## Caller example - byte-identical consumer wrapper
##
##   name: bandit
##   on:
##     push:
##       branches: [master]
##     pull_request:
##       branches: [master]
##     workflow_dispatch:
##   permissions:
##     contents: read
##   jobs:
##     bandit:
##       uses: org-ai-assisted/developer-meta-files/.github/workflows/reusable-bandit.yml@master
##       permissions:
##         contents: read
##
## Note: no 'with:' block on the reusable call. Per-repo state is
## not used.

name: bandit (reusable)

on:
  workflow_call:

permissions:
  contents: read

## No 'concurrency:' block - see agents/github-actions.md G-A-006.

jobs:
  bandit:
    name: bandit
    ## Pinned to ubuntu-24.04 for consistency with the other
    ## parameterized reusables.
    runs-on: ubuntu-24.04
    timeout-minutes: 10

    if: github.event.pull_request.head.repo.full_name == github.repository || github.event_name != 'pull_request'

    permissions:
      contents: read

    env:
      ## Dogfood ref: caller's commit when developer-meta-files is its
      ## own caller (so a PR that modifies the script under test gets
      ## exercised); 'master' for every other caller. Routed through
      ## env for the org-wide env-routing convention (same shape as
      ## JOB_STATUS / BASE_REF). The job-level fork-PR guard above is
      ## the actual mitigation for actions/untrusted-checkout.
      DMF_REF: ${{ github.repository == 'org-ai-assisted/developer-meta-files' && github.sha || 'master' }}

    steps:
      - name: Checkout repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
        with:
          persist-credentials: false

      - name: Install bandit
        uses: org-ai-assisted/developer-meta-files/.github/actions/apt-install-with-cache@master
        with:
          packages: 'bandit'
          cache-key: ${{ runner.os }}-apt-bandit-${{ hashFiles('.github/workflows/reusable-bandit.yml') }}

      - name: Checkout developer-meta-files (discovery + step-summary helpers)
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd  # v6.0.2
        with:
          repository: org-ai-assisted/developer-meta-files
          ref: ${{ env.DMF_REF }}
          path: .github/dmf
          persist-credentials: false

      - name: bandit
        ## Shebang-aware discovery via dmf helper. Feeds explicit
        ## NUL-separated file list to bandit via xargs -0.
        ##
        ## -ll  severity threshold >= medium
        ## -ii  confidence threshold >= medium
        ## --   end of options (defensive; file list may begin
        ##      with names like './-foo.py' that would otherwise
        ##      be parsed as options).
        ##
        ## --no-run-if-empty: bandit exits 0 with no files (the
        ## "no Python in this repo" case).
        run: |
          set -o errexit
          set -o nounset
          set -o pipefail
          .github/dmf/ci/bandit-discover-python.sh \
            | xargs -0 --no-run-if-empty -- bandit -ll -ii --

      - name: Emit step summary
        if: always()
        ## Route values through env and consume with shell-native
        ## expansion inside run: to avoid expression interpolation
        ## in shell arguments (CodeQL js/actions/command-injection).
        env:
          JOB_STATUS: ${{ job.status }}
        run: |
          .github/dmf/ci/step-summary-emit.sh \
            --tool 'bandit' \
            --column-header field \
            --row "outcome=${JOB_STATUS}" \
            --row "severity=medium" \
            --row "confidence=medium" \
            --row "discovery=.py extension + shebang scan"
