ci: harden PR security pipeline (#10)
Add comprehensive security scanning to CI/CD pipeline including CodeQL, Gitleaks, Semgrep custom rules, dependency review, and lifecycle script checks
This commit is contained in:
@@ -8,6 +8,9 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
# ──────────────────────────────────────────────────────────
|
||||
# Code correctness
|
||||
# ──────────────────────────────────────────────────────────
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -29,3 +32,140 @@ jobs:
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run format:check
|
||||
|
||||
# ──────────────────────────────────────────────────────────
|
||||
# Code-level vulnerability scanning (SAST)
|
||||
# ──────────────────────────────────────────────────────────
|
||||
codeql:
|
||||
name: CodeQL SAST
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
queries: +security-extended,security-and-quality
|
||||
- uses: github/codeql-action/autobuild@v3
|
||||
- uses: github/codeql-action/analyze@v3
|
||||
|
||||
# ──────────────────────────────────────────────────────────
|
||||
# Custom static analysis — pi extension attack patterns
|
||||
# ──────────────────────────────────────────────────────────
|
||||
semgrep:
|
||||
name: Semgrep — pi extension audit
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
container:
|
||||
image: semgrep/semgrep:latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run Semgrep with custom rules
|
||||
run: |
|
||||
semgrep --config .semgrep/ --error --output semgrep-report.sarif --sarif .
|
||||
- name: Upload SARIF
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
with:
|
||||
sarif_file: semgrep-report.sarif
|
||||
category: semgrep-pi-audit
|
||||
if: always()
|
||||
|
||||
# ──────────────────────────────────────────────────────────
|
||||
# Hardcoded secrets detection
|
||||
# ──────────────────────────────────────────────────────────
|
||||
gitleaks:
|
||||
name: Gitleaks — secrets scan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: gitleaks/gitleaks-action@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITLEAKS_ENABLE_COMMENTS: "true"
|
||||
|
||||
# ──────────────────────────────────────────────────────────
|
||||
# Dependency supply-chain security
|
||||
# ──────────────────────────────────────────────────────────
|
||||
deps-review:
|
||||
name: Dependency review
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check if dependency graph is enabled
|
||||
run: |
|
||||
echo "Dependency review requires enabling Dependency graph in repo settings."
|
||||
echo "Go to: https://github.com/patlux/pi-commandcode-provider/settings/security_analysis"
|
||||
echo "Enable: Dependency graph"
|
||||
- uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
fail-on-severity: high
|
||||
allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, 0BSD
|
||||
comment-summary-in-pr: always
|
||||
continue-on-error: true
|
||||
|
||||
deps-audit:
|
||||
name: npm audit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm audit --audit-level=moderate
|
||||
- name: Exit gracefully on audit findings
|
||||
if: failure()
|
||||
run: |
|
||||
echo "::warning::npm audit found vulnerabilities. Review and patch before merging."
|
||||
|
||||
# ──────────────────────────────────────────────────────────
|
||||
# Postinstall script check — prevents install-time malware
|
||||
# ──────────────────────────────────────────────────────────
|
||||
check-scripts:
|
||||
name: Check lifecycle scripts
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for malicious lifecycle scripts
|
||||
run: |
|
||||
echo "::group::package.json scripts"
|
||||
node -e "
|
||||
const pkg = require('./package.json');
|
||||
const dangerous = ['preinstall','install','postinstall','prepublish','prepare'];
|
||||
const found = dangerous.filter(s => pkg.scripts && pkg.scripts[s]);
|
||||
if (found.length) {
|
||||
found.forEach(s => console.log('WARNING: package.json has "' + s + '":', pkg.scripts[s]));
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('No dangerous lifecycle scripts in package.json');
|
||||
}
|
||||
"
|
||||
echo "::endgroup::"
|
||||
echo "::group::dependency scripts (top-level)"
|
||||
npm query '.scripts' --all 2>/dev/null | node -e "
|
||||
const d = require('fs').readFileSync('/dev/stdin','utf8');
|
||||
if (!d.trim()) { console.log('No dependency scripts found'); process.exit(0); }
|
||||
let pkgs;
|
||||
try { pkgs = JSON.parse(d); } catch(e) { console.log('Could not parse npm query output'); process.exit(0); }
|
||||
if (!Array.isArray(pkgs)) pkgs = Object.values(pkgs);
|
||||
const withScripts = pkgs.filter(p => p && p.pkgid && p.scripts);
|
||||
withScripts.forEach(p => {
|
||||
const dangerous = ['preinstall','install','postinstall','prepublish','prepare'];
|
||||
const has = Object.keys(p.scripts || {}).filter(s => dangerous.includes(s));
|
||||
if (has.length) console.log('⚠', p.pkgid, 'has scripts:', Object.keys(p.scripts));
|
||||
});
|
||||
if (withScripts.length === 0) console.log('No dependency lifecycle scripts');
|
||||
" 2>&1 || true
|
||||
echo "::endgroup::"
|
||||
|
||||
Reference in New Issue
Block a user