berghain

package module
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

README

Berghain

🕺 Welcome to Berghain: Where Only Valid Browsers Get the Backend Party Started! 🎉

Berghain is your trusty SPOE-Agent, guarding the entrance to the backend like a seasoned bouncer. This Go and HAProxy-powered tool ensures that only the coolest and most valid browsers can access the exclusive party happening on the other side.

With Berghain in charge, you can be confident that your backend is reserved for the true VIPs of the internet, keeping out any uninvited guests. It's like the bouncer of the web world, ensuring that your resources are reserved for the browsers that really know how to dance!

Seeing a "Request on Hold" screen, or want to understand what visitors experience? See the help page for troubleshooting, browser compatibility and privacy details.

Supported CAPTCHAs

Planned support

  • Simple Captcha (Including Sound)

Captcha challenge types

The turnstile, hcaptcha and recaptcha (v2 checkbox) level types render the provider widget on the challenge page and exchange its response token for a Berghain cookie after verifying it against the provider:

default:
  levels:
    - duration: 12h
      type: turnstile   # or hcaptcha / recaptcha
      sitekey: <your sitekey>
      secret: <your secret>

Things to know when enabling a captcha level:

  • The Berghain agent verifies tokens against the provider's siteverify endpoint, so it needs outbound HTTPS access. Verification fails closed: if the provider is unreachable, the challenge fails and the visitor can retry. verify_url overrides the endpoint, e.g. for recaptcha.net.
  • Challenge verification does a network round-trip, so the SPOE challenge group needs a larger timeout processing than the validate path. The example config runs the two groups as separate agents (berghain at 100ms, berghain_challenge at 6s) for this reason.
  • The token is only accepted when the provider-reported hostname matches the request identity (subdomains included). Provider test keys report a fixed hostname, so tests can set skip_hostname_check: true; production setups should never need it.
  • Visitors' browsers load the widget script from the provider's domain. If you serve the challenge page with a Content-Security-Policy, allow the provider in script-src and frame-src.

Example setup with HAProxy

To start berghain locally you can follow these easy steps:

For Debian / Ubuntu: apt install npm

  1. Run npm install inside web/
  2. Run npm run build inside web/
  3. Run haproxy -f examples/haproxy/haproxy.cfg
  4. Run go run ./cmd/spop/. -config cmd/spop/config.yaml

For production use, generate a random secret to place in the Berghain configuration file using openssl rand -base64 32.

Optional User-Agent policy

examples/haproxy/haproxy-ua-policy.cfg is an alternative HAProxy example that challenges known AI crawlers and browser-like clients, allows selected tools and public API/feed paths, and tarpits selected scraper libraries. The default example remains User-Agent neutral.

Customize the maps under examples/haproxy/maps/ and the public_endpoint ACLs before use. Map keys are lowercase literal substrings without whitespace. User-Agent headers are trivial to spoof, so this policy is traffic shaping only; it must not protect authenticated or otherwise sensitive endpoints.

Running with Docker

To run the project using Docker, follow these steps:

  1. Build the Docker images:

    docker-compose build
    
  2. Start the services:

    docker-compose up
    
  3. Access the application:

Make sure to have Docker and Docker Compose installed on your system before running these commands.

Customising the challenge page

The frontend build supports two environment variables for operator-owned customisation:

  • VITE_ENTRYPOINT selects an alternative HTML entrypoint.
  • VITE_HOOKS selects a JavaScript file whose labeled phase blocks are compiled directly into the default challenge flow.

Paths may be absolute or relative to the web/ directory when running the npm scripts. The file can contain shared static imports and any of these optional labeled blocks:

  • init runs after the DOM is ready and before the cookie check.
  • challengeStart runs after the challenge is fetched and can access challenge.
  • success runs before the success UI and can access challenge and countdown.
  • failure runs before the failure UI and can access challenge, countdown, and result.

For example:

init:{
    await document.fonts.ready;
    document.title = "Verifying - Example";
}

challengeStart:{
    console.info("Challenge started", challenge.t);
}

success:{
    document.documentElement.dataset.challengeStatus = "success";
    console.info("Challenge passed", {challenge, countdown});
}

failure:{
    document.documentElement.dataset.challengeStatus = "failure";
    console.error("Challenge failed", {challenge, countdown, result});
}

