Gå til hovedindhold

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ændelseSendes, nårAlvorlighed
security.finding.createdEt nyt åbent fund dukker op: sårbarhed i en afhængighed eller et container-image, SAST, secret, licenspolitikcritical, eller high, hvis du vælger det
security.malware.matchedMalware i en upload (ClamAV) eller en kendt ondsindet pakke (OSV MAL-*) i en SBOM for standardbranchen eller et imagecritical
security.pull.blockedEt pull af et container-image eller en pakke blev afvist af malware-gaten eller din image-scan-politik block_pullhigh
security.policy.violationEt brud på godkendelsespolitikken (tokenlevetid, hardwarenøgle, SSH-certifikat, deploy-nøgle) eller et push afvist af push-beskyttelsenhigh
security.exception.expiredEn afvisning (risikoaccept) med udløbsdato er udløbet, og fundet er åbent igeningen
security.exception.requestedNogen 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.approvedEn godkender har godkendt en undtagelse for et fund; fundet er afvistfundets, hvor det har en
security.exception.rejectedEn godkender har afvist en undtagelse for et fund; fundet forbliver åbentfundets, 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
  }
}
FeltBetydning
eventEt af hændelsesnavnene ovenfor
event_idStabilt pr. hændelse. Brug det til at kassere en levering, du allerede har behandlet
occurred_atHvornår fundet, afvisningen eller bruddet skete (UTC)
severitycritical, high eller null
sourceHvad der frembragte det: dep, image, sast, secret, license, malware, dependency_malware, push_protection, auth_policy, registry eller et scannernavn for et udløb
correlation_keyFundets identitet. Den samme nøgle bruges for alle hændelser om det samme fund
console_urlDen side i konsollen, der viser det
detailsFelter, 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 leveringen dlq og 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