Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .github/workflows/security_audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: Security Audit

on:
push:
branches:
- master
- ci-security
pull_request:
branches:
- master
workflow_dispatch:

jobs:
sast:
name: Bandit and Semgrep
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install SAST dependencies
run: |
python -m pip install --upgrade pip
pip install -r security_audit/requirements.txt

- name: Run Bandit
run: |
mkdir -p security_audit/out
bandit -r TVWeb/app TVSecure TVConnections \
-f json \
-o security_audit/out/bandit.json || true
if [ ! -s security_audit/out/bandit.json ]; then
printf '{\n "results": []\n}\n' > security_audit/out/bandit.json
fi

- name: Run Semgrep
run: |
semgrep scan \
--config auto \
--metrics off \
--json \
--output security_audit/out/semgrep.json \
TVWeb/app TVSecure TVConnections || true
if [ ! -s security_audit/out/semgrep.json ]; then
printf '{\n "results": []\n}\n' > security_audit/out/semgrep.json
fi

- name: Render readable reports
run: |
python security_audit/bandit_json.py normalize security_audit/out/bandit.json -o security_audit/out/bandit.normalized.json
python security_audit/semgrep_json.py normalize security_audit/out/semgrep.json -o security_audit/out/semgrep.normalized.json
python security_audit/bandit_json.py report security_audit/out/bandit.json -o security_audit/out/bandit.md
python security_audit/semgrep_json.py report security_audit/out/semgrep.json -o security_audit/out/semgrep.md

- name: Compare with baseline when available
run: |
if [ -f security_audit/baseline/bandit.json ]; then
python security_audit/bandit_json.py diff \
security_audit/baseline/bandit.json \
security_audit/out/bandit.json \
-o security_audit/out/bandit_diff.md \
--fail-on-new
else
echo "No Bandit baseline found; diff skipped."
fi

if [ -f security_audit/baseline/semgrep.json ]; then
python security_audit/semgrep_json.py diff \
security_audit/baseline/semgrep.json \
security_audit/out/semgrep.json \
-o security_audit/out/semgrep_diff.md \
--fail-on-new
else
echo "No Semgrep baseline found; diff skipped."
fi

- name: Run helper tests
run: |
python -m unittest security_audit/test_sast_json.py -v

- name: Upload SAST artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: security-audit-reports
path: security_audit/out/
2 changes: 2 additions & 0 deletions security_audit/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__/
*.pyc
46 changes: 46 additions & 0 deletions security_audit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Outils SAST JSON - Bandit & Semgrep

Inspiration : scripts `sort_json.py` / `compare_json.py` (Martial Mancip) -
**normaliser -> comparer -> rapporter**. Transposes aux JSON Bandit / Semgrep.

## Outils

| Script | Role |
|--------|------|
| `bandit_json.py` | Bandit uniquement |
| `semgrep_json.py` | Semgrep uniquement |
| `_sast_json_common.py` | Helpers partages |
| `test_sast_json.py` | Tests unitaires |

## Sous-commandes communes

| Commande | Entree | Sortie |
|----------|--------|--------|
| `normalize` | `*.json` brut | JSON stable |
| `report` | `*.json` brut ou normalise | Markdown lisible |
| `diff` | deux `*.json` (A = baseline, B = nouveau) | Markdown + code retour CI |

## Exemples

```bash
python3 security_audit/bandit_json.py report bandit.json -o bandit.md
python3 security_audit/semgrep_json.py report semgrep.json -o semgrep.md

python3 security_audit/bandit_json.py diff old.json new.json -o bandit_diff.md --fail-on-new
python3 security_audit/semgrep_json.py diff old.json new.json -o semgrep_diff.md --fail-on-new
```

## CI

- `requirements.txt` installe les scanners Python.
- Les scripts Python ici sont non interactifs et n'utilisent que la stdlib.
- `diff --fail-on-new` echoue si le nouveau run introduit des findings absents de la baseline.
- `diff --fail-on-severity` permet de bloquer a partir d'un seuil.

## Tests

```bash
cd security_audit
python3 test_sast_json.py
python3 -m unittest test_sast_json -v
```
59 changes: 59 additions & 0 deletions security_audit/_sast_json_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Helpers partages - normalisation de chemins et I/O JSON (stdlib only)."""

from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import Any


def load_json(path: str | Path) -> Any:
p = Path(path)
with p.open(encoding="utf-8") as f:
return json.load(f)


def dump_json(data: Any, path: str | Path | None) -> None:
text = json.dumps(data, indent=2, ensure_ascii=False, sort_keys=True) + "\n"
if path is None or path == "-":
sys.stdout.write(text)
else:
Path(path).write_text(text, encoding="utf-8")


def write_text(text: str, path: str | Path | None) -> None:
if not text.endswith("\n"):
text += "\n"
if path is None or path == "-":
sys.stdout.write(text)
else:
Path(path).write_text(text, encoding="utf-8")


def norm_path(path: str) -> str:
"""Chemin stable pour comparaison (sans ./ initial)."""
p = path.replace("\\", "/").strip()
while p.startswith("./"):
p = p[2:]
return p


def sev_rank_bandit(sev: str) -> int:
return {"HIGH": 3, "MEDIUM": 2, "LOW": 1, "UNDEFINED": 0}.get(
(sev or "").upper(), 0
)


def sev_rank_semgrep(sev: str) -> int:
# Semgrep: ERROR / WARNING / INFO (parfois HIGH/MEDIUM via metadata)
s = (sev or "").upper()
return {
"ERROR": 3,
"HIGH": 3,
"WARNING": 2,
"MEDIUM": 2,
"INFO": 1,
"LOW": 1,
}.get(s, 0)
Loading
Loading