This is the multi-page printable view of this section. .

Return to the regular view of this page.

CheeseWAF

Self-hosted Web Application Firewall. Install, configure, and operate it.

CheeseWAF is a self-hosted Web Application Firewall. It ships as one Go binary with an embedded SQLite store, a Web console, a CLI / TUI, and a REST management API.

The data plane inspects requests, then proxies them upstream. It does not call a large language model on every request. After the response is sent, an optional ALAP queue can review suspicious samples.

Download packaged builds from GitHub Releases. The project is licensed under Apache License 2.0.

How it works

  1. Data plane. Parameters are decoded, then parsed. Deterministic SQL injection, XSS, and command execution can be blocked immediately.
  2. ALAP. After the response is sent, ambiguous or embedded samples go to a background queue. Any OpenAI-compatible model can review them.
  3. Review results. Findings marked high or critical can become lasting IP, fingerprint, or signature rules when auto-agree is on.

ALAP stands for AI Large-Language-Model Auto Pilot.

Default listeners

PlaneDefault addressRole
Data planehttp://127.0.0.1:8080Receive site traffic, inspect, proxy upstream
Management planehttp://127.0.0.1:9443Web console, REST API, setup wizard. Docker defaults to HTTPS
Cluster planehttp://127.0.0.1:9444Node sync in cluster mode
Local controllerhttp://127.0.0.1:17943Windows / macOS desktop controller only

Start here

Linux, Docker, Windows, and macOS.

Pipeline, paranoia levels, isolated vs embedded.

Semantic engine, IP, bot, rate limit, ACL.

1 - How CheeseWAF works

Data plane vs ALAP, why requests are not sent to a model in line, and what ships in one binary.

Traditional regex WAFs keep large signature libraries. They are expensive to maintain and easy to evade with encoding or wrapping.

Calling a large language model on every request adds network latency you cannot hide.

CheeseWAF splits the work.

Two planes

Data plane

The process that accepts HTTP, HTTPS, or HTTP/3 does this in order:

  1. IP, GeoIP, and client soft-fingerprint checks
  2. Bot challenge, rate limit, and waiting room
  3. Semantic analysis of decoded parameter values
  4. Reverse proxy to the configured upstream

This path must stay fast. It never waits on a remote model.

Control plane

The management listener hosts:

  • the setup wizard at /setup
  • the Web console
  • the REST API under /api
  • optional Prometheus metrics

Keep server.admin_public false unless you also enable TLS and restrict who can reach the port.

ALAP

After the client already has a response, CheeseWAF can enqueue:

  • isolated hits that were blocked at paranoia 5
  • embedded hits that were allowed at levels 2–4
  • other borderline samples the engine marks for review

A worker calls the configured model. The operator (or auto-agree) then saves a lasting rule or dismisses the sample.

See ALAP and the review queue.

What ships together

PieceRole
cheesewafForwarding process. Default command is serve
waf-cliSame binary or a symlink. Default command is the TUI panel
cheesewaf-guiLoopback-only desktop controller on Windows and macOS
Web consoleReact UI served from the management plane
SQLiteDefault store, CGO-free (modernc.org/sqlite)

You do not need Redis, Nginx, or an external database to start.

2 - Install

Choose a CheeseWAF package for Linux, Docker, Windows, or macOS.

Pick one install path. Do not mix an NSIS install with a hand-copied Linux tree on the same host unless you know which process owns the ports.

systemd unit, system user, /etc/cheesewaf.

Compose, read-only root, non-root UID 10001.

Single exe, zip, or NSIS. Local controller on loopback.

DMG app or tar.gz CLI.

Release files

Download Alpha pre-releases, or take the same files from Actions artifacts.

FilePlatform
cheesewaf-*-linux-amd64.tar.gzLinux x86_64
cheesewaf-*-linux-arm64.tar.gzLinux ARM64
cheesewaf-*-linux-loong64.tar.gzLinux LoongArch
cheesewaf-*-darwin-amd64.tar.gz / .dmgmacOS Intel
cheesewaf-*-darwin-arm64.tar.gz / .dmgmacOS Apple Silicon
cheesewaf-*-windows-amd64.exeWindows x86_64 CLI
cheesewaf-*-windows-arm64.exeWindows ARM64 CLI
cheesewaf-*-windows-amd64.zipWindows x86_64 portable tree
cheesewaf-*-windows-arm64.zipWindows ARM64 portable tree
CheeseWAF-*-windows-*-setup.exeWindows NSIS installer

After install, continue with Quick start.

2.1 - Linux (systemd)

Install the CheeseWAF binary, create the system user, and enable the systemd unit.

Use this path on a Linux VM or bare metal host.

Unpack

BASH
tar -xzf cheesewaf-*-linux-amd64.tar.gz
cd cheesewaf-*

Replace amd64 with arm64 or loong64 when that is the CPU.

Install files

BASH
sudo install -m 0755 cheesewaf /usr/local/bin/cheesewaf
sudo ln -sf /usr/local/bin/cheesewaf /usr/local/bin/waf-cli

sudo mkdir -p /etc/cheesewaf /var/lib/cheesewaf /var/log/cheesewaf
sudo cp configs/cheesewaf.yaml /etc/cheesewaf/cheesewaf.yaml

sudo useradd --system --home /var/lib/cheesewaf --shell /usr/sbin/nologin cheesewaf
sudo chown -R cheesewaf:cheesewaf /etc/cheesewaf /var/lib/cheesewaf /var/log/cheesewaf

The Linux tarball includes systemd/cheesewaf.service.

BASH
sudo cp systemd/cheesewaf.service /etc/systemd/system/cheesewaf.service
sudo systemctl daemon-reload
sudo systemctl enable --now cheesewaf
sudo systemctl status cheesewaf

Open http://<host>:9443/setup and continue with Initialize.

Paths

PathRole
/usr/local/bin/cheesewafBinary
/etc/cheesewaf/cheesewaf.yamlConfig
/var/lib/cheesewafData, SQLite, certs
/var/log/cheesewafLogs

Bind the data plane to a public address only after you have a site, an upstream, and a paranoia level you accept.

2.2 - Docker Compose

Run CheeseWAF in Compose with a read-only root filesystem and a non-root user.

Use this path in a container host. docker compose build produces linux/amd64 or linux/arm64 for the host CPU.

The image runs as UID 10001. The root filesystem is read-only.

Compose file

The repository file is deploy/docker/docker-compose.yml. A minimal copy:

YAML
services:
  cheesewaf:
    image: cheesewaf:latest
    build:
      context: .
      dockerfile: deploy/docker/Dockerfile
    user: "10001:10001"
    restart: unless-stopped
    read_only: true
    cap_drop:
      - ALL
    security_opt:
      - no-new-privileges:true
    tmpfs:
      - /tmp:size=32m,mode=1777,noexec,nosuid,nodev
    ports:
      - "8080:8080"
      - "9443:9443"
    volumes:
      - cheesewaf-data:/var/lib/cheesewaf
      - cheesewaf-logs:/var/log/cheesewaf
    healthcheck:
      test: ["CMD", "/usr/local/bin/cheesewaf-entrypoint", "healthcheck"]
      interval: 30s
      timeout: 5s
      retries: 3

volumes:
  cheesewaf-data:
  cheesewaf-logs:

Build context must be the CheeseWAF repository root when you use that Dockerfile.

Start

BASH
docker compose up -d
docker compose logs -f cheesewaf

Open https://<host>:9443/setup. The container uses a self-signed admin certificate by default. The first-run token is in the startup log.

docker compose down keeps the named volumes. Site config and SQLite live in cheesewaf-data.

2.3 - Windows

Single-file CLI, portable zip, or NSIS installer. The GUI controller listens on loopback only.

Windows has three shapes. They are not three different WAFs.

A. Single-file CLI

  1. Download cheesewaf-*-windows-amd64.exe or the arm64 file.
  2. Run:
POWERSHELL
.\cheesewaf-*-windows-amd64.exe serve --config .\cheesewaf.yaml --data-dir .\data
.\cheesewaf-*-windows-amd64.exe status
.\cheesewaf-*-windows-amd64.exe stop

The forwarding process does not need the installer. The Web UI assets live in web/dist next to the executable in zip / DMG / tar packages.

B. Portable zip

  1. Unpack cheesewaf-*-windows-amd64.zip to a directory such as D:\CheeseWAF.
  2. Run:
POWERSHELL
.\cheesewaf.exe serve --config .\configs\cheesewaf.yaml --data-dir .\data
.\cheesewaf.exe status
.\cheesewaf.exe stop

C. NSIS installer

  1. Run CheeseWAF-*-windows-amd64-setup.exe or the arm64 setup.
  2. Follow the wizard.
  3. Uninstall keeps data\ by default.

The installer may register a Windows service (sc.exe create CheeseWAF …). Treat that as best-effort.

Local controller

cheesewaf-gui is not a second admin console. It only starts, stops, and opens the real management UI.

  • Bind address: 127.0.0.1:17943
  • Shows PID and running state
  • Opens the Web console and the config folder
  • Optional current-user autostart (HKCU\Run)
POWERSHELL
.\cheesewaf-gui.exe --config .\configs\cheesewaf.yaml --data-dir .\data

The browser opens http://127.0.0.1:17943/.

2.4 - macOS

Install CheeseWAF from a DMG, or run the tar.gz CLI.

DMG

  1. Download cheesewaf-*-darwin-arm64.dmg (Apple Silicon) or cheesewaf-*-darwin-amd64.dmg (Intel).
  2. Open the image and drag CheeseWAF into Applications.
  3. Launch CheeseWAF from Launchpad or Applications.

The app starts the local controller. Use it to start, stop, and open the Web console.

Runtime data is under ~/Library/Application Support/CheeseWAF.

CLI tarball

If you only want the command line:

BASH
tar -xzf cheesewaf-*-darwin-arm64.tar.gz
cd cheesewaf-*
./cheesewaf serve --config ./configs/cheesewaf.yaml --data-dir ./data

Then open http://127.0.0.1:9443/setup.

3 - Quick start

Initialize CheeseWAF, add the first site, and connect a model for ALAP.

Do these three steps after the process is running.

Open /setup, create the first admin, store the generated secrets.

The data plane works without a model. ALAP review stays empty until you configure ai.

3.1 - Initialize

Create the first administrator at /setup and lock down the management listener.

Open the wizard

On a local install open http://127.0.0.1:9443/setup. On Docker open https://<host>:9443/setup and accept the self-signed certificate.

If the process prints a setup token, paste it when the wizard asks.

Create the admin

Set a username and a password that meets the console password policy. Save every generated secret the wizard shows. CheeseWAF will not print them again in clear text.

Confirm the listener

Leave server.admin_listen on loopback for a single-host install. Set server.admin_public to true only with TLS and a network policy in front.

After setup, the same URL becomes the login page. CLI users can also run waf-cli (TUI) or cheesewaf user.

3.2 - Add the first site

Point CheeseWAF at a domain and an upstream, then pick paranoia level 3.

In the console open SitesNew site.

Domain

Enter the hostname clients already use, for example app.example.com. CheeseWAF matches sites[].domains.

Upstream

Enter the origin address, for example 10.0.0.10:8000. More than one upstream uses the site loadbalance policy (round_robin by default).

Paranoia

Use level 3 for a first production site. Level 3 blocks isolated attack values and allows embedded hits for later ALAP review.

Save

Save the site. The process reloads the site list without a full restart.

Point DNS or the local hosts file at the CheeseWAF data-plane address. Confirm the origin still answers through CheeseWAF before you raise the level.

Details: Sites and reverse proxy.

3.3 - Connect a model

Point ALAP at an OpenAI-compatible or Anthropic-compatible endpoint.

ALAP is optional for a first day. Turn it on when you want asynchronous review and lasting rules.

In the console open AI.

FieldMeaning
EnabledMaster switch (ai.enabled)
Provideropenai or anthropic
EndpointChat Completions / Messages base URL, for example https://api.openai.com/v1
API keySecret for that endpoint
ModelModel name, for example gpt-4o-mini
Auto-agreeWhen on, high / critical findings can become lasting rules

The sample config starts with ai.enabled: false. ai.async stays true so the data plane never waits on the model.

Use Test connection in the console before you trust auto-agree.

See ALAP and the review queue.

4 - Concepts

Pipeline, paranoia levels, isolated vs embedded payloads, and the three management surfaces.

Read these pages before you raise a site from level 3 to 4 or 5.

4.1 - Request pipeline

Solid lines are the millisecond path. Dashed lines are ALAP after the response.
flowchart TB
  Client[Client] --> Ingress[HTTP / HTTPS / HTTP3]
  Ingress --> IP{IP / geo / fingerprint}
  IP -->|deny list| Block[Block page]
  IP -->|allow| Bot{Bot / rate limit / waiting room}
  Bot -->|challenge| Challenge[CAPTCHA or queue]
  Challenge -->|pass| Sem
  Bot -->|allow| Sem[Semantic engine]
  Sem --> Shape{Isolated or embedded}
  Shape -->|isolated 2-5| Block
  Shape -->|embedded 5| Block
  Shape -->|embedded 2-4| Pass[Allow and enqueue]
  Shape -->|clean| Origin[Upstream]
  Pass --> Origin
  Pass -.-> Queue[ALAP queue]
  Sem -.->|level 5 block| Queue
  Queue --> LLM[Configured model]
  LLM --> Review{Decision}
  Review -->|high / critical| Rule[Lasting rule]
  Review -->|low or false positive| Dismiss[Archive or allow list]
  Rule -.-> IP

Solid arrows stay on the request path. Dashed arrows run after the client already has a response.

Site-level waf.mode can be block or a record-only style depending on paranoia. Level 0 and 1 never block on semantic hits.

See Protection for each filter in this diagram.

4.2 - Paranoia levels

Per-site levels 0–5. Default is 3. Level 4 can rise to 5 for a timed window.

Set sites[].waf.paranoia_level per site. Legal values are 0–5. The default is 3.

The engine looks at one decoded parameter value at a time. Path and parameter names stay visible.

LevelNameIsolatedEmbeddedTimed rise
0Record onlyLog, allowLog, allowNo
1Low monitorLog, allowLog, allowNo
2Low-mediumBlockAllow, review laterNo
3StandardBlockAllow, review laterNo
4Medium-highBlockAllow, review laterYes (rise to 5)
5StrictBlockBlock, then reviewAlready max

Temporary rise

At level 4, an embedded hit can raise the site to level 5 for promote_seconds (for example 300 seconds). The deadline is stored in SQLite. A process restart does not clear it.

Level 5 review

A sample blocked at level 5 still enters the review queue with a blocked mark. You cannot flip it to allow. You can save a lasting deny rule (feature, URL, IP, or fingerprint).

4.3 - Isolated vs embedded

Isolated payloads are almost only attack text. Embedded payloads sit inside long ordinary text.

The semantic engine classifies each decoded value as isolated or embedded. Paranoia levels treat the two shapes differently.

Isolated

The value is almost entirely an attack payload. Weak wrappers such as @, a trailing semicolon, or /{${...}} still count as isolated.

Example: a search box that contains UNION SELECT 1,2,3.

Embedded

Attack-like tokens sit inside a long article, a product description, or a technical discussion.

Example: a forum post that quotes a SQL snippet.

Levels 2–4 allow embedded hits and enqueue them for ALAP. Level 5 blocks them.

Current isolation scope

This is an implementation fact, not a marketing promise:

  • Isolated gadget coverage includes PHP/JSP live shells, Log4j JNDI, and short quoted/predicate SQL (at most 96 runes).
  • XSS, command/RCE, SSTI, SSRF, and XXE use the document-shape guard. They are not on that gadget list.
  • Hits marked embedded skip the block below level 5.
  • Unclassified hits still follow blockableHit evidence. They are not auto-treated as embedded.
  • Isolation lowers false positives on covered gadgets. It is not a free pass for every technical article.

