- Build:
go build ./cmd/sbproxy/ - Full build:
go build ./... - Test:
go test ./... - Config tests:
go test ./internal/config/ -count=1 -timeout 120s - Module tests:
go test ./internal/modules/... -v - Lint:
golangci-lint run ./... - Validate config:
go run ./cmd/sbproxy/ validate -c sb.yml
Before committing ANY change, you MUST verify:
go build ./...- zero errorsgo test ./internal/config/ -count=1 -timeout 120s- all tests passgo test ./internal/modules/... -v- all module tests passgo vet ./...- zero warnings
Do NOT commit if any of these fail. Fix the issue first.
pkg/plugin/- Public plugin interfaces (ActionHandler, PolicyEnforcer, AuthProvider, TransformHandler, RequestEnricher) and registrypkg/config/- Public config types (zero internal imports)pkg/httpkit/- Public HTTP utilities (ClientIP, SplitHostPort)pkg/events/- Public EventBus interface (no-op default)pkg/proxy/- Public lifecycle API: New(), Run(), Shutdown()internal/modules/- Self-contained modules registered via pkg/plugininternal/modules/action/- Action handlers: proxy, redirect, static, echo, loadbalancer, aiproxy, mcp, a2a, websocket, grpc, graphql, mock, beacon, noop, storageinternal/modules/auth/- Auth providers: apikey, basicauth, bearer, jwt, forwardauth, digest, grpcauth, noopinternal/modules/policy/- Policy enforcers: ratelimit, ipfilter, expression (CEL), waf, ddos, csrf, secheaders, requestlimit, assertion, sriinternal/modules/transform/- Response transforms: json, jsonprojection, jsonschema, html, markdown, css, template, luajson, encoding, formatconvert, normalize, replacestrings, ssechunking, payloadlimit, discard, optimizehtml, javascript, htmltomarkdown, noop
internal/config/- Config loading, validation, compilation, and the 18-layer handler chaininternal/engine/- HTTP request pipeline (chi router, middleware, streaming, transport)internal/ai/- AI gateway handler (providers, routing, guardrails, streaming)internal/extension/- Scripting runtimes (CEL, Lua, MCP)internal/cache/- Response and object caching (memory, file, pebble, redis backends)internal/loader/- Config lifecycle (configloader, featureflags, manager, settings)internal/observe/- Observability (events, logging, metrics, telemetry)internal/platform/- Infrastructure (circuitbreaker, dns, health, messenger, storage)internal/request/- Per-request context (classifier, ratelimit, session)internal/security/- Security primitives (certpin, crypto, hostfilter)internal/service/- Server lifecycle (server, signals, hotreload)internal/transformer/- Response body transformation (css, html, json)cmd/sbproxy/- Binary entry pointexamples/- Working config examples (all use test.sbproxy.dev)docs/- Documentation
sbproxy uses a Caddy-style module architecture:
- Each module registers itself via
init()intopkg/pluginregistry - Modules are imported via blank imports in
internal/modules/imports.go - The config compiler (
internal/config/compiler.go) discovers modules from thepkg/pluginregistry - New modules implement one of:
plugin.ActionHandler,plugin.PolicyEnforcer,plugin.AuthProvider,plugin.TransformHandler, orplugin.RequestEnricher - Optional lifecycle:
plugin.Provisioner,plugin.Validator,plugin.Cleanup - Additional registrations:
plugin.RegisterMiddleware,plugin.RegisterHealthChecker,plugin.RegisterTransport,plugin.RegisterEnricher
The plugin.RequestEnricher interface enables extensible per-request context enrichment without hardcoding features like GeoIP or user-agent parsing into the core:
- Enterprise/third-party packages implement
RequestEnricher(Name + Enrich methods) - Register via
plugin.RegisterEnricher()ininit() - The enricher middleware (
internal/engine/middleware/enricher.go) calls all registered enrichers on every request - Enrichers store results via
plugin.SetEnrichmentData(), and the middleware applies them toRequestData - Errors are logged but never block request processing
The compiler (internal/config/compiler.go) builds each origin's handler chain inside-out:
- Action handler (innermost)
- Response cache
- Transforms
- on_response callbacks
- Response modifiers
- Request modifiers
- Auth
- on_request callbacks
- Compression
- CORS
- HSTS
- Policies (outermost of per-origin middleware)
- Rate limit headers
- Bot detection
- Threat protection
- Session
- Message signatures (RFC 9421)
- Traffic capture, error pages, force_ssl, allowed_methods (outermost)
The chain is compiled once per origin and cached. Requests execute the pre-compiled chain with zero per-request allocation.
- Create package under
internal/modules/{type}/{name}/ - Define config struct and implement the appropriate
pkg/plugininterface - Register via
plugin.Register{Action|Policy|Auth|Transform|Enricher}()ininit() - Add blank import to
internal/modules/imports.go - Run full build and test suite before committing
pkg/packages must NEVER import frominternal/- Modules should NOT import
internal/config- usepkg/plugininterfaces - Run
go build ./...after every change - All examples use test.sbproxy.dev as the backend
- Do NOT use em dashes in any content
- Do NOT include enterprise features in OSS code
- Enterprise features are available via sbproxy Cloud (cloud.sbproxy.dev)
- GeoIP and UA parser enrichers are enterprise-only (registered via
plugin.RegisterEnricherin sbproxy-enterprise)
- This project is licensed under Apache 2.0 (see
LICENSE) - NOTICE file maintenance: When adding or upgrading a dependency that is licensed under Apache 2.0 (not dual MIT/Apache-2.0), update the
NOTICEfile with the dependency's copyright notice and license. Apache 2.0 Section 4 requires this. - To check: run
go-licenses csv ./...or inspectgo.modfor Apache-only deps - Copyright holder: Soap Bucket LLC
- Do NOT expose internal implementation details (language, libraries, algorithms) in user-facing content per the root CLAUDE.md anti-patterns