routewarden

package module
v0.2.4 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 13 Imported by: 0

README

RouteWarden Logo

RouteWarden

High-performance Traefik middleware to stop sensitive file exposure (.env, .git, backups), neutralize path-evasion attacks, whitelist IPs, and serve custom error/captcha responses before requests reach your backend.

GitHub Release CI Status Go Reference Go Report Card Documentation Site


📖 Full Documentation, Guides & Wiki: https://routewarden.github.io/docs/
📂 Runnable Scenarios: examples/ (Docker Compose & Kubernetes CRDs)


Supported Traefik Versions

Traefik Version Status Notes
Traefik v3.x (v3.0, v3.1, v3.2+) ✅ Fully Supported Standard Yaegi runtime, Docker labels & Kubernetes CRDs
Traefik v2.x (v2.8 – v2.11+) ✅ Fully Supported Compatible with standard plugin mechanism
Traefik v1.x ❌ Not Supported Plugins are not supported in Traefik v1

What is RouteWarden?

RouteWarden is a lightweight Traefik middleware written in pure Go (with zero external dependencies) that intercepts malicious reconnaissance probing, sensitive file exposure, and automated bot scans before requests ever reach your backend:

  • 🛡️ Anti-Probing & Scanner Defense: Instantly halts automated web vulnerability scanners and bots probing for exposed secrets, configuration files, and unprotected admin interfaces.
  • 📁 Zero-Config File Guard: Out-of-the-box blocking for .env*, .git, .aws, .sql, .bak, .conf, .yaml, server logs, and debug endpoints.
  • ⚡ Anti-Evasion Engine: Normalizes multi-layer URL encoding (%252e%252e), semicolon matrix parameters (/;param/.env), Windows backslashes (\), and null bytes.
  • 🌐 IP & CIDR Whitelist: Bypass blocking for corporate VPNs, office IPs, or developer subnets (10.0.0.0/8, 100.64.0.0/10).
  • 🎭 Flexible Responses & Active Defense: Neutralize probe attempts with standard 404 Not Found (making endpoints appear non-existent), 403 Forbidden, custom JSON, HTML, honeypot Redirects, interactive Turnstile / hCaptcha challenges, silent TCP drops, or an active Gzip Bomb (gzipBomb) that expands ~1000x in crawler RAM to halt automated reconnaissance scanners.

Quick Start (404 Response Example)

The cleanest way to handle reconnaissance bots is returning a standard 404 Not Found so attackers believe the file does not exist.

Option A: Docker Compose

services:
  traefik:
    image: traefik:v3.1
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--experimental.plugins.routewarden.modulename=github.com/routewarden/traefik-warden"
      - "--experimental.plugins.routewarden.version=v0.2.4"
    ports:
      - "80:80"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  webapp:
    image: nginx:alpine
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.webapp.rule=Host(`localhost`)"
      - "traefik.http.routers.webapp.entrypoints=web"
      - "traefik.http.routers.webapp.middlewares=warden-shield"

      # RouteWarden Configuration
      # (Default: true) Enable or disable middleware
      - "traefik.http.middlewares.warden-shield.plugin.routewarden.enabled=true"
      # (Default: true) Block sensitive files (.env*, .git, .aws, .sql, .bak, .log, configs)
      - "traefik.http.middlewares.warden-shield.plugin.routewarden.enableDefaultPatterns=true"
      # (Optional) Custom regex patterns to block (Default: [])
      - "traefik.http.middlewares.warden-shield.plugin.routewarden.pathPatterns=(?i)^/admin(/.*)?$,(?i)^/api/internal(/.*)?$"
      # (Optional) Safe exception overrides to allow (Default: robots.txt, ads.txt, sitemap.xml, .well-known/*)
      - "traefik.http.middlewares.warden-shield.plugin.routewarden.allowPatterns=(?i)^/api/internal/health$,(?i)^/robots\\.txt$"
      # Return a clean 404 response (Default mode: text, Default statusCode: 403)
      - "traefik.http.middlewares.warden-shield.plugin.routewarden.response.mode=text"
      - "traefik.http.middlewares.warden-shield.plugin.routewarden.response.statusCode=404"
      - "traefik.http.middlewares.warden-shield.plugin.routewarden.response.body=404 page not found"

Option B: Traefik Dynamic Configuration (dynamic_conf.yml)

1. Static Configuration (traefik.yml)
experimental:
  plugins:
    routewarden:
      moduleName: github.com/routewarden/traefik-warden
      version: v0.2.4
2. Dynamic Configuration (dynamic_conf.yml)
http:
  middlewares:
    warden-404:
      plugin:
        routewarden:
          enabled: true                # Default: true
          enableDefaultPatterns: true  # Default: true (.env*, .git, .aws, .sql, .bak, etc.)
          # (Optional) Custom regex patterns to block (Default: [])
          pathPatterns:
            - '(?i)^/admin(/.*)?$'
            - '(?i)^/api/internal(/.*)?$'
          # (Optional) Safe exceptions to allow (Default: robots.txt, ads.txt, sitemap.xml, .well-known/*)
          allowPatterns:
            - '(?i)^/api/internal/health$'
            - '(?i)^/robots\.txt$'
          # (Optional) Trusted developer/VPN IP bypass (Default: [])
          allowedIps:
            - "127.0.0.1"
            - "10.0.0.0/8"
          # Response action (Default mode: text, Default statusCode: 403)
          response:
            mode: text
            statusCode: 404
            body: "404 page not found"

  routers:
    app-router:
      rule: "Host(`app.example.com`)"
      entryPoints:
        - web
      middlewares:
        - warden-404
      service: app-service