4.4 - Three management surfaces

Web console, CLI / TUI, and REST share one user, session, and audit model.
SurfaceWhen to useHow to reach it
Web consoleDaily ops, rules, logs, attack maphttp://127.0.0.1:9443/ after setup
CLI / TUIHeadless hosts, scriptswaf-cli or cheesewaf panel
RESTAutomation, CI/api/... with a session cookie or a management API token

setup.three_end_unified is on in the sample config. A user created in the console can use the CLI. A token created under System can call REST with the same RBAC permissions.

Permissions live under apisec.permissions. The sample grants admin: ["*"] and readonly: ["read:*", "read:cluster"].

Audit events write to apisec.audit.path when apisec.audit.enabled is true.

See Console, CLI, and REST API.

5 - Sites and reverse proxy

Domains, upstreams, load balancing, health checks, and per-site WAF switches.

A site is one public hostname set plus one or more origins. CheeseWAF is the reverse proxy in front of those origins.

Create and edit

Console: Sites. REST: GET/POST /api/sites, GET/PUT/DELETE /api/sites/{id}. You can also import an Nginx server block with POST /api/nginx/import.

Fields that matter

FieldConfig keyNotes
Site idsites[].idStable id, used in URLs
Namesites[].nameDisplay name
Domainssites[].domainsHost header match
Upstreamssites[].upstreams[].addresshost:port, optional weight
Listen portsites[].listen_portOptional extra listener
Load balancesites[].loadbalanceDefault round_robin
Enabledsites[].enabledOff = skip this site
WAF onsites[].waf.enabled
Modesites[].waf.modeUsually block
Paranoiasites[].waf.paranoia_level0–5
Enginessites[].waf.semantic_enginessql, xss, rce, lfi, xxe, ssrf, nosql, ssti
Custom rulessites[].waf.custom_rulesRegex on URI or other locations
Rewritesites[].waf.rewritePath rewrite or redirect
Health checksites[].waf.health_checkPath, interval, thresholds
Trusted CIDRssites[].waf.access_control.trusted_cidrsReal client IP behind another proxy

Health checks

When health_check.enabled is true, CheeseWAF probes health_check.path on each upstream. Unhealthy origins leave the pool after unhealthy_threshold failures.

Rewrites

A rewrite rule has pattern, replacement, and optional redirect_code. redirect_code: 0 rewrites internally. A 3xx code sends the client to the new path.

Per-site policy overlay

sites[].waf.protection_policy can override the global protection.policy keys:

  • web_attack
  • api_security
  • bot_cc
  • threat_intel

Empty strings inherit the global value (smart in the sample).

6 - Protection

Semantic engine, custom rules, IP and geo, bot challenges, rate limits, ACL, and block pages.

Global defaults live under protection and protection.policy. A site can overlay sites[].waf.protection_policy.

Console pages: Protection, Rules, IP, Bot challenge, Block pages.

Method, path, and header denies.

6.1 - Semantic engine

Multi-stage decoding and AST checks. Toggle engines per site.

The semantic engine does not ship a huge regex corpus as the primary detector. It decodes the parameter, then walks an abstract syntax tree for the enabled families.

Enable engines

Under sites[].waf.semantic_engines:

KeyLooks for
sqlSQL injection
xssCross-site scripting
rceCommand / RCE
lfiLocal file include
xxeXML external entity
ssrfServer-side request forgery
nosqlNoSQL injection
sstiServer-side template injection

Turn an engine off when that family cannot appear on the site. Do not turn them all off and expect CheeseWAF to still catch web attacks.

Budget and allow lists

sites[].waf.semantic_policy:

  • budget_exhausted_policy: auto follows the web_attack policy when the analysis budget is spent
  • path_allowlist: skip semantic analysis on these paths
  • param_allowlist: skip these parameter names

sites[].waf.performance caps max_body_bytes, max_header_bytes, and proxy_timeout.

Response inspection

sites[].waf.response can scan the origin body for leaked secrets (AWS key pattern, password assignments, and similar). Keep max_body_bytes modest.

How isolated vs embedded hits are treated: Isolated vs embedded.

6.2 - Custom rules

Regular-expression rules on URI and other locations, with priority and severity.

Custom rules sit next to the semantic engine. They are useful for admin probes, scanner paths, and one-off business denials.

Console: Rules. REST: /api/rules and sites[].waf.custom_rules.

The sample ships this rule:

YAML
custom_rules:
  - id: "block-admin-probe"
    name: "Admin path probe"
    pattern: "(?i)/(wp-admin|phpmyadmin|\\.git)"
    location: "uri"
    action: "block"
    severity: "medium"
    enabled: true
    priority: 180
FieldMeaning
idStable id
patternRegular expression
locationWhere to match. Sample uses uri
actionUsually block
severityShown in logs and review
priorityLower number runs earlier when the engine sorts that way — keep ids unique

Do not try to rebuild a full ModSecurity ruleset here. Use custom rules for short, reviewable patterns.

6.3 - IP, geo, and fingerprint

Allow lists, deny lists, GeoIP, reputation overrides, and threat-intel feeds.

Console: IP. Config: protection.ip. REST: /api/ip, /api/protection/ip, /api/ip/threat-intel/*.

Static lists

YAML
protection:
  ip:
    whitelist: ["127.0.0.1", "::1"]
    blacklist: []
    access_rules: []
    reputation_overrides: {}
    tags: {}
    threat_intel: []
    geoip:
      enabled: false
      database: "./data/GeoLite2-Country.mmdb"
      blocked_countries: []

Allow-listed addresses skip later IP denies. Deny-listed addresses never reach the semantic engine.

GeoIP

Set geoip.enabled: true and point database at a MaxMind-style Country MMDB. blocked_countries uses ISO country codes. CheeseWAF does not download GeoLite2 for you.

Threat intel

Operators can import, export, sync, and test providers from the console. Lookups are available at POST /api/ip/threat-intel/lookup.

Fingerprints

The data plane records a soft client fingerprint (not a hardware TPM identity). After a high-confidence review, ALAP can save a fingerprint deny rule. Treat fingerprint hits as supporting evidence, not as the only control.

When CheeseWAF sits behind another proxy, fill sites[].waf.access_control.trusted_cidrs or trusted_proxy_providers so the client IP is not the proxy’s address.

6.4 - Bot challenge and CAPTCHA

JS clearance, PoW, slider, image CAPTCHA, login CAPTCHA, and the waiting room.

Console: Bot challenge, plus CAPTCHA lab for operators who design challenges. Config: protection.bot and console.login.captcha.

The sample starts with protection.bot.enabled: false. Turn it on only after you have a site that can complete a browser challenge.

Traffic challenge

KeyRole
js_challengeIssue a JS clearance cookie
captchaExtra CAPTCHA after JS
captcha_typepow, image, or slider
cookie_nameDefault cheesewaf_js_clearance
path_prefixesWhere the challenge applies
exempt_path_prefixesSkip, sample includes /health
suspicious_user_agentsExtra scrutiny for curl, sqlmap, nuclei, and similar

Keep secret out of git. Let the process generate it into the data directory.

Challenge kinds

  • PoW / Altcha. Header X-CheeseWAF-Altcha by default.
  • Slider. Geometry and min-drag live under slider_captcha_*.
  • Image. Length, size, and audio-limit knobs.
  • Behavior pack. Curve draw, scratch, icon click, and related lab types. Use the lab before you enable them on production traffic.

Upload custom assets under /api/captcha/assets. Quota and remote source tests are on the same console page.

Login CAPTCHA

console.login.captcha protects the management login, not the data plane. The sample uses a slider with an optional PoW.

console.login.security_entry can hide the login behind a secret path and cookie.

Waiting room

waiting_room plus waiting_room_max_active queues excess clients instead of dropping them immediately. See also Rate limit.

6.5 - Rate limit

Token-bucket limits on the data plane. API-specific limits live under apisec.

Config: protection.ratelimit. REST: PUT /api/protection/ratelimit.

YAML
protection:
  ratelimit:
    enabled: true
    default:
      requests: 100
      window: 60s
      burst: 20

This is a token bucket on the data plane. It is not the same as apisec.rate_limits, which match one method + path on discovered APIs.

When the bucket is empty, CheeseWAF can:

  • return a 429-style block page
  • or send the client to the waiting room when that is enabled

Start with the sample numbers. Lower requests only after you have a week of logs.

6.6 - ACL

Deny or allow by HTTP method, path prefix, and header.

Config: protection.acl. REST: PUT /api/protection/acl.

The sample denies /debug:

YAML
protection:
  acl:
    enabled: true
    rules:
      - id: "deny-debug"
        name: "Deny debug endpoints"
        method: ""
        path_prefix: "/debug"
        header: ""
        header_value: ""
        action: "block"
        severity: "high"
        enabled: true

Empty method means any method. Set header + header_value to require or reject a header.

ACL runs early. Use it for operator-known junk paths. Use custom rules when you need a regex, not a prefix.

6.7 - Block pages

Built-in templates, custom HTML, and a preview window.

Config: block_page. Console: Block pages. REST: /api/block-pages/*.

YAML
block_page:
  template_id: "minimal"
  custom_enabled: false
  custom_html: ""

GET /api/block-pages/templates lists built-in templates. POST /api/block-pages/preview and the /block-pages/preview window show the rendered page without publishing it.

Upload custom HTML with POST /api/block-pages/upload. Delete it with DELETE /api/block-pages/custom.

A block page can include a trace id. Give that id to the operator when you open a log detail. Do not put origin hostnames or internal IPs in custom HTML.

7 - API security

Endpoint discovery, schema checks, JWT / JWKS, per-route rate limits, and RBAC.

Console: API security. Config: apisec. REST: /api/apisec/* plus the permission tables used by every other /api route.

Discovery

When apisec.discovery.enabled is true, CheeseWAF samples recent traffic (sample_limit, window) and lists endpoints. ignore_prefixes skips static assets.

GET /api/apisec/endpoints returns the current map. POST /api/apisec/validate checks one request against a schema.

Validation

YAML
apisec:
  validation:
    enabled: true
    schemas:
      - id: "api-search"
        method: "GET"
        path_pattern: "^/api/search$"
        required_params: ["q"]
        required_headers: []
        max_body_bytes: 0
        enabled: false

Enable a schema only after you have confirmed the path and required fields.

JWT

apisec.auth can require JWT issuers, audiences, scopes, and algorithms. Keys can come from a shared secret, a PEM file, inline PEM, a JWKS file, inline JWKS, or a remote jwks_url. Remote JWKS is cached in jwks_cache_file and refreshed every jwks_refresh_interval.

API rate limits

YAML
apisec:
  rate_limits:
    - id: "login-api"
      method: "POST"
      path_pattern: "^/api/auth/login$"
      requests: 10
      window: 1m
      enabled: true

This is per discovered API route, not the global data-plane bucket.

Permissions

YAML
apisec:
  permissions:
    admin: ["*"]
    readonly: ["read:*", "read:cluster"]

Management routes use names such as read:sites, write:protection, use:ai, approve:ai, manage:api_tokens. See REST API for the route-to-permission map.

8 - Edge headers, cache, and compression

Set or delete response headers, cache static prefixes, and compress JSON or HTML.

Console: Edge. Config: edge. REST: GET/PUT /api/edge.

Headers

YAML
edge:
  headers:
    enabled: true
    rules:
      - id: "set-edge-marker"
        operation: "set"
        header: "X-CheeseWAF"
        value: "edge"
        enabled: true
      - id: "remove-origin-leak"
        operation: "delete"
        header: "X-Origin-Secret"
        enabled: true

Use set to add a marker. Use delete to strip origin-only headers before they reach the client.

Cache

YAML
edge:
  cache:
    enabled: true
    mode: "public"
    ttl: 5m
    status_codes: [200, 304]
    path_prefixes: ["/assets/", "/static/"]
    max_body_bytes: 2097152

Only cache prefixes you know are static. Do not cache authenticated HTML.

Compression

YAML
edge:
  compression:
    enabled: true
    algorithms: ["br", "gzip"]
    level: 5
    min_bytes: 1024

content_types in the sample covers text/, JSON, JavaScript, XML, and SVG.

9 - TLS and certificates

Admin TLS, site certificates, ACME issuance, HTTP/3, and HSTS.

Console: SSL. Config: server.admin_tls, tls, and per-site ACME calls. REST: /api/acme/providers, POST /api/sites/{id}/acme/issue.

Admin listener

YAML
server:
  admin_tls:
    enabled: false
    cert_file: "./data/certs/admin.crt"
    key_file: "./data/certs/admin.key"
    self_signed: true

Docker images turn admin TLS on with a self-signed cert. A public admin listener must use a real certificate.

Site TLS

YAML
tls:
  auto_cert: false
  cert_file: "./data/certs/admin.crt"
  key_file: "./data/certs/admin.key"
  min_version: "1.3"
  hsts: true

server.listen_tls and server.listen_http3 bind the data plane. HTTP/3 needs server.http3.enabled and a TLS listener.

ACME

The console lists DNS providers at GET /api/acme/providers. POST /api/sites/{id}/acme/issue requests a certificate for that site. Keep account keys in the data directory, not in the git repo.

10 - ALAP and the review queue

Asynchronous model review, auto-agree, the assistant, tool approvals, and self-learning.

Console: AI and Review. Config: ai. REST: /api/ai/* and /api/review/*.

Queue

After the response, CheeseWAF can enqueue samples for the model. The worker uses Chat Completions or Messages, depending on ai.provider.

Keep ai.async: true. The data plane must not wait on this path.

Decisions

GET /api/review lists items. POST /api/review/{id}/decide records allow, deny, or save as a rule.

At paranoia 5, a blocked item cannot be flipped to allow. You can still save a lasting rule.

Auto-agree

When auto-agree is on, high and critical findings can become IP, fingerprint, or signature rules without a human click. Start with auto-agree off until you have reviewed a week of queue items.

Assistant and tools

POST /api/ai/assistant (and the stream variant) chats with tools that can change config. Dangerous tools go through /api/ai/tools/approvals. Roles:

  • use:ai — analyze
  • write:ai — change AI config, run self-learning
  • approve:ai — approve a pending tool call

Self-learning

POST /api/ai/self-learning/run starts a scheduled-style pass over recent samples. The scheduler can also run this on a timer. See Storage and scheduler.

11 - Monitor, logs, and attack map

Dashboard stats, access logs, Prometheus, alerts, notifications, and the attack map.

Console: Dashboard, Logs, Monitor, Attack map. Config: logging, monitor. REST: /api/stats, /api/logs, /api/monitor, /api/metrics, /api/notifications, /api/audit.

Logs

YAML
logging:
  level: "info"
  format: "json"
  output:
    type: "file"
    file:
      path: "./logs/access.log"
      max_size: "100MB"
      max_backups: 10

GET /api/logs lists events. /logs/{traceId} in the console opens one request.

Optional sinks: PostgreSQL, ClickHouse, VictoriaLogs. See Storage.

Prometheus

YAML
monitor:
  prometheus:
    enabled: true
    path: "/metrics"
    public: false

When public is false, scrape /api/metrics with a management token. When public is true, the same path is exposed on the router root. Do not do that on the internet.

monitor.remote_write can push to a remote Prometheus-compatible endpoint.

Alerts

The sample defines high-block-rate and disk-usage rules. Notifiers support webhook endpoints (monitor.notifiers). In-app notifications use /api/notifications.

Attack map

/attack-map and /attack-map/screen plot recent blocks. console.map.china_boundary can load a reviewed China boundary file. Do not point source at an untrusted URL (allow_insecure / allow_private stay false unless you know why).

12 - Cluster

Join tokens, mTLS interconnect, builtin consensus, rolling upgrade, and traffic peers.

Console: Cluster. Config: cluster. CLI: cheesewaf cluster. REST: /api/cluster/*.

The sample is a single node:

YAML
deployment:
  mode: "standalone"
cluster:
  enabled: false
  ha_mode: "single-node"
  interconnect:
    listen: "127.0.0.1:9444"
    mtls_required: true

Turn clustering on

  1. Set cluster.enabled: true and pick a cluster_id.
  2. Give every node a unique node_id.
  3. Point interconnect.advertise_addr at an address other nodes can reach.
  4. Keep mtls_required: true. Fill ca_file, cert_file, key_file.

cluster.protection.freeze_writes_without_majority stops config writes without a majority. allow_traffic_in_protection_mode decides whether the data plane still forwards during that freeze.

Join

POST /api/cluster/join-tokens mints a token (token_ttl, default 15m). A new node calls POST /api/cluster/join. require_approval: true waits for an operator.

Operations

ActionRoute
StatusGET /api/cluster/status
NodesGET /api/cluster/nodes
HeartbeatPOST /api/cluster/nodes/{id}/heartbeat
Rotate certPOST /api/cluster/nodes/{id}/rotate-certificate
Revoke nodePOST /api/cluster/nodes/{id}/revoke
Ansible packPOST /api/cluster/deploy/ansible
Rolling upgradePOST /api/cluster/orchestrate/rolling-upgrade
RollbackPOST /api/cluster/orchestrate/rolling-upgrade/{id}/rollback
ConsensusGET /api/cluster/consensus

cluster.consensus.provider is builtin in the sample. etcd_endpoints is reserved for an external provider and stays empty unless you switch.

13 - Storage and scheduler

SQLite, optional PostgreSQL and log sinks, backups, cleanup, and scheduled reports.

Config: storage, setup.data_dir, scheduler. Console: Operations, System. REST: /api/storage, /api/backup/*, /api/scheduler/*.

Default store

YAML
setup:
  data_dir: "./data"
storage:
  sqlite:
    path: "./data/cheesewaf.db"

SQLite holds users, review items, promote deadlines, and operational state. It uses modernc.org/sqlite (no CGO).

Extra sinks

SinkWhen to enable
storage.postgresqlShare logs with an existing Postgres
storage.clickhouseHigh-volume log analytics
storage.victorialogsVictoriaLogs HTTP ingest
storage.redisOptional cache / coordination. Off in the sample

Private endpoints stay blocked unless allow_private_endpoint is true. POST /api/system/storage/test checks a backend before you switch.

Scheduler

YAML
scheduler:
  enabled: true
  tasks:
    - id: "log-cleanup"
      type: "cleanup"
      every: 24h
      target: "./logs"
      keep: 14
      enabled: true
    - id: "config-backup"
      type: "backup"
      every: 24h
      target: "./data/backups"
      keep: 7
      enabled: false
    - id: "security-daily-report"
      type: "security_report"
      frequency: "daily"
      at: "08:00"
      enabled: false

GET /api/scheduler/tasks and PUT /api/scheduler/tasks edit the list. GET /api/scheduler/history shows runs.

Backup

POST /api/backup/export downloads a backup. POST /api/backup/restore applies one. POST /api/storage/cleanup and POST /api/system/reclaim free disk after you have a backup.

14 - Web console

Pages in the management UI and how they map to this manual.

After setup, open the management URL (default http://127.0.0.1:9443/).

The console is a React app served by the same process. It uses session cookies plus CSRF. Login can require a login CAPTCHA.

Page map

Console routeThis manual
/ DashboardMonitor
/sitesSites
/sslTLS
/rulesCustom rules
/reviewALAP
/logsMonitor
/ipIP and geo
/protectionProtection
/bot-challengeBot and CAPTCHA
/edgeEdge
/aiALAP
/monitorMonitor
/apisecAPI security
/usersOperations
/opsStorage
/updatesOperations
/block-pagesBlock pages
/attack-mapMonitor
/clusterCluster
/systemOperations
/captcha-labBot and CAPTCHA

Themes (light, dark, and several color packs) are local to the browser. They do not change the data plane.

15 - CLI and TUI

cheesewaf and waf-cli share one binary. Default commands depend on the executable name.

The same binary answers to two names.

Invoked asDefault command
cheesewafserve
waf-cliinteractive TUI (panel)

Global flags:

TEXT
-c, --config     path to cheesewaf.yaml (default ./data/cheesewaf.yaml)
    --data-dir   runtime data directory (default ./data)
    --lang       en or zh-CN

Language order: flag, then environment, then data-dir, then the OS locale.

Commands

CommandPurpose
serveStart the WAF
panelTUI
statusIs the process up
healthcheckExit non-zero when unhealthy (Compose uses this)
stopStop a running process
restartStop then serve
userManage local users
clusterJoin, certs, runtime
versionVersion, channel, build time
langPersist CLI language
logsPack or inspect logs

Examples:

BASH
cheesewaf serve --config /etc/cheesewaf/cheesewaf.yaml --data-dir /var/lib/cheesewaf
cheesewaf status
waf-cli
cheesewaf user
cheesewaf cluster

On Windows, copy cheesewaf.exe to waf-cli.exe if you want the TUI name. The desktop controller is a separate binary: Windows install.

16 - REST API

Health endpoints, session login, management tokens, CSRF, and the permission map.

The management API lives under /api on the admin listener, not on the data plane.

Auth

Two ways in after setup:

  1. Session. POST /api/auth/login, then send the session cookie. State-changing calls need the CSRF middleware.
  2. Management token. Create one at POST /api/system/api-tokens (manage:api_tokens). Send it as a bearer token on later calls.

Public before login:

MethodPath
GET/health, /health/live, /health/ready, /health/cluster
GET/api/auth/login-options
POST/api/auth/captcha, /api/auth/captcha/verify, /api/auth/login
POST/api/setup, /api/setup/probe
GET/PATCH/api/setup/draft
POST/api/cluster/join
POST/api/cluster/nodes/{id}/heartbeat

Permission map

Common require("…") names from the router:

PrefixExamples
read: / write: sitesList and edit sites, ACME issue
read: / write: rulesCustom rules
read: / write: protectionIP, ACL, bot, rate limit, review decide
read: / write: threat_intelImport, sync, lookup
read: / write: edgeHeader / cache / compression policy
read: / write: ai, use:ai, approve:aiConfig, analyze, assistant, approvals
read: / write: clusterNodes, join tokens, rolling upgrade
read: / write: systemVersion, time sync, backup
manage:api_tokensCreate and revoke tokens
read: / write: usersLocal users and 2FA
read: logsAccess logs and review list
read: monitorStats, metrics, notifications
read: auditAudit log
read: realtimeSSE /api/realtime/events, WebSocket /api/realtime/ws
read: / write: opsScheduler
read: / write: storageStats and cleanup
read: apisecDiscovered endpoints

admin: ["*"] in the sample bypasses individual checks.

Errors

Failed calls return JSON with an error field and an HTTP status. Do not retry login blindly after a CAPTCHA failure — request a new challenge.

17 - Configuration reference

Top-level keys in cheesewaf.yaml and where this manual explains each block.

First start writes cheesewaf.yaml into the data directory. The template is configs/cheesewaf.yaml in the product repo.

KeyManual
serverTLS, Intro
tlsTLS
setupInitialize, Storage
deployment / clusterCluster
consoleBot and CAPTCHA, Monitor
sitesSites
protectionProtection
block_pageBlock pages
storageStorage
loggingMonitor
aiALAP
updateOperations
schedulerStorage
edgeEdge
monitorMonitor
apisecAPI security

Timeouts

server.read_timeout, write_timeout, and idle_timeout apply to the HTTP servers. sites[].waf.performance.proxy_timeout applies to the origin.

Reload

Saving a site or a protection policy in the console hot-reloads that slice. A change to listen addresses still needs a process restart (cheesewaf restart or systemd).

18 - Operations

Users, 2FA, time sync, OTA updates, and system maintenance.

Console: Users, System, Updates.

Users

GET/POST /api/users, PUT /api/users/{id}. Each user can enable TOTP: /api/users/{id}/2fa/setup, enable, disable, recover.

CLI: cheesewaf user.

Do not share the first admin password. Create a readonly role account for people who only need logs.

Time sync

GET /api/system/time-sync shows the current clock source. POST /api/system/time-sync/reselect picks again. POST /api/system/time-sync/sync syncs now.

JWT and TOTP break when the host clock is wrong. Fix time before you debug “invalid token”.

Updates

YAML
update:
  ota:
    enabled: false
    server: ""
    channel: "stable"
    check_interval: 6h
    auto_update_rules: true
    auto_update_binary: false
    verify_signature: true

Keep auto_update_binary false until you trust the OTA server and the public key. verify_signature must stay true.

System

GET /api/system and PUT /api/system read and write system settings. GET /api/version prints the running version.

See also Storage and scheduler for backup and cleanup.

19 - Build from source

Go and Node versions, frontend build, tests, and the attack corpus tool.

You only need this page if you compile CheeseWAF yourself. Operators should use Releases.

Toolchain

  • Go 1.26 or newer
  • Node.js 24.x and npm

Build

BASH
git clone https://github.com/LaokeQwQ/CheeseWAF.git
cd CheeseWAF

cd web
npm ci
npm run build
cd ..

go build -o bin/cheesewaf ./cmd/cheesewaf
./bin/cheesewaf serve --config ./configs/cheesewaf.yaml

Tests

BASH
go test -v ./cmd/... ./internal/...
go vet ./cmd/... ./internal/...

cd web && npm run typecheck && npm test && cd ..

go run ./cmd/cheesewaf-corpus --mode analyzer

cheesewaf-corpus runs the built-in attack corpus against the analyzer. It is a development check, not a production daemon.

20 - License and related repositories

Apache-2.0 for CheeseWAF source. Names and logos are not a trademark grant.

CheeseWAF source code is licensed under Apache License 2.0.

The CheeseSec and CheeseWAF names, and the product logo, are not licensed as trademarks. See the NOTICE file in CheeseSec_pages when you reuse branding.

Repositories

RepoRole
LaokeQwQ/CheeseWAFProduct source
LaokeQwQ/CheeseSec_DocsThis documentation site
LaokeQwQ/CheeseSec_pagesMarketing site

Report product bugs on the CheeseWAF issue tracker. Report documentation bugs on CheeseSec_Docs.