Help us shape the future of Go! Take the 2026 Go Developer Survey

camada

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 30 Imported by: 0

README

camada-go

camada for Go: enforces the tenant snapshot inline (your ordered custom rules, then allow, block, challenge), serves a first-party proof-of-work challenge page and beacon, records the outcomes your handlers know (Track), and ships wire events in batches off the request path. One module, one public package, a net/http middleware — http.ListenAndServe(":8080", camada.Handler(mux)) — that any router built on http.Handler sits behind. Fails open by design: a camada outage or bug never 5xxes your app.

Not yet tagged — use it from a sibling checkout with a replace directive, as camada-go-example does; publishing is one decision with the npm packages (SDK-G01). Go 1.23 or newer, no dependencies outside the standard library.

Quickstart

import "github.com/camada-app/camada-go"

mux := http.NewServeMux()
mux.HandleFunc("GET /{$}", home)
http.ListenAndServe(":8080", camada.Handler(mux))   // outermost, so camada answers before routing

Env (the key is printed once when you create a project in the app):

CAMADA_KEY=<ingest_token>.<snap_token>
CAMADA_INGEST_URL=http://localhost:8787        # dev only; defaults to production ingest

camada.Handler shares one lazy engine (camada.Default()) built from the environment on the first request. That build starts the snapshot poll on a goroutine and never blocks, so the request that triggered it is answered cold: it passes (fail open), and so does anything else that arrives before that first poll lands (a few hundred milliseconds against a local analyst; snapshot-size and network bound). To enforce from request 1, build the engine eagerly at startup and wait for the boot poll — Snap.Refresh() alone is not it, the boot poll already holds the single-in-flight lock:

import "github.com/camada-app/camada-go/snapshot"

cam := camada.Default()   // builds the engine; the boot poll is already running on its goroutine
if cam.Snap != nil {      // nil when CAMADA_KEY is unset or CAMADA_DISABLED=1
	deadline := time.Now().Add(5 * time.Second)
	for cam.Snap.Verdict(snapshot.MatchInput{IP: "0.0.0.0"}).Reason == "cold" && time.Now().Before(deadline) {
		time.Sleep(10 * time.Millisecond)   // bounded: an unreachable analyst leaves it cold, and the app still fails open
	}
}
defer cam.Stop(context.Background())   // drains the last event batch, at most 500 ms

Without CAMADA_KEY the engine is inert (one log line, no requests, no enforcement). An app that reads its own config builds the engine itself and hands it in:

cam := camada.New(camada.Options{Env: map[string]string{"CAMADA_KEY": myKey, "CAMADA_INGEST_URL": myIngest}})
http.ListenAndServe(":8080", cam.Handler(mux))