Basic Configuration Options

Option Type Default Description
enabled bool true Turn the middleware on or off.
enableDefaultPatterns bool true Block common sensitive files (.env*, .git, .aws, .sql, .bak, .log, configs).
enableDefaultAllowPatterns bool true Enable built-in allowlist exemptions (/robots.txt, /sitemap.xml, /.well-known/*).
pathPatterns []string [] Additional custom regex patterns to block (e.g. ['(?i)^/admin/.*']).
allowPatterns []string [] Custom safe regex overrides to always allow.
allowedIps []string [] Whitelisted IPv4/IPv6 addresses or CIDR subnets (e.g. 127.0.0.1, 10.0.0.0/8).
checkQuery bool false Also inspect query parameters for blocked patterns.
response.mode string "text" Action on block: "text", "json", "html", "xml", "captcha", "redirect", "proxy", "silentDrop", "gzipBomb", "tarpit", "fakeSuccess", "rateLimitChallenge", or "infiniteStream".
response.statusCode int 403 HTTP status code returned to client (e.g. 404, 403, 401, 429, or 200 for honeypots).
response.body string "" Custom payload returned in the response body.

💡 For the complete list of settings (including Captcha providers, custom HTML templates, and header injection), visit the Full Configuration Reference.
⚠️ Note on gzipBomb: Only attach this mode to confirmed exploit endpoints (/.env, wp-login.php, honeypots). Never attach it globally to public routes where legitimate search engine bots (Googlebot, Bingbot) or normal visitors could be impacted. Always keep enableDefaultAllowPatterns: true so /robots.txt is allowed.


Documentation & Advanced Examples

For in-depth setup guides, anti-evasion architecture, and ready-to-run blueprints, visit our Documentation Wiki:


Testing & Quality Assurance

RouteWarden maintains a comprehensive automated testing pipeline with 94.5% statement test coverage and automated data race detection:

Test Suite Scope Command CI Status
Go Unit & Race Tests Core engine, IP CIDR filter, path normalization, response modes, and security evasion vectors go test -v -race ./... CI
Statement Coverage Full test coverage report across all packages (94.5%) go test -coverprofile=coverage.out ./... ✅ 94.5% Coverage
# Run all Go tests with race detector
go test -v -race ./...

# Run statement coverage breakdown
go test -coverprofile=coverage.out ./... && go tool cover -func=coverage.out

License

This project is licensed under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var DefaultAllowPatterns = []string{
	`(?i)^/robots\.txt$`,
	`(?i)^/sitemap.*\.xml$`,
	`(?i)^/ads\.txt$`,
	`(?i)^/security\.txt$`,
	`(?i)^/\.well-known(/.*)?$`,
}

DefaultAllowPatterns contains typical legitimate endpoints that might otherwise match broad patterns.

View Source
var DefaultBlockPatterns = []string{

	`(?i)(^|/)(\.env.*|.*\.(txt|log|bak|backup|sql|conf|config|ini|yaml|yml))$`,

	`(?i)(^|/)\.(git|svn|hg|bzr|cvs)(/.*|$)`,

	`(?i)(^|/)\.(aws|ssh|kube|docker)(/.*|$)`,

	`(?i).*\.(tar|tar\.gz|tgz|zip|rar|7z|gz|bz2|iso|dump|sqlite|sqlite3|db)$`,

	`(?i)(^|/)(phpinfo\.php|info\.php|server-status|server-info|actuator(/.*)?|metrics|heapdump|trace|env)$`,

	`(?i)(^|/)(composer\.(json|lock)|package-lock\.json|yarn\.lock|pnpm-lock\.yaml|Pipfile|Pipfile\.lock|requirements\.txt)$`,
}

DefaultBlockPatterns contains well-known sensitive endpoints and file extensions.

Functions

func ExtractCandidatePaths

func ExtractCandidatePaths(rawPath, pathStr, requestURI string) []string

ExtractCandidatePaths normalizes and extracts all representations of a request URI path, neutralizing common evasion techniques like double encoding, backslash substitution, matrix parameters, and null bytes.

func ExtractClientIP

func ExtractClientIP(req *http.Request) string

ExtractClientIP extracts the client IP address from proxy headers or RemoteAddr socket.

func New

func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error)

New creates a new RouteWarden plugin handler.

Types

type CaptchaConfig

type CaptchaConfig struct {
	Provider string `json:"provider,omitempty"` // "turnstile", "hcaptcha", "recaptcha", or "custom"
	SiteKey  string `json:"siteKey,omitempty"`  // Public site key
	Title    string `json:"title,omitempty"`    // Challenge page title
	Template string `json:"template,omitempty"` // Custom HTML template
}

CaptchaConfig holds captcha configuration options.

type Config

type Config struct {
	Enabled                    bool            `json:"enabled,omitempty"`
	EnableDefaultPatterns      bool            `json:"enableDefaultPatterns,omitempty"`
	EnableDefaultAllowPatterns bool            `json:"enableDefaultAllowPatterns,omitempty"` // Controls built-in whitelist (robots.txt, sitemap.xml, .well-known)
	PathPatterns               []string        `json:"pathPatterns,omitempty"`               // Synonym for blockPatterns
	BlockPatterns              []string        `json:"blockPatterns,omitempty"`
	AllowPatterns              []string        `json:"allowPatterns,omitempty"`
	AllowedIPs                 []string        `json:"allowedIps,omitempty"` // Whitelist of IPs or CIDR subnets exempt from blocking
	StatusCode                 int             `json:"statusCode,omitempty"`
	CustomResponseText         string          `json:"customResponseText,omitempty"`
	SilentDrop                 bool            `json:"silentDrop,omitempty"`
	CheckQuery                 bool            `json:"checkQuery,omitempty"`
	Response                   *ResponseConfig `json:"response,omitempty"`
}

Config holds the plugin configuration.

func CreateConfig

func CreateConfig() *Config

CreateConfig creates the default plugin configuration.

type IPFilter

type IPFilter struct {
	// contains filtered or unexported fields
}

IPFilter evaluates incoming requests against an IP or CIDR subnet whitelist.

func NewIPFilter

func NewIPFilter(allowedIPs []string) (*IPFilter, error)

NewIPFilter parses and creates an IPFilter from a list of IP strings and CIDR notation subnets.

func (*IPFilter) IsAllowed

func (f *IPFilter) IsAllowed(req *http.Request) bool

IsAllowed returns true if the client IP in the request matches any whitelisted IP or subnet.

type ResponseConfig

type ResponseConfig struct {
	Mode                     string            `json:"mode,omitempty"`                     // "text", "json", "html", "captcha", "redirect"
	StatusCode               int               `json:"statusCode,omitempty"`               // HTTP status code (e.g. 403, 404, 429)
	ContentType              string            `json:"contentType,omitempty"`              // Custom Content-Type header override
	Body                     string            `json:"body,omitempty"`                     // Response payload (JSON string, HTML, or text)
	Headers                  map[string]string `json:"headers,omitempty"`                  // Custom response headers (e.g. Retry-After, X-Blocked-By)
	RedirectURL              string            `json:"redirectUrl,omitempty"`              // Target URL when Mode is "redirect"
	ProxyURL                 string            `json:"proxyUrl,omitempty"`                 // Target backend honeypot URL when Mode is "proxy"
	Captcha                  *CaptchaConfig    `json:"captcha,omitempty"`                  // Captcha settings when Mode is "captcha"
	GzipBombMB               int               `json:"gzipBombMB,omitempty"`               // Uncompressed size in Megabytes for gzipBomb mode (default: 10)
	RetryAfterSeconds        int               `json:"retryAfterSeconds,omitempty"`        // Seconds for Retry-After header when Mode is "rateLimitChallenge" (default: 300)
	TarpitDelayMs            int               `json:"tarpitDelayMs,omitempty"`            // Milliseconds between bytes for tarpit mode (default: 1000)
	TarpitMaxDurationSeconds int               `json:"tarpitMaxDurationSeconds,omitempty"` // Max seconds before terminating tarpit connection (default: 60)
	StreamSizeMB             int               `json:"streamSizeMB,omitempty"`             // Size in Megabytes for infiniteStream/garbageStream mode (default: 100)
}

ResponseConfig defines how blocked requests should be answered.

type ResponseHandler

type ResponseHandler struct {
	// contains filtered or unexported fields
}

ResponseHandler manages custom response execution (JSON, HTML, Captcha, Redirect, Text).

func NewResponseHandler

func NewResponseHandler(respCfg *ResponseConfig, topStatusCode int, topCustomText string, silentDrop bool) (*ResponseHandler, error)

NewResponseHandler initializes a ResponseHandler with compiled templates and proxy handlers.

func (*ResponseHandler) ServeBlockedRequest

func (h *ResponseHandler) ServeBlockedRequest(w http.ResponseWriter, req *http.Request)

ServeBlockedRequest handles writing the configured response to the client.

func (*ResponseHandler) SetProxyHandlerForTest

func (h *ResponseHandler) SetProxyHandlerForTest(p http.Handler)

SetProxyHandlerForTest allows unit tests to inject a mock reverse proxy handler without listening on network sockets.

type RouteWarden

type RouteWarden struct {
	// contains filtered or unexported fields
}

RouteWarden is the Traefik middleware plugin handler.

func (*RouteWarden) ServeHTTP

func (rw *RouteWarden) ServeHTTP(w http.ResponseWriter, req *http.Request)

Jump to

Keyboard shortcuts

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