This example is available as web/examples/challenge-page.js.

cd web
VITE_HOOKS=./examples/challenge-page.js npm run build:default

The phase blocks are inserted as scoped statements during Vite's transform phase; there is no runtime hook registry or callback dispatch. Static imports are resolved relative to the customization file. Top-level await is supported and delays the next challenge step. Exceptions follow the surrounding challenge flow's normal error behavior, and omitted phase blocks are ignored.

The file cannot export values or contain other top-level statements. Phase blocks cannot use top-level var, dynamic import(), import.meta, or top-level arguments. Environment path changes require restarting the Vite development server.

Attributions

Thanks to @NullDev and @arellak, as they did most of the frontend work.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrEmpty         = fmt.Errorf("empty")
	ErrInvalidLength = fmt.Errorf("invalid length")
	ErrLevelTooLow   = fmt.Errorf("cookie level too low")
	ErrExpired       = fmt.Errorf("expired")
	ErrInvalidHMAC   = fmt.Errorf("invalid hmac")
)

Functions

func AcquireCookieBuffer

func AcquireCookieBuffer() *buffer.SliceBuffer

func NewZeroHasher

func NewZeroHasher(h hash.Hash) hash.Hash

func ReleaseCookieBuffer

func ReleaseCookieBuffer(b *buffer.SliceBuffer)

func ReleaseValidatorRequest

func ReleaseValidatorRequest(v *ValidatorRequest)

func ReleaseValidatorResponse

func ReleaseValidatorResponse(v *ValidatorResponse)

func ValidSupportID added in v0.3.0

func ValidSupportID(id []byte) bool

Types

type Berghain

type Berghain struct {
	Levels         []*LevelConfig
	TrustedDomains []string

	// HTTPClient is used for captcha siteverify requests.
	// Defaults to a client with a 5 second timeout.
	HTTPClient *http.Client
	// contains filtered or unexported fields
}

func NewBerghain

func NewBerghain(secret []byte) *Berghain

func (*Berghain) IsValidCookie

func (b *Berghain) IsValidCookie(ri RequestIdentifier, cookie []byte) error

func (*Berghain) LevelConfig

func (b *Berghain) LevelConfig(level uint8) *LevelConfig

type LevelConfig

type LevelConfig struct {
	Countdown int
	Duration  time.Duration
	Type      ValidationType

	// Captcha configuration, required for the turnstile, hcaptcha and
	// recaptcha validation types.
	CaptchaSitekey string
	CaptchaSecret  string
	// CaptchaVerifyURL overrides the provider siteverify endpoint,
	// e.g. for regional endpoints or tests.
	CaptchaVerifyURL string
	// CaptchaSkipHostnameCheck disables binding the provider-reported
	// hostname to the request identity. Provider test keys report a
	// fixed hostname, so tests need this; production setups do not.
	CaptchaSkipHostnameCheck bool
	// contains filtered or unexported fields
}

type RequestIdentifier

type RequestIdentifier struct {
	SrcAddr netip.Addr
	Host    []byte
	Level   uint8
}

func (RequestIdentifier) ToCookie

func (ri RequestIdentifier) ToCookie(b *Berghain, enc *buffer.SliceBuffer) error

func (RequestIdentifier) WriteTo

func (ri RequestIdentifier) WriteTo(h io.Writer) (int64, error)

type ValidationType

type ValidationType int
const (
	ValidationTypeNone ValidationType
	ValidationTypePOW

	ValidationTypeTurnstile
	ValidationTypeHCaptcha
	ValidationTypeReCaptcha
)

func (ValidationType) RunValidator

func (v ValidationType) RunValidator(b *Berghain, req *ValidatorRequest, resp *ValidatorResponse) error

type ValidatorRequest

type ValidatorRequest struct {
	Method     string
	Body       []byte
	Identifier *RequestIdentifier
	SupportID  []byte
}

func AcquireValidatorRequest

func AcquireValidatorRequest() *ValidatorRequest

type ValidatorResponse

type ValidatorResponse struct {
	Body  *buffer.SliceBuffer
	Token *buffer.SliceBuffer
}

func AcquireValidatorResponse

func AcquireValidatorResponse() *ValidatorResponse

Directories

Path Synopsis
cmd
spop command

Jump to

Keyboard shortcuts

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