fix(ci): rendre la chaine d'integration reellement verifiante

Les quatre jobs existaient ; aucun ne verifiait ce qu'il annoncait.

security-audit
  `pip-audit --require-hashes --no-deps || pip-audit`. L'etape
  d'installation ne posait que pip-audit, et aucune des deux formes ne
  nommait le fichier d'exigences : le repli auditait l'environnement du
  runner, qui ne contenait que pip-audit lui-meme. Le job passait au vert
  sans avoir examine une seule dependance de l'application. Remplace par
  `pip-audit -r requirements.txt`.

security-scan
  Appelait `python security_scan.py`, alors que le fichier se trouve dans
  app/supporting_scripts/. En echec a chaque execution depuis le
  deplacement du fichier. Trois autres defauts sont apparus en le faisant
  tourner :
    - la CI passe --skip-http, un argument que l'argparse du script
      n'acceptait pas : sortie en erreur 2 meme avec le bon chemin.
    - check_dependencies lisait data['dependencies'] comme la liste des
      vulnerabilites. Ce tableau liste en realite TOUTES les dependances,
      chacune portant un champ vulns vide si le paquet est sain. Les ~45
      paquets installes etaient donc signales vulnerables a chaque
      execution. Le filtrage se fait desormais sur vulns non vide.
    - check_flask_config interceptait son exception et renvoyait quand
      meme all_ok : ne pas reussir a charger l'application comptait comme
      un controle reussi. La section la plus importante du rapport n'avait
      jamais tourne. Elle renvoie desormais False, et l'import fonctionne
      grace a l'ajout de la racine du projet dans sys.path.
    - la banniere en caracteres semi-graphiques faisait planter le script
      sur une console Windows en cp1252, la plateforme meme du projet.
      Passee en ASCII.

lint
  Ruff n'avait aucun fichier de configuration : le job tournait sur le jeu
  de regles par defaut. La configuration vit maintenant dans pyproject.toml.
  `ruff format --check` est retire pour l'instant : la base n'ayant jamais
  ete formatee, il echouerait sur 62 fichiers sur 64 pour des raisons
  etrangeres a la correction. Reformatage puis application : QUA-002.

test
  Un `echo` protege par continue-on-error : le job annoncait un succes
  sans rien executer. Il lance desormais pytest avec couverture, et bloque.

permissions: contents: read au niveau du workflow, aucune etape n'ecrivant
dans le depot.

Deploiement Gitea
  actions/checkout@v7 n'existe pas (derniere majeure : v5) : le workflow
  echouait des sa premiere etape. Ramene a v4.
  Le miroir lftp poussait l'integralite de l'arbre de travail, dont
  clear_db.py -- un script qui vide toutes les tables et recree
  admin/password -- vers le noeud de production. Liste d'exclusions ajoutee.
  --delete reste volontairement absent : les contrats televerses, les
  journaux et le .env du serveur vivent sous la racine de deploiement et
  sont absents du depot ; les supprimer detruirait des donnees.

Le workflow de deploiement n'a pas pu etre execute depuis ici : la
syntaxe lftp reste a valider lors du prochain deploiement manuel.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
GGThed
2026-08-07 19:47:24 -04:00
co-authored by Claude Opus 5
parent a793b7ed0d
commit 5ecea55f55
3 changed files with 117 additions and 40 deletions
+52 -19
View File
@@ -177,10 +177,21 @@ def check_dependencies():
else:
try:
data = json.loads(result.stdout)
vulns = data.get('dependencies', [])
if vulns:
for vuln in vulns:
print(f'[FAIL] {vuln["name"]}=={vuln["version"]}: {vuln.get("description", "Vulnerability found")}')
# pip-audit's "dependencies" array lists EVERY dependency, each
# carrying a "vulns" list that is empty when the package is
# clean. Treating the array itself as the vulnerability list
# reported all ~45 installed packages as vulnerable on every
# run, which is why this check was pure noise.
affected = [
dep for dep in data.get('dependencies', [])
if dep.get('vulns')
]
if affected:
for dep in affected:
ids = ', '.join(
v.get('id', '?') for v in dep.get('vulns', [])
)
print(f'[FAIL] {dep["name"]}=={dep["version"]}: {ids}')
return False
else:
print('[OK] No vulnerabilities found')
@@ -264,9 +275,20 @@ def check_flask_config():
all_ok = True
try:
# The script lives two levels below the project root; without this the
# import fails and the whole check was silently skipped.
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
if root not in sys.path:
sys.path.insert(0, root)
from app.app import create_app
app = create_app()
# Inspect configuration only: no schema creation, no Discord bot.
app = create_app({
'SQLALCHEMY_DATABASE_URI': os.getenv('DATABASE_URL') or 'sqlite:///:memory:',
'AUTO_CREATE_TABLES': False,
'ENABLE_DISCORD_BOT': False,
})
# Check session cookie settings
cookie_checks = [
('SESSION_COOKIE_SECURE', True, 'Secure cookies'),
@@ -313,8 +335,12 @@ def check_flask_config():
print('[OK] DEBUG mode: disabled')
except Exception as e:
print(f'[SKIP] Cannot check Flask config: {e}')
# Returning all_ok (still True) here meant that failing to load the
# application at all was counted as a passing check — the most
# important section of the report silently never ran.
print(f'[FAIL] Cannot check Flask config: {e}')
return False
return all_ok
@@ -329,22 +355,29 @@ def main():
parser = argparse.ArgumentParser(description='Security validation scanner')
parser.add_argument('--url', default='http://localhost:5000',
help='Application URL to check headers (default: http://localhost:5000)')
parser.add_argument('--skip-http', action='store_true',
help='Skip the live HTTP header check (no server running, e.g. in CI)')
args = parser.parse_args()
print('╔══════════════════════════════════════════════════════════╗')
print('║ TEAM TRYOUTS - SECURITY VALIDATION SCANNER ║')
print('╠══════════════════════════════════════════════════════════╣')
print(f'║ Time: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
print('╚══════════════════════════════════════════════════════════╝')
checks = [
check_environment,
lambda: check_https_headers(args.url),
# Plain ASCII: the box-drawing characters this banner used crashed the
# script outright on a cp1252 Windows console, which is the platform the
# project is developed and deployed on.
print('=' * 60)
print('TEAM TRYOUTS - SECURITY VALIDATION SCANNER')
print(f'Time: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
print('=' * 60)
checks = [check_environment]
if args.skip_http:
print('\n[SKIP] HTTP header check disabled via --skip-http')
else:
checks.append(lambda: check_https_headers(args.url))
checks += [
check_dependencies,
check_file_permissions,
check_flask_config,
]
results = []
for check in checks:
results.append(check())