Mamoru-WAF

february 25, 2026

A lightweight, zero-dependency Go Web Application Firewall with a premium Terminal UI and hot-reloading rule engine.

software-engineering · security · golang · tui · devops

GoBubble TeaDockerYAML

Mamoru-WAF: The Lightweight Guardian

A high-performance reverse proxy and Web Application Firewall built in Go with zero external dependencies. Mamoru-WAF sits in front of your backend services and evaluates every incoming request against a hot-reloadable rule engine — blocking SQLi, XSS, path traversal, and bot traffic before it reaches your app.

Mamoru-kun Mascot

The Problem

Every enterprise WAF assumes you have a Redis cluster for rate limiting, a PostgreSQL database for logging, and a DevOps team to manage updates. For a solo developer, a hobby project, or a small microservice, this is overkill that introduces unnecessary cost, latency, and operational burden.

The Solution

Mamoru-WAF is a single binary. No database. No external state. No infrastructure dependencies. Drop it in front of any HTTP service and get OWASP Top 10 protection in under 60 seconds.

Prerequisites

  • Go 1.21 or higher — go version to check
  • Docker 24+ (optional, for containerized deployment)
  • A running backend HTTP service to protect (any language/framework)

Installation

Option A: Build from Source

bash
git clone https://github.com/Mic-360/mamoru-waf.git
cd mamoru-waf

go build -o mamoru ./cmd/server
./mamoru --config rules.yaml
git clone https://github.com/Mic-360/mamoru-waf.git
cd mamoru-waf

go build -o mamoru ./cmd/server
./mamoru --config rules.yaml

Option B: Docker

bash
# Build the image locally
docker build -t mamoru-waf .

# Run with your config file mounted
docker run -d \
  -p 8080:8080 \
  -v $(pwd)/rules.yaml:/app/rules.yaml \
  mamoru-waf
# Build the image locally
docker build -t mamoru-waf .

# Run with your config file mounted
docker run -d \
  -p 8080:8080 \
  -v $(pwd)/rules.yaml:/app/rules.yaml \
  mamoru-waf

Option C: Docker Compose Sidecar

yaml
# docker-compose.yml
version: '3'
services:
  mamoru:
    build: ./mamoru-waf
    ports:
      - '8080:8080'
    volumes:
      - ./rules.yaml:/app/rules.yaml
    environment:
      - MAMORU_TARGET=http://app:3000
      - MAMORU_MODE=block
    depends_on:
      - app
  app:
    image: your-backend-image
    expose:
      - '3000'
# docker-compose.yml
version: '3'
services:
  mamoru:
    build: ./mamoru-waf
    ports:
      - '8080:8080'
    volumes:
      - ./rules.yaml:/app/rules.yaml
    environment:
      - MAMORU_TARGET=http://app:3000
      - MAMORU_MODE=block
    depends_on:
      - app
  app:
    image: your-backend-image
    expose:
      - '3000'

Configuration: rules.yaml

Every rule is evaluated in order. First match wins.

yaml
proxy:
  target: 'http://localhost:3000' # The backend service to protect
  listen: ':8080' # Port Mamoru listens on
  timeout: 30s

mode: 'block' # "block" → block matching requests | "detect" → log only

rate_limit:
  enabled: true
  requests_per_second: 100
  burst: 200
  per_path:
    - path: '/api/login'
      requests_per_second: 5
      burst: 10

rules:
  - name: 'block-sqli'
    type: 'regex'
    target: 'url' # options: "url" | "body" | "headers" | "all"
    pattern: '(?i)(union|select|insert|drop|sleep|benchmark)'
    action: 'block'
    status: 403

  - name: 'block-xss'
    type: 'regex'
    target: 'all'
    pattern: '(?i)(<script|javascript:|onerror=|onload=)'
    action: 'block'
    status: 403

  - name: 'block-path-traversal'
    type: 'regex'
    target: 'url'
    pattern: "(\\.\\./|%2e%2e%2f)"
    action: 'block'
    status: 403

  - name: 'block-scanners'
    type: 'regex'
    target: 'headers'
    pattern: '(?i)(sqlmap|nikto|masscan|nmap)'
    action: 'block'
    status: 403

