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
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.

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 versionto check - Docker 24+ (optional, for containerized deployment)
- A running backend HTTP service to protect (any language/framework)
Installation
Option A: Build from Source
git clone https://github.com/Mic-360/mamoru-waf.git
cd mamoru-waf
go build -o mamoru ./cmd/server
./mamoru --config rules.yamlgit clone https://github.com/Mic-360/mamoru-waf.git
cd mamoru-waf
go build -o mamoru ./cmd/server
./mamoru --config rules.yamlOption B: Docker
# 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-wafOption C: Docker Compose Sidecar
# 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.
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 deploymentsproxy:
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 deploymentsCLI Flags
| Flag | Default | Description |
|---|---|---|
--config | rules.yaml | Path to the config file |
--headless | false | Disable the TUI, log to stdout |
--log-file | "" | Write logs to file (in addition to TUI) |
--port | 8080 | Override the listen port |
--mode | (from config) | Override mode: block or detect |
# 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.logHot 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.
# Edit rules live
vim rules.yaml
# Changes apply automatically — no restart needed# Edit rules live
vim rules.yaml
# Changes apply automatically — no restart neededThe TUI Dashboard
When tui.enabled: true, Mamoru launches an interactive terminal dashboard:
| Panel | Description |
|---|---|
| Live Event Log | Real-time feed of blocked requests with reason, IP, and timestamp |
| Backend Health | Status indicators for your proxied upstream services |
| Request Stats | Requests/sec, block rate, top blocked rule patterns |
| Rule Summary | Active rules and their match counts |
Hotkeys:
| Key | Action |
|---|---|
r | Reload rules.yaml manually |
d | Toggle between block and detect mode live |
q | Quit |
↑ / ↓ | Scroll the event log |

Adding Custom Rules
Implement the Rule interface in internal/rules/:
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