What it does per request

  1. Keeps the snapshot fresh. A Go server is a long-lived process, so the default is one poll goroutine at the cadence your tenant config sets (poll_seconds), with ETag/304 and gzip on the wire. CAMADA_SERVERLESS=1 drops the poll goroutine: each request checks staleness, and a stale one kicks a single refresh off the request path. Every poll and event batch carries x-camada-sdk: @camada/go/<version>, and polls ask for snapshot v5 (x-camada-snapshot: 5) — the container that carries your ordered custom rules.

  2. Resolves the client from the socket peer (r.RemoteAddr), combined with X-Forwarded-For only under your tenant's trusted-proxy config (or CAMADA_TRUSTED_PROXY locally). A forwarded header on its own is never the ip: any caller can set it. Nothing needs disabling for proxies — net/http itself never rewrites the peer — so a reverse proxy in front of the app is declared, not inferred. Entries may carry a port (ip:port, [v6]:port, as Azure Application Gateway writes them) or the ::ffff:a.b.c.d form, and are trimmed of spaces and tabs only. The resolved ip is always a valid address or none. Under cidrs (and the dashboard's Cloudflare option, stored as cidrs) the socket peer must itself be one of the trusted ranges or a loopback address (a local front end or sidecar); otherwise the peer is the client and X-Forwarded-For is ignored, so a direct hit on your origin cannot pick its own ip. The peer is the host of net.SplitHostPort(r.RemoteAddr). What a RemoteAddr it cannot split means depends on the listener, read from http.LocalAddrContextKey:

    • TCP listener: the peer could not be read (@, "", or a bare ip), and the request has no ip at all, never the X-Forwarded-For walk.
    • unix-socket listener: no peer. The walk runs over X-Forwarded-For alone and trusts your network edge, so the app must not be reachable around it.
    • no local address (a Lambda / API Gateway adapter, a request built with http.NewRequest): no socket. "" is no peer and walks X-Forwarded-For; a bare ip the platform put there is the peer; anything else is not an ip and resolves to none.

    Mount camada.Handler outside chi's middleware.RealIP and gorilla's handlers.ProxyHeaders. Both rewrite r.RemoteAddr to a bare ip taken from a forwarded header. Inside them, camada sees an unreadable peer on a TCP listener: no ip, so ip blocks and challenges fail open (camada logs a warning, at most once a minute). Outside them, camada reads the real socket peer and your trusted-proxy config decides; the app behind still gets the rewritten RemoteAddr.

  3. Enforces before anything else, beacon endpoints included: your ordered custom rules first (first match wins; they read ip, path, user-agent and request headers), then allow → block → challenge. A block answers 403 Forbidden with x-block-reason, x-block-version and, when a rule decided, x-block-rule; its event ships with blk (and rl). A warn rule passes and stamps wrn; a skip rule passes with nothing stamped. Cold (no snapshot yet) passes: fail open.

  4. Challenge: a challenge verdict gets the self-contained proof-of-work page (a non-HTML request passes to the app with x-camada-challenge: 1, no-store on cache-control and the CDN cache headers, and a chs: 1 event); POST /__camada/challenge verifies the solution, sets _cch (bound to the ip, one hour) and 302s back. A request whose ip cannot be resolved is never challenged. The page carries its own Content-Security-Policy (its inline solver and style by hash, nothing else) and replaces any policy a middleware already put on the response, so a strict site CSP cannot block it.

  5. Serves the beacon: GET /_cam/b.js (the @camada/browser build, embedded) and POST /_cam/fp (≤ 32 KB, relayed onto the event batch as a sig: 1 row with the ip camada resolved). Both fall through to your app when the tenant switched the beacon off.

  6. Runs your app with X-Rid (the rid of the request's event row; never on a 101 handshake) and the _sfp session cookie on its response, and when the response is done ships one redacted event: method, host, path, scrubbed query (and Referer query and fragment), status, latency, route pattern (r.Pattern), header names/sizes, the auth scheme (never the credential), cookie count (never values). A panic in your handler ships as st: 500 and propagates unchanged (net/http answers as it always does); a hijacked connection ships as st: 101. A 103 Early Hints is not the status: the final one is.

Options

camada.New(camada.Options{...}); everything credential-shaped comes from the environment.

option default meaning
Env the process environment where CAMADA_* are read from (a map[string]string)
Transport net/http the func(HTTPRequest) HTTPResponse that reaches the analyst (tests inject a fake)
Refresh server-steered poll cadence; set, it is pinned
Challenge nil (= true) serve the proof-of-work page for challenge verdicts (CAMADA_CHALLENGE=0 too)
ChallengePath /__camada/challenge where the page posts its solution
SnapshotVersion 5 4 drops your custom rules; 3 the allow/challenge sides too
ScriptPath / FPPath /_cam/b.js / /_cam/fp the beacon endpoints; keep them in one directory

Env: CAMADA_KEY (or CAMADA_TOKEN + CAMADA_SNAPSHOT_TOKEN), CAMADA_INGEST_URL, CAMADA_SNAPSHOT_URL, CAMADA_TRUSTED_PROXY (none | vercel | hops:N | cidrs:a,b), CAMADA_SERVERLESS=1, CAMADA_CHALLENGE=0, and the kill switch CAMADA_DISABLED=1 (checked per request; set at boot, no goroutines start at all).

The first-party beacon

fmt.Fprintf(w, "<html><head>%s</head>…", camada.ScriptTag(r))

The tag is <script src="/_cam/b.js?r=<rid>" async>, so the beacon joins the page view that served it. Move both paths with ScriptPath / FPPath when /_cam/ is not yours; the script derives the post path from its own URL, so the two must share a directory.

App-context events

camada.Track(r, "login_failed", email)   // "" when there is no identifier

The identifier is HMAC-hashed in-process with your ingest token; the raw value never reaches the queue. Track never panics. Outside the middleware (a request it did not run for) the outcome still ships, with no rid/sid/ip to join on — and it builds the default engine if nothing has yet. The event name is free-form; the analyst's app-context rules read this vocabulary:

event when
login_failed / login_succeeded a login attempt settled; pass the user so attempts per account can be counted
signup an account was created
password_reset a reset was requested
mfa_failed a second factor was rejected
payment_failed / payment_succeeded a payment authorisation settled
coupon_failed a promo/voucher code was rejected

A route you gate yourself: if camada.ServeChallenge(w, r) { return } writes the page for a page load until the browser holds a valid _cch, then reports false so you render your own. A non-HTML request also gets false: it is signalled (x-camada-challenge: 1, no-store, chs: 1). camada.FromRequest(r) exposes the request's RID, SID and resolved IP.

What this tap can see

sdk-go is an in-app tap: status, latency, session, route pattern, the beacon's browser signals and your outcomes. net/http parses headers into a map, so the event's header order (hord) is the sorted name list, not the wire's; the analyst knows what this tap can see and never scores the absence of header order, ASN, country or a TLS fingerprint against a request; ASN and country it resolves itself. Enforcement at this position covers ip, path, user-agent and header conditions — ASN, country and TLS entries fail open in-app. matches patterns are JS regexes read by Go's RE2 ((?<name> is native, [^] and \cX are translated, \d/\w/\b are ASCII as JS reads them); a spelling RE2 rejects — lookaround, backreferences — never matches here, while it does at the edge.

Not yet in this SDK: the TLS fingerprint (ja4). A Go server terminating TLS itself could read the ClientHello through tls.Config.GetConfigForClient; the event builder already carries the field, always empty, until that lands.

Deploying it

  • Every process polls its own snapshot (about 5 MB resident) and flushes its own batches; the tenant's poll_seconds keeps the cadence honest across a fleet.
  • Pending events drain when you call Stop (defer cam.Stop(ctx) in main), within half a second. No signal handlers are installed — an app owns its own shutdown — so a process killed without one drops its last batch.
  • Serverless: CAMADA_SERVERLESS=1. A cold invocation fails open and catches up on the next one.
  • Behind a reverse proxy or load balancer, declare it: CAMADA_TRUSTED_PROXY=hops:1 (or the tenant config) is what makes X-Forwarded-For count.

Fail open

Every entry point runs inside the fail-open envelope: a dead ingest drops telemetry (logged at most once a minute, through log.Default() or the logger you pass to camada.SetLogger), a corrupt snapshot keeps the previous one, a bug in the package costs the request its join, never its response. A snapshot poll that fails (any status but 200/204/304, or no answer) keeps the rules you have and is not retried sooner than max(Retry-After, 5 s), capped at the refresh interval. CAMADA_DISABLED=1 bypasses everything.

Development

gofmt -l . | (! grep .) && go vet ./... && go test ./...

The suite reads the golden snapshot fixtures from the camada-core sibling checkout (CAMADA_FIXTURES_DIR overrides) and pins the embedded beacon to camada-browser/dist/auto.global.js (CAMADA_BROWSER_DIST overrides; npm run build there first, then go run ./scripts/sync-beacon after a beacon release). Both fail by name when the checkout is missing rather than skipping.

camada-go-example is the hand-test bench (net/http on :3003), and node scripts/e2e-sdk-go.mjs in camada/edge-analyst drives it against a seeded local analyst over real HTTP, cold first request included.

Documentation

Overview

Package camada is camada for Go: inline enforcement of your tenant snapshot (ordered custom rules, then allow, block, challenge), a first-party proof-of-work challenge and beacon, app-context events, and batched wire events shipped off the request path. Fails open by design.

Quickstart (env: CAMADA_KEY, plus CAMADA_INGEST_URL in dev):

mux := http.NewServeMux()
http.ListenAndServe(":8080", camada.Handler(mux))

The engine here is the host-neutral request handling the net/http adapter (handler.go) delegates to — the Go twin of camada-python's engine.py. An adapter turns its request into a Req, asks WantsBody and reads at most that many bytes, then calls Handle: an Answer means camada fully answered the request (block, challenge, verify, beacon endpoints); a Passed means run the app, stamp the rid header and session cookie on its response, and call OnFinish(status) once when it is done. Everything runs inside the fail-open envelope: a camada bug must never 5xx the customer, and CAMADA_DISABLED=1 bypasses the SDK entirely.

Index

Constants

View Source
const (
	ScriptPath    = "/_cam/b.js"
	FPPath        = "/_cam/fp"
	FPMax         = 32 * 1024 // matches the server's /fp cap: never accept what ingest will 413
	ChallengePath = "/__camada/challenge"
	BodyMax       = 4 * 1024 // the verify form is ~120 bytes; anything larger is not ours

	SessionCookie = "_sfp"  // same cookie as the edge collector: sid/ns comparable across taps
	SessionMaxAge = 2592000 // 30 days
)
View Source
const DefaultIngestURL = "https://in.camada.app"

DefaultIngestURL is the production ingest; CAMADA_INGEST_URL overrides it.

View Source
const DefaultRefresh = snapshot.DefaultRefresh

The snapshot client owns these defaults; the root re-exports them so one value exists.

View Source
const DefaultSnapshotVersion = snapshot.DefaultSnapshotVersion

5 carries the tenant's ordered custom rules (§D3); a tenant without one is answered with the next container down.

View Source
const KillSwitchEnv = "CAMADA_DISABLED"
View Source
const SDKID = "@camada/go/" + Version

SDKID rides every snapshot poll and event batch as x-camada-sdk.

View Source
const TAP = "sdk-go"

Tap identifier this SDK claims on the wire. The server validates against its own enum and derives the capability mask itself (edge-analyst src/capabilities.js): an SDK can never grant itself capability bits, only name its position — and an unknown name is silently read as a proxy, so this literal is load-bearing.

View Source
const Version = "0.2.0"

The SDK's wire identity (SDK-03): x-camada-sdk: @camada/go/<version>. This literal is the single source: the git tag is the release version, and the sibling drift guards (camada-backend, camada-web, camada-mkt) parse this file the way they parse a package.json. Plain X.Y.Z only, and @camada/go must stay in the analyst's SDK_NAMES: its sdkId drops anything else.

Variables

This section is empty.

Functions

func BodyShape

func BodyShape(obj any) map[string]int

BodyShape is a decoded JSON body's field names and sizes, never its values; nil for a non-object.

func Handler

func Handler(next http.Handler) http.Handler

Handler wraps an http.Handler with the default engine, built from the environment on the first request (so nothing polls before the app serves).

func HashUserID

func HashUserID(userID, ingestToken string) string

HashUserID is the per-tenant pseudonym track() ships: HMAC-SHA256 under the ingest token, 32 hex chars.

func LogRateLimited

func LogRateLimited(err any)

LogRateLimited reports a suppressed error the way the SDK does: at most one line a minute.

func ParseIP4

func ParseIP4(s string) (uint32, bool)

ParseIP4 reads a dotted quad into a uint32; ok is false when the string is not a plain IPv4 address.

func ParseKey

func ParseKey(key string) (ingest, snap string, ok bool)

ParseKey splits CAMADA_KEY, `<ingest_token>.<snap_token>`; ok is false without both halves.

func ResolveClientIP

func ResolveClientIP(peer, xff string, cfg *TrustedProxy) string

ResolveClientIP is the client IP from the socket peer and X-Forwarded-For per the trusted-proxy config. The result is always a valid ip or "" (no address), in every mode (C6 v3). Anything unresolvable falls back to the peer.

XFF entries are trimmed of SP/HTAB only, read without a port (`ip:port`, `[v6]:port`, as Azure writes them) and with `::ffff:a.b.c.d` reduced to `a.b.c.d`, in every mode. The peer is unmap(peer) when that is a valid ip, else "": a non-empty peer that is not an ip (a socket path, `@`, `ip:port`, a zone id) could not be read, and the result falls back to "", never to the XFF walk. The peer is never trimmed or port-stripped.

`cidrs` (also what the dashboard's Cloudflare option stores) walks the chain `[...xff, peer]` from the right: if the peer is not a trusted proxy, the request did not come through one and the peer IS the client; XFF is ignored, since anyone hitting the origin directly can write it. A loopback peer (127.0.0.0/8, ::1, ::ffff:127.x.x.x) counts as a trusted hop: it is a local front end or sidecar (App Engine's is always 127.0.0.1), and no remote client can be one. Loopback is trusted only as the peer, never as an XFF entry; an XFF entry that is not an ip is never a trusted hop. With no peer at all (peer "": a unix-socket listener, see Handler) the walk runs over XFF alone: such a host trusts its network edge to have written XFF, and the app must not be reachable around that edge. `hops` and `vercel` trust XFF by definition and ignore the peer.

func ScriptTag

func ScriptTag(r *http.Request) string

ScriptTag is the first-party beacon tag for this request's HTML ("" when the beacon is off).

func ScrubQuery

func ScrubQuery(query string) string

ScrubQuery replaces credential-looking query parts with ~r, preserving structure and order (C6 v3): names and values are percent-decoded before testing, a bare part or a value holding a nested query with a credential segment is replaced whole, and the output keeps the raw text.

func ScrubURL added in v0.1.4

func ScrubURL(url string) string

ScrubURL is a URL (a Referer) with its query and fragment run through ScrubQuery; the path is kept.

func ServeChallenge

func ServeChallenge(w http.ResponseWriter, r *http.Request) bool

ServeChallenge writes the proof-of-work page for a route you gate yourself and reports true; false — write your own response — once the browser holds a valid _cch, when the client cannot be identified (fail open), or when the request is not a page load (it is signalled instead: x-camada-challenge: 1, no-store, and a `chs: 1` event).

func SetLogger

func SetLogger(l *log.Logger)

SetLogger routes the SDK's one-line-a-minute error report somewhere other than log.Default().

func Track

func Track(r *http.Request, event, user string)

Track ships an app-context outcome event for this request; user (an email, an account id) is HMAC-hashed in-process and may be "". Never panics. Outside the middleware the outcome still ships, with no rid/sid/ip to join on (and the default engine is built if it was not yet).

Types

type Answer

type Answer struct {
	Status  int
	Headers []Header
	Body    []byte
}

Answer: camada answered the request; the adapter writes exactly this.

type Camada

type Camada struct {
	ScriptPath, FPPath, ChallengePath string
	Env                               *Env             // nil when unconfigured: the engine is inert
	Snap                              *snapshot.Client // nil when inert or killed at boot
	Queue                             *events.Queue
	Kit                               *challenge.Kit
	// contains filtered or unexported fields
}

Camada is the engine: one per process, built from the environment.

func Configure

func Configure(o Options) *Camada

Configure replaces the default engine (stopping the old one) — for tests and explicit wiring.

func Default

func Default() *Camada

Default is the lazy singleton wired from the process environment on first use: what Handler(next) and the app helpers share when no engine was handed in.

func New

func New(o Options) *Camada

New builds an engine eagerly: the snapshot poll starts now (timer mode) and the request that arrives first is answered cold unless you wait for the boot poll (see the README's warm-up).

func (*Camada) Disabled

func (c *Camada) Disabled() bool

Disabled is true when unconfigured or when CAMADA_DISABLED=1 (read per request).

func (*Camada) Handle

func (c *Camada) Handle(req *Req, body []byte) (a *Answer, p *Passed)

Handle decides one request; exactly one of the results is non-nil, and it never panics. `body` is the request body when WantsBody asked for one; nil when the adapter refused to read it (declared or actual size over the cap) or was not asked; []byte{} when it was empty.

func (*Camada) Handler

func (c *Camada) Handler(next http.Handler) http.Handler

Handler wraps an http.Handler with this engine.

func (*Camada) NowMS

func (c *Camada) NowMS() int64

NowMS is the engine's clock in milliseconds since the epoch.

func (*Camada) ScriptTag

func (c *Camada) ScriptTag(ctx *Ctx) string

ScriptTag is the first-party beacon tag with the request's rid, for HTML templates; "" when the beacon is off or the SDK is inert.

func (*Camada) ServeChallenge

func (c *Camada) ServeChallenge(ctx *Ctx) (a *Answer)

ServeChallenge serves the challenge for this request on demand — for a route the app wants to gate itself. nil when the client already holds a valid _cch (render your own page), or when the client cannot be identified (fail open). A non-HTML request is never challenged: it is signalled (header and `chs: 1` event) and nil is returned, so the app answers.

func (*Camada) Stop

func (c *Camada) Stop(ctx context.Context)

Stop ends the poll and flush goroutines and drains pending events, for at most 500 ms or until ctx is done, whichever comes first. No signal handlers are installed: call it from your own shutdown path (`defer cam.Stop(context.Background())`).

func (*Camada) Track

func (c *Camada) Track(ctx *Ctx, event, user string)

Track ships an app-context outcome event (login_failed, signup, ...). The identifier is HMAC-hashed in-process; the raw value never reaches the queue. Never panics; a no-op when inert.

func (*Camada) WantsBody

func (c *Camada) WantsBody(method, path string) (int, bool)

WantsBody is the byte cap to read the body under, when camada itself may answer this request.

type Ctx

type Ctx struct {
	RID string
	SID string
	IP  string // "" when the client could not be identified
	// contains filtered or unexported fields
}

Ctx is the per-request context the app helpers read (ScriptTag, Track, ServeChallenge).

func FromRequest

func FromRequest(r *http.Request) *Ctx

FromRequest is the camada context of a request the middleware ran for; nil otherwise.

func (*Ctx) Signalled added in v0.2.0

func (x *Ctx) Signalled() bool

Signalled reports whether the response must carry x-camada-challenge and no-store. Adapters read it when the response headers are written, since ServeChallenge can set it from inside the app.

type Env

type Env struct {
	IngestToken  string
	SnapToken    string
	Secret       string // HMAC key for the challenge nonce/cookie — never leaves the process
	IngestURL    string
	SnapshotURL  string
	Serverless   bool
	TrustedProxy *TrustedProxy // nil = defer to server-delivered config
}

Env is the resolved configuration; credentials come from the environment only.

func ResolveEnv

func ResolveEnv(env map[string]string) *Env

ResolveEnv reads the CAMADA_* variables from a map (tests, or an app's own config); nil (the SDK stays inert, one log line) rather than an error on bad config.

type HTTPRequest

type HTTPRequest = transport.Request

HTTPRequest is one call to the analyst, as the Transport seam sees it.

type HTTPResponse

type HTTPResponse = transport.Response

HTTPResponse is the analyst's answer; Status 0 means the request never got one.

func HTTPTransport

func HTTPTransport(req HTTPRequest) HTTPResponse

HTTPTransport is the production transport over net/http: 2-3 s deadlines, transparent gzip, never panics (a network failure is a status-0 response).

type Header = events.Header

Header is one request or response header; request names arrive lower-cased.

type Options

type Options struct {
	Env             map[string]string // where CAMADA_* are read from; nil = the process environment
	Transport       Transport         // the HTTP func that reaches the analyst (tests inject a fake)
	Refresh         time.Duration     // poll cadence; 0 = server-steered, set = pinned
	ScriptPath      string            // the beacon script endpoint; default /_cam/b.js
	FPPath          string            // the beacon post endpoint; default /_cam/fp (same directory as ScriptPath)
	Challenge       *bool             // serve the proof-of-work page for challenge verdicts; nil = true (CAMADA_CHALLENGE=0 also off)
	ChallengePath   string            // where the page posts its solution; default /__camada/challenge
	SnapshotVersion int               // 5 (default) asks for your custom rules; 4 the sides only; 3 opts out of both
}

Options configure New; everything credential-shaped comes from the environment.

type Passed

type Passed struct {
	RID       string
	SetCookie string
	Ctx       *Ctx
	OnFinish  func(status int)
}

Passed: run the app. RID/SetCookie ride the response; Ctx is stored on the host request; OnFinish(status) is called once at the end. The inert Passed has every field zero.

type RemoteConfig

type RemoteConfig = config.RemoteConfig

RemoteConfig is what GET /snapshot hands back in x-camada-config (whitelisted server-side).

type Req

type Req struct {
	Method      string
	Path        string // no query
	Query       string // with the leading '?', or ""
	Host        string
	HTTPVersion string // "1.1", "2.0"; "" when unknown
	Peer        string // the socket peer the host vouches for; "" when there is none (no socket, a unix-socket listener)
	// PeerUnreadable: the host has a socket but could not read its peer for this request; the ip is then "" (C6 v3).
	PeerUnreadable bool
	HTTPS          bool
	Headers        []Header
	Route          string // the matched route pattern, when the host knows it at finish time
}

Req is what an adapter hands the engine. Header names are lower-cased; the list keeps the order the host gave (net/http normalises headers into a map, so handler.go sorts them).

func (*Req) Header

func (r *Req) Header(name string) (string, bool)

Header joins a header the client repeated the way node:http does it: cookies with "; " (HTTP/2 clients split them into several fields; cookieValue looks for "; name="), the rest with ", ". ok is false when the request does not carry it.

type Transport

type Transport = transport.Transport

Transport performs one HTTPRequest. The engine, the snapshot client and the event queue all go through it, so tests inject an in-process fake and production uses HTTPTransport.

type TrustedProxy

type TrustedProxy = config.TrustedProxy

TrustedProxy is the tenant's trusted-proxy config: {mode: none} | {mode: hops, hops: N} | {mode: cidrs, cidrs: [...]} | {mode: vercel}. CAMADA_TRUSTED_PROXY overrides it locally.

func ParseTrustedProxyEnv

func ParseTrustedProxyEnv(v string) *TrustedProxy

ParseTrustedProxyEnv reads CAMADA_TRUSTED_PROXY: none | vercel | hops:N | cidrs:a,b. Unset or malformed is nil — defer to the server-delivered config, never trust.

type Words

type Words = ipparse.Words

Words is an IPv6 address as four big-endian uint32 words, the form the BLK container stores.

func ParseIP6

func ParseIP6(s string) (Words, bool)

ParseIP6 reads IPv6 text into four big-endian words; ok is false for zone ids, v4-mapped and malformed forms.

Directories

Path Synopsis
Package challenge is the SDK-served proof-of-work challenge (contracts §D2), ported from @camada/core src/challenge/{format,verify,page}.ts: wire constants and pure helpers here, the HMAC/SHA-256 kit in verify.go, the self-contained page in page.go.
Package challenge is the SDK-served proof-of-work challenge (contracts §D2), ported from @camada/core src/challenge/{format,verify,page}.ts: wire constants and pure helpers here, the HMAC/SHA-256 kit in verify.go, the self-contained page in page.go.
Package events builds the wire events and ships them in batches.
Package events builds the wire events and ships them in batches.
internal
beacon
Package beacon is the first-party beacon: @camada/browser's dist/auto.global.js vendored by `go run ./scripts/sync-beacon` and embedded, so the SDK serves it at /_cam/b.js with no runtime file read.
Package beacon is the first-party beacon: @camada/browser's dist/auto.global.js vendored by `go run ./scripts/sync-beacon` and embedded, so the SDK serves it at /_cam/b.js with no runtime file read.
config
Package config holds the configuration shapes shared by the snapshot client and the engine: ParseKey splits CAMADA_KEY, and RemoteConfig is what GET /snapshot hands back in x-camada-config (whitelisted server-side).
Package config holds the configuration shapes shared by the snapshot client and the engine: ParseKey splits CAMADA_KEY, and RemoteConfig is what GET /snapshot hands back in x-camada-config (whitelisted server-side).
guarded
Package guarded is the fail-open envelope: a camada bug must never 5xx the customer.
Package guarded is the fail-open envelope: a camada bug must never 5xx the customer.
ipparse
Package ipparse holds the allocation-free IP parsers, ported 1:1 from edge-analyst src/blocklist.js through @camada/core src/snapshot/ipparse.ts (the reference the conformance fixtures are generated from).
Package ipparse holds the allocation-free IP parsers, ported 1:1 from edge-analyst src/blocklist.js through @camada/core src/snapshot/ipparse.ts (the reference the conformance fixtures are generated from).
redact
Package redact is redaction, non-configurable-off.
Package redact is redaction, non-configurable-off.
testutil
Package testutil holds the test doubles the suite shares: the golden fixture readers and the in-process fake analyst.
Package testutil holds the test doubles the suite shares: the golden fixture readers and the in-process fake analyst.
transport
Package transport is the one HTTP seam.
Package transport is the one HTTP seam.
scripts
sync-beacon command
Re-vendors @camada/browser's auto build into internal/beacon (auto.global.js, version.txt, sha256.txt), which the SDK embeds and serves at /_cam/b.js with no runtime file read.
Re-vendors @camada/browser's auto build into internal/beacon (auto.global.js, version.txt, sha256.txt), which the SDK embeds and serves at /_cam/b.js with no runtime file read.
Package snapshot is the BLK snapshot parser (v3, v4, v5) and matcher, ported from @camada/core src/snapshot/parse.ts and match.ts, themselves ports of edge-analyst src/blocklist.js (the reference implementation).
Package snapshot is the BLK snapshot parser (v3, v4, v5) and matcher, ported from @camada/core src/snapshot/parse.ts and match.ts, themselves ports of edge-analyst src/blocklist.js (the reference implementation).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL