Aller au contenu

GET /v1/projects/{id}/alerts

Les alertes sont créées automatiquement quand une CVE correspond à un item de la stack d’un projet. Chaque alerte inclut les métadonnées de la CVE associée (score CVSS, sévérité, KEV, PoC).

GET /api/v1/projects/{project_id}/alerts
Authorization: Bearer twa_votre_cle_ici
ParamètreTypeDescription
project_idUUIDIdentifiant du projet
ParamètreTypeDéfautDescription
unread_onlybooleanfalseSi true, retourne uniquement les alertes non lues
severitystringFiltre par sévérité(s), séparées par virgule. Valeurs : CRITICAL, HIGH, MEDIUM, LOW
sincedatetime (ISO 8601)Retourne uniquement les alertes déclenchées après cette date
pageinteger ≥ 11Numéro de page
limitinteger 1–20050Nombre d’alertes par page
{
"items": [
{
"id": "018e9999-0000-7000-8000-000000000030",
"cve_id": "CVE-2024-11477",
"triggered_at": "2024-11-05T14:32:00Z",
"is_read": false,
"severity": "CRITICAL",
"description": "7-Zip contains a heap-based buffer overflow vulnerability...",
"vendor": "7-zip",
"product": "7-zip",
"cvss_score": 7.8,
"is_kev": false,
"has_poc": true
},
{
"id": "018e9999-0000-7000-8000-000000000031",
"cve_id": "CVE-2024-38816",
"triggered_at": "2024-09-13T08:00:00Z",
"is_read": true,
"severity": "HIGH",
"description": "Applications serving static resources through the functional web...",
"vendor": "vmware",
"product": "spring_framework",
"cvss_score": 7.5,
"is_kev": false,
"has_poc": false
}
],
"total": 42,
"unread_count": 7
}
ChampTypeDescription
itemsarrayAlertes de la page courante
totalintegerNombre total d’alertes correspondant aux filtres actifs
unread_countintegerTotal des alertes non lues dans ce projet (indépendant des filtres)
ChampTypeDescription
idstring (UUID)Identifiant de l’alerte
cve_idstringIdentifiant CVE (ex : CVE-2024-11477)
triggered_atstring (ISO 8601)Horodatage de déclenchement
is_readbooleantrue si l’alerte a été marquée comme lue dans l’application
severitystringSévérité CVE : CRITICAL, HIGH, MEDIUM, LOW, NONE, UNKNOWN
descriptionstring | nullDescription anglaise de la CVE
vendorstring | nullÉditeur principal identifié
productstring | nullProduit principal identifié
cvss_scorefloat | nullScore CVSS (0.0 – 10.0)
is_kevbooleantrue si référencée dans le catalogue CISA KEV
has_pocbooleantrue si au moins un PoC public est connu (nomi-sec/PoC-in-GitHub)

GET /api/v1/projects/{project_id}/alerts/{alert_id}
Authorization: Bearer twa_votre_cle_ici
ParamètreTypeDescription
project_idUUIDIdentifiant du projet
alert_idUUIDIdentifiant de l’alerte

Même structure qu’un élément de la liste.

CodeDétailCause
404Projet introuvableproject_id inexistant
403Accès refusé à ce projetProjet non accessible
404Alerte introuvablealert_id inexistant ou n’appartient pas au projet

Alertes critiques non lues depuis 30 jours

Fenêtre de terminal
SINCE=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| date -u -v-30d +%Y-%m-%dT%H:%M:%SZ)
curl -s \
-H "Authorization: Bearer twa_votre_cle_ici" \
"https://app.techwatchalert.com/api/v1/projects/${PROJECT_ID}/alerts?severity=CRITICAL,HIGH&unread_only=true&since=${SINCE}&limit=100" \
| jq '.items[] | {cve: .cve_id, score: .cvss_score, kev: .is_kev, poc: .has_poc}'

Script CI/CD — bloquer si CVE critique avec PoC

#!/bin/bash
set -euo pipefail
PROJECT_ID="${TWA_PROJECT_ID}"
API_KEY="${TWA_API_KEY}"
BASE="https://app.techwatchalert.com/api/v1"
CRITICAL_POC=$(curl -sf \
-H "Authorization: Bearer ${API_KEY}" \
"${BASE}/projects/${PROJECT_ID}/alerts?severity=CRITICAL&unread_only=true&limit=200" \
| jq '[.items[] | select(.has_poc == true)] | length')
if [ "$CRITICAL_POC" -gt 0 ]; then
echo "ERREUR : ${CRITICAL_POC} CVE(s) critiques avec PoC non traitées."
exit 1
fi
echo "OK — aucune CVE critique avec PoC."

Python — export CSV des alertes

import csv, httpx
from datetime import datetime, timedelta, timezone
BASE = "https://app.techwatchalert.com/api/v1"
headers = {"Authorization": "Bearer twa_votre_cle_ici"}
project_id = "018e1234-abcd-7000-8000-000000000010"
since = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat()
page, all_alerts = 1, []
while True:
r = httpx.get(
f"{BASE}/projects/{project_id}/alerts",
headers=headers,
params={"since": since, "limit": 200, "page": page},
).json()
all_alerts.extend(r["items"])
if len(all_alerts) >= r["total"]:
break
page += 1
with open("alertes.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=["cve_id", "severity", "cvss_score", "is_kev", "has_poc", "triggered_at"])
w.writeheader()
w.writerows(all_alerts)
print(f"{len(all_alerts)} alertes exportées.")