Culvert

command module
v1.0.126 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 133 Imported by: 0

README

Culvert

Culvert

Self-hosted Secure Web Gateway & identity-aware forward proxy
HTTP · HTTPS · SOCKS5 · WebSocket  |  Single Go binary · No runtime service dependencies

CI CodeQL Security Gate Go Report Card


What is Culvert?

Culvert is a Secure Web Gateway (SWG) - a policy-enforcing forward proxy that sits between your users and the internet, deciding what traffic is allowed, inspecting it, and recording it. It delivers the capabilities enterprises buy from commercial appliances - TLS inspection, identity-aware policy, malware scanning, threat intelligence, SSO, and centralized management - as a single Go binary you run yourself.

There is no agent to install, no plugin marketplace to assemble, and no external database or message bus to operate. The binary is statically linked (no CGO); the only optional runtime companion is a ClamAV sidecar if you enable antivirus scanning.

Who it's for: security teams that need egress control and DLP-style inspection; network and platform teams that need a manageable forward proxy with SSO; and operators who want the transparency and cost profile of open source without giving up an admin console, metrics, and a control plane.

docker compose up -d          # a working proxy + admin UI, no config required

Core Capabilities

Domain What you get
Proxy HTTP/HTTPS forward proxy, full CONNECT tunneling, SOCKS5 (RFC 1928/1929, CONNECT), WebSocket relay, PAC auto-config, upstream proxy chaining
TLS inspection Opt-in MITM with on-the-fly ECDSA P-256 leaf certs, per-host bypass, bounded leaf cache (10k entries / 1h TTL)
Policy Priority-ordered, first-match rules across 8 condition types; default-deny (Zero Trust); Allow / Drop / Block Page / Redirect actions; per-rule TLS inspect-or-bypass
Identity Local (bcrypt), OIDC (Auth-Code + PKCE), SAML 2.0, LDAP/AD; multi-IdP with email-domain routing; TOTP 2FA; admin RBAC (admin/operator/viewer)
Content security ClamAV antivirus, pure-Go YARA engine, URLhaus + OpenPhish threat feeds, regex DPI on decrypted responses, file-type blocking, domain blocklists, UT1 URL categories, CDR
Observability Prometheus metrics, live SSE dashboard, structured JSON logs, syslog forwarding (RFC 3164/5424), signed webhook alerts, tamper-evident audit trail
Distributed gRPC Control Plane / Data Plane with mTLS, config-snapshot sync, node groups, per-group bandwidth/QoS, config versioning, rolling upgrades, optional etcd fencing lease for HA
Supply chain Signed release catalog (Ed25519 + Sigstore keyless), digest-pinned image dispatch, SLSA L3 provenance, Cosign-signed artifacts

See the interactive architecture overview and the panel-by-panel admin UI reference for detail.


Quick Start

One-line install (Linux)

This script does more than start containers - it provisions the host. Tested on Ubuntu, Debian, RHEL, CentOS, Rocky, Alma, Fedora, Amazon Linux, and Arch.

curl -fsSL https://raw.githubusercontent.com/KidCarmi/Culvert/main/scripts/install.sh | bash

What it does:

  1. Installs Docker Engine + Compose v2 from Docker's official repo (removing conflicting snap/distro packages first).
  2. Provisions /srv/culvert (override with CULVERT_DIR=...) without cloning the source repo: it seeds the local culvert/proxy:pinned image (pulled from the public ghcr.io/kidcarmi/culvert), extracts the deployment files (compose files + maintenance-agent packaging) from the image's built-in /app/deploy bundle, and runs docker compose up -d. Running the script from inside a source checkout uses the checkout's files instead; images that predate the bundle fall back to a git clone.
  3. Sets up encryption at rest - prompts for (or auto-generates) a passphrase and writes it to .env.
  4. Installs the host-side maintenance agent as a systemd service (culvert-maint) - a best-effort, opt-out step that creates a culvert-maint user, a systemd unit, a scoped sudoers entry, and /etc/culvert-maint/config.toml, then wires Release Management to the proxy over a local Unix socket. The agent binary comes from the image's deploy bundle (falling back to the cosign-verified signed-release download, then a local source build). It never mounts the Docker socket into the proxy, and a failure here never fails the install. The /srv/culvert default is what makes the wiring work out of the box — a home-directory stack under a 0700 home (EC2, modern Ubuntu) is unreachable for the unprivileged agent user, and the installer then skips the agent fail-closed (the Release Management panel shows "Agent unreachable").

Opt out of the systemd agent with CULVERT_SKIP_MAINT_AGENT=1, and skip only the Release-Management wiring with CULVERT_SKIP_RELEASE_AGENT_WIRING=1. If you want a containers-only install with no host-side service, set CULVERT_SKIP_MAINT_AGENT=1 before running the script, or use the Docker (manual) path below.

Docker (manual)

git clone https://github.com/KidCarmi/Culvert
cd Culvert
docker build -t culvert/proxy:pinned .   # seed the local-only image tag the compose file resolves
docker compose up -d

No configuration is required - the setup wizard creates your first admin account on first visit.

pull access denied for culvert/proxy? The compose file intentionally resolves the local-only tag culvert/proxy:pinned (the proxy image is pinned at the sudo boundary and never pulled by name). Seed it with the docker build line above, or: docker pull ghcr.io/kidcarmi/culvert:latest && docker tag ghcr.io/kidcarmi/culvert:latest culvert/proxy:pinned

Endpoints

Endpoint URL Notes
HTTP/HTTPS proxy http://localhost:8080 Point your browser / PAC here
SOCKS5 proxy socks5://localhost:1080 Disabled by default (socks5_port: 0); enable in config
PAC file http://localhost:8080/proxy.pac Browser auto-config
Admin UI https://localhost:9090 Accept the self-signed cert on first visit
Health (liveness) http://localhost:8080/health {"status":"ok", ...}
Readiness http://localhost:8080/ready 200 {"status":"ready","checks":{…}} or 503 not_ready
Prometheus metrics http://localhost:8080/metrics Optional bearer-token protection

Note: /health, /ready, and /metrics are served on the proxy port (8080), not the admin-UI port.

curl http://localhost:8080/health
curl -x http://localhost:8080 https://example.com

First-run checklist

  1. Setup wizard - open https://<host>:9090, accept the cert, create the first admin. Required before any other API call.
  2. Verify readiness - curl http://<host>:8080/ready should return 200; see docs/OPERATIONS.md for the full checks map.
  3. Review Diagnostics - Admin UI → Infrastructure → Diagnostics surfaces the operator contract (storage, policy load, root CA, session key, cluster TLS posture, release-management and config-version health). Resolve any fail rows before taking traffic.
  4. Enforce Zero Trust - a fresh install with no policy rules starts in passthrough so you can't lock yourself out. Set default_action: deny (or add rules) to enforce default-deny from boot. See Policy Model.

Monitoring stack (optional)

docker compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d
# Grafana → http://localhost:3000  (user: admin, password: $GF_ADMIN_PASSWORD, default "changeme")

The 12-panel Culvert Overview dashboard is auto-provisioned from deploy/grafana/dashboards/culvert-overview.json.


Configuration

Culvert runs with sensible defaults and can be driven by the Admin UI, a YAML file, CLI flags, or any combination. See config.example.yaml for all documented fields.

Minimal config.yaml

proxy:
  port: 8080
  ui_port: 9090
  socks5_port: 1080        # 0 = disabled

default_action: deny       # allow (passthrough) | deny (zero-trust). Auto-detected if empty.