fail_safe: 'open' # "open" → let traffic through if engine crashes | "block" → block all

tui:
  enabled: true # Set to false for headless/production deployments
proxy:
  target: 'http://localhost:3000' # The backend service to protect
  listen: ':8080' # Port Mamoru listens on
  timeout: 30s

mode: 'block' # "block" → block matching requests | "detect" → log only

rate_limit:
  enabled: true
  requests_per_second: 100
  burst: 200
  per_path:
    - path: '/api/login'
      requests_per_second: 5
      burst: 10

rules:
  - name: 'block-sqli'
    type: 'regex'
    target: 'url' # options: "url" | "body" | "headers" | "all"
    pattern: '(?i)(union|select|insert|drop|sleep|benchmark)'
    action: 'block'
    status: 403

  - name: 'block-xss'
    type: 'regex'
    target: 'all'
    pattern: '(?i)(<script|javascript:|onerror=|onload=)'
    action: 'block'
    status: 403

  - name: 'block-path-traversal'
    type: 'regex'
    target: 'url'
    pattern: "(\\.\\./|%2e%2e%2f)"
    action: 'block'
    status: 403

  - name: 'block-scanners'
    type: 'regex'
    target: 'headers'
    pattern: '(?i)(sqlmap|nikto|masscan|nmap)'
    action: 'block'
    status: 403

fail_safe: 'open' # "open" → let traffic through if engine crashes | "block" → block all

tui:
  enabled: true # Set to false for headless/production deployments

CLI Flags

FlagDefaultDescription
--configrules.yamlPath to the config file
--headlessfalseDisable the TUI, log to stdout
--log-file""Write logs to file (in addition to TUI)
--port8080Override the listen port
--mode(from config)Override mode: block or detect
bash
# Example: headless production run with log file
./mamoru --config /etc/mamoru/rules.yaml --headless --log-file /var/log/mamoru.log
# Example: headless production run with log file
./mamoru --config /etc/mamoru/rules.yaml --headless --log-file /var/log/mamoru.log

Hot Reloading Rules

Edit rules.yaml at any time while Mamoru is running. The AtomicEngine detects the change and applies it within 2 seconds without dropping any active connections.

bash
# Edit rules live
vim rules.yaml
# Changes apply automatically — no restart needed
# Edit rules live
vim rules.yaml
# Changes apply automatically — no restart needed

The TUI Dashboard

When tui.enabled: true, Mamoru launches an interactive terminal dashboard:

PanelDescription
Live Event LogReal-time feed of blocked requests with reason, IP, and timestamp
Backend HealthStatus indicators for your proxied upstream services
Request StatsRequests/sec, block rate, top blocked rule patterns
Rule SummaryActive rules and their match counts

Hotkeys:

KeyAction
rReload rules.yaml manually
dToggle between block and detect mode live
qQuit
↑ / ↓Scroll the event log

Mamoru Banner

Adding Custom Rules

Implement the Rule interface in internal/rules/:

go
type Rule interface {
    Name() string
    Evaluate(r *http.Request) (blocked bool, reason string)
}

type BlockBadHeaderRule struct{}

func (b BlockBadHeaderRule) Name() string { return "block-bad-header" }

func (b BlockBadHeaderRule) Evaluate(r *http.Request) (bool, string) {
    if r.Header.Get("X-Suspicious") == "yes" {
        return true, "suspicious header detected"
    }
    return false, ""
}
type Rule interface {
    Name() string
    Evaluate(r *http.Request) (blocked bool, reason string)
}

type BlockBadHeaderRule struct{}

func (b BlockBadHeaderRule) Name() string { return "block-bad-header" }

func (b BlockBadHeaderRule) Evaluate(r *http.Request) (bool, string) {
    if r.Header.Get("X-Suspicious") == "yes" {
        return true, "suspicious header detected"
    }
    return false, ""
}

Register it in the engine initializer and it evaluates on every request automatically.

Performance

  • Latency overhead: < 2ms per request in standard configurations
  • Binary size: < 15MB uncompressed
  • Memory footprint: < 30MB resident set at steady state
  • Throughput: Handles thousands of req/s on a single CPU core

Source: github.com/Mic-360/mamoru-waf

back to projects