Data-plane key functions

Internals live

Heimdall's code carries no explanatory comments by design — behavior is documented here instead. This page maps the request flow to the functions that carry it, so a contributor can find the right place fast. It lists the load-bearing entry points per stage, not every helper.

All paths are under dataplane/security-engine/. The connection loop lives in internal/proxy/pipeline.go; the entry binary is cmd/security-engine/main.go, which parses flags, builds the telemetry sink and plugin host, and calls proxy.New(...).Run(ctx).

Ingress · internal/proxy, internal/httpengine, internal/proxyproto

Function Location Role
Proxy.Run proxy/proxy.go:188 Accept loop; MaxConns admission gate; spawns serve per connection
Proxy.serve proxy/pipeline.go:36 Per-connection state machine: PROXY header → geo/anon → conn-enrich → H2 sniff → keep-alive loop → forward
proxyproto.ReadV2WithTLVs proxyproto/proxyproto.go:55 Parse PROXY v2 + TLVs into a fingerprint (when a plugin enricher is present)
proxyproto.parseTLVs proxyproto/proxyproto.go:78 TLV walk: 0x02 SNI, 0xE0 JA3, 0xE1 JA4, 0x20 nested SSL
Proxy.fillHead proxy/pipeline.go:372 Read until the head is framable; else errDegrade → raw splice
Parser.Parse httpengine/httpengine.go:49 H1 head parser → fills canonical.Request, sets anomaly flags
httpengine.resolveFraming httpengine/httpengine.go:198 Content-Length vs Transfer-Encoding resolution; sets smuggling anomalies
Proxy.sniffH2 / serveH2 proxy/h2.go:30 / :62 HTTP/2 detection (h2c/ALPN) and serving (own HPACK)
Proxy.relayWebSocket proxy/websocket.go:33 Inspect frames after a 101 upgrade

Normalization · internal/normalize

Function Location Role
Normalizer.Request normalize/normalize.go:23 Normalize path + every param raw → norm, backed by the request scratch arena
Normalizer.apply normalize/normalize.go:49 The multi-pass loop: percent → entity → NFKC + homoglyph, then whitespace + lowercase
percentDecode normalize/normalize.go:155 %XX decode, optional + → space
decodeEntities normalize/normalize.go:191 Named (generated table) + numeric/hex HTML entities
foldUnicode normalize/normalize.go:85 NFKC then confusable/homoglyph → ASCII
StripSQLComments normalize/normalize.go:333 Engine-local comment strip (used by the SQL engine)
Normalizer.TrackRawSpan normalize/track.go:11 Norm→Raw offset map for evidence capture

Parsers · internal/parsers

Orchestrated by Proxy.analyze (proxy/pipeline.go:416), content-type gated. Each calls canonical.AddParam(src, name, raw).

Function Location Source
Query / Form parsers/query.go:12 / :16 Query / Form
Headers parsers/headers.go:22 Header / Cookie
JSON parsers/json.go:30 JSON (recursive flatten)
Multipart parsers/multipart.go:20 Multipart
GraphQL / XML / SOAP parsers/graphql.go:21 / xml.go:28 / soap.go:20 GraphQL / XML / SOAP
JWT parsers/jwt.go:21 JWT (feeds auth alg:none)
WebSocketHandshake / IsWebSocketUpgrade parsers/websocket.go:219 / :237 WebSocket

Canonical model · internal/canonical

Symbol Location Role
Request canonical/canonical.go:88 The model; two arenas: scratch (normalize) + arena (parser output)
Source enum canonical/canonical.go:31 Param origin tags
AnomalyFlags canonical/canonical.go:50 13-flag bitset (smuggling + H2 misuse)
Request.Reset canonical/canonical.go:114 Pooled reuse (reqPool)
AddParam canonical/canonical.go:163 Append a flattened param

Features · internal/features

Function Location Role
features.Extract features/features.go:27 Per-request maxima: depth, entropy, tokens, non-ASCII, param count
shannon / log2fixed features/features.go:90 / :108 Fixed-point Shannon entropy
Tokenize features/features.go:132 Token-run callback shared with SQL/XSS engines

Detection · internal/detect

Symbol Location Role
Registry.Analyze detect/detect.go:101 Iterate engines sequentially over the params
EngineID / Category detect/detect.go:9 / :28 Engine + category enums; EnginePluginBase = 128
engines detect/{sql,xss,ssrf,cmdi,traversal,header,protocol,api,rate,auth}/… One package per engine (see Detection)
custom.Analyze detect/custom/custom.go Operator + signed-feed rules

Registry wiring: proxy/proxy.go:127. Custom-rule and plugin findings are added in Proxy.analyze (proxy/pipeline.go:441 / :445).

Risk & decision · internal/risk, internal/decision

Function Location Role
risk.Engine.Assess risk/risk.go:65 Risk × weight/256 × conf/100, saturating; adds anomaly weights → Assessment
decision.Engine.Decide decision/decision.go:86 Map total → Action via thresholds; clamp to Monitor in shadow
actionFor decision/decision.go:109 The threshold ladder

Telemetry · internal/telemetry

Function Location Role
Proxy.buildEvent proxy/pipeline.go:587 Build the event from the analyzed request
AsyncSink.Emit telemetry/async.go:36 Non-blocking enqueue (drops + counts if full)
encode telemetry/async.go:65 Hand-rolled JSONL encoder
Anonymizer.HashIP telemetry/anonymize.go:39 HMAC-SHA256(IP, key) → uint64; IP never stored

The one-screen sequence

Proxy.Run → Proxy.serve
  ReadV2WithTLVs (IP + JA3/JA4/SNI)
  HashIP + geo lookup
  EnrichConnection (plugins)                      ── conn findings cached
  keep-alive loop:
    fillHead → Parser.Parse → canonical.Request (+anomalies)
    router.match(Host) → site bundle
    access.Check ──deny──► emitAccess → writeBlock
    Proxy.analyze:
      parsers.* → Params
      Normalizer.Request → Param.Norm
      Registry.Analyze + custom.Analyze + plugin findings
      features.Extract
      risk.Assess → decision.Decide  (shadow clamps to Monitor)
      buildEvent → AsyncSink.Emit
    Action → challenge | block | throttle | forward → origin