Vidarebefordran till SIEM
fremforge skriver varje administrativ åtgärd som ändrar tillstånd till en granskningslogg per tenant med en hashkedjad integritetsgaranti (se Granskningskedjans integritet). Samma händelser kan skickas vidare till ditt SIEM så snart de skrivs, så att dina detekteringsregler ser dem inom sekunder i stället för vid nästa CSV-export.
Kommer snart: samma ström kommer också att bära en händelse per livscykeländring för ett säkerhetsfynd: när ett fynd om en sårbarhet, licens, SAST, hemlighet, skadlig kod eller paketpolicy öppnas, öppnas igen, avfärdas eller löses. Se Fyndhändelser.
Den här sidan beskriver vidarebefordrarens utformning, protokollkontraktet för verifiering och hur populära SIEM-system tar emot strömmen.
Hur leveransen fungerar
audit-emit skriver den kedjade raden och köar, i samma databastransaktion, händelsen för varje aktiverad endpoint vars åtgärdsprefix matchar. En leveransprocess skickar varje endpoints kö i ordning, en eller flera händelser per begäran beroende på endpointens format. En händelse tas bort ur kön först när din mottagare svarar 2xx.
Inställningarna finns under Organisationsadmin → Integration → SIEM-vidarebefordran (/<org>/_admin/siem), och samma inställningar finns i REST-API:t (/_app/api/v1/orgs/<org>/siem/endpoints).
Sidan för SIEM-vidarebefordran har formuläret Lägg till en endpoint och listan över endpoints: namn, URL (med inloggningsuppgifter maskerade), status, senaste leverans och antalet händelser som väntar. Varje endpoints namn öppnar dess egen sida (/<org>/_admin/siem/<endpoint id>), med ett kort per inställning:
- Översikt: status, orsaken till senaste felet, filtret för åtgärdsprefix, Inaktivera/Aktivera och Skicka test.
- Auth-header: ange, rotera eller ta bort den.
- Signatur: vad HMAC:en omfattar.
- Leverans: väntande händelser, den äldsta av dem, nästa försök, totalt levererade och kasserade, samt inställningarna för format och samling.
- Skicka om: skicka tidigare händelser igen.
- Ta bort endpoint: skriv endpointens namn för att bekräfta.
Konfigurera en endpoint
- Gå till Organisationsadmin → SIEM-vidarebefordran → Lägg till en endpoint.
- Ange ett Namn (fritext, unikt inom organisationen) och URL:en till din mottagare:
https://…, ellersyslog+tls://host:portför syslog över TLS.http://,localhost, RFC1918-adresser och metadataadresser nekas; använd en mottagare som går att nå publikt. - Välj Format (se Format och samling). De återstående stegen finns under Avancerade alternativ och kan också ändras senare på endpointens sida.
- Begränsa eventuellt tillåtelselistan med åtgärdsprefix (till exempel
forgejo.push, billing.). Tom betyder att allt vidarebefordras. Varje prefix matchas medaction.startsWith(prefix). - Slå eventuellt på Inkludera åtkomstloggen för registret för att också ta emot en händelse per begäran mot paket- eller container-registret (
registry.access.*). Den är av som standard; se Åtkomstlogg för registry. Image-spärrarnas beslut om hämtningar,registry.pull.blockedochregistry.pull.would_block, är granskningshändelser, inte åtkomsthändelser, så de vidarebefordras utan det här alternativet, en per image-digest, spärr och beslut per timme; se Spärrar för hämtning av containrar. Kommer snart: Inkludera fyndhändelser, förvalt, kommer att göra att endpointen också tar emot en händelse per livscykeländring för ett säkerhetsfynd (security.finding.*), från och med att den skapas. Tills funktionen släpps visas alternativet inte i formuläret. Se Fyndhändelser. - Ange eventuellt en Auth-header: ett headernamn och ett hemligt värde som skickas med varje begäran (till exempel
AuthorizationochSplunk <token>). Se Autentisering. - Välj vad signaturen omfattar (tidsstämpel och body som standard, eller endast body).
- Ange samlingsstorlek, storlek per begäran och väntetid.
- Klicka på Skapa endpoint. Den delade HMAC-hemligheten visas en gång på nästa skärm; kopiera den till mottagarens konfiguration. fremforge visar den aldrig igen. Om du tappar bort den, ta bort endpointen och lägg till den igen.
Endpointen blir aktiv direkt; bekräftelsen länkar till dess sida. Skicka test skickar en syntetisk händelse fremforge.siem.test i endpointens format, med dess headers och signatur, och visar svarsstatusen och de första 200 byten av mottagarens svar.
Inloggningsuppgifter i URL:en. Vissa insamlare tar emot sina inloggningsuppgifter i URL:en (?token=, ?key=&secret= eller user:password@). fremforge lagrar URL:en som den angavs krypterad, och visar den överallt annars (administrationssidan, API:t, testresultatet, granskningsloggen) med varje värde i frågesträngen och användarinformationen ersatta med ***. Bara leveransen använder den fullständiga URL:en.
Autentisering
Två mekanismer, som kan användas tillsammans:
- Auth-header. En header per endpoint, med namn och värde som du väljer. Värdet krypteras i vila och är endast skrivbart: ingen sida, inget API-svar, ingen loggrad och ingen granskningshändelse innehåller det. Granskningsloggen registrerar headerns namn och när den angavs. För att rotera sparar du ett nytt värde; det gamla slutar skickas direkt, så lägg till den nya nyckeln i insamlaren först. Headernamn som skulle förstöra begäran nekas:
Host,Content-Length,Content-Type,Content-Encoding,Transfer-Encoding,Connection,Keep-Alive,Upgrade,TE,Trailer,Expect,Accept-Encoding,User-Agent,Cookie,Proxy-*ochX-Fremforge-*. Värden är utskrivbar ASCII, upp till 4096 tecken. - HMAC-signatur. Varje begäran bär
X-Fremforge-Signature: sha256=<hex>, med endpointens HMAC-hemlighet som nyckel. Som standard omfattar den<timestamp>.<body>, så att mottagaren kan avvisa en gammal begäran utifrån tidsstämpeln. Med Signaturen omfattar: Endast body omfattar den bara den råa bodyn, vilket är vad mottagare med inbyggd verifiering av bodyn (Elastics HTTP endpoint-input) kontrollerar. En mottagare som bara verifierar bodyn kan inte avvisa en omsänd begäran utifrån ålder; deduplicera på händelsensidi stället.
Protokollkontrakt
Varje HTTPS-leverans är en POST:
| Header | Värde |
|---|---|
Content-Type | application/json (json, json_array), application/x-ndjson (ndjson), text/plain; charset=utf-8 (cef) |
User-Agent | fremforge-siem-forwarder/1 |
X-Fremforge-Timestamp | Unix-sekunder (sträng) när fremforge byggde begäran |
X-Fremforge-Signature | sha256=<hex> över <timestamp>.<body>, eller över <body> i läget endast body |
Idempotency-Key | Händelsens id för en enskild händelse; för en samling, en SHA-256 av samlingens händelse-id:n i ordning |
X-Fremforge-Event-Count | Antalet händelser i bodyn |
| din auth-header | När den är angiven |
Varje händelse har den här formen (nycklar på toppnivå i den här ordningen; nycklarna i fields.* sorterade alfabetiskt så att byten är stabila):
{
"id": "00000000-0000-0000-0000-000000000000",
"tenant_id": "11111111-1111-1111-1111-111111111111",
"actor": "alice",
"action": "policy.update.api",
"fields": { "...": "..." },
"created_at": "2026-05-15T13:42:11.123Z",
"hash_chain": {
"prev_hash": "ab12...",
"this_hash": "cd34..."
}
}id är granskningshändelsens id. Det är detsamma vid varje omförsök och omsändning av den händelsen: använd det för att deduplicera.
actor är ett bart Forgejo-användarnamn för en åtgärd som en person utför (i webbkonsolen, och för många API-anrop, den användare som skapade token), oidc:idp:<sub> för en person som loggade in via din IdP och agerar med en utbytt token, token:<id> för andra API-anrop, operator:<email> för en åtgärd av fremverks personal, och andra former av <kind>:<id> för systemkomponenter.
hash_chain.this_hash är samma hash som förankras i OBS Object Lock, så att ditt SIEM kan bevisa att en händelse är äldre än en känd förankring.
En åtkomsthändelse för registret (bara på en endpoint med include_access_log) har samma form, med action registry.access.<action> och "hash_chain": null: åtkomsthändelser ingår inte i granskningens hashkedja. Dess fields är händelsen så som API:t för åtkomstloggen returnerar den.
En fyndhändelse (kommer snart; bara på en endpoint med include_finding_events) kommer också att ha den här formen, med action security.finding.<transition> och "hash_chain": null.
Format och samling
| Format | Body | Händelser per begäran |
|---|---|---|
json (standard) | Ett händelseobjekt | 1 |
ndjson | Ett händelseobjekt per rad, varje rad avslutad med \n | Upp till max händelser |
json_array | En JSON-array med händelseobjekt | Upp till max händelser |
cef | En CEF-rad per händelse, var och en avslutad med \n | Upp till max händelser |
För formaten med samling:
- Max händelser per begäran: 1 till 1000 (100 när du byter till ett format med samling utan att välja).
- Max storlek per begäran: 1 KiB till 4 MiB (standard 1 MiB). En samling stängs innan den skulle överskrida gränsen; en enskild händelse skickas alltid.
- Max väntetid: 0 till 60 000 ms (standard 0). Hur länge en delvis fylld samling väntar på fler händelser innan den skickas.
Händelser levereras i den ordning de skrevs.
Mappning av CEF-fält
CEF:0|fremverk|fremforge|1|<action>|<action>|<severity>|<extension>
| CEF | Värde |
|---|---|
| Signature ID, Name | action |
| Severity | 9 när hash_chain.this_hash är chain-broken; 7 när action innehåller fail, denied, deny, blocked, violation, malware, secret, breach eller tamper; 5 när den innehåller delete, revoke, disable, remove, bypass, suspend eller dismiss; annars 3 |
rt | created_at, millisekunder sedan epoken |
externalId | id (dedupliceringsnyckeln) |
suser | actor |
act | action |
cat | action fram till första . |
cs1Label=tenantId, cs1 | tenant_id |
cs2Label=fields, cs2 | fields som JSON |
cs3Label=hashChainThis, cs3 | hash_chain.this_hash |
cs4Label=hashChainPrev, cs4 | hash_chain.prev_hash, tomt när det inte finns något |
dvchost | frem.sh |
Headerfält escapar \ och |; extensionvärden escapar \, = och radbrytningar (\n, \r). Ett värde från en granskningshändelse kan därför inte starta ett nytt fält eller en ny rad.
Fyndhändelser
Kommer snart. Fyndhändelser är inte tillgängliga än. Det här avsnittet beskriver dem inför lanseringen; tills dess erbjuder varken konsolen eller API:t
include_finding_eventseller ögonblicksbilden, och ingen händelsesecurity.finding.*skickas. Ändringsloggen meddelar lanseringen.
En endpoint med Inkludera fyndhändelser påslaget (include_finding_events i API:t) tar emot en händelse varje gång ett säkerhetsfynd byter tillstånd. Alla typer av fynd som fremforge rapporterar ingår:
alert_category | Fynd | Källa |
|---|---|---|
dependency_vulnerability | Ett sårbart beroende på standardbranchen | Beroendeskanning |
image_vulnerability | En sårbarhet eller felkonfiguration i en container-image i ditt registry (inklusive proxade images) eller i en skanning av ett repo | Image-skanning |
licence | Ett beroende under en licens som är forbidden eller restricted | Licensskanning |
sast | Ett fynd från statisk analys på standardbranchen | SAST |
secret | En incheckad hemlighet (aldrig själva hemligheten) | Hemlighetsskanning |
malware | Ett skadligt paket (OSV MAL-*), eller en uppladdning som antivirusskanningen (ClamAV) nekade | Beroende- och registry-skanningar, skanning av uppladdningar |
package_policy | En fil i paketregistret som paketpolicyn blockerar eller varnar för | Paketpolicy |
Fynd från skanningar av pull requests ingår inte: de kommer och går med varje pull request. Det är på standardbranchen som ett fynds livscykel följs. Händelser för paketpolicy skickas bara för organisationer där resultaten av paketpolicyn är synliga.
Händelsekatalog
action | När |
|---|---|
security.finding.opened | Fyndet upptäcks för första gången. |
security.finding.reopened | Fyndet upptäcks igen efter att ha lösts (transition_reason reappeared), eller dess avfärdande drogs tillbaka innan det löpte ut (undismissed). |
security.finding.dismissed | Någon avfärdade fyndet (risk accepterad, falskt positivt), en tillåtelselista täcker det, eller ett antivirusfynd åsidosattes. actor är den användare som avfärdade det, eller system:<kind>. |
security.finding.dismissal_expired | Ett tidsbegränsat avfärdande löpte ut och fyndet är öppet igen. Det omfattar ett undantag som nådde sitt slutdatum. |
security.finding.exception_revoked | Ett godkänt undantag som avfärdade fyndet återkallades, och fyndet är öppet igen. transition_reason är utlösaren: manual (någon återkallade det), severity_raised, new_vulnerability eller approver_left. actor är den person som återkallade det, eller system:exception-revocation. |
security.finding.dismissal_revoked | fremforge återkallade ett avfärdande som byggde på bedömningen ej nåbar från nåbarhet, eftersom en senare skanning motsäger den, och fyndet är öppet igen. transition_reason är reachability_changed, finding.revocation anger varför och actor är system:exception-revocation. (Före 2026-10-12 skickades detta som dismissal_expired.) Ett återkallat undantag är exception_revoked med samma orsak. |
security.finding.resolved | Fyndet upptäcks inte längre: åtgärdat, borttaget, ersatt av en nyare skanning som inte längre rapporterar det, eller paketfilen raderad. |
security.finding.known_exploited | En sårbarhet i ett beroende eller en image, öppen eller avfärdad, listades som utnyttjad i praktiken av ENISA:s EU Vulnerability Database eller CISA:s KEV-katalog. Skickas en gång per listning. transition_reason är listed (listan har lagt till den sedan dess), eller new (fyndet var redan listat när det först sågs; händelsen skickas tillsammans med dess opened, i valfri ordning). known_exploited anger CVE:erna och listorna, och actor är system:known-exploited-feed. Ett avfärdat fynd rapporteras också: risken som det accepterades under har ändrats. |
security.finding.snapshot | En per öppet fynd, skickas en gång när du begär en ögonblicksbild. |
Ändringar fångas upp inom ungefär en minut. Ett fynd behåller en identitet mellan omskanningar, så en omskanning som rapporterar samma fynd igen skickar ingenting; fields.original_alert_id är detsamma på varje händelse om ett och samma fynd.
Vad som skickas, och från när
- Bara ändringar från och med att du valde det. En endpoint tar emot händelser för ändringar som sker efter att den började ta emot dem (
finding_events_sincei API:t). Att slå på fyndhändelser skickar inte dina befintliga fynd. - Ögonblicksbild av öppna fynd. För att läsa in det som är öppet nu, använd Skicka ögonblicksbild av öppna fynd på endpointens sida, eller
POST /_app/api/v1/orgs/<org>/siem/endpoints/<id>/finding-snapshot. Det köar en händelsesecurity.finding.snapshotper öppet fynd, bara till den endpointen. Den kan tas en gång per endpoint; en andra begäran besvaras med409. - Standard. En endpoint som skapas efter lanseringen inkluderar fyndhändelser om du inte avmarkerar rutan (eller skickar
"include_finding_events": false). Endpoints som finns före lanseringen lämnas som de var, utan fyndhändelser, så att en mottagare som byggts för granskningshändelser inte börjar ta emot en ny händelsefamilj utan förvarning; slå på dem per endpoint på dess sida eller medPATCH … {"include_finding_events": true}. - Åtgärdsprefix gäller som för granskningshändelser. En endpoint med prefix tar bara emot fyndhändelser om något av dem matchar, till exempel
security.finding.. - Leverans, samling, omförsök, omsändning, HMAC, auth-headern och hur länge olevererade händelser sparas är desamma som för granskningshändelser. Händelsens
idär detsamma vid varje omförsök och omsändning: deduplicera på det.
Fält
fields i en fyndhändelse:
| Fält | Värde | Cortex XDM |
|---|---|---|
alert_name | Kort rubrik, till exempel CVE-2024-3094 in xz-utils (frem.sh/acme/api@sha256:…) | xdm.alert.name |
alert_category | Fyndtypen (tabellen ovan) | xdm.alert.category |
alert_severity | critical, high, medium, low eller informational | xdm.alert.severity |
alert_description | Sammanfattning av säkerhetsmeddelandet eller regelns meddelande, högst 500 tecken, eller null | xdm.alert.description |
original_alert_id | Stabilt fynd-id (32 hexadecimala tecken), detsamma på varje händelse om ett och samma fynd | xdm.alert.original_alert_id |
org | Organisationens slug | |
finding.type, finding.source | Fyndtyp, och skannern som rapporterade fyndet | |
finding.state, finding.previous_state | open, dismissed eller resolved; tillståndet före (null när fyndet ses första gången) | |
finding.transition, finding.transition_reason | Övergången (som i action) och varför: new, reappeared, undismissed, dismissal_expired, manual, severity_raised, new_vulnerability, approver_left, reachability_changed, dismissed, new_dismissed, not_detected, superseded, no_longer_present, allowed, package_deleted, listed, snapshot | |
finding.first_seen_at, finding.state_changed_at | När fyndet sågs första gången; när det gick in i sitt nuvarande tillstånd | |
finding.key | Den identitet fyndet behåller mellan omskanningar, till exempel dep:acme/api:GHSA-…:npm:lodash@4.17.20 | |
finding.row_id | Fyndets nuvarande id i Findings API; det ändras när en omskanning ersätter raden | |
finding.console_url | Var fyndet visas i konsolen | |
finding.dismissal | {by, reason, expires_at, approved_by, exception_id} på en händelse dismissed, annars null. approved_by och exception_id anges när ett godkänt undantag avfärdade fyndet | |
finding.exception | {id, status, approved_by, revoked_by, revoked_reason, revoked_detail} för undantaget som upphörde, på exception_revoked och på en dismissal_expired som avslutade ett undantag; annars null | |
finding.revocation | {reason, detail, revoked_verdict, revoked_at} på dismissal_revoked: utlösaren (reachability_changed), vad som ändrades, den senare bedömning som motsade ej nåbar, och när; annars null | |
cvss | {score, vector} när säkerhetsmeddelandet har en, annars null | |
identifiers | Listor över id:n för säkerhetsmeddelanden: cve, ghsa, mal och other; rule_id (regler för SAST, hemligheter och felkonfiguration i images, regler för paketpolicy); licence; signature (ClamAV) | |
component | Den berörda komponenten: purl, name, version, ecosystem, fixed_version, sha256. Null för SAST och hemligheter. För en sårbarhet i en image har paketet i imagen ett namn men ingen purl; imagens purl finns i location.image.purl. | |
location | kind (repository, image, package eller upload), repository, path, line, commit_sha; image {reference, purl, proxied, tags}, med reference i den publika formen frem.sh/<org>/<image>@sha256:<digest>; package {type, owner, name, version, file, sha256}; upload_kind | xdm.target.resource.name (reference, repo eller package) |
policy | Bara händelser för paketpolicy: {verdict, reasons} | |
known_exploited | På varje händelse: {cve_ids, sources} när fyndet är en sårbarhet i ett beroende eller en image som är listad som utnyttjad i praktiken (sources: euvd för ENISA EUVD, cisa_kev för CISA KEV), annars null. CVE-id:na är fyndets egna eller alias för dess säkerhetsmeddelande. |
Exempel (formatet json):
{
"id": "6f0b1f7e-2c55-4c55-9a0e-6b8f8f7e1a10",
"tenant_id": "11111111-1111-1111-1111-111111111111",
"actor": "system:finding-lifecycle",
"action": "security.finding.opened",
"fields": {
"alert_category": "dependency_vulnerability",
"alert_description": "Command injection in lodash",
"alert_name": "GHSA-35jh-r3h4-6jhm in lodash 4.17.20",
"alert_severity": "high",
"component": { "purl": "pkg:npm/lodash@4.17.20", "name": "lodash", "version": "4.17.20", "ecosystem": "npm", "fixed_version": "4.17.21", "sha256": null },
"cvss": { "score": 7.2, "vector": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H" },
"finding": {
"id": "3c1d2a8e5b0f4e6a9d7c1b2a3e4f5a6b",
"key": "dep:acme/api:GHSA-35jh-r3h4-6jhm:npm:lodash@4.17.20",
"type": "dependency_vulnerability",
"source": "dep-scan",
"state": "open",
"previous_state": null,
"transition": "opened",
"transition_reason": "new",
"first_seen_at": "2026-10-08T09:12:40.120Z",
"state_changed_at": "2026-10-08T09:13:02.457Z",
"row_id": "0d6c7c43-2b1f-4f5e-8a3e-0b2f9f1c2d3e",
"console_url": "https://frem.sh/acme/_admin/code-security/deps",
"dismissal": null
},
"identifiers": { "cve": [], "ghsa": ["GHSA-35jh-r3h4-6jhm"], "mal": [], "other": [], "rule_id": null, "licence": null, "signature": null },
"known_exploited": null,
"location": { "kind": "repository", "repository": "acme/api", "path": "package-lock.json", "line": null, "commit_sha": null, "image": null, "package": null, "upload_kind": null },
"org": "acme",
"original_alert_id": "3c1d2a8e5b0f4e6a9d7c1b2a3e4f5a6b"
},
"created_at": "2026-10-08T09:13:02.457Z",
"hash_chain": null
}Fyndhändelser ingår inte i granskningens hashkedja. En ändring av en endpoints inställning för fyndhändelser, och en begäran om ögonblicksbild, är granskningshändelser (siem.endpoint.finding_events.update, siem.endpoint.finding_snapshot).
Fyndhändelser i CEF
En fyndhändelse använder CEF-mappningen med de här skillnaderna:
| CEF | Värde |
|---|---|
| Name | alert_name (Signature ID förblir action) |
| Severity | Medan fyndet är öppet (opened, reopened, dismissal_expired, exception_revoked, dismissal_revoked, snapshot): 10 critical, 8 high, 5 medium, 3 low, 1 informational. 1 för dismissed och resolved, så att goda nyheter inte utlöser ett larm som fyndet gjorde. known_exploited är minst 8, oavsett fyndets allvarlighetsgrad |
cat | alert_category (fyndtypen) |
msg | alert_name |
outcome | finding.state |
reason | finding.transition_reason |
start | finding.first_seen_at, millisekunder sedan epoken |
cs5Label=findingId, cs5 | original_alert_id |
cs6Label=purl, cs6 | component.purl, när det finns en |
flexString1Label=identifiers, flexString1 | CVE-, GHSA-, MAL- och andra id:n, regel-id, licens och signatur, kommaseparerade |
flexString2Label=location, flexString2 | Image-referensen, repot eller paketet |
cfp1Label=cvssScore, cfp1 | cvss.score, när det finns ett |
filePath | location.path |
fileHash | component.sha256 (antivirusfynd, paketfiler) |
cs3 och cs4 är tomma, som för åtkomsthändelser för registret. Över syslog följer allvarlighetsgraden enligt RFC 5424 CEF-allvarlighetsgraden som för granskningshändelser: ett kritiskt fynd skickas med 2 (critical), ett löst med 6 (informational).
Syslog över TLS
En endpoint-URL på formen syslog+tls://host[:port] (standardport 6514) levererar över syslog i stället för HTTPS:
- Transport: TCP med TLS 1.2 eller senare, med ramning genom oktetträkning (RFC 5425). fremforge verifierar mottagarens certifikat mot den publika uppsättningen CA:er, för värdnamnet i URL:en, så mottagaren behöver ett publikt betrott certifikat. Använd port 6514 eller 443; andra portar går inte att nå från fremforge.
- Meddelande: ett meddelande enligt RFC 5424 per händelse,
<PRI>1 TIMESTAMP frem.sh fremforge - MSGID - MSG. PRI är facility 13 (log audit) × 8 + allvarlighetsgrad: 2 för CEF-allvarlighetsgrad 9, 4 för 7, 5 för 5, annars 6. MSGID äraction(upp till 32 tecken). MSG är en UTF-8-byteordningsmarkering följd av CEF-raden (formatetcef) eller JSON-händelsen (alla andra format). - Samling: upp till max händelser meddelanden inom max storlek per begäran delar en anslutning.
- Ingen bekräftelse. Syslog har inget svar på applikationsnivå. En samling räknas som levererad när varje meddelande har skrivits och mottagaren har stängt anslutningen korrekt. En nekad anslutning, ett TLS-fel eller en mottagare som stänger innan samlingen är skriven gör att samlingen misslyckas, och den skickas om i sin helhet. En mottagare som accepterar anslutningen och sedan kastar det den läser ser levererad ut. Deduplicera på
externalId(CEF) ellerid(JSON). - Auth-headern och HMAC-signaturen gäller bara HTTPS. Begränsa lyssnaren till fremforge med TLS och, om du behöver, en tillåtelselista för källadresser.
Verifiera signaturen
HMAC:en beräknas över exakt de byte som bodyn består av, i den ordning de kommer. Tolka och serialisera inte om innan du verifierar.
Mottagare i Node.js (tidsstämpel och body):
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(req, body) {
const sig = req.headers['x-fremforge-signature'];
const ts = req.headers['x-fremforge-timestamp'];
if (!sig?.startsWith('sha256=') || !ts) throw new Error('missing headers');
// 5-minute replay window. Adjust to your tolerance.
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) throw new Error('stale');
const expected = createHmac('sha256', SHARED_SECRET)
.update(`${ts}.${body}`)
.digest('hex');
const got = sig.slice(7); // strip "sha256="
if (expected.length !== got.length) throw new Error('bad sig');
if (!timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(got, 'hex'))) {
throw new Error('bad sig');
}
}Mottagare i Python (tidsstämpel och body):
import hmac, hashlib, time
def verify(headers, raw_body: bytes) -> None:
sig = headers.get('X-Fremforge-Signature', '')
ts = headers.get('X-Fremforge-Timestamp', '')
if not sig.startswith('sha256=') or not ts:
raise ValueError('missing headers')
if abs(time.time() - int(ts)) > 300:
raise ValueError('stale')
expected = hmac.new(
SHARED_SECRET.encode(),
f'{ts}.'.encode() + raw_body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, sig[7:]):
raise ValueError('bad sig')I läget endast body utelämnar du tidsstämpeln: HMAC-SHA256 över raw_body.
Felhantering, omförsök och omsändning
- Varje begäran har en tidsgräns på 10 sekunder.
- Ett svar som inte är
2xx, en tidsgräns som löper ut eller ett anslutningsfel lämnar händelserna i kön. Endpointen backar: 5 sekunder, fördubblat för varje fel i följd, högst 10 minuter, med ±20 % jitter. När mottagaren svarar igen töms kön i ordning. - Efter 5 fel i följd visar endpointen
failingmed orsaken till det senaste felet. Leveransen fortsätter att försöka; ingenting behöver slås av och på. Att inaktivera en endpoint gör att nya händelser inte längre köas för den; att aktivera den igen återupptar leveransen direkt. - En
413halverar samlingen. En enskild händelse som mottagaren ändå nekar som för stor kasseras och räknas. - Olevererade händelser sparas under organisationens lagringstid för granskning (Organisationsadmin → Auth-policy; 90 dagar som standard), och högst 200 000 per endpoint. Äldre kasseras och räknas.
- Kortet Leverans på endpointens sida (och API:ts block
delivery,dropped_total) visar antalet väntande händelser, den äldsta av dem, nästa försök, och totalt levererade och kasserade. En händelse kasseras, och levereras aldrig till den endpointen om du inte skickar om den, bara i dessa fall: mottagaren svarade413på just den händelsen ensam; den väntade fortfarande när den blev äldre än din lagringstid för granskning; endpointen hade redan 200 000 väntande händelser, och då går de äldsta först; eller själva granskningshändelsen finns inte längre, vilket inte händer inom lagringstiden. Inget annat kasserar en händelse: en mottagare som felar eller är inaktiverad gör bara att händelser väntar. - Skicka om köar dina granskningshändelser igen från en tidpunkt (och, när fyndhändelser är tillgängliga, på en endpoint som inkluderar dem, dess fyndhändelser i samma fönster, aldrig äldre än när endpointen började ta emot dem): Skicka om från (UTC) på endpointens sida, eller
POST /_app/api/v1/orgs/<org>/siem/endpoints/<id>/replaymed{"since": "<ISO 8601>", "until": "<ISO 8601, optional>"}.sincefår ligga högst din lagringstid för granskning bakåt i tiden. En begäran köar högst 100 000 händelser och returnerarnext_sincenär fler återstår. Händelser som fortfarande väntar köas inte två gånger; omsända händelser behåller sittid.
Leveransen sker minst en gång: ett omförsök efter ett förlorat svar, eller en omsändning, skickar en händelse igen. Deduplicera på id.
Populära SIEM-system
| SIEM | Tar emot över HTTPS via | Inställningar i fremforge | Direkt från fremforge? |
|---|---|---|---|
| Cortex XSIAM | HTTP log collector | Auth-header Authorization: <API key>, format json | Ja |
| Splunk | HTTP Event Collector, /services/collector/raw | Auth-header Authorization: Splunk <token>, format ndjson | Ja |
| Microsoft Sentinel | Azure Monitor Logs Ingestion API | Kräver en kortlivad Entra ID-token | Nej, relä |
| Elastic | HTTP endpoint-input i Elastic Agent / Filebeat | Signaturen omfattar endast body, eventuellt en hemlig header | Ja |
| IBM QRadar | Loggkälla av typen HTTP Receiver, eller en loggkälla av typen TLS Syslog | Format cef | Ja, om lyssnaren går att nå publikt |
| Google SecOps | Webhook-feed | URL med ?key=&secret=, format ndjson | Ja |
Cortex XSIAM
Tar emot JSON från tredje part via en HTTP log collector (Settings → Data Sources & Integrations → + Add New → HTTP). Välj Log Format: JSON; den Vendor och Product du anger namnger datasetet <vendor>_<product>_raw. Insamlarens URL har formen https://api-<tenant external URL>/logs/v1/event och tar sin API-nyckel i Authorization (Palo Altos dokumentation).
Konfiguration: URL = insamlarens URL; auth-header Authorization = insamlarens API-nyckel; format json.
Exempel på regler för parsning och datamodell för fremforge_audit_raw
Parsningsregel (Settings → Configurations → Data Management → Parsing Rules), så att _time är fremforges händelsetid i stället för ankomsttiden. created_at har alltid tre decimaler och ett Z:
[INGEST:vendor="fremforge", product="audit", target_dataset="fremforge_audit_raw", no_hit=keep]
alter _time = parse_timestamp("%Y-%m-%dT%H:%M:%E3SZ", created_at);Datamodellregel (Settings → Configurations → Data Management → Data Model Rules):
[MODEL: dataset="fremforge_audit_raw"]
filter action != "fremforge.siem.test"
| alter
xdm.event.id = id,
xdm.event.type = "fremforge_audit",
xdm.event.original_event_type = action,
xdm.event.operation_sub_type = action,
xdm.event.description = to_json_string(fields),
xdm.observer.vendor = "fremforge",
xdm.observer.product = "fremforge",
xdm.observer.unique_identifier = tenant_id,
xdm.source.user.identifier = actor;Båda förutsätter att XSIAM lagrar JSON-nycklarna på toppnivå som kolumner. Om ditt dataset bara visar _raw_log, läs värdena med json_extract_scalar(_raw_log, "$.created_at") och så vidare.
Datamodellregel för fyndhändelser
Fyndhändelser (kommer snart) hamnar i samma dataset. Deras larmfält är namngivna efter XDM:s, så de mappas ett till ett. När du använder regeln tillsammans med granskningsregeln ovan, lägg till | filter not (action ~= "^security\.finding\.") i granskningsregeln så att varje händelse modelleras en gång:
[MODEL: dataset="fremforge_audit_raw"]
filter action ~= "^security\.finding\."
| alter
xdm.event.id = id,
xdm.event.type = "fremforge_finding",
xdm.event.original_event_type = action,
xdm.event.outcome = json_extract_scalar(fields, "$.finding.state"),
xdm.event.outcome_reason = json_extract_scalar(fields, "$.finding.transition_reason"),
xdm.alert.name = json_extract_scalar(fields, "$.alert_name"),
xdm.alert.category = json_extract_scalar(fields, "$.alert_category"),
xdm.alert.severity = json_extract_scalar(fields, "$.alert_severity"),
xdm.alert.description = json_extract_scalar(fields, "$.alert_description"),
xdm.alert.original_alert_id = json_extract_scalar(fields, "$.original_alert_id"),
xdm.target.resource.name = coalesce(json_extract_scalar(fields, "$.location.image.reference"), json_extract_scalar(fields, "$.location.repository"), json_extract_scalar(fields, "$.location.package.name")),
xdm.observer.vendor = "fremforge",
xdm.observer.product = "fremforge",
xdm.observer.unique_identifier = tenant_id,
xdm.source.user.identifier = actor;HTTP log collector tar emot de här händelserna som loggposter. För att skapa ett larm i XSIAM från dem, lägg till en korrelationsregel på datasetet, till exempel på action = "security.finding.opened" och alert_severity critical eller high, och gruppera på original_alert_id så att de senare händelserna om samma fynd (dismissed, resolved) knyts till det.
Splunk
Tar emot via HTTP Event Collector. /services/collector/raw tar bodyn som den är; ange sourcetype=_json i URL:en så att varje rad blir en händelse. Token skickas i Authorization: Splunk <token>. Med indexer acknowledgement påslaget behöver raw-endpointen också ett kanal-GUID; lägg det i URL:en som channel=<GUID> (Splunks dokumentation).
Konfiguration: URL https://<hec-host>:8088/services/collector/raw?sourcetype=_json; auth-header Authorization = Splunk <token>; format ndjson.
Microsoft Sentinel
Tar emot egen JSON via Azure Monitors Logs Ingestion API, till en egen tabell _CL som definieras av en datainsamlingsregel (DCR). Begäran går till {endpoint}/dataCollectionRules/{dcrImmutableId}/streams/{streamName}?api-version=2023-01-01 med en bearer-token från Entra ID via flödet client credentials (scope https://monitor.azure.com/.default), och bodyn måste vara en JSON-array (Microsofts dokumentation). Det äldre HTTP Data Collector API nådde slutet av sin support den 14 september 2026; bygg inte på det.
Konfiguration: token löper ut efter ungefär en timme, så en statisk auth-header kan inte bära den. Placera reläet emellan: det hämtar och cachar token och vidarebefordrar bodyn. Använd formatet json_array, så att bodyn redan är den array som API:t vill ha. Mappa created_at till TimeGenerated i DCR-transformationen.
Elastic
Tar emot via HTTP endpoint-inputen i Elastic Agent (integrationen Custom HTTP Endpoint Logs) eller Filebeat. Den lyssnar på en egen port (standard 8000), kan terminera TLS och kan kontrollera en HMAC över bodyn (Elastics dokumentation).
Konfiguration: signaturen omfattar endast body; ange i inputen hmac.header: X-Fremforge-Signature, hmac.key: <the endpoint's HMAC secret>, hmac.type: sha256 och hmac.prefix: "sha256=". För att även använda inputens alternativ för en hemlig header, ange samma headernamn och värde som endpointens auth-header. Format json, eller ndjson för samlingar. Indexera actor, action och tenant_id som keyword.
IBM QRadar
Tar emot bodyn i HTTP POST via en loggkälla som använder protokollet HTTP Receiver. QRadar kör en HTTP- eller HTTPS-server på en Event Collector (standardport 12469); HTTPS kräver ett certifikat utfärdat av en CA, och ömsesidig TLS är valfri (IBM:s dokumentation). CEF-rader tolkas av QRadars Universal CEF DSM.
Konfiguration: URL = mottagarens publika HTTPS-URL, eller syslog+tls://<collector>:6514 för en loggkälla av typen TLS Syslog; format cef. fremforge kan inte visa upp ett klientcertifikat, så lämna ömsesidig TLS avstängd. När Event Collector inte går att nå från internet vidarebefordrar en omvänd proxy eller ett syslog-relä i din kant till den.
Google SecOps (Chronicle)
Tar emot via en webhook-feed (Settings → Feeds → Add new, källtypen Webhook, välj sedan en loggtyp). Begäranden går till feedens endpoint ...:importPushLogs med en API-nyckel och feedens hemlighet, som headers X-goog-api-key och X-Webhook-Access-Key, eller som ?key=<API key>&secret=<secret>. Flera radseparerade JSON-objekt kan dela en begäran, upp till 4 MB (Googles dokumentation). Det finns ingen färdig parser för fremforge; du behöver en loggtyp och en parser för de här händelserna.
Konfiguration: URL = feedens endpoint med ?key=<API key>&secret=<secret> (lagras krypterad och visas med ***); format ndjson; max storlek per begäran högst 4096 KiB.
Ett relä som verifierar signaturen
Sentinel, och varje mottagare som behöver mer än en header eller en kortlivad token, använder en liten tjänst: verifiera fremforges signatur och vidarebefordra sedan bodyn oförändrad med SIEM-systemets inloggningsuppgift. Den här versionen använder bara Pythons standardbibliotek; ange TARGET_URL och TARGET_AUTH för ditt SIEM. För Sentinel lägger du till hämtningen av token; med formatet json_array är bodyn redan den array den vill ha. MAX_BODY motsvarar standardstorleken 1 MiB per begäran. Reläet vidarebefordrar en korrekt signerad händelse med inloggningsuppgiften, svarar 401 på en felaktig signatur eller en tidsstämpel som är mer än fem minuter gammal, och 502 när målet inte går att nå.
"""fremforge SIEM forwarder -> SIEM relay: verify the signature, forward with the SIEM's credential."""
import hashlib
import hmac
import os
import time
import urllib.error
import urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
SECRET = os.environ["FREMFORGE_SIEM_SECRET"].encode()
TARGET_URL = os.environ["TARGET_URL"] # e.g. https://api-<tenant>/logs/v1/event
TARGET_AUTH = os.environ["TARGET_AUTH"] # e.g. "<XSIAM API key>" or "Splunk <HEC token>"
MAX_SKEW_S = 300
MAX_BODY = 1024 * 1024
class Relay(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get("Content-Length") or 0)
if length <= 0 or length > MAX_BODY:
return self.reply(413)
body = self.rfile.read(length)
sig = self.headers.get("X-Fremforge-Signature", "")
ts = self.headers.get("X-Fremforge-Timestamp", "")
if not sig.startswith("sha256=") or not ts.isdigit():
return self.reply(401)
if abs(time.time() - int(ts)) > MAX_SKEW_S:
return self.reply(401)
expected = hmac.new(SECRET, ts.encode() + b"." + body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig[7:]):
return self.reply(401)
req = urllib.request.Request(
TARGET_URL,
data=body,
method="POST",
headers={"Authorization": TARGET_AUTH, "Content-Type": "application/json"},
)
try:
# fremforge gives up after 10 s; answer before that.
with urllib.request.urlopen(req, timeout=4) as res:
status = res.status
except urllib.error.HTTPError as e:
status = e.code
except OSError:
status = 0
# Pass failure back, so fremforge keeps the event and retries it.
self.reply(200 if 200 <= status < 300 else 502)
def reply(self, code):
self.send_response(code)
self.send_header("Content-Length", "0")
self.end_headers()
def log_message(self, fmt, *args):
pass
if __name__ == "__main__":
ThreadingHTTPServer(("0.0.0.0", int(os.environ.get("PORT", "8080"))), Relay).serve_forever()Kör det bakom din vanliga TLS-terminering: fremforge levererar bara till publika https://-URL:er. Håll reläets svarstid under 10 sekunder, och returnera något annat än 2xx när SIEM-systemet nekade händelsen. Ett relä som alltid svarar 200 tappar händelser: fremforge betraktar ett 2xx som levererat och tar bort händelsen ur kön.
Generisk webhook
Vilken HTTPS-endpoint som helst som returnerar 2xx när den har accepterat begäran. Verifiera med kodexemplen ovan. Det passar SIEM-system som du driftar själv (Wazuh, Graylog, …) och HTTP-intag som Logflare eller Datadog.
Hälsa på operatörssidan
fremverks operatörer ser varje endpoints tillstånd på hälsosidan för SIEM i operatörsportalen. Ett larm utlöses när en endpoints äldsta olevererade händelse är mer än 24 timmar gammal, och vi kontaktar organisationens ägare. Larma också i ditt SIEM när händelser från fremforge uteblir.
Vad som inte går över protokollet
- Syslog över UDP eller okrypterad TCP. Syslog stöds bara med TLS.
- Klientcertifikat (ömsesidig TLS) mot din mottagare.
- Mer än en egen header per endpoint.
- Utskick mellan tenants. Varje endpoint är knuten till en tenant. Det finns ingen spegel på operatörssidan; händelser från din organisation lämnar aldrig kedjan för din tenant.
Relaterat
- Granskningskedjans integritet: vad händelserna faktiskt innehåller, och hur du verifierar kedjan från början till slut.
- Webhooks: samma protokollsemantik för händelser i repon och organisationer, signerade och levererade på samma sätt.
- Säkerhetsmodell för hostade runners: var händelser som runners skickar kommer ifrån i kedjan.