name: "CodeQL & Security Audit" on: push: branches: ["master", "main"] pull_request: branches: ["master", "main"] schedule: # Run every Monday at 08:00 UTC to catch newly disclosed CVEs - cron: "0 8 * * 1" jobs: # ── CodeQL analysis ────────────────────────────────────────────────────────── codeql: name: CodeQL runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: ["javascript-typescript"] # Rust is not yet supported by CodeQL; covered by cargo-audit below. steps: - name: Checkout repository uses: actions/checkout@v4 - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: security-extended,security-and-quality - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" # ── Rust security audit (cargo-audit) ──────────────────────────────────────── rust-audit: name: Rust Security Audit runs-on: ubuntu-latest permissions: contents: read security-events: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust stable uses: dtolnay/rust-toolchain@stable - name: Cache cargo registry uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ runner.os }}-cargo-audit-${{ hashFiles('**/Cargo.lock') }} - name: Install cargo-audit run: cargo install cargo-audit --locked - name: Run cargo audit # --deny warnings treats advisories as errors so the job fails on CVEs run: | if [ -f Cargo.toml ]; then cargo audit --deny warnings else echo "No Cargo.toml at repo root — skipping rust audit" fi # ── Shell script lint (ShellCheck) ─────────────────────────────────────────── shellcheck: name: ShellCheck runs-on: ubuntu-latest permissions: contents: read security-events: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Run ShellCheck uses: ludeeus/action-shellcheck@master with: severity: warning scandir: "." ignore_paths: ".git"