# LDAP is a TOP-LEVEL block (a sibling of `auth:`), not nested under it.
ldap:
  url: ldaps://ldap.corp.com:636
  bind_dn: "cn=svc-culvert,ou=Services,dc=corp,dc=com"
  base_dn: "ou=Users,dc=corp,dc=com"
  required_group: "proxy-users"

security:
  ip_filter_mode: allow    # allow | block | "" (off)
  ip_list:
    - 192.168.1.0/24
  rate_limit: 60           # requests/min per IP
  max_conns_per_ip: 256

upstream:
  proxies:
    - url: http://parent-proxy:3128
  health_interval: 30s        # sibling of `proxies` (a field on `upstream`, not on a proxy entry)
  circuit_breaker:
    threshold: 5
    timeout: 30s

rewrite:
  - host: "*.internal.example.com"
    req_set:
      X-Forwarded-By: Culvert
    resp_remove:
      - Server

Key CLI flags

Core        -port 8080  -ui-port 9090  -socks5-port 0  -config <file>
TLS         -ca-path <bundle>  -tls-cert  -tls-key  -ui-no-tls
Auth        -ui-users-file <db>  -ui-allow-ip <cidrs>  -session-timeout 8
Filtering   -blocklist  -policy  -geoip-db  -clamav-addr  -yara-rules-dir  -threat-feed-db
Logging     -logfile  -log-max-mb 50  -request-log-max-mb 100  -audit-log  -syslog
Metrics     -metrics-token  -rate-limit  -otlp-endpoint <url>
Control Pl. -cp-grpc-addr  -cp-grpc-cert  -cp-grpc-key  -cp-grpc-ca
Data Plane  -dp-cp-addr  -dp-node-id  -dp-cert  -dp-key  -dp-ca

In the shipped Docker image, persistence paths such as /data/ca.bundle and /data/ui_users.json are supplied by the compose command line, not by binary flag defaults.

Environment variables

Variable Purpose
CULVERT_CA_PASSPHRASE CA private-key encryption passphrase (required for TLS inspection)
CULVERT_LOG_PASSPHRASE Encryption-at-rest passphrase for the persistent log store. Falls back to CULVERT_CA_PASSPHRASE if unset; empty (both unset) = encryption off.
CULVERT_C2_ENFORCE Admin RBAC enforcement mode. Default enforce (fail-closed); set false/0/no/off for shadow (log-only). Read once at startup.
CULVERT_RELEASE_PROXY_REPO Bare image repository allowed for release dispatch (default ghcr.io/kidcarmi/culvert; no tag/digest).
CULVERT_MAINT_AGENT_URL Local maintenance-agent endpoint (default Unix socket /run/culvert-maint/culvert-maint.sock).
CULVERT_RELEASE_CATALOG_TRUST_KEYS JSON array of public Ed25519 catalog trust roots. Public keys only.
CULVERT_RELEASE_CATALOG_VERIFY Signature mode: enforce (default when a trust root is present), permissive, or disabled. Break-glass only - leave unset in production.
CULVERT_RELEASE_CATALOG_URL Optional signed-catalog origin for verified auto-seed at startup (enforce mode only).
CULVERT_RELEASE_SIGSTORE_IDENTITY / _TRUSTED_ROOT Optional overrides for the keyless (Sigstore-identity) release-trust policy. Public material only.

Policy Model

The policy engine evaluates rules in priority order and stops at the first match (policy.go). Each rule combines any of 8 condition types:

  1. Source IP / CIDR
  2. Authenticated identity
  3. IdP group membership
  4. Auth source (OIDC / SAML / LDAP / local)
  5. Destination FQDN - exact + wildcard
  6. URL category (UT1 + curated: Social, Streaming, Gambling, Malicious, Adult, …)
  7. Destination country (GeoIP; fail-closed - unknown country does not match)
  8. Time schedule - day-of-week + time window + IANA timezone

Actions: Allow, Drop, Block Page, Redirect. Every rule also carries a TLS action - Inspect (full MITM) or Bypass (transparent tunnel).

Default-deny (Zero Trust): once any rule exists or default_action: deny is set, unmatched traffic is blocked. A fresh install with zero rules and no explicit default_action starts in passthrough by design, so you can't lock yourself out - enforce deny explicitly for production.

Conflict detection: overlapping rules with the same priority but different actions are surfaced as warnings at load and in the UI. Policy Tester dry-runs any host/user/IP against the live ruleset. Per-rule hit counters are exported to Prometheus (cardinality-capped at 200 series).


Security Model

Culvert is built defense-in-depth. Every claim below is enforced in code.

Area Implementation
Zero Trust Default-deny policy engine (see Policy Model)
SSRF prevention isPrivateHost() resolves DNS and rejects private/loopback/metadata IPs before every outbound dial, and re-checks the resolved IP at connect time to close the DNS-rebinding TOCTOU window
Log injection (CWE-117) sanitizeLog() strips CR/LF/TAB and C0 controls; %q formatting; internally generated request IDs are safe by construction
Open redirect isSafeRedirectURL() requires absolute URL, http/https scheme, and non-private resolvable host
Brute-force IP + user lockout after 5 failures (15-min cooldown), plus a 20-failure account-wide tier over a 10-min window
Admin API rate limiting 60 req/min per IP on mutating (POST/PUT/DELETE) /api/ endpoints
Slowloris 60s read deadline on TLS-inspected client connections, re-armed per read
CSRF Origin-based same-origin enforcement on mutating requests + X-Frame-Options: DENY and CSP
Session security HMAC-SHA256 signed cookies, per-session 128-bit jti, dynamic Secure flag, fresh token per login, disk-persisted revocation list synced across the cluster via the control plane
CA key protection AES-256-GCM with PBKDF2-SHA256 (600,000 iterations, NIST SP 800-132) at rest; atomic bundle writes
Certificate revocation Upstream OCSP checking (fail-closed when a published responder is unreachable). CRL checking is not yet implemented - see Limitations
Hop-by-hop stripping RFC 7230-compliant - parses the Connection header for dynamically listed hop-by-hop names
Header scrubbing Strips private IPs from X-Forwarded-For, drops private X-Real-IP, always removes X-User-Identity before forwarding
Password complexity 8+ chars, mixed case, and a digit required
Admin RBAC (defense-in-depth) Metadata-driven enforcement layer in addition to per-handler requireRole; report-only audit-completeness and role-divergence detectors; read-only governance surface at /api/governance/control-plane

Post-Quantum key exchange

Culvert runs on Go 1.25, whose crypto/tls auto-negotiates the hybrid X25519 + ML-KEM-768 key exchange when the peer supports it - protecting confidentiality against "Harvest Now, Decrypt Later" attacks on all TLS connections. This capability is inherited from the Go toolchain rather than configured by Culvert, and adds ~1ms to the initial handshake with no ongoing cost. Certificate signing remains classical ECDSA P-256; PQC signatures (ML-DSA) will follow when the Go standard library adds native support.

Supply-chain integrity

  • Signed release catalog - the Control Plane loads a catalog validated with fail-closed manifest-hash binding and Ed25519 signatures (enforce-by-default when trust roots are present), plus a second Sigstore keyless (identity-pinned) scheme; freshness (expires_at) and rollback (catalog_version) protection are enforced in enforce mode. An unsigned catalog is never auto-trusted.
  • Digest-pinned dispatch - releases resolve to an immutable repo@sha256:<digest> ref; the image is pinned at the sudo boundary and retagged locally, so a compromised maintenance user can only run a digest of the one configured repo.
  • Provenance & signing - releases ship SLSA Level 3 provenance and Cosign keyless signatures on images, binaries, SBOMs, and the catalog itself.

Observability

Prometheus metrics (GET /metrics, proxy port)

Metric Type Description
culvert_requests_total counter All proxy requests
culvert_requests_allowed counter Forwarded requests
culvert_requests_blocked counter Blocked requests (all reasons)
culvert_requests_auth_fail counter Authentication failures
culvert_bytes_sent_total / culvert_bytes_recv_total counter Bytes to client / from upstream
culvert_request_duration_seconds histogram Request latency (11 buckets, 5ms-10s)
culvert_policy_rule_hits_total{rule="..."} counter Per-rule hit counter (capped)
culvert_clamav_blocked_total counter ClamAV blocks
culvert_yara_blocked_total counter YARA blocks
culvert_threat_feed_blocked_total counter Threat-feed blocks
culvert_blocklist_size gauge Blocklist entry count
culvert_uptime_seconds gauge Proxy uptime

Metric names are authoritative as of metrics.go. Run curl -s localhost:8080/metrics | grep culvert_ against your build for the full current set.

OTLP export (metrics + traces)

Optional push export to an OTLP/HTTP collector (e.g. the OpenTelemetry Collector, Grafana Alloy), independent of the pull-based /metrics endpoint above. Configure via -otlp-endpoint <url> / otlp_endpoint in config.yaml, or from the Admin UI's OpenTelemetry (OTLP) panel — cluster-synced and admin-durable, no restart required to change the endpoint. Unset by default (no export).

Logging & SIEM

  • Structured logging - text or JSON, with rotating files (configurable size thresholds).
  • Syslog forwarding - UDP/TCP, RFC 3164 and RFC 5424, for Splunk / Elastic / QRadar.
  • Live SSE feed - the Admin UI dashboard streams request activity in real time.
  • Signed webhook alerts - HMAC-SHA256 (X-Culvert-Signature) for threats, blocks, and lockouts.
  • Tamper-evident audit trail - JSONL of every admin action, enriched with the authenticated actor.

Deployment

Single node

A single node with TLS inspection + ClamAV comfortably serves ~500 concurrent users. Minimum footprint for a full-feature deployment:

2 vCPU | 1.5 GB RAM | 2.5 GB disk (SSD recommended)

Proxy-only (no AV, no inspection): 1 vCPU | 128 MB RAM | 100 MB disk.

Control Plane / Data Plane cluster

Beyond ~500 users or ~1 Gbps sustained, scale horizontally with a gRPC Control Plane and stateless Data Plane nodes.

Component CPU RAM Storage Role
Control Plane 2 vCPU 512 MB 500 MB Config sync, enrollment, dashboard - no proxy traffic
Data Plane node 2 vCPU 1 GB 1 GB Handles proxy traffic; receives full config from CP on connect
DP + ClamAV 2 vCPU 2 GB 1.5 GB Add ~1 GB RAM + 300 MB disk for AV
  • DP nodes are stateless - they receive their entire config (policy, blocklist, PAC, threat feeds, session key, bandwidth policies, node groups) from the CP over mTLS gRPC. Lose one, spin up a replacement, it auto-enrolls in seconds.
  • HA fencing - optional etcd fencing lease (-ha-etcd-*) provides fail-closed leader election with epoch-based fencing; without it, the legacy leader/standby model applies. See docs/operator/ha-lease-failover.md.
  • Config versioning - every mutation snapshots automatically (50-version history) with side-by-side diff and one-click rollback.
  • Backup / restore - via the profile-gated cli compose service; see docs/operator/docker-compose-backup-restore.md.

Detailed single-node, multi-node, and upstream-chaining topologies are in the Deployment Guide. Full sizing tables (per-connection memory, cluster throughput) are in docs/OPERATIONS.md.


Development

Requires Go 1.25+.

go build -o culvert .                        # build
go test -race ./...                          # full suite with race detector
go test -coverprofile=cover.out ./...        # coverage
go test -fuzz FuzzIsPrivateHost -fuzztime=30s  # fuzz the SSRF guard

Repository layout: everything is package main at the root (composition roots and thin shims); logic/state/persistence live in ~48 packages under internal/. Coding conventions, the internal/ decomposition, and the admin-API route-metadata contract are documented in CLAUDE.md.

Fuzz targets: FuzzIsPrivateHost, FuzzIsSafeRedirectURL, FuzzParseClamResponse, FuzzNormaliseFeedURL, FuzzMatchDest, FuzzParseYARALiteral.

CI & security pipeline

Pull requests are gated by two required checks - Fast PR Gate and Deep PR Gate - covering fmt/vet/build, diff-scoped golangci-lint (22 linters), the full -race suite with a 55% coverage floor, govulncheck, gosec, gitleaks, Trivy, and image/compose validation.

The main/tag security-release gate adds 9 blocking checks - gosec, govulncheck, Trivy (filesystem + image), gitleaks, staticcheck, hadolint, -race tests, the coverage floor, and license compliance (blocks GPL/AGPL/LGPL) - plus CodeQL SAST, an informational CycloneDX SBOM (Syft), Cosign keyless signing, and SLSA L3 provenance on releases.


Limitations & Known Gaps

Stated plainly, because a security product should be honest about its edges:

  • Revocation: OCSP only - CRL checking is not implemented. OCSP fails open when a certificate publishes no responder.
  • Fresh-install posture: with zero rules and no default_action, the proxy starts in passthrough (allow), not deny. Enforce Zero Trust explicitly.
  • Post-quantum: key exchange is quantum-resistant (inherited from Go 1.25); certificate signing remains classical ECDSA P-256.
  • SOCKS5: CONNECT only - UDP ASSOCIATE is rejected.
  • License gate: blocks GPL/AGPL/LGPL; CPAL is not currently enforced by the license check.
  • Sizing figures in the deployment tables are engineering estimates, not published benchmarks - validate against your own workload before capacity planning.

Roadmap

Development phases, production-readiness items, and the engineering governance model are tracked under roadmap/ and docs/engineering/. Near-term focus: CRL support, GUI parity for the remaining startup-scoped HA settings, and completing the catalog-driven release path as the default update mechanism.


Contributing

  1. Fork and branch (git checkout -b feature/my-feature)
  2. go test -race ./...
  3. Keep the admin-API route-metadata and config-surface parity tests green (see CLAUDE.md)
  4. Open a PR - the full CI pipeline (CodeQL, security gate, golangci-lint) runs automatically

License

MIT

Documentation

Overview

crashguard.go — M1 panic-recovery (supportability framework).

Design (adversarially synthesized): a single top-level middleware CANNOT catch panics in detached relay/geo/alert/SOCKS5 goroutines (Go's recover() only unwinds its own goroutine) and MUST NOT try to write an HTTP status after a CONNECT hijack. So recovery is split into structural planes plus a per- goroutine tap at every detached-goroutine birth site, all funneling into ONE re-panic-proof, rate-limited, redaction-aware sink (recordCrash).

  • PROXY plane = record-only guard (proxyCrashGuard): never touches the ResponseWriter, so the happy path is byte-identical and zero-alloc and a hijacked tunnel is never corrupted. Re-panics http.ErrAbortHandler so net/http keeps its intentional silent-abort protocol.
  • ADMIN plane = withAdminPanicRecovery: the admin chain never hijacks, so a clean 500 is valid when nothing was committed. trackedRW implements Unwrap()+Flush() so it is NOT interface-masking (ResponseController + SSE keep working).
  • Detached go-sites = recoverGoroutine / the relay defer.

The sink logs only crash id/component/correlation (never the raw panic value — sanitizeLog scrubs control chars, it does NOT mask a secret), keeps the bounded text only in an in-memory lastCrash reachable exclusively via a redacting accessor, and is throttled per component so a request-triggered panic cannot flood the SIEM or evict the shared audit ring. Timeline note: M1 builds no timeline store; the crashRecord is shaped as the enumerated "crash" event so the M3 timeline consumes it with no schema change (ADR-0012).

D1.6b: --list-backups one-shot CLI.

The Maintenance Agent's GET /v1/backups endpoint shells out to the cli container (via `docker compose --profile cli run --rm cli --list-backups --backup-dir /backup`) and parses the JSON array this command emits to stdout. Operators can also invoke it manually for debugging — same output format either way.

Encryption detection uses the D1.4 magic prefix `CVRTBK01` at byte 0 (see backup_encrypt.go: backupEncMagic). We peek 8 bytes; entries whose first 8 bytes match are flagged encrypted=true. Anything else is reported as encrypted=false; we do NOT attempt deeper validation (gzip-magic, tar header, etc.) because the agent only needs an operator-facing "encrypted-or-not" hint here, and partial reads are cheap.

Symlinks, directories, and unreadable entries are skipped silently rather than failing the whole listing — the human (or agent) can act on whatever IS readable. Errors at the directory level (e.g. non-existent --backup-dir) DO fail the command.

Culvert — Enterprise-grade open source HTTP/HTTPS proxy https://github.com/KidCarmi/Claude-Test

Release Management API — P1.6d-0 (foundation; NO GUI in this slice).

Read-only catalog/current endpoints plus the async dispatch + resume control endpoints, all backed by the P1.6c DispatchService. The handlers own only HTTP concerns (auth, decode, the async 202 split, and a bounded per-agent status store); planning/execution/verify-by-digest/audit/alert all live in the service. Agent endpoints are resolved by an injected resolver (NOT a config route — config is a later slice), so this slice adds no /api/releases/config, no GUI panel, no auto-update, no scheduling.

Dispatch is asynchronous: the handler starts DispatchService.Dispatch in the background and returns 202 the instant the op_id + resume context are durably recorded (the service's onApplied observer). A planner/preflight refusal that happens BEFORE apply returns a synchronous 4xx/503 instead. The watch then runs to terminal in the background and updates the status store, which the GET status endpoint surfaces.

Release Catalog Verified Auto-Seed — P1.7.

At Control-Plane startup, if CULVERT_RELEASE_CATALOG_URL is set AND signature verification is in enforce mode, fetch the official signed catalog from that URL, verify it (signature + freshness + rollback), and atomically install it into <dataDir>/release_catalog/ so a clean install reaches available:true without manual file placement. Anything wrong ⇒ leave the existing on-disk catalog untouched and continue (fail closed). NO unsigned auto-download.

Verification lives HERE, in the Go binary (it holds the trust roots) — never in the installer/shell. The installer only forwards the env var.

Scope (roadmap/D1.6d-P1.7-catalog-autoseed-plan.md): boot-time seed only. NO background ticker/polling, NO GUI, NO mirror/air-gap, NO agent changes. The provider + this function are reusable by a later refresh slice.

Release Catalog Runtime — Slice 1 (Catalog Load + Resolve).

A pure, in-process, UNSIGNED, local-source, read-only release catalog. It parses the release index + referenced manifests, validates them fail-closed, verifies each manifest's content hash against its RAW bytes, and builds the forward (channel → release) and reverse (pinned ref → release) indexes the rest of the release layer depends on.

Scope (roadmap/D1.6d-P1.2-release-catalog-runtime-plan.md): Catalog Load + Resolve only. NO signatures, NO network/refresh, NO GUI, NO agent calls, NO upgrade dispatch, NO rollback-candidate computation, NO CP→DP propagation.

Control-Plane-only: nothing here is wired into the proxy/data-plane path by this slice; it returns a DIGEST (`repo@sha256:…`, the agent's native currency) and has no release/channel awareness on the agent side.

Integrity ≠ authenticity: the manifest_sha256 check (§4.9) is a content hash, NOT a signature. It catches corruption/drift but is worthless against a tampering attacker, and the INDEX itself has no integrity protection until a later slice signs it. Do not treat a loaded catalog as authenticated.

Release Catalog Distribution — P1.5 Slice d (air-gap BundleCatalogProvider).

BundleCatalogProvider is a TRANSPORT that stages the CATALOG portion of a signed offline release bundle (a tar / tar.gz archive carried into an air-gapped site) into a fresh temp dir, which the caller then hands to LoadVerifiedCatalog (the P1.3 trust boundary) exactly like any other source. Verification is identical to online — only the transport differs (plan §8).

Unlike the HTTP provider, the bundle is COMPLETE before any parse, so no two-phase gate is needed (plan §5.1: local/bundle providers stage-then-verify unchanged). The provider therefore does NO trust work at all — it only moves bytes, under a hostile-archive discipline:

  • any absolute / traversal ("..") / backslash / NUL entry name rejects the whole bundle (never silently skipped);
  • any symlink or hardlink entry rejects the whole bundle;
  • catalog entries must be regular files within per-file and total bounds;
  • non-catalog entries (the future image blobs, plan §8) are skipped WITHOUT reading their content;
  • index.json is required; index.json.sig and index.json.sigstore are staged when present — the enforce/permissive decision for a missing signature belongs to the P1.3 mode at verify time, not to the transport.

Scope (roadmap/D1.6d-P1.5-catalog-distribution-plan.md — Slice d): catalog extraction + staging + cleanup only. NO image docker-load (the bundle slice that loads images MUST bind loaded-image-digest == authenticated list_digest, P1.3 §7 — recorded there, not here), NO outer bundle signature, NO GUI/API, NO dispatch, NO agent changes, NO HTTP changes.

Release Catalog Freshness & Rollback Protection — Phase 1.

Authenticity (release_catalog_verify.go) proves a catalog is GENUINE; it does NOT prove it is the LATEST. This file adds the two missing trust dimensions a signed update channel needs (TUF "timestamp"/"snapshot" roles, in spirit):

  • Freshness: a signed catalog declares expires_at; a CP past that instant (plus a small clock-skew tolerance) refuses it, so a captured-and-replayed stale-but-validly-signed catalog cannot silently pin users to an old, vulnerable release forever.
  • Rollback / freeze protection: each catalog carries a monotonic catalog_version; the CP persists the highest version it has ever accepted and refuses anything below it, so an attacker who can serve an OLD signed catalog cannot downgrade the deployment.

Both checks are ENFORCE-mode only and are composed ON TOP of signature verification — they never widen trust, only narrow it. They are deliberately kept out of the structural loader (release_catalog.go) and the signature gate (release_catalog_verify.go) so those layers stay pure/offline; freshness needs a clock and rollback needs persisted state, which only the holder owns.

Release Catalog Distribution — P1.5 Slice a (CatalogHolder + atomic publish).

The CatalogHolder owns the live, atomically-swappable verified *Catalog that the rest of the Control Plane reads. It is the swap component P1.2 deferred ("reload = construct a new *Catalog; the refresh slice will own swap semantics; not here"): this slice loads + verifies from a single local directory, publishes atomically, exposes an explicit no-catalog state, and a manual reload that keeps the current catalog on any failure.

Scope (roadmap/D1.6d-P1.5-catalog-distribution-plan.md — Slice a): holder + atomic publish + local-dir reload only. NO goroutine, NO HTTP provider, NO cache persistence, NO staleness, NO GUI/API, NO agent/dispatch/air-gap. The only path to a published catalog is LoadVerifiedCatalog (the P1.3 trust boundary) — there is deliberately no "publish raw *Catalog" entry point.

Release Catalog Distribution — P1.5 Slice c (HTTP CatalogProvider).

HTTPCatalogProvider is a TRANSPORT that stages a catalog candidate (index.json + index.json.sig + referenced manifests) from an HTTP(S) origin into a fresh temp dir, which the caller then hands to LoadVerifiedCatalog (the P1.3 trust boundary) exactly like a local-dir source.

The §5.1 contract is the heart of this slice: because manifests cannot be fetched without first reading their refs out of the index, the provider runs a TWO-PHASE verify — it verifies the index signature over the RAW index bytes BEFORE parsing the index to enumerate manifest fetches. A forged/unsigned index (in enforce mode) therefore triggers ZERO manifest requests. The final LoadVerifiedCatalog over the staged dir re-verifies everything (defense in depth, incl. the manifest_sha256 hash check that authenticates each fetched manifest).

Scope (roadmap/D1.6d-P1.5-catalog-distribution-plan.md — Slice c): the HTTP transport + two-phase verify + staging + cleanup + timeout + minimal retry/backoff + conditional (ETag/If-Modified-Since) fetch. NO GUI, NO dispatch, NO agent changes, NO air-gap bundle, NO CP→DP propagation. The provider is transport-only apart from the §5.1 index-verify gate.

Release Catalog Distribution — P1.5 Slice b (Refresher + last-good cache + staleness).

The Refresher orchestrates stage → verify → publish on top of the P1.5a CatalogHolder, adds a last-good on-disk cache (restart durability), single- flight, refresh metadata, and staleness. It still uses a LOCAL directory as the source — the HTTP provider and air-gap bundle are later slices.

Scope (roadmap/D1.6d-P1.5-catalog-distribution-plan.md — Slice b): refresher + cache + staleness + single-flight + an OPTIONAL (default-off) interval ticker. NO HTTP provider, NO air-gap bundle, NO GUI/API, NO agent/dispatch, NO metrics/alert wiring. Every publish still goes through LoadVerifiedCatalog (the P1.3 trust boundary); the cache is re-verified on load.

Release Catalog Runtime — Slice 1: query surface (Resolve / Lookup / Current / List). All methods are pure and I/O-free after LoadCatalog.

Release Catalog Keyless Trust — P2b-1 (Sigstore-identity verifier).

Adds a SECOND catalog-signature scheme that coexists with the ed25519 scheme (release_catalog_verify.go): a cosign "keyless" bundle (Fulcio cert + signature + Rekor inclusion proof) over the RAW index.json bytes, verified offline against a baked Sigstore TUF trusted root plus a PINNED workflow identity (issuer + SAN regex). Because the index binds every manifest by manifest_sha256, one signature over one document authenticates the whole catalog — same kernel as ed25519.

Trust model: NO private key exists anywhere (keyless). Trust is "this exact GitHub Actions workflow, on a release tag, signed via Fulcio". Verification is fully offline: the bundle carries its own Rekor inclusion proof + integrated timestamp, and the trusted root is baked, so nothing reaches sigstore.dev at runtime (air-gap safe).

Scope (roadmap/D1.6d-P2b-sigstore-identity-trust-plan.md): P2b-1 shipped the verifier, the .sigstore sidecar source, scheme selection, and wiring resolution. As of P2b-2a the official Sigstore public-good trusted root is BAKED into the embed below, so the scheme is ACTIVE by default (an operator can override or deactivate it via CULVERT_RELEASE_SIGSTORE_TRUSTED_ROOT). NO release-side signing yet — CI keyless signing + the end-to-end/image-sig gates are P2b-2b. Verifying identity (cert chain + SAN + issuer) happens BEFORE any other bundle content is trusted.

Release Catalog Authenticity — P1.3 Slice 1 (index signature verification).

Adds a Control-Plane-side authenticity gate on TOP of the P1.2 catalog runtime (release_catalog.go): an ed25519 detached signature over the RAW index.json bytes, verified against a baked/operator TrustStore BEFORE the index is parsed or any manifest_sha256 entry is trusted (plan §5/§5.0). Signing the index transitively authenticates every manifest (the index binds each by sha256), so one signature over one document is the whole authenticity kernel.

Scope (roadmap/D1.6d-P1.3-catalog-authenticity-plan.md — Slice 1): TrustStore + envelope parse + LoadVerifiedCatalog + the three enforcement modes. NO GUI, NO agent/dispatch, NO air-gap bundle, NO network/refresh, NO CP→DP propagation, NO metrics wiring. The verifier is release-agnostic and verifies fully offline.

Release Dispatch — P1.6 Slice a (CP-side dispatch kernel, PURE PLANNING).

The Dispatcher turns an operator target (release_id or channel) into a DispatchPlan: it resolves the release from a PINNED immutable catalog snapshot (P1.5 holder), reconciles the catalog repo against the deployment's proxy_repo (repo equality / one explicit air-gap rewrite, design §4), derives Current/already-current from the agent's running_image.repo_digests (P1.1, design §3/§5), and builds the EXISTING upgrades.apply request object (design §6) — WITHOUT sending it.

It is a PURE, deterministic planner: no I/O, no randomness, no agent contact. The idempotency key is an INPUT (higher orchestration owns op identity); the catalog snapshot is read exactly once at plan start. The agent receives only an image_ref + existing apply flags — no release/channel/version/catalog data crosses to it, and it stays release-agnostic.

Scope (roadmap/D1.6d-P1.6-release-dispatch-plan.md — Slice a): planning + the request object + tests. NO agent POST, NO upgrades.check, NO tags, NO tag updater, NO new agent endpoint, NO agent changes, NO GUI, NO auto-update, NO rollback-candidate computation, NO legacy-updater changes.

Release Dispatch — P1.6 Slice b (CP-side execution wrapper).

DispatchExecutor takes a FROZEN DispatchPlan (P1.6a), generates the CP idempotency key, POSTs the EXISTING upgrades.apply, polls the EXISTING agent op to a terminal state, re-reads running_image.repo_digests, and classifies a terminal DispatchTerminal by VERIFYING THE RUNNING DIGEST itself (never the agent's self-report). It is single-flight per agent and emits audit events via a hook.

The agent is untouched and stays release-agnostic: only image_ref + existing apply flags cross the wire (no upgrades.check, no tags, no fallback). The transport is behind the AgentClient seam so the orchestration is fully testable with a fake; httpAgentClient is the concrete adapter over the existing /v1 endpoints.

Scope (roadmap/D1.6d-P1.6-release-dispatch-plan.md — Slice b): execution + verify-by-digest + terminal classification + audit structs/hooks. NO GUI, NO auto-update, NO rollback-candidate computation, NO legacy-updater change, NO new agent endpoint.

Release Dispatch — P1.6 Slice c (CP-side orchestration wiring).

DispatchService is the CP-side wrapper that ties the PURE planner (P1.6a) to the execution wrapper (P1.6b/c-0) and the rest of the Control Plane: the audit ring (release.dispatch + release.dispatch.outcome), the alert webhook hook, and the real HTTP transport to the EXISTING agent /v1 surface.

It owns an AGENT-KEYED single-flight registry: exactly one DispatchExecutor per agent identity, so the executor's per-instance single-flight becomes a per-AGENT guarantee regardless of how many Dispatch calls arrive. A second dispatch to an agent already mid-op is rejected (errDispatchInFlight) and audited, never queued or duplicated.

The agent stays release-agnostic: only image_ref + existing apply flags cross the wire (no upgrades.check, no tags, no fallback). Verify-by-digest remains the only success gate, and the CP idempotency key is generated ONCE per dispatch op and threaded through the plan so the executor honors it (P1.6c-0).

Scope (roadmap/D1.6d-P1.6-release-dispatch-plan.md — Slice c): service wrapper, agent registry, Resume/re-poll, audit + alert wiring, real transport. NO GUI, NO agent change, NO new agent endpoint, NO legacy-updater work, NO auto-update, NO rollback-candidate logic.

Release Catalog Generator — P2a.

Deterministically generates a release catalog (index.json + per-release manifests) from release metadata, so an official catalog is produced by CI from the digest the workflow actually pushed — never hand-edited. The output is the EXACT on-disk shape the P1.2 loader (release_catalog.go) consumes, and the P2a CI gate (release_gen_test.go) round-trips the generated bundle back through LoadVerifiedCatalog so the generator can never drift from runtime verification.

Determinism is load-bearing: the loader authenticates each manifest by hashing its RAW bytes, so generation MUST be byte-stable — compact json.Marshal (no indent; encoding/json already emits map keys in sorted order), RFC3339-UTC timestamps supplied by the caller, and releases emitted in sorted release_id order. TestGenerateReleaseCatalog_Deterministic is the merge gate.

Scope (roadmap/D1.6d-P2-release-pipeline-signing-plan.md — P2a): generation + validation only. NO signing trust decision (the CI gate signs with an EPHEMERAL key for round-trip proof only), NO Control-Plane trust-model change (that is P2b), NO network.

Release Catalog Spec Construction — M0 / E1 + E2 (correctness core).

buildReleaseSpec assembles a *deterministic* releaseCatalogSpec from immutable inputs (a release version, the pushed image digest, and the tagged commit's date) so that re-running the same release produces byte-identical catalog bytes. It is PURE: no wall-clock, no git, no network. All timestamp and version derivation that used to live in CI shell (`date -u`, `git tag | wc -l`) moves here where it is unit-tested.

The two load-bearing correctness properties (M0 design v2):

  1. Deterministic timestamps — generated_at/created_at come from the tagged commit's committer date (a fixed input per tag), normalized to UTC "Z". expires_at = generated_at + ExpiryDays. Re-running a tag yields identical bytes; the loader authenticates each manifest by hashing its RAW bytes (release_catalog.go), so a one-byte timestamp drift would break the digest binding.

  2. Race-free monotonic version — catalog_version is a pure function of the release semver (catalogVersionFromSemver), NOT a live count of git tags. This makes allocation idempotent (re-running any tag, before or after other tags exist, yields the same version), monotonic (a later GA release encodes higher), and collision-free (distinct semver ⇒ distinct integer) — with no shared mutable source to race on. Re-signing the same release trivially reuses its version (same semver), so "re-sign does not allocate" holds by construction.

buildReleaseSpec validates only its DERIVATION inputs (required fields, a strict GA semver for the version encoding, timestamp parseability, the expiry/dead-on- arrival guards); all OUTPUT-shape/semantic validation (repo format, digest, channel-known, created_at) stays in generateReleaseCatalog (release_gen.go), which remains the sole shape validator. The strict semver core here is deliberately no looser than the generator's, so this file never accepts a version the generator would reject.

Release Management startup wiring (P1.6d-0.1).

Constructs and publishes the Release Management backend (catalog provider + DispatchService + releaseManager) so the /api/releases* routes are actually usable instead of reporting "not configured". It is deliberately MINIMAL and NON-FATAL: any failure leaves globalReleaseMgr nil and the routes report a clear 503 — never a panic.

Scope: empty catalog holder (a later refresh slice populates it), the default proxy_repo, an optional empty repo_rewrite, and the single CP-LOCAL maintenance agent (key "local"), reached over its unix socket by default or an http(s) URL via CULVERT_MAINT_AGENT_URL. NO mutable config route, NO GUI.

Source Files

Directories

Path Synopsis
internal
alerts
Package alerts is the producer-facing seam for security alerting.
Package alerts is the producer-facing seam for security alerting.
audit
Package audit is the admin-action audit trail engine: a bounded in-memory ring (the newest MaxRing entries), optional append-only JSONL persistence with rotation, paginated/time-filtered reads over both, and the Data-Plane → Control-Plane push queue.
Package audit is the admin-action audit trail engine: a bounded in-memory ring (the newest MaxRing entries), optional append-only JSONL persistence with rotation, paginated/time-filtered reads over both, and the Data-Plane → Control-Plane push queue.
autoexclude
Package autoexclude is the adaptive decryption-exclusion cache: a bounded, TTL-bounded, in-memory learned set of hosts that could not be SSL-inspected, so that subsequent CONNECTs to them can fail open (bypass decryption) instead of breaking.
Package autoexclude is the adaptive decryption-exclusion cache: a bounded, TTL-bounded, in-memory learned set of hosts that could not be SSL-inspected, so that subsequent CONNECTs to them can fail open (bypass decryption) instead of breaking.
backupcrypt
Package backupcrypt is the D1.4 backup-envelope cryptography leaf: optional AES-256-GCM encryption of the compressed tar.gz backup blob, with the fixed 43-byte header bound to the ciphertext via AAD so any header tampering — including a KDF iteration-count downgrade — fails authentication.
Package backupcrypt is the D1.4 backup-envelope cryptography leaf: optional AES-256-GCM encryption of the compressed tar.gz backup blob, with the fixed 43-byte header bound to the ciphertext via AAD so any header tampering — including a KDF iteration-count downgrade — fails authentication.
bandwidth
Package bandwidth is the per-node-group bandwidth/QoS policy engine: named policies with label selectors, priority-based matching (F10 overlap detection), token-bucket rate limiting, and atomic JSON persistence.
Package bandwidth is the per-node-group bandwidth/QoS policy engine: named policies with label selectors, priority-based matching (F10 overlap detection), token-bucket rate limiting, and atomic JSON persistence.
blocklist
Package blocklist is the host blocklist engine: O(labels) exact/wildcard matching with a never-block exceptions list, block/allow modes, sidecar persistence (.mode/.manual/.exceptions/.sources), per-feed attribution, and hosts-format line normalization.
Package blocklist is the host blocklist engine: O(labels) exact/wildcard matching with a never-block exceptions list, block/allow modes, sidecar persistence (.mode/.manual/.exceptions/.sources), per-feed attribution, and hosts-format line normalization.
blocklistfeed
Package blocklistfeed is the remote blocklist syncer: it periodically downloads one or more remote blocklists (one domain per line, '#' comments allowed) and merges new entries into the live blocklist without removing manually-added entries.
Package blocklistfeed is the remote blocklist syncer: it periodically downloads one or more remote blocklists (one domain per line, '#' comments allowed) and merges new entries into the live blocklist without removing manually-added entries.
blockpage
Package blockpage owns the corporate "Access Denied" block page: the default HTML template, the runtime-mutable override, and the 403 response writer.
Package blockpage owns the corporate "Access Denied" block page: the default HTML template, the runtime-mutable override, and the 403 response writer.
bootstrap
Package bootstrap generates the one-click DP-node deployment artifacts: the shell install script and the docker-compose.yml that the Control Plane serves against a single-use enrollment token.
Package bootstrap generates the one-click DP-node deployment artifacts: the shell install script and the docker-compose.yml that the Control Plane serves against a single-use enrollment token.
ca
Package ca implements Culvert's SSL-inspection Root CA — the MITM trust core (ADR-0002 extraction; engine was ca.go in package main).
Package ca implements Culvert's SSL-inspection Root CA — the MITM trust core (ADR-0002 extraction; engine was ca.go in package main).
catdb
Package catdb is Layer 2 of the two-tier URL categorisation engine — the community category store.
Package catdb is Layer 2 of the two-tier URL categorisation engine — the community category store.
catgroup
Package catgroup is the named category-group engine: bundles of URL categories (e.g.
Package catgroup is the named category-group engine: bundles of URL categories (e.g.
clamav
Package clamav implements a ClamAV CLAMD protocol client (INSTREAM scanning) over Unix domain sockets or TCP, with zero external API dependency.
Package clamav implements a ClamAV CLAMD protocol client (INSTREAM scanning) over Unix domain sockets or TCP, with zero external API dependency.
clientclass
Package clientclass is the deterministic client classifier: whether a request comes from an interactive browser (eligible for a captive-portal SSO redirect), an opaque CONNECT tunnel (never redirectable), or a non-browser service client.
Package clientclass is the deterministic client classifier: whether a request comes from an interactive browser (eligible for a captive-portal SSO redirect), an opaque CONNECT tunnel (never redirectable), or a non-browser service client.
configver
Package configver implements the numbered config-version snapshot store (ADR-0002 extraction; engine was the store half of configversion.go in package main).
Package configver implements the numbered config-version snapshot store (ADR-0002 extraction; engine was the store half of configversion.go in package main).
connlimit
Package connlimit provides per-IP connection limiting.
Package connlimit provides per-IP connection limiting.
decryptobs
Package decryptobs is the bounded-enum vocabulary for Culvert's decryption- observability model (ADR-0011).
Package decryptobs is the bounded-enum vocabulary for Culvert's decryption- observability model (ADR-0011).
decryptprofile
Package decryptprofile is the named SSL-decryption-profile engine: a PAN-OS-style "how to decrypt" object that policy rules reference by name to control HOW an inspected (SSLAction=Inspect) tunnel is decrypted — whether to inspect natively as HTTP/2, the upstream server-certificate-verification posture, the unsupported-TLS failure posture, the TLS version floor/cap, and the per-stream inactivity bound.
Package decryptprofile is the named SSL-decryption-profile engine: a PAN-OS-style "how to decrypt" object that policy rules reference by name to control HOW an inspected (SSLAction=Inspect) tunnel is decrypted — whether to inspect natively as HTTP/2, the upstream server-certificate-verification posture, the unsupported-TLS failure posture, the TLS version floor/cap, and the per-stream inactivity bound.
feedsync
Package feedsync is the UT1 URL-category feed syncer: it periodically downloads the UT1 Capitole blacklist tarball (via the NethServer mirror), extracts the categories in the ingest map, and bulk-writes them into the community URL-category database (internal/catdb).
Package feedsync is the UT1 URL-category feed syncer: it periodically downloads the UT1 Capitole blacklist tarball (via the NethServer mirror), extracts the categories in the ingest map, and bulk-writes them into the community URL-category database (internal/catdb).
fileblock
Package fileblock is the file-type blocking engine: an extension/MIME/ Content-Disposition blocklist (FileBlocker) and named file-type profiles (FileProfileStore, in fileprofile.go).
Package fileblock is the file-type blocking engine: an extension/MIME/ Content-Disposition blocklist (FileBlocker) and named file-type profiles (FileProfileStore, in fileprofile.go).
filemagic
Package filemagic does magic-byte detection for archive identification and polyglot detection — examining the first bytes of a response body to catch archives disguised as safe types and Content-Type/content mismatches.
Package filemagic does magic-byte detection for archive identification and polyglot detection — examining the first bytes of a response body to catch archives disguised as safe types and Content-Type/content mismatches.
fileutil
Package fileutil provides durable filesystem helpers shared by package main and internal/* packages (ADR-0003).
Package fileutil provides durable filesystem helpers shared by package main and internal/* packages (ADR-0003).
geoip
Package geoip is the GeoIP country-lookup engine: a local MaxMind GeoLite2 (.mmdb) reader with an in-memory IP→country cache.
Package geoip is the GeoIP country-lookup engine: a local MaxMind GeoLite2 (.mmdb) reader with an in-memory IP→country cache.
halease
Package halease is the HA fencing-lease primitive (ADR-0005 S1): a single cluster-wide lease with a strictly-monotonic epoch (the fencing token), arbitrated by a strongly-consistent backend.
Package halease is the HA fencing-lease primitive (ADR-0005 S1): a single cluster-wide lease with a strictly-monotonic epoch (the fencing token), arbitrated by a strongly-consistent backend.
hashcache
Package hashcache provides a size-bounded, TTL-evicting cache of security scan results keyed on the SHA-256 digest of scanned content.
Package hashcache provides a size-bounded, TTL-evicting cache of security scan results keyed on the SHA-256 digest of scanned content.
hostutil
Package hostutil provides pure host-string normalization helpers shared across the proxy (policy matching, category lookups, scan/bypass keys).
Package hostutil provides pure host-string normalization helpers shared across the proxy (policy matching, category lookups, scan/bypass keys).
lockout
Package lockout provides the login account-lockout limiter and the admin-API rate limiter.
Package lockout provides the login account-lockout limiter and the admin-API rate limiter.
logstore
Package logstore is the Badger-backed, time-ordered persistent request-log history engine.
Package logstore is the Badger-backed, time-ordered persistent request-log history engine.
nodegroup
Package nodegroup is the node-group definition store: named collections of Data-Plane nodes matched by label selectors (geo-aware grouping, tiered routing, policy scoping), with atomic JSON persistence.
Package nodegroup is the node-group definition store: named collections of Data-Plane nodes matched by label selectors (geo-aware grouping, tiered routing, policy scoping), with atomic JSON persistence.
obs
Package obs is the shared logging facade for internal/* packages (ADR-0003).
Package obs is the shared logging facade for internal/* packages (ADR-0003).
ocsp
Package ocsp is the OCSP revocation-checking engine for upstream TLS certificates: a TTL'd verdict cache plus the responder query pipeline behind a tls.Config VerifyPeerCertificate callback.
Package ocsp is the OCSP revocation-checking engine for upstream TLS certificates: a TTL'd verdict cache plus the responder query pipeline behind a tls.Config VerifyPeerCertificate callback.
otlp
Package otlp exports metrics and request spans to any OpenTelemetry Collector via the OTLP/HTTP JSON protocol (POST /v1/metrics, /v1/traces).
Package otlp exports metrics and request spans to any OpenTelemetry Collector via the OTLP/HTTP JSON protocol (POST /v1/metrics, /v1/traces).
pac
Package pac is the PAC (proxy auto-config) engine: the persisted PAC configuration store and the FindProxyForURL generator.
Package pac is the PAC (proxy auto-config) engine: the persisted PAC configuration store and the FindProxyForURL generator.
plugin
Package plugin is the Culvert middleware plugin API: the Middleware contract, the process-wide plugin chain, and the panic-safe dispatch the proxy hot path calls.
Package plugin is the Culvert middleware plugin API: the Middleware contract, the process-wide plugin chain, and the panic-safe dispatch the proxy hot path calls.
redaction
Package redaction is the data-governance engine for support bundles (REDACTION-MODEL.md, ADR-0009).
Package redaction is the data-governance engine for support bundles (REDACTION-MODEL.md, ADR-0009).
reqlog
Package reqlog is the request-log engine: a bounded in-memory ring (the newest MaxRing entries), an optional persistent JSONL layer with rotation and a count-once-log-first write-error contract, a bounded newest-first read over the persistent file with a short-TTL shared-parse cache, and the status→level mapping.
Package reqlog is the request-log engine: a bounded in-memory ring (the newest MaxRing entries), an optional persistent JSONL layer with rotation and a count-once-log-first write-error contract, a bounded newest-first read over the persistent file with a short-TTL shared-parse cache, and the status→level mapping.
rewrite
Package rewrite owns per-host HTTP header rewrite rules: the rule DTO, the ordered active rule set, and the request/response header mutators.
Package rewrite owns per-host HTTP header rewrite rules: the rule DTO, the ordered active rule set, and the request/response header mutators.
saasfeed
Package saasfeed is the SaaS category feed syncer: it periodically fetches a curated JSON file of SaaS URL categories (AI, Marketing, Messaging, …) from a remote URL and hands the parsed categories to an injected merge callback.
Package saasfeed is the SaaS category feed syncer: it periodically fetches a curated JSON file of SaaS URL categories (AI, Marketing, Messaging, …) from a remote URL and hands the parsed categories to an injected merge callback.
scanexcl
Package scanexcl holds the admin-managed scan-exclusion store: known-good SHA-256 content hashes and hostnames that bypass all body scanning.
Package scanexcl holds the admin-managed scan-exclusion store: known-good SHA-256 content hashes and hostnames that bypass all body scanning.
scanner
Package scanner is the DPI content scanner: a set of pre-compiled regex signatures applied to HTTP response bodies flowing through SSL-Inspect tunnels, with a per-host bypass list and atomic persistence.
Package scanner is the DPI content scanner: a set of pre-compiled regex signatures applied to HTTP response bodies flowing through SSL-Inspect tunnels, with a per-host bypass list and atomic persistence.
sealbox
Package sealbox is the recipient-public-key sealing leaf for support-bundle export (M4 E2E).
Package sealbox is the recipient-public-key sealing leaf for support-bundle export (M4 E2E).
secret
Package secret is the compiler-enforced containment boundary for key-encryption-key (KEK) material and key-at-rest wrapping, per docs/adr/0007-secret-containment-boundary.md and roadmap/SECRET-CONTAINMENT-PLAN.md.
Package secret is the compiler-enforced containment boundary for key-encryption-key (KEK) material and key-at-rest wrapping, per docs/adr/0007-secret-containment-boundary.md and roadmap/SECRET-CONTAINMENT-PLAN.md.
secscan
Package secscan is the security-scan orchestrator: it ties ClamAV, YARA, the threat feed, the hash cache, and the admin hash allowlist into the single pipeline the proxy hot path calls.
Package secscan is the security-scan orchestrator: it ties ClamAV, YARA, the threat feed, the hash cache, and the admin hash allowlist into the single pipeline the proxy hot path calls.
session
Package session implements Culvert's HMAC-SHA256-signed session tokens and the revocation machinery behind them (ADR-0002 extraction; engine was session.go in package main).
Package session implements Culvert's HMAC-SHA256-signed session tokens and the revocation machinery behind them (ADR-0002 extraction; engine was session.go in package main).
sse
Package sse is the Server-Sent-Events client hub: registration under a connection cap, fan-out broadcast with slow-client eviction, and the hub-level observability counters.
Package sse is the Server-Sent-Events client hub: registration under a connection cap, fan-out broadcast with slow-client eviction, and the hub-level observability counters.
sslbypass
Package sslbypass is the SSL-inspection bypass matcher: a runtime-managed list of host patterns (FQDN globs via hostutil.MatchFQDN semantics, or "~"-prefixed regexes) that must always bypass SSL inspection regardless of what the PBAC policy says, with pre-compiled matching and JSON file persistence.
Package sslbypass is the SSL-inspection bypass matcher: a runtime-managed list of host patterns (FQDN globs via hostutil.MatchFQDN semantics, or "~"-prefixed regexes) that must always bypass SSL inspection regardless of what the PBAC policy says, with pre-compiled matching and JSON file persistence.
ssrf
Package ssrf is the SSRF (server-side request forgery) guard: the private/internal CIDR table, host resolution checks with a short-TTL DNS cache, and the connect-time dialer control that closes the DNS-rebinding TOCTOU window.
Package ssrf is the SSRF (server-side request forgery) guard: the private/internal CIDR table, host resolution checks with a short-TTL DNS cache, and the connect-time dialer control that closes the DNS-rebinding TOCTOU window.
support
Package support is the support-bundle engine (SUPPORT-BUNDLE-SPEC.md, COLLECTOR-CONTRACT.md): a plugin registry of small, isolated, budgeted, self-redacting collectors plus a runner that assembles a deterministic, integrity-hashed `csb/1` bundle.
Package support is the support-bundle engine (SUPPORT-BUNDLE-SPEC.md, COLLECTOR-CONTRACT.md): a plugin registry of small, isolated, budgeted, self-redacting collectors plus a runner that assembles a deterministic, integrity-hashed `csb/1` bundle.
syslog
Package syslog forwards log lines to a remote syslog server over UDP or TCP.
Package syslog forwards log lines to a remote syslog server over UDP or TCP.
threatfeed
Package threatfeed is the local threat-feed manager: it downloads, persists, and provides instant offline lookups for known-malicious URL/domain lists.
Package threatfeed is the local threat-feed manager: it downloads, persists, and provides instant offline lookups for known-malicious URL/domain lists.
totp
Package totp implements TOTP (RFC 6238) validation using only the standard library: HMAC-SHA1 with a 30-second step, 6-digit output, and ±1 step clock skew.
Package totp implements TOTP (RFC 6238) validation using only the standard library: HMAC-SHA1 with a 30-second step, 6-digit output, and ±1 step clock skew.
uitls
Package uitls generates the self-signed TLS certificate for the admin UI: baseline loopback SANs, all local interface IPs, the hostname, operator extras (CULVERT_PUBLIC_IP + --ui-san values passed in by the caller), and best-effort cloud public-IP detection via instance metadata.
Package uitls generates the self-signed TLS certificate for the admin UI: baseline loopback SANs, all local interface IPs, the hostname, operator extras (CULVERT_PUBLIC_IP + --ui-san values passed in by the caller), and best-effort cloud public-IP detection via instance metadata.
upstream
Package upstream implements parent-proxy chaining with failover and circuit-breaker protection (ADR-0002 extraction; engine was upstream.go in package main).
Package upstream implements parent-proxy chaining with failover and circuit-breaker protection (ADR-0002 extraction; engine was upstream.go in package main).
urlcat
Package urlcat is the admin-managed URL-category engine: named categories with lowercase host-set indexing for O(labels) membership checks during policy evaluation, JSON file persistence, and the built-in + embedded-SaaS default seed list.
Package urlcat is the admin-managed URL-category engine: named categories with lowercase host-set indexing for O(labels) membership checks during policy evaluation, JSON file persistence, and the built-in + embedded-SaaS default seed list.

Jump to

Keyboard shortcuts

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