Webhooks til sikkerhedsalarmer
Sikkerhedsalarmer når frem til folk via e-mail. For også at nå dit hændelses- eller ticketsystem abonnerer du en udgående webhook-destination på en eller flere sikkerhedshændelser. Hver hændelse er ét signeret HTTPS-POST, der forsøges igen, indtil din modtager accepterer det, og som registreres i leveringsloggen.
Der er to body-formater pr. destination:
- Generisk (standard): fremforges JSON-hændelse, som er beskrevet nedenfor.
- ServiceNow: en sagspost, som ServiceNows Table API accepterer, som den er. Se ServiceNow.
Hændelser
| Hændelse | Sendes, når | Alvorlighed |
|---|---|---|
security.finding.created | Et nyt åbent fund dukker op: sårbarhed i en afhængighed eller et container-image, SAST, secret, licenspolitik | critical, eller high, hvis du vælger det |
security.malware.matched | Malware i en upload (ClamAV) eller en kendt ondsindet pakke (OSV MAL-*) i en SBOM for standardbranchen eller et image | critical |
security.pull.blocked | Et pull af et container-image eller en pakke blev afvist af malware-gaten eller din image-scan-politik block_pull | high |
security.policy.violation | Et brud på godkendelsespolitikken (tokenlevetid, hardwarenøgle, SSH-certifikat, deploy-nøgle) eller et push afvist af push-beskyttelsen | high |
security.exception.expired | En afvisning (risikoaccept) med udløbsdato er udløbet, og fundet er åbent igen | ingen |
security.exception.requested | Nogen har bedt om en undtagelse for et fund eller har afvist et, mens afvisninger kræver godkendelse (titlen siger det så) | fundets, hvor det har en |
security.exception.approved | En godkender har godkendt en undtagelse for et fund; fundet er afvist | fundets, hvor det har en |
security.exception.rejected | En godkender har afvist en undtagelse for et fund; fundet forbliver åbent | fundets, hvor det har en |
To hændelsesnavne mere når en destination med abonnement uden selv at være abonneret på:
security.summary: sendes efter en time, hvor grænsen pr. time holdt hændelser tilbage.security.test: sendes af testknappen eller test-endpointet.
Sikkerhedshændelser skal vælges ved navn. En destination med en tom hændelsesliste modtager alle Forgejo-, status- og vedligeholdelseshændelser, men ingen sikkerhedshændelser. Angiv eksplicit de sikkerhedshændelser, du vil have.
Et abonnement starter, når du opretter det. Fund, der fandtes, før du abonnerede, sendes ikke igen. Brug fund-API’et til det aktuelle efterslæb.
Én hændelse pr. fund. En ny scanning sender ikke et fund igen. Dets correlation_key er den samme for alle hændelser om det, også hændelsen security.exception.expired, når en risikoaccept på det udløber.
Alvorlighed. security.finding.created sendes som standard for critical-fund. Sæt security_min_severity til high for også at modtage høje fund. SAST-fejl tæller som high. Secrets tæller som critical, og det gør forbudte licenser også. Begrænsede licenser tæller som high. De andre hændelser sendes altid.
Body
{
"event": "security.finding.created",
"event_id": "3f0c2a9e5b1d4c7a8e6f1b2c3d4e5f60",
"occurred_at": "2026-10-04T10:12:44.000Z",
"org": "acme",
"severity": "critical",
"title": "CVE-2026-12345 in lodash 4.17.20",
"repository": "acme/web",
"source": "dep",
"correlation_key": "dep:acme/web:CVE-2026-12345:npm:lodash@4.17.20",
"console_url": "https://frem.sh/acme/_admin/code-security/deps",
"details": {
"finding_id": "8b8d2c7e-0f6a-4a51-9a63-0c1f6f0b2a11",
"vulnerability_id": "CVE-2026-12345",
"ecosystem": "npm",
"package": "lodash",
"version": "4.17.20",
"fixed_version": "4.17.21",
"manifest_path": "package-lock.json",
"cvss_score": 9.8
}
}| Felt | Betydning |
|---|---|
event | Et af hændelsesnavnene ovenfor |
event_id | Stabilt pr. hændelse. Brug det til at kassere en levering, du allerede har behandlet |
occurred_at | Hvornår fundet, afvisningen eller bruddet skete (UTC) |
severity | critical, high eller null |
source | Hvad der frembragte det: dep, image, sast, secret, license, malware, dependency_malware, push_protection, auth_policy, registry eller et scannernavn for et udløb |
correlation_key | Fundets identitet. Den samme nøgle bruges for alle hændelser om det samme fund |
console_url | Den side i konsollen, der viser det |
details | Felter, der er specifikke for hændelsen. Værdier af secrets medtages aldrig: et fund af en secret indeholder kun regel, fil og linje |
Signatur og headers
Hver forespørgsel har:
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: fremforge-webhook/1
X-Fremforge-Event: security.finding.created
X-Fremforge-Delivery: ffm-attempt-6d0f…
X-Fremforge-Timestamp: 1791108764
X-Fremforge-Signature: t=1791108764,v1=5d41402abc4b2a76b9719d911017c592…v1 er HMAC-SHA256 i hex, nøglet med destinationens signeringssecret, over tidsstemplet, et punktum og den rå body: <t>.<body>. Verificér den over de bytes, du har modtaget, før du fortolker dem. Afvis et tidsstempel, der er mere end et par minutter gammelt. Efter en rotation af secreten bærer X-Fremforge-Signature-Previous i 7 dage den samme signatur lavet med den gamle secret.
import hmac, hashlib, time
def verify(headers, raw_body: bytes, secret: str) -> None:
parts = dict(p.split('=', 1) for p in headers['X-Fremforge-Signature'].split(','))
ts, sig = parts['t'], parts['v1']
if abs(time.time() - int(ts)) > 300:
raise ValueError('stale')
expected = hmac.new(secret.encode(), ts.encode() + b'.' + raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig):
raise ValueError('bad signature')En destination kan også have én ekstra header til modtagere, der godkender via en header i stedet for en signatur. ServiceNow er én af dem: Authorization: Basic …. Værdien gemmes krypteret og vises aldrig igen. Headers, der ville ødelægge forespørgslen (Host, Content-Type, X-Fremforge-*, proxy-headers og lignende), afvises.
Levering
- Genforsøg: et svar, der ikke er
2xx, en timeout eller en forbindelsesfejl forsøges igen efter 2 minutter, derefter efter 5 og 15 minutter, 1 time og 6 timer. Det giver i alt seks forsøg over omkring 7,5 timer. Efter det sjette markeres leveringendlqog bliver i loggen. - Mål: en offentlig HTTPS-URL. Private adresser, loopback-, link-local- og metadata-adresser afvises. Forespørgsler sendes ud gennem fremforges udgående proxy.
- Tilstand: efter fem mislykkede leveringer i træk får organisationens faktureringskontakter en e-mail, der nævner destinationen.
- Log: hvert forsøg, med status, HTTP-svar og fejl, står i
GET /orgs/{org}/webhooks/deliveries?destination_id={id}.
Grænse pr. time
Hver destination tager højst imod security_hourly_cap sikkerhedshændelser pr. klokketime (UTC). Standarden er 100 og maksimum 1000. Hændelser ud over grænsen leveres ikke; de vises i leveringsloggen som suppressed. Efter timen fortæller én security.summary-hændelse, hvor mange der blev holdt tilbage, pr. hændelsesnavn:
{
"event": "security.summary",
"title": "240 security event(s) held back by the hourly cap (2026-10-04T10:00 UTC)",
"details": {
"hour_start": "2026-10-04T10:00:00.000Z",
"suppressed_total": 240,
"suppressed_by_event": { "security.finding.created": 240 }
}
}Grænsen beskytter dit ticketsystem mod den første scanning af et stort monorepo. Selve fundene står alle i konsollen og i fund-API’et.
Opsætning i konsollen
Åbn Webhooks i din organisations administration (https://frem.sh/<org>/_admin/webhooks). Tilføj en destination, eller vælg hurtigstarten ServiceNow-sager. Derefter, under Sikkerhedshændelser på destinationen:
- sæt flueben ved de hændelser, der skal sendes;
- vælg Fund, der sendes: Kun kritiske eller Kritiske og høje;
- sæt Grænse pr. time;
- vælg Format: Generisk JSON (signeret) eller ServiceNow-sag (Table API), med de valgfri felter for ServiceNow-sager;
- sæt eventuelt en Auth-header.
Send test sætter en security.test-hændelse i kø og viser, om den blev leveret, og hvad modtageren svarede. En destination, der allerede modtager alle repository-hændelser (en tom hændelsesliste), kan ikke indsnævres til sikkerhedshændelser. Opret en separat destination til dem.
Opsætning med API’et
Scope webhooks:write. Opret en destination, der abonnerer på kritiske og høje fund og malware:
curl -sS -X POST "https://frem.sh/_app/api/v1/orgs/acme/webhooks/destinations" \
-H "Authorization: Bearer $FREMFORGE_TOKEN" -H "Content-Type: application/json" \
-d '{
"name": "SOC intake",
"target_url": "https://soc.example.com/hooks/fremforge",
"events": ["security.finding.created", "security.malware.matched", "security.pull.blocked"],
"security_min_severity": "high",
"security_hourly_cap": 200
}'Svaret indeholder signerings-secret én gang. Gem den hos din modtager.
Ændr senere enhver indstilling med PATCH /orgs/{org}/webhooks/destinations/{id}: events, payload_format, security_min_severity, security_hourly_cap, servicenow, auth_header, name, target_url eller active. "auth_header": null fjerner headeren.
Send en testhændelse:
curl -sS -X POST "https://frem.sh/_app/api/v1/orgs/acme/webhooks/destinations/$ID/test" \
-H "Authorization: Bearer $FREMFORGE_TOKEN"
# {"id":"…","attempt_id":"…","event":"security.test","status":"queued"}
curl -sS "https://frem.sh/_app/api/v1/orgs/acme/webhooks/deliveries?destination_id=$ID&limit=5" \
-H "Authorization: Bearer $FREMFORGE_TOKEN"Testen går samme vej som en rigtig hændelse: destinationens format, signatur, auth-header, proxy og genforsøg. Endpointet svarer 202. En sat-på-pause destination ("active": false) svarer 409, så genoptag den først.
Relateret
- ServiceNow: sager fra sikkerhedshændelser.
- SIEM-videresendelse: hele revisionsloggen, til detektionsregler frem for tickets.
- Webhooks: repository-hændelser fra Forgejo.