diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 8cceffb..89f3796 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -20,7 +20,7 @@ concurrency:
jobs:
build:
- runs-on: ubuntu-latest
+ runs-on: ${{ fromJSON(vars.RUNS_ON || '["self-hosted","runner-02"]') }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -55,7 +55,7 @@ jobs:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
- runs-on: ubuntu-latest
+ runs-on: ${{ fromJSON(vars.RUNS_ON || '["self-hosted","runner-02"]') }}
steps:
- name: Deploy to GitHub Pages
id: deployment
diff --git a/.github/workflows/test_apache_docker.yml b/.github/workflows/test_apache_docker.yml
index c46c605..654d2e9 100644
--- a/.github/workflows/test_apache_docker.yml
+++ b/.github/workflows/test_apache_docker.yml
@@ -13,7 +13,7 @@ on:
jobs:
validate-waf-patterns:
- runs-on: ubuntu-latest
+ runs-on: ${{ fromJSON(vars.RUNS_ON || '["self-hosted","runner-02"]') }}
steps:
- name: Checkout repository
diff --git a/.github/workflows/test_nginx.yml b/.github/workflows/test_nginx.yml
index 55eb905..673fed1 100644
--- a/.github/workflows/test_nginx.yml
+++ b/.github/workflows/test_nginx.yml
@@ -13,7 +13,7 @@ on:
jobs:
validate-nginx-configuration:
- runs-on: ubuntu-latest
+ runs-on: ${{ fromJSON(vars.RUNS_ON || '["self-hosted","runner-02"]') }}
steps:
- name: Checkout repository
diff --git a/.github/workflows/update_patterns.yml b/.github/workflows/update_patterns.yml
index 3f4f811..6758275 100644
--- a/.github/workflows/update_patterns.yml
+++ b/.github/workflows/update_patterns.yml
@@ -13,7 +13,7 @@ on:
jobs:
update-owasp-waf:
- runs-on: ubuntu-latest
+ runs-on: ${{ fromJSON(vars.RUNS_ON || '["self-hosted","runner-02"]') }}
steps:
- name: π Checkout Repository
diff --git a/README.md b/README.md
index c3bb320..54eab68 100644
--- a/README.md
+++ b/README.md
@@ -1,212 +1,233 @@
-# π Patterns: OWASP CRS and Bad Bot Detection for Web Servers
-
-Automate the scraping of **OWASP Core Rule Set (CRS)** patterns and convert them into **Apache, Nginx, Traefik, and HAProxy** WAF configurations.
-Additionally, **Bad Bot/User-Agent detection** is integrated to block malicious web crawlers and scrapers.
-
-> π **Protect your servers against SQL Injection (SQLi), XSS, RCE, LFI, and malicious bots β with automated daily updates.**
+
+
+
Patterns
+
Production-grade WAF rules, on autopilot.
+
+ Automated OWASP Core Rule Set and
+ bad-bot patterns, converted into native configurations for
+ Nginx , Apache , Traefik , and HAProxy
+ — refreshed every day.
+
+
+
+
+
+
+
+
+
+ Documentation
+ ·
+ Get started
+ ·
+ Latest release
+
+
---
-## π Project Highlights
-- **π‘οΈ OWASP CRS Protection** β Leverages OWASP Core Rule Set for web application firewall (WAF) defense.
-- **π€ Bad Bot Blocking** β Blocks known malicious bots using public bot lists.
-- **βοΈ Multi-Web Server Support** β Generates WAF configs for **Apache, Nginx, Traefik, and HAProxy**.
-- **π Automatic Updates** β GitHub Actions fetch new rules **daily** and push updated configs.
-- **π¦ Pre-Generated Configurations** β Download ready-to-use WAF configurations from [GitHub Releases](https://github.com/fabriziosalmi/patterns/releases).
-- **π§© Scalable and Modular** β Easily extendable to support other web servers or load balancers.
+## Why Patterns
----
+The OWASP Core Rule Set (CRS) is the de-facto open-source rule base behind ModSecurity, but plugging it into anything other than Apache is non-trivial. Patterns automates the whole pipeline:
-## π Supported Web Servers
-- **π΅ Nginx**
-- **π Apache (ModSecurity)**
-- **π£ Traefik**
-- **π΄ HAProxy**
+1. Pull the latest CRS rules straight from upstream.
+2. Convert them into the **native** syntax of each web server — not a generic shim.
+3. Package the output as ready-to-deploy archives, refreshed every day by GitHub Actions.
-> [!NOTE]
-> If you are using Caddy, check the [caddy-waf](https://github.com/fabriziosalmi/caddy-waf) project.
+You get equivalent protection across SQL injection, XSS, RCE, LFI, and bad-bot traffic, regardless of which proxy you run.
----
+## Highlights
-## π Project Structure
-```
-patterns/
-βββ waf_patterns/ # π§ Generated WAF config files
-β βββ nginx/ # Nginx WAF configs
-β βββ apache/ # Apache WAF configs (ModSecurity)
-β βββ traefik/ # Traefik WAF configs
-β βββ haproxy/ # HAProxy WAF configs
-βββ import_apache_waf.py # π₯ Import Apache WAF configurations
-βββ import_haproxy_waf.py # π₯ Import HAProxy WAF configurations
-βββ import_nginx_waf.py # π₯ Import Nginx WAF configurations
-βββ import_traefik_waf.py # π₯ Import Traefik WAF configurations
-βββ owasp2json.py # π΅οΈ OWASP scraper (fetch CRS rules)
-βββ json2nginx.py # π Convert OWASP JSON to Nginx WAF configs
-βββ json2apache.py # π Convert OWASP JSON to Apache ModSecurity configs
-βββ json2traefik.py # π Convert OWASP JSON to Traefik WAF configs
-βββ json2haproxy.py # π Convert OWASP JSON to HAProxy WAF configs
-βββ badbots.py # π€ Generate WAF configs to block bad bots
-βββ requirements.txt # π Required dependencies
-βββ .github/workflows/ # π€ GitHub Actions for automation
- βββ update_patterns.yml
-```
+| | |
+|---|---|
+| **OWASP CRS coverage** | SQLi, XSS, RCE, LFI, RFI, plus generic anomaly and protocol-violation rules. |
+| **Native output** | Nginx `map`/`if`, Apache `SecRule`, Traefik middleware TOML, HAProxy ACL files. |
+| **Bad-bot blocking** | Curated User-Agent lists from public sources, with safe defaults that do **not** block major search engines. |
+| **Daily refresh** | A scheduled GitHub Actions workflow rebuilds every backend and publishes a fresh release. |
+| **Pre-built archives** | Skip the toolchain — download `nginx_waf.zip`, `apache_waf.zip`, `traefik_waf.zip`, or `haproxy_waf.zip`. |
+| **Composable** | Each backend is a small Python converter on top of one JSON intermediate. Adding a new platform is a few hundred lines. |
----
+> Using **Caddy**? See the dedicated [`caddy-waf`](https://github.com/fabriziosalmi/caddy-waf) project.
-## π οΈ How It Works
-### πΉ 1. Scraping OWASP Rules
-- **`owasp2json.py`** scrapes the latest OWASP CRS patterns from GitHub.
-- Extracts **SQLi, XSS, RCE, LFI** patterns from OWASP CRS `.conf` files.
+## Quick start
-### πΉ 2. Generating WAF Configs for Each Platform
-- **`json2nginx.py`** β Generates **Nginx WAF** configurations.
-- **`json2apache.py`** β Outputs **Apache ModSecurity** rules.
-- **`json2traefik.py`** β Creates **Traefik WAF** rules.
-- **`json2haproxy.py`** β Builds **HAProxy ACL** files.
+### Option 1 — download a pre-built release
-### πΉ 3. Bad Bot/User-Agent Detection
-- **`badbots.py`** fetches public bot lists and generates bot-blocking configs.
-- Supports fallback lists to ensure reliable detection.
-
----
-
-## βοΈ Installation
-
-### Prerequisites
-
-Before installing, ensure you have the following:
-- **Python 3.11 or higher** (the project uses Python 3.11 as specified in the GitHub Actions workflow)
-- **pip** (Python package installer)
-- **git** (for cloning the repository)
-
-### Option 1: Download Pre-Generated Configurations
-You can download the latest pre-generated WAF configurations directly from the [GitHub Releases](https://github.com/fabriziosalmi/patterns/releases) page.
-
-1. Go to the [Releases](https://github.com/fabriziosalmi/patterns/releases) section.
-2. Download the zip file for your web server (e.g., `nginx_waf.zip`, `apache_waf.zip`).
-3. Extract the files and follow the integration instructions below.
-
-### Option 2: Build from Source
-If you prefer to generate the configurations yourself:
-
-**1. Clone the Repository:**
```bash
-git clone https://github.com/fabriziosalmi/patterns.git
+# Pick the archive that matches your stack
+curl -LO https://github.com/fabriziosalmi/patterns/releases/latest/download/nginx_waf.zip
+unzip nginx_waf.zip -d /etc/nginx/waf_patterns
+```
+
+Then follow the [Nginx](https://fabriziosalmi.github.io/patterns/nginx),
+[Apache](https://fabriziosalmi.github.io/patterns/apache),
+[Traefik](https://fabriziosalmi.github.io/patterns/traefik), or
+[HAProxy](https://fabriziosalmi.github.io/patterns/haproxy) integration guide.
+
+### Option 2 — build from source
+
+Requires **Python 3.11+**, `pip`, and `git`.
+
+```bash
+git clone https://github.com/fabriziosalmi/patterns.git
cd patterns
-```
-
-**2. Install Dependencies:**
-```bash
pip install -r requirements.txt
+
+python owasp2json.py # 1. Fetch the latest OWASP CRS into owasp_rules.json
+python json2nginx.py # 2. Convert into Nginx WAF config
+python json2apache.py # β¦or Apache (ModSecurity)
+python json2traefik.py # β¦or Traefik middleware
+python json2haproxy.py # β¦or HAProxy ACL files
+python badbots.py # 3. Generate bad-bot blocklists
```
-**3. Run Manually (Optional):**
-```bash
-python owasp2json.py
-python json2nginx.py
-python json2apache.py
-python json2haproxy.py
-python json2traefik.py
-python badbots.py
+Generated files land in `waf_patterns//`.
+
+## Architecture
+
+```text
+ βββββββββββββββββββββββ daily cron ββββββββββββββββββββββββ
+ β coreruleset/ β ββββββββββββββββΆ β owasp2json.py β
+ β coreruleset (GH) β β β owasp_rules.json β
+ βββββββββββββββββββββββ ββββββββββββ¬ββββββββββββ
+ β
+ βββββββββββββββββββ¬βββββββββββββββββββ¬βββββββ΄βββββββββββ
+ βΌ βΌ βΌ βΌ
+ json2nginx.py json2apache.py json2traefik.py json2haproxy.py
+ β β β β
+ βΌ βΌ βΌ βΌ
+ nginx_waf.zip apache_waf.zip traefik_waf.zip haproxy_waf.zip
+ (published as a GitHub Release)
```
----
+Each converter is independent, idempotent, and configured exclusively through environment variables (`INPUT_FILE`, `OUTPUT_DIR`). Full reference at [docs/api](https://fabriziosalmi.github.io/patterns/api).
-## π Usage (Web Server Integration)
+## Repository layout
-### πΉ 1. Nginx WAF Integration
-1. Download the `nginx_waf.zip` file from the [Releases](https://github.com/fabriziosalmi/patterns/releases) page.
-2. Extract the files to your Nginx configuration directory.
-3. Include the generated `.conf` files in your Nginx configuration:
- ```nginx
- include /path/to/waf_patterns/nginx/*.conf;
- ```
+```text
+patterns/
+βββ owasp2json.py # Pull and parse OWASP CRS into a JSON intermediate
+βββ json2nginx.py # JSON β Nginx (map + if directives)
+βββ json2apache.py # JSON β Apache (ModSecurity SecRule)
+βββ json2traefik.py # JSON β Traefik (middleware TOML)
+βββ json2haproxy.py # JSON β HAProxy (ACL files)
+βββ badbots.py # Public bot lists β per-platform blocklists
+βββ import_*_waf.py # Optional installers for each platform
+βββ waf_patterns/ # Generated outputs
+β βββ nginx/
+β βββ apache/
+β βββ traefik/
+β βββ haproxy/
+βββ docs/ # VitePress documentation site
+βββ tests/ # Validation tests for each backend
+βββ .github/workflows/ # Daily build + release automation
+```
-### πΉ 2. Apache WAF Integration
-1. Download the `apache_waf.zip` file from the [Releases](https://github.com/fabriziosalmi/patterns/releases) page.
-2. Extract the files to your Apache configuration directory.
-3. Include the generated `.conf` files in your Apache configuration:
- ```apache
- Include /path/to/waf_patterns/apache/*.conf
- ```
+## Integration in 60 seconds
-### πΉ 3. Traefik WAF Integration
-1. Download the `traefik_waf.zip` file from the [Releases](https://github.com/fabriziosalmi/patterns/releases) page.
-2. Extract the files and use the `middleware.toml` file in your Traefik configuration.
+### Nginx
-### πΉ 4. HAProxy WAF Integration
-1. Download the `haproxy_waf.zip` file from the [Releases](https://github.com/fabriziosalmi/patterns/releases) page.
-2. Extract the files and include the `waf.acl` file in your HAProxy configuration.
+```nginx
+http {
+ include /etc/nginx/waf_patterns/nginx/waf_maps.conf;
+ include /etc/nginx/waf_patterns/nginx/bots.conf;
+}
+server {
+ include /etc/nginx/waf_patterns/nginx/waf_rules.conf;
+ if ($bad_bot) { return 403; }
+}
+```
----
+### Apache (ModSecurity)
+
+```apache
+
+ SecRuleEngine On
+ Include /etc/apache2/waf_patterns/apache/*.conf
+
+```
+
+### Traefik
+
+```yaml
+http:
+ routers:
+ app:
+ rule: "Host(`example.com`)"
+ service: app
+ middlewares: [waf-protection@file, bot-blocker@file]
+```
+
+### HAProxy
+
+```haproxy
+frontend http-in
+ bind *:80
+ acl waf_match path,url_dec -m reg -i -f /etc/haproxy/waf.acl
+ acl bad_bot hdr(User-Agent) -m reg -i -f /etc/haproxy/bots.acl
+ http-request deny deny_status 403 if waf_match || bad_bot
+```
+
+Full guides — with logging, whitelists, and tuning — live in the [docs](https://fabriziosalmi.github.io/patterns/).
+
+## Bad-bot example output (Nginx)
-## π§ Example Output (Bot Blocker β Nginx)
```nginx
map $http_user_agent $bad_bot {
- "~*AhrefsBot" 1;
- "~*SemrushBot" 1;
- "~*MJ12bot" 1;
default 0;
+ "~*AhrefsBot" 1;
+ "~*SemrushBot" 1;
+ "~*MJ12bot" 1;
+ "~*GPTBot" 1;
}
-if ($bad_bot) {
- return 403;
-}
+
+if ($bad_bot) { return 403; }
```
----
+The default list blocks SEO crawlers, AI training bots, and known scanners while explicitly **allowing** major search engines (Google, Bing, DuckDuckGo, Yandex, Baidu).
-## π€ Automation (GitHub Workflow)
-- **π Daily Updates** β GitHub Actions fetch the latest OWASP CRS rules every day.
-- **π Auto Deployment** β Pushes new `.conf` files directly to `waf_patterns/`.
-- **π¦ Release Automation** β Automatically creates a new release with pre-generated configurations.
-- **π― Manual Trigger** β Updates can also be triggered manually.
+## Automation
+
+| Workflow | Schedule | Purpose |
+|----------|----------|---------|
+| [`update_patterns.yml`](.github/workflows/update_patterns.yml) | Daily + manual | Re-fetch CRS, regenerate every backend, publish a release |
+| [`test_nginx.yml`](.github/workflows/test_nginx.yml) | On PR | Validate generated Nginx rules against a live container |
+| [`test_apache_docker.yml`](.github/workflows/test_apache_docker.yml) | On PR | Validate generated Apache rules against ModSecurity in Docker |
+| [`docs.yml`](.github/workflows/docs.yml) | On `docs/` change | Build and deploy the VitePress docs to GitHub Pages |
+
+All workflows target the **`runner-02`** self-hosted runner by default, with an automatic fallback to GitHub-hosted runners by setting the repository variable `RUNS_ON` to `'["ubuntu-latest"]'`.
+
+## Documentation
+
+The full documentation lives at **[fabriziosalmi.github.io/patterns](https://fabriziosalmi.github.io/patterns/)** — built with [VitePress](https://vitepress.dev/) and deployed automatically.
+
+- [Getting Started](https://fabriziosalmi.github.io/patterns/getting-started)
+- [Nginx](https://fabriziosalmi.github.io/patterns/nginx) · [Apache](https://fabriziosalmi.github.io/patterns/apache) · [Traefik](https://fabriziosalmi.github.io/patterns/traefik) · [HAProxy](https://fabriziosalmi.github.io/patterns/haproxy)
+- [Bad Bot Detection](https://fabriziosalmi.github.io/patterns/badbots)
+- [API & Scripts Reference](https://fabriziosalmi.github.io/patterns/api)
+
+## Contributing
+
+1. Fork the repository.
+2. Create a feature branch: `git checkout -b feature/your-change`.
+3. Commit and push.
+4. Open a pull request — the test workflows will run automatically.
+
+See [CONTRIBUTING.md](CONTRIBUTING.md) for details and [SECURITY.md](SECURITY.md) for the disclosure policy.
+
+## License
+
+Released under the [MIT License](LICENSE).
+
+## Resources
+
+- [OWASP Core Rule Set](https://github.com/coreruleset/coreruleset)
+- [ModSecurity](https://modsecurity.org/)
+- [Nginx](https://nginx.org/) · [Apache HTTPD](https://httpd.apache.org/) · [Traefik](https://traefik.io/) · [HAProxy](https://www.haproxy.org/)
+- [ai.robots.txt](https://github.com/ai-robots-txt/ai.robots.txt) — upstream AI-bot list
---
-## π€ Contributing
-1. **Fork** the repository.
-2. Create a **feature branch** (`feature/new-patterns`).
-3. **Commit** and push changes.
-4. Open a **Pull Request**.
-
----
-
-## π License
-This project is licensed under the **MIT License**.
-See the [LICENSE](LICENSE) file for details.
-
----
-
-## Other Projects
-
-If you like this project, you may also like these:
-
-- [caddy-waf](https://github.com/fabriziosalmi/caddy-waf) Caddy WAF (Regex Rules, IP and DNS filtering, Rate Limiting, GeoIP, Tor, Anomaly Detection)
-- [blacklists](https://github.com/fabriziosalmi/blacklists) Hourly updated domains blacklist π«
-- [proxmox-vm-autoscale](https://github.com/fabriziosalmi/proxmox-vm-autoscale) Automatically scale virtual machines resources on Proxmox hosts
-- [UglyFeed](https://github.com/fabriziosalmi/UglyFeed) Retrieve, aggregate, filter, evaluate, rewrite and serve RSS feeds using Large Language Models for fun, research and learning purposes
-- [proxmox-lxc-autoscale](https://github.com/fabriziosalmi/proxmox-lxc-autoscale) Automatically scale LXC containers resources on Proxmox hosts
-- [DevGPT](https://github.com/fabriziosalmi/DevGPT) Code togheter, right now! GPT powered code assistant to build project in minutes
-- [websites-monitor](https://github.com/fabriziosalmi/websites-monitor) Websites monitoring via GitHub Actions (expiration, security, performances, privacy, SEO)
-- [caddy-mib](https://github.com/fabriziosalmi/caddy-mib) Track and ban client IPs generating repetitive errors on Caddy
-- [zonecontrol](https://github.com/fabriziosalmi/zonecontrol) Cloudflare Zones Settings Automation using GitHub Actions
-- [lws](https://github.com/fabriziosalmi/lws) linux (containers) web services
-- [cf-box](https://github.com/fabriziosalmi/cf-box) cf-box is a set of Python tools to play with API and multiple Cloudflare accounts.
-- [limits](https://github.com/fabriziosalmi/limits) Automated rate limits implementation for web servers
-- [dnscontrol-actions](https://github.com/fabriziosalmi/dnscontrol-actions) Automate DNS updates and rollbacks across multiple providers using DNSControl and GitHub Actions
-- [proxmox-lxc-autoscale-ml](https://github.com/fabriziosalmi/proxmox-lxc-autoscale-ml) Automatically scale the LXC containers resources on Proxmox hosts with AI
-- [csv-anonymizer](https://github.com/fabriziosalmi/csv-anonymizer) CSV fuzzer/anonymizer
-- [iamnotacoder](https://github.com/fabriziosalmi/iamnotacoder) AI code generation and improvement
-
----
-## π Need Help?
-- **Issues?** Open a ticket in the [Issues Tab](https://github.com/fabriziosalmi/patterns/issues).
-
----
-
-## π Resources
-- [OWASP CRS](https://github.com/coreruleset/coreruleset)
-- [Apache ModSecurity](https://modsecurity.org/)
-- [Nginx](https://nginx.org/)
-- [Traefik](https://github.com/traefik/traefik)
-- [HaProxy](https://www.haproxy.org/)
+
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index c910f4d..0a87182 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -1,37 +1,77 @@
import { defineConfig } from 'vitepress'
+// Inline SVG icon factory for the features grid (renders inside .VPFeature .icon)
+const svg = (paths: string) =>
+ `${paths} `
+
+const icons = {
+ shield: svg(' '),
+ bot: svg(' '),
+ servers: svg(' '),
+ refresh: svg(' '),
+ package: svg(' '),
+ puzzle: svg(' ')
+}
+
export default defineConfig({
title: 'Patterns',
- description: 'OWASP CRS and Bad Bot Detection for Web Servers',
+ titleTemplate: ':title β Patterns',
+ description: 'Automated OWASP CRS and bad-bot rules for Nginx, Apache, Traefik, and HAProxy.',
base: '/patterns/',
+ cleanUrls: true,
+ lastUpdated: true,
+
+ markdown: {
+ languageAlias: {
+ haproxy: 'apache'
+ },
+ theme: {
+ light: 'github-light',
+ dark: 'github-dark-dimmed'
+ }
+ },
head: [
- ['link', { rel: 'icon', href: '/patterns/favicon.ico' }]
+ ['link', { rel: 'icon', type: 'image/svg+xml', href: '/patterns/favicon.svg' }],
+ ['meta', { name: 'theme-color', content: '#0071e3' }],
+ ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
+ ['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'default' }],
+ ['meta', { property: 'og:type', content: 'website' }],
+ ['meta', { property: 'og:title', content: 'Patterns β OWASP CRS WAF rules' }],
+ ['meta', { property: 'og:description', content: 'Automated OWASP CRS and bad-bot rules for Nginx, Apache, Traefik, and HAProxy.' }],
+ ['meta', { name: 'twitter:card', content: 'summary_large_image' }]
],
themeConfig: {
- logo: '/logo.svg',
+ logo: { src: '/logo.svg', width: 24, height: 24 },
+ siteTitle: 'Patterns',
+
+ // Expose icons to markdown via Vite config? Instead we expose them through home features below.
+ // (Used by index.md frontmatter via `icon: { svg: ... }`)
+ // @ts-expect-error β custom field for home page
+ icons,
nav: [
- { text: 'Home', link: '/' },
- { text: 'Getting Started', link: '/getting-started' },
+ { text: 'Documentation', link: '/getting-started', activeMatch: '^/(getting-started|nginx|apache|traefik|haproxy|badbots|api)' },
{
text: 'Web Servers',
items: [
{ text: 'Nginx', link: '/nginx' },
- { text: 'Apache', link: '/apache' },
+ { text: 'Apache (ModSecurity)', link: '/apache' },
{ text: 'Traefik', link: '/traefik' },
{ text: 'HAProxy', link: '/haproxy' }
]
},
{ text: 'Bad Bots', link: '/badbots' },
- { text: 'API', link: '/api' }
+ { text: 'API', link: '/api' },
+ { text: 'Releases', link: 'https://github.com/fabriziosalmi/patterns/releases' }
],
sidebar: [
{
text: 'Introduction',
items: [
+ { text: 'Overview', link: '/' },
{ text: 'Getting Started', link: '/getting-started' }
]
},
@@ -45,10 +85,10 @@ export default defineConfig({
]
},
{
- text: 'Features',
+ text: 'Reference',
items: [
{ text: 'Bad Bot Detection', link: '/badbots' },
- { text: 'API Reference', link: '/api' }
+ { text: 'API & Scripts', link: '/api' }
]
}
],
@@ -59,16 +99,21 @@ export default defineConfig({
footer: {
message: 'Released under the MIT License.',
- copyright: 'Copyright Β© 2024-present Fabrizio Salmi'
+ copyright: `Copyright Β© 2024β${new Date().getFullYear()} Fabrizio Salmi`
},
- search: {
- provider: 'local'
- },
+ search: { provider: 'local' },
editLink: {
pattern: 'https://github.com/fabriziosalmi/patterns/edit/main/docs/:path',
text: 'Edit this page on GitHub'
- }
+ },
+
+ outline: { level: [2, 3], label: 'On this page' },
+
+ docFooter: { prev: 'Previous', next: 'Next' }
}
})
+
+// Re-export icons so the home page (index.md) can pull them via a JS hash, if needed.
+export { icons }
diff --git a/docs/.vitepress/theme/components/HomeShowcase.vue b/docs/.vitepress/theme/components/HomeShowcase.vue
new file mode 100644
index 0000000..fa0d8e4
--- /dev/null
+++ b/docs/.vitepress/theme/components/HomeShowcase.vue
@@ -0,0 +1,63 @@
+
+
+
+
+ Integrations
+ Four web servers, one source of truth
+
+ The same OWASP CRS rule set is converted into the native syntax of each platform —
+ so you get equivalent protection whether you run Nginx, Apache, Traefik, or HAProxy.
+
+
+
+
+
+
+
+
+
600+
+
OWASP CRS patterns extracted
+
+
+
Daily
+
Automated rule refresh
+
+
+
4
+
Web server backends
+
+
+
MIT
+
Open-source license
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconApache.vue b/docs/.vitepress/theme/components/icons/IconApache.vue
new file mode 100644
index 0000000..0deda2a
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconApache.vue
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconArrow.vue b/docs/.vitepress/theme/components/icons/IconArrow.vue
new file mode 100644
index 0000000..93735ad
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconArrow.vue
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconBot.vue b/docs/.vitepress/theme/components/icons/IconBot.vue
new file mode 100644
index 0000000..5c6e1b1
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconBot.vue
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconGitHub.vue b/docs/.vitepress/theme/components/icons/IconGitHub.vue
new file mode 100644
index 0000000..529d45e
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconGitHub.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconHaproxy.vue b/docs/.vitepress/theme/components/icons/IconHaproxy.vue
new file mode 100644
index 0000000..e6769ed
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconHaproxy.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconNginx.vue b/docs/.vitepress/theme/components/icons/IconNginx.vue
new file mode 100644
index 0000000..4f9c320
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconNginx.vue
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconPackage.vue b/docs/.vitepress/theme/components/icons/IconPackage.vue
new file mode 100644
index 0000000..a94e55e
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconPackage.vue
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconPuzzle.vue b/docs/.vitepress/theme/components/icons/IconPuzzle.vue
new file mode 100644
index 0000000..bd8b796
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconPuzzle.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconRefresh.vue b/docs/.vitepress/theme/components/icons/IconRefresh.vue
new file mode 100644
index 0000000..8d730b7
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconRefresh.vue
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconServers.vue b/docs/.vitepress/theme/components/icons/IconServers.vue
new file mode 100644
index 0000000..51964db
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconServers.vue
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconShield.vue b/docs/.vitepress/theme/components/icons/IconShield.vue
new file mode 100644
index 0000000..e6966bf
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconShield.vue
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/components/icons/IconTraefik.vue b/docs/.vitepress/theme/components/icons/IconTraefik.vue
new file mode 100644
index 0000000..ab0e5fc
--- /dev/null
+++ b/docs/.vitepress/theme/components/icons/IconTraefik.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
new file mode 100644
index 0000000..ef1009f
--- /dev/null
+++ b/docs/.vitepress/theme/index.ts
@@ -0,0 +1,14 @@
+import { h } from 'vue'
+import type { Theme } from 'vitepress'
+import DefaultTheme from 'vitepress/theme'
+import HomeShowcase from './components/HomeShowcase.vue'
+import './style.css'
+
+export default {
+ extends: DefaultTheme,
+ Layout: () => {
+ return h(DefaultTheme.Layout, null, {
+ 'home-features-after': () => h(HomeShowcase)
+ })
+ }
+} satisfies Theme
diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css
new file mode 100644
index 0000000..3c8424e
--- /dev/null
+++ b/docs/.vitepress/theme/style.css
@@ -0,0 +1,621 @@
+/* =============================================================
+ Patterns Docs β Apple-native theme
+ System fonts, refined typography, soft surfaces, glass nav.
+ ============================================================= */
+
+:root {
+ /* Typography β system stack, identical to Apple's web properties */
+ --vp-font-family-base:
+ -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
+ "Helvetica Neue", "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
+ --vp-font-family-mono:
+ ui-monospace, "SF Mono", SFMono-Regular, "JetBrains Mono",
+ Menlo, Consolas, "Liberation Mono", monospace;
+
+ /* Brand β Apple-system blue, refined */
+ --apple-blue: #0071e3;
+ --apple-blue-hover: #0077ed;
+ --apple-blue-active: #006edb;
+ --apple-cyan: #5ac8fa;
+
+ /* Light surfaces */
+ --vp-c-bg: #ffffff;
+ --vp-c-bg-alt: #fbfbfd;
+ --vp-c-bg-soft: #f5f5f7;
+ --vp-c-bg-elv: #ffffff;
+
+ /* Borders β almost invisible, like Apple */
+ --vp-c-divider: rgba(0, 0, 0, 0.07);
+ --vp-c-border: rgba(0, 0, 0, 0.09);
+ --vp-c-gutter: rgba(0, 0, 0, 0.06);
+
+ /* Text β Apple uses near-black, high contrast */
+ --vp-c-text-1: #1d1d1f;
+ --vp-c-text-2: #515154;
+ --vp-c-text-3: #86868b;
+
+ /* Brand color slots used by VitePress */
+ --vp-c-brand-1: var(--apple-blue);
+ --vp-c-brand-2: var(--apple-blue-hover);
+ --vp-c-brand-3: var(--apple-blue-active);
+ --vp-c-brand-soft: rgba(0, 113, 227, 0.10);
+
+ /* Tip / warning / etc β calmer than defaults */
+ --vp-c-tip-1: #0071e3;
+ --vp-c-tip-soft: rgba(0, 113, 227, 0.08);
+ --vp-c-warning-1: #b25000;
+ --vp-c-warning-soft: rgba(255, 159, 10, 0.10);
+ --vp-c-danger-1: #c53030;
+ --vp-c-danger-soft: rgba(255, 59, 48, 0.08);
+
+ /* Layout rhythm */
+ --vp-layout-max-width: 1440px;
+ --vp-nav-height: 60px;
+
+ /* Radii β Apple loves soft, generous corners */
+ --radius-xs: 6px;
+ --radius-sm: 10px;
+ --radius-md: 14px;
+ --radius-lg: 20px;
+ --radius-xl: 28px;
+
+ /* Elevation β soft, low-spread shadows */
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 3px rgba(0, 0, 0, 0.05);
+ --shadow-2: 0 4px 16px -4px rgba(0, 0, 0, 0.08), 0 2px 6px -2px rgba(0, 0, 0, 0.04);
+ --shadow-3: 0 16px 40px -12px rgba(0, 0, 0, 0.16), 0 4px 12px -4px rgba(0, 0, 0, 0.06);
+
+ /* Buttons / brand surface */
+ --vp-button-brand-border: transparent;
+ --vp-button-brand-text: #fff;
+ --vp-button-brand-bg: var(--apple-blue);
+ --vp-button-brand-hover-border: transparent;
+ --vp-button-brand-hover-text: #fff;
+ --vp-button-brand-hover-bg: var(--apple-blue-hover);
+ --vp-button-brand-active-border: transparent;
+ --vp-button-brand-active-text: #fff;
+ --vp-button-brand-active-bg: var(--apple-blue-active);
+
+ --vp-button-alt-border: transparent;
+ --vp-button-alt-text: #1d1d1f;
+ --vp-button-alt-bg: rgba(0, 0, 0, 0.06);
+ --vp-button-alt-hover-bg: rgba(0, 0, 0, 0.09);
+ --vp-button-alt-active-bg: rgba(0, 0, 0, 0.12);
+
+ /* Code blocks β soft surface in light, deep in dark */
+ --vp-code-block-bg: #f5f5f7;
+ --vp-code-block-color: #1d1d1f;
+ --vp-code-line-highlight-color: rgba(0, 0, 0, 0.05);
+ --vp-code-tab-bg: #f5f5f7;
+ --vp-code-tab-divider: var(--vp-c-divider);
+}
+
+.dark {
+ --vp-c-bg: #000000;
+ --vp-c-bg-alt: #0a0a0c;
+ --vp-c-bg-soft: #161618;
+ --vp-c-bg-elv: #1c1c1e;
+
+ --vp-c-divider: rgba(255, 255, 255, 0.08);
+ --vp-c-border: rgba(255, 255, 255, 0.10);
+ --vp-c-gutter: rgba(255, 255, 255, 0.06);
+
+ --vp-c-text-1: #f5f5f7;
+ --vp-c-text-2: #a1a1a6;
+ --vp-c-text-3: #6e6e73;
+
+ --apple-blue: #0a84ff;
+ --apple-blue-hover: #409cff;
+ --apple-blue-active: #0a84ff;
+
+ --vp-c-brand-1: var(--apple-blue);
+ --vp-c-brand-2: var(--apple-blue-hover);
+ --vp-c-brand-3: var(--apple-blue-active);
+ --vp-c-brand-soft: rgba(10, 132, 255, 0.16);
+
+ --vp-c-tip-1: #0a84ff;
+ --vp-c-tip-soft: rgba(10, 132, 255, 0.12);
+ --vp-c-warning-1: #ffb340;
+ --vp-c-warning-soft: rgba(255, 159, 10, 0.14);
+ --vp-c-danger-1: #ff6961;
+ --vp-c-danger-soft: rgba(255, 69, 58, 0.14);
+
+ --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.5);
+ --shadow-2: 0 4px 16px -4px rgba(0, 0, 0, 0.6), 0 2px 6px -2px rgba(0, 0, 0, 0.4);
+ --shadow-3: 0 16px 40px -12px rgba(0, 0, 0, 0.7), 0 4px 12px -4px rgba(0, 0, 0, 0.5);
+
+ --vp-button-alt-text: #f5f5f7;
+ --vp-button-alt-bg: rgba(255, 255, 255, 0.10);
+ --vp-button-alt-hover-bg: rgba(255, 255, 255, 0.14);
+ --vp-button-alt-active-bg: rgba(255, 255, 255, 0.18);
+
+ --vp-code-block-bg: #161618;
+ --vp-code-block-color: #f5f5f7;
+ --vp-code-line-highlight-color: rgba(255, 255, 255, 0.06);
+ --vp-code-tab-bg: #161618;
+ --vp-code-tab-divider: var(--vp-c-divider);
+}
+
+/* ---------- Global typography ---------- */
+
+html {
+ font-feature-settings: "kern", "liga", "calt", "ss01";
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ font-family: var(--vp-font-family-base);
+ letter-spacing: -0.01em;
+}
+
+.VPDoc h1 {
+ letter-spacing: -0.025em;
+ font-weight: 700;
+ font-size: 2.4rem;
+ line-height: 1.15;
+}
+
+.VPDoc h2 {
+ letter-spacing: -0.02em;
+ font-weight: 650;
+ margin-top: 3rem;
+ padding-top: 1.5rem;
+ border-top: 1px solid var(--vp-c-divider);
+}
+
+.VPDoc h3 {
+ letter-spacing: -0.015em;
+ font-weight: 600;
+ margin-top: 2rem;
+}
+
+.VPDoc p,
+.VPDoc li {
+ font-size: 1rem;
+ line-height: 1.7;
+ color: var(--vp-c-text-1);
+}
+
+.VPDoc a:not(.header-anchor) {
+ text-decoration: none;
+ font-weight: 500;
+ color: var(--vp-c-brand-1);
+ transition: color 0.15s ease;
+}
+.VPDoc a:not(.header-anchor):hover {
+ color: var(--vp-c-brand-2);
+ text-decoration: underline;
+ text-decoration-thickness: 1px;
+ text-underline-offset: 3px;
+}
+
+/* ---------- Glass navigation ---------- */
+
+.VPNav {
+ background: transparent !important;
+}
+
+.VPNavBar {
+ background: rgba(255, 255, 255, 0.72) !important;
+ -webkit-backdrop-filter: saturate(180%) blur(20px);
+ backdrop-filter: saturate(180%) blur(20px);
+ border-bottom: 1px solid var(--vp-c-divider) !important;
+}
+.dark .VPNavBar {
+ background: rgba(0, 0, 0, 0.72) !important;
+}
+.VPNavBar.has-sidebar .content-body {
+ background: transparent !important;
+}
+.VPNavBar .divider {
+ display: none;
+}
+.VPNavBarTitle .title {
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ border-bottom: none !important;
+}
+.VPNavBarMenuLink,
+.VPNavBarMenuGroup .text {
+ font-size: 14px !important;
+ font-weight: 450 !important;
+ color: var(--vp-c-text-1) !important;
+ opacity: 0.92;
+}
+.VPNavBarMenuLink:hover,
+.VPNavBarMenuGroup:hover .text {
+ opacity: 1;
+ color: var(--vp-c-text-1) !important;
+}
+
+/* ---------- Sidebar ---------- */
+
+.VPSidebar {
+ background: var(--vp-c-bg-alt) !important;
+ border-right: 1px solid var(--vp-c-divider) !important;
+}
+.VPSidebarItem.level-0 > .item > .text {
+ font-weight: 600 !important;
+ letter-spacing: -0.005em;
+ color: var(--vp-c-text-1) !important;
+}
+.VPSidebarItem .link .link-text {
+ font-weight: 450;
+ font-size: 14px;
+}
+.VPSidebarItem.is-active > .item .link-text {
+ font-weight: 600 !important;
+ color: var(--vp-c-brand-1) !important;
+}
+
+/* ---------- Buttons ---------- */
+
+.VPButton {
+ border-radius: 980px !important;
+ padding: 0 22px !important;
+ height: 44px !important;
+ line-height: 44px !important;
+ font-weight: 500 !important;
+ font-size: 15px !important;
+ letter-spacing: -0.005em !important;
+ transition: background-color 0.18s ease, transform 0.12s ease, box-shadow 0.18s ease !important;
+}
+.VPButton.medium {
+ height: 40px !important;
+ line-height: 40px !important;
+ font-size: 14px !important;
+ padding: 0 18px !important;
+}
+.VPButton.brand {
+ box-shadow: 0 1px 2px rgba(0, 113, 227, 0.18);
+}
+.VPButton.brand:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 14px rgba(0, 113, 227, 0.30);
+}
+.VPButton.brand:active {
+ transform: translateY(0);
+}
+
+/* ---------- Hero ---------- */
+
+.VPHome .VPHero {
+ padding: 96px 24px 72px !important;
+}
+.VPHero .container {
+ max-width: 1120px !important;
+}
+.VPHero .name,
+.VPHero .text {
+ font-weight: 700 !important;
+ letter-spacing: -0.04em !important;
+ line-height: 1.05 !important;
+ font-size: clamp(2.6rem, 5.4vw, 4.4rem) !important;
+}
+.VPHero .name .clip {
+ background: linear-gradient(120deg, #0a84ff 0%, #5ac8fa 50%, #0071e3 100%) !important;
+ -webkit-background-clip: text !important;
+ background-clip: text !important;
+ -webkit-text-fill-color: transparent !important;
+}
+.VPHero .tagline {
+ margin-top: 22px !important;
+ font-size: 1.25rem !important;
+ font-weight: 400 !important;
+ line-height: 1.5 !important;
+ color: var(--vp-c-text-2) !important;
+ letter-spacing: -0.005em !important;
+ max-width: 720px;
+}
+.VPHero .actions {
+ margin-top: 36px !important;
+ gap: 12px !important;
+}
+.VPHero .image-container {
+ transform: none !important;
+}
+.VPHero .image-bg {
+ display: none !important;
+}
+.VPHero .image-src {
+ max-width: 100% !important;
+ filter: drop-shadow(0 30px 60px rgba(10, 132, 255, 0.18));
+}
+
+/* ---------- Features grid (default VitePress homepage) ---------- */
+
+.VPFeatures {
+ padding: 8px 24px 80px !important;
+}
+.VPFeatures .container {
+ max-width: 1120px !important;
+}
+.VPFeature {
+ border: 1px solid var(--vp-c-divider) !important;
+ background: var(--vp-c-bg-elv) !important;
+ border-radius: var(--radius-lg) !important;
+ padding: 28px !important;
+ transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease !important;
+ height: 100% !important;
+ box-shadow: var(--shadow-1);
+}
+.VPFeature:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-2);
+ border-color: var(--vp-c-border) !important;
+}
+.VPFeature .icon {
+ width: 52px !important;
+ height: 52px !important;
+ margin-bottom: 20px !important;
+ background: var(--vp-c-brand-soft) !important;
+ border-radius: 14px !important;
+ font-size: 24px !important;
+ display: inline-flex !important;
+ align-items: center;
+ justify-content: center;
+ color: var(--vp-c-brand-1);
+}
+.VPFeature .title {
+ font-size: 1.05rem !important;
+ font-weight: 600 !important;
+ letter-spacing: -0.01em !important;
+ margin-bottom: 8px !important;
+ color: var(--vp-c-text-1) !important;
+}
+.VPFeature .details {
+ font-size: 0.94rem !important;
+ line-height: 1.55 !important;
+ color: var(--vp-c-text-2) !important;
+}
+
+/* ---------- Custom blocks (tip / warning) ---------- */
+
+.custom-block {
+ border-radius: var(--radius-md) !important;
+ border: 1px solid var(--vp-c-divider) !important;
+ padding: 16px 18px !important;
+}
+.custom-block.tip {
+ border-color: var(--vp-c-brand-soft) !important;
+ background: var(--vp-c-tip-soft) !important;
+}
+.custom-block.warning {
+ background: var(--vp-c-warning-soft) !important;
+ border-color: rgba(255, 159, 10, 0.18) !important;
+}
+.custom-block .custom-block-title {
+ font-weight: 600 !important;
+ letter-spacing: -0.005em !important;
+}
+
+/* ---------- Code blocks ---------- */
+
+.vp-code-group .tabs {
+ border-radius: var(--radius-md) var(--radius-md) 0 0 !important;
+}
+div[class*="language-"] {
+ border-radius: var(--radius-md) !important;
+ box-shadow: var(--shadow-1);
+}
+div[class*="language-"] code {
+ font-feature-settings: "calt" 1, "liga" 0 !important;
+ font-size: 13.5px !important;
+ letter-spacing: -0.005em;
+}
+
+/* Inline code */
+:not(pre) > code {
+ background: var(--vp-c-bg-soft) !important;
+ border: 1px solid var(--vp-c-divider) !important;
+ border-radius: 6px !important;
+ padding: 2px 6px !important;
+ font-size: 0.88em !important;
+ color: var(--vp-c-text-1) !important;
+}
+
+/* ---------- Tables ---------- */
+
+.vp-doc table {
+ border-collapse: separate !important;
+ border-spacing: 0 !important;
+ border: 1px solid var(--vp-c-divider) !important;
+ border-radius: var(--radius-md) !important;
+ overflow: hidden !important;
+ display: table !important;
+ width: 100% !important;
+ margin: 24px 0 !important;
+}
+.vp-doc th {
+ background: var(--vp-c-bg-soft) !important;
+ font-weight: 600 !important;
+ letter-spacing: -0.005em !important;
+ font-size: 0.9rem !important;
+ text-align: left !important;
+}
+.vp-doc th,
+.vp-doc td {
+ border: none !important;
+ border-bottom: 1px solid var(--vp-c-divider) !important;
+ padding: 12px 16px !important;
+}
+.vp-doc tr:last-child td {
+ border-bottom: none !important;
+}
+.vp-doc tr {
+ background: transparent !important;
+}
+
+/* ---------- Footer ---------- */
+
+.VPFooter {
+ border-top: 1px solid var(--vp-c-divider) !important;
+ padding: 28px 24px !important;
+ background: var(--vp-c-bg-alt) !important;
+}
+.VPFooter .message,
+.VPFooter .copyright {
+ font-size: 13px !important;
+ color: var(--vp-c-text-3) !important;
+}
+
+/* ---------- Search input ---------- */
+
+.DocSearch-Button {
+ border-radius: 980px !important;
+ background: var(--vp-c-bg-soft) !important;
+ border: 1px solid var(--vp-c-divider) !important;
+ box-shadow: none !important;
+ height: 36px !important;
+}
+
+/* ---------- Subtle ambient gradient on home ---------- */
+
+.VPHome::before {
+ content: "";
+ position: absolute;
+ inset: 0 0 auto 0;
+ height: 720px;
+ background:
+ radial-gradient(60% 60% at 50% 0%, rgba(10, 132, 255, 0.10) 0%, transparent 70%),
+ radial-gradient(40% 40% at 80% 10%, rgba(90, 200, 250, 0.08) 0%, transparent 70%);
+ pointer-events: none;
+ z-index: -1;
+}
+
+/* ---------- Custom home sections (used by HomeShowcase.vue) ---------- */
+
+.home-section {
+ max-width: 1120px;
+ margin: 0 auto;
+ padding: 0 24px 96px;
+}
+.home-section + .home-section {
+ padding-top: 0;
+}
+.home-eyebrow {
+ font-size: 0.85rem;
+ font-weight: 600;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ color: var(--vp-c-brand-1);
+ margin-bottom: 12px;
+}
+.home-title {
+ font-size: clamp(1.75rem, 3.2vw, 2.4rem);
+ font-weight: 650;
+ letter-spacing: -0.025em;
+ line-height: 1.15;
+ margin: 0 0 12px;
+ color: var(--vp-c-text-1);
+}
+.home-lede {
+ font-size: 1.1rem;
+ line-height: 1.55;
+ color: var(--vp-c-text-2);
+ max-width: 720px;
+ margin: 0 0 40px;
+}
+
+.platform-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
+ gap: 16px;
+}
+.platform-card {
+ position: relative;
+ display: block;
+ padding: 24px;
+ border-radius: var(--radius-lg);
+ border: 1px solid var(--vp-c-divider);
+ background: var(--vp-c-bg-elv);
+ text-decoration: none;
+ color: inherit;
+ transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
+}
+.platform-card:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-2);
+ border-color: var(--vp-c-border);
+ text-decoration: none !important;
+}
+.platform-card .platform-icon {
+ width: 44px;
+ height: 44px;
+ border-radius: 12px;
+ background: var(--vp-c-brand-soft);
+ color: var(--vp-c-brand-1);
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 16px;
+}
+.platform-card .platform-icon svg {
+ width: 24px;
+ height: 24px;
+}
+.platform-card .platform-name {
+ font-size: 1.05rem;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ margin-bottom: 4px;
+}
+.platform-card .platform-meta {
+ font-size: 0.88rem;
+ color: var(--vp-c-text-3);
+ margin-bottom: 14px;
+}
+.platform-card .platform-arrow {
+ position: absolute;
+ top: 24px;
+ right: 24px;
+ color: var(--vp-c-text-3);
+ opacity: 0;
+ transform: translateX(-4px);
+ transition: opacity 0.2s ease, transform 0.2s ease;
+}
+.platform-card:hover .platform-arrow {
+ opacity: 1;
+ transform: translateX(0);
+ color: var(--vp-c-brand-1);
+}
+.platform-card .platform-arrow svg {
+ width: 18px;
+ height: 18px;
+}
+
+/* ---------- Stat strip ---------- */
+
+.stats-strip {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
+ gap: 24px;
+ padding: 28px 32px;
+ border-radius: var(--radius-lg);
+ border: 1px solid var(--vp-c-divider);
+ background: var(--vp-c-bg-alt);
+}
+.stat-item {
+ text-align: left;
+}
+.stat-value {
+ font-size: 2rem;
+ font-weight: 650;
+ letter-spacing: -0.02em;
+ color: var(--vp-c-text-1);
+ line-height: 1.1;
+}
+.stat-label {
+ font-size: 0.88rem;
+ color: var(--vp-c-text-3);
+ margin-top: 4px;
+}
+
+/* ---------- Reduce motion respect ---------- */
+
+@media (prefers-reduced-motion: reduce) {
+ * {
+ transition-duration: 0s !important;
+ animation-duration: 0s !important;
+ }
+}
diff --git a/docs/apache.md b/docs/apache.md
index 54ef177..e9bf2c5 100644
--- a/docs/apache.md
+++ b/docs/apache.md
@@ -1,93 +1,100 @@
# Apache Integration
-This guide explains how to integrate the WAF patterns with Apache using ModSecurity.
+This guide explains how to deploy the generated rules in Apache HTTPD using the **ModSecurity** engine.
## Prerequisites
-- Apache 2.4+
-- ModSecurity module installed
-
-### Install ModSecurity
+- Apache HTTPD **2.4+**
+- The **ModSecurity** module installed and enabled
::: code-group
-```bash [Debian/Ubuntu]
+```bash [Debian / Ubuntu]
sudo apt install libapache2-mod-security2
sudo a2enmod security2
```
-```bash [RHEL/CentOS]
-sudo yum install mod_security
+```bash [RHEL / CentOS / Rocky]
+sudo dnf install mod_security
+```
+
+```bash [Alpine]
+sudo apk add mod_security
```
:::
-## Quick Start
+## Quick start
-1. Download `apache_waf.zip` from [Releases](https://github.com/fabriziosalmi/patterns/releases)
-2. Extract to your Apache configuration directory
-3. Include the files in your Apache configuration
+1. Download `apache_waf.zip` from the [latest release](https://github.com/fabriziosalmi/patterns/releases/latest).
+2. Extract under your Apache config tree (e.g. `/etc/apache2/waf_patterns/apache/`).
+3. Include the `.conf` files from the relevant virtual host or globally.
-## Configuration Files
+## Files in the archive
-The Apache WAF package includes ModSecurity rules organized by attack type:
+The Apache output is split by attack family, each containing standard ModSecurity `SecRule` directives.
-| File | Protection Type |
-|------|-----------------|
-| `sqli.conf` | SQL Injection |
-| `xss.conf` | Cross-Site Scripting |
-| `rce.conf` | Remote Code Execution |
-| `lfi.conf` | Local File Inclusion |
-| `rfi.conf` | Remote File Inclusion |
-| `bots.conf` | Bad Bot Detection |
+| File | Protection |
+|------|------------|
+| `sqli.conf` | SQL injection |
+| `xss.conf` | Cross-site scripting |
+| `rce.conf` | Remote code execution |
+| `lfi.conf` | Local file inclusion |
+| `rfi.conf` | Remote file inclusion |
+| `php.conf`, `java.conf`, `iis.conf`, `shells.conf` | Stack-specific exploits |
+| `attack.conf`, `generic.conf`, `correlation.conf`, `evaluation.conf` | Generic anomaly detection |
+| `bots.conf` | Bad-bot User-Agent rules |
-## Integration
+## Step 1 — Enable the engine
-### Step 1: Enable ModSecurity
-
-Create or edit `/etc/apache2/mods-enabled/security2.conf`:
+In `/etc/apache2/mods-enabled/security2.conf` (or equivalent):
```apache
SecRuleEngine On
SecRequestBodyAccess On
SecResponseBodyAccess Off
- SecDebugLogLevel 0
+ SecAuditEngine RelevantOnly
+ SecAuditLog /var/log/apache2/modsec_audit.log
+ SecAuditLogParts ABCDEFHZ
```
-### Step 2: Include WAF Rules
+::: tip Run in detection mode first
+Set `SecRuleEngine DetectionOnly` for the first deployment. Watch the audit log, tune false positives, then flip to `On`.
+:::
-Add to your Apache configuration or virtual host:
+## Step 2 — Include the rules
+
+Either include all files in one go:
```apache
-
+
ServerName example.com
-
- # Include all WAF patterns
- Include /path/to/waf_patterns/apache/*.conf
-
- # ... other configurations ...
+
+ Include /etc/apache2/waf_patterns/apache/*.conf
+ # β¦other directives
```
-Or include specific rule sets:
+β¦or pick the categories you want:
```apache
-Include /path/to/waf_patterns/apache/sqli.conf
-Include /path/to/waf_patterns/apache/xss.conf
-Include /path/to/waf_patterns/apache/bots.conf
+Include /etc/apache2/waf_patterns/apache/sqli.conf
+Include /etc/apache2/waf_patterns/apache/xss.conf
+Include /etc/apache2/waf_patterns/apache/rce.conf
+Include /etc/apache2/waf_patterns/apache/bots.conf
```
-### Step 3: Restart Apache
+## Step 3 — Validate and restart
```bash
sudo apachectl configtest && sudo systemctl restart apache2
```
-## Rule Format
+## Rule format
-The rules follow ModSecurity syntax:
+Generated rules follow the standard ModSecurity DSL:
```apache
SecRule REQUEST_URI "@rx union.*select" \
@@ -95,68 +102,50 @@ SecRule REQUEST_URI "@rx union.*select" \
phase:2,\
deny,\
status:403,\
+ log,\
msg:'SQL Injection Attempt',\
severity:CRITICAL"
```
## Customization
-### Adjust Severity Levels
+### Detection-only mode
-Modify the action from `deny` to `log` for monitoring mode:
+Switch a noisy rule from blocking to logging without removing it:
```apache
-SecRule REQUEST_URI "@rx pattern" \
- "id:100001,\
- phase:2,\
- log,\
- pass,\
- msg:'Potential attack detected'"
+SecRuleUpdateActionById 100001 "pass,log,msg:'SQLi candidate (audit only)'"
```
-### Whitelist Paths
-
-Add exceptions for specific paths:
+### Whitelist a path
```apache
SecRule REQUEST_URI "@beginsWith /api/webhook" \
- "id:1,\
- phase:1,\
- allow,\
- nolog"
+ "id:1,phase:1,nolog,allow"
```
-## Logging
-
-ModSecurity logs are typically found at:
-- `/var/log/apache2/modsec_audit.log`
-- `/var/log/httpd/modsec_audit.log`
-
-Enable detailed logging:
+### Disable a single rule
```apache
-SecAuditEngine RelevantOnly
-SecAuditLog /var/log/apache2/modsec_audit.log
-SecAuditLogParts ABCDEFHZ
+SecRuleRemoveById 100001
```
+## Logs
+
+ModSecurity logs land in:
+
+- `/var/log/apache2/modsec_audit.log` — full audit trail
+- `/var/log/apache2/error.log` — rule matches and engine messages
+
## Testing
```bash
-# Test SQL injection detection
-curl -I "http://example.com/?id=1' UNION SELECT * FROM users--"
-
-# Check Apache error log
+curl -I "https://example.com/?id=1' UNION SELECT * FROM users--"
sudo tail -f /var/log/apache2/error.log
```
## Troubleshooting
-### ModSecurity not loading
-Ensure the module is enabled: `sudo a2enmod security2`
-
-### Rules not triggering
-Check that `SecRuleEngine` is set to `On` and rules are being included.
-
-### Performance issues
-Consider using `SecRuleRemoveById` to disable noisy rules that cause false positives.
+- **Module not loading** — confirm with `apachectl -M | grep security2`. Re-enable with `sudo a2enmod security2`.
+- **No rules triggering** — double-check `SecRuleEngine On` and that the include path resolves; `apachectl -S` lists the parsed config.
+- **Performance regressions** — identify hot rules in the audit log and disable or scope them with `SecRuleRemoveById` / `SecRule β¦ chain`.
diff --git a/docs/api.md b/docs/api.md
index 544ec58..df327ca 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -1,223 +1,236 @@
-# API Reference
+# API & Scripts Reference
-This page documents the Python scripts that power the Patterns project.
+Patterns is a small Python toolchain. Every script does one job and communicates with the rest through plain JSON or files on disk — no shared state, no daemon, no database.
-## Core Scripts
-
-### owasp2json.py
-
-Fetches and parses OWASP Core Rule Set patterns from GitHub.
-
-```bash
-python owasp2json.py
+```text
+owasp2json.py βββΆ owasp_rules.json βββΆ json2{nginx,apache,traefik,haproxy}.py
+ ββΆ badbots.py (independent)
```
-**Output**: `owasp_rules.json`
+All scripts are configured through **environment variables** (not CLI flags) except `owasp2json.py`, which has a small `argparse` interface.
-**Configuration**:
-- Uses environment variable `OWASP_REPO` to specify source repository
-- Default: `coreruleset/coreruleset`
+## Pipeline scripts
-**Features**:
-- Fetches latest CRS rules from GitHub
-- Parses `.conf` files for regex patterns
-- Extracts rule metadata (ID, severity, category)
-- Outputs structured JSON for conversion scripts
+### `owasp2json.py`
+
+Fetches the OWASP Core Rule Set from GitHub and emits a flat JSON rule list.
+
+```bash
+python owasp2json.py --ref v4.0 --output owasp_rules.json
+```
+
+| Argument / env | Default | Purpose |
+|----------------|---------|---------|
+| `--output` | `owasp_rules.json` | Output JSON path |
+| `--ref` | `v4.0` | Tag prefix to resolve (e.g. `v4.0`, `v3.3`, `dev`) |
+| `--dry-run` | off | Fetch and parse without writing |
+| `GITHUB_TOKEN` (env) | unset | Raises the GitHub API rate limit while iterating |
+
+The script verifies each blob's SHA against the GitHub-reported value before parsing it.
---
-### json2nginx.py
+### `json2nginx.py`
-Converts OWASP JSON rules to Nginx WAF configuration.
+Converts `owasp_rules.json` into Nginx `map`-based rules.
```bash
python json2nginx.py
+INPUT_FILE=custom.json OUTPUT_DIR=/tmp/out python json2nginx.py
```
-**Input**: `owasp_rules.json`
-**Output**: `waf_patterns/nginx/`
+**Generated files** (in `OUTPUT_DIR`):
-**Generated Files**:
| File | Purpose |
|------|---------|
-| `waf_maps.conf` | Map directives (http block) |
-| `waf_rules.conf` | If statements (server block) |
-| `README.md` | Integration instructions |
+| `waf_maps.conf` | `map` directives — include in the `http` block |
+| `waf_rules.conf` | `if` rules — include in the `server` block |
+| `.conf` | One file per OWASP category, **for inspection only** |
+| `README.md` | In-tree usage notes |
-**Environment Variables**:
-- `INPUT_FILE` - Path to OWASP JSON (default: `owasp_rules.json`)
-- `OUTPUT_DIR` - Output directory (default: `waf_patterns/nginx`)
+| Env var | Default |
+|---------|---------|
+| `INPUT_FILE` | `owasp_rules.json` |
+| `OUTPUT_DIR` | `waf_patterns/nginx` |
---
-### json2apache.py
+### `json2apache.py`
-Converts OWASP JSON rules to Apache ModSecurity format.
+Converts `owasp_rules.json` into ModSecurity `SecRule` directives, partitioned by attack category.
```bash
python json2apache.py
```
-**Input**: `owasp_rules.json`
-**Output**: `waf_patterns/apache/`
+**Generated files**: one `.conf` per OWASP category (`sqli.conf`, `xss.conf`, `rce.conf`, `lfi.conf`, β¦) — each contains pure ModSecurity rules ready to `Include`.
-**Generated Files**:
-- Category-specific `.conf` files (sqli.conf, xss.conf, etc.)
-- Each file contains ModSecurity `SecRule` directives
+| Env var | Default |
+|---------|---------|
+| `INPUT_FILE` | `owasp_rules.json` |
+| `OUTPUT_DIR` | `waf_patterns/apache` |
---
-### json2traefik.py
+### `json2traefik.py`
-Converts OWASP JSON rules to Traefik middleware configuration.
+Converts `owasp_rules.json` into a Traefik file-provider middleware.
```bash
python json2traefik.py
```
-**Input**: `owasp_rules.json`
-**Output**: `waf_patterns/traefik/`
+**Generated files**:
-**Generated Files**:
-- `middleware.toml` - Traefik middleware configuration
-- `README.md` - Integration instructions
+- `middleware.toml` — complete WAF middleware definition
+- `README.md` — in-tree integration notes
+
+| Env var | Default |
+|---------|---------|
+| `INPUT_FILE` | `owasp_rules.json` |
+| `OUTPUT_DIR` | `waf_patterns/traefik` |
---
-### json2haproxy.py
+### `json2haproxy.py`
-Converts OWASP JSON rules to HAProxy ACL format.
+Converts `owasp_rules.json` into HAProxy ACL files.
```bash
python json2haproxy.py
```
-**Input**: `owasp_rules.json`
-**Output**: `waf_patterns/haproxy/`
+**Generated files**:
-**Generated Files**:
-- `waf.acl` - Main WAF ACL rules
-- `README.md` - Integration instructions
+- `waf.acl` — one regex per line, designed for `-f /etc/haproxy/waf.acl`
+- `README.md` — in-tree integration notes
+
+| Env var | Default |
+|---------|---------|
+| `INPUT_FILE` | `owasp_rules.json` |
+| `OUTPUT_DIR` | `waf_patterns/haproxy/` |
---
-### badbots.py
+### `badbots.py`
-Generates bad bot blocking configurations from public bot lists.
+Independently fetches public bad-bot User-Agent lists and emits a `bots.*` file in each platform output directory.
```bash
python badbots.py
```
-**Output**: Bot configurations in each `waf_patterns/*/` directory
+**Generated files** (per platform):
-**Features**:
-- Fetches from multiple public bot lists
-- Includes fallback sources for reliability
-- Generates platform-specific configs
+| Platform | File |
+|----------|------|
+| Nginx | `waf_patterns/nginx/bots.conf` |
+| Apache | `waf_patterns/apache/bots.conf` |
+| Traefik | `waf_patterns/traefik/bots.toml` |
+| HAProxy | `waf_patterns/haproxy/bots.acl` |
----
+| Env var | Purpose |
+|---------|---------|
+| `GITHUB_TOKEN` | Raises the GitHub API rate limit when fetching upstream lists |
-## Import Scripts
+If a remote source is unreachable, the script falls back to a bundled list.
-These scripts help import existing WAF configurations.
+## Import / install scripts
-### import_nginx_waf.py
+The `import_*.py` scripts copy generated files into a server's runtime configuration directory and (optionally) splice an `Include` line into the main config. They are configured **entirely** through environment variables.
-Import Nginx WAF patterns from external sources.
+### `import_nginx_waf.py`
-```bash
-python import_nginx_waf.py --source /path/to/external/rules
-```
+| Env var | Default |
+|---------|---------|
+| `WAF_DIR` | `waf_patterns/nginx` |
+| `NGINX_WAF_DIR` | `/etc/nginx/waf/` |
+| `NGINX_CONF` | `/etc/nginx/nginx.conf` |
+| `BACKUP_DIR` | `/etc/nginx/waf_backup/` |
-### import_apache_waf.py
+### `import_apache_waf.py`
-Import Apache ModSecurity rules.
+| Env var | Default |
+|---------|---------|
+| `WAF_DIR` | `waf_patterns/apache` |
+| `APACHE_WAF_DIR` | `/etc/modsecurity.d/` |
+| `APACHE_CONF` | `/etc/apache2/apache2.conf` |
+| `BACKUP_DIR` | `/etc/modsecurity.d/backup` |
-```bash
-python import_apache_waf.py --source /path/to/modsec/rules
-```
+### `import_traefik_waf.py`
-### import_traefik_waf.py
+| Env var | Default |
+|---------|---------|
+| `WAF_DIR` | `waf_patterns/traefik` |
+| `TRAEFIK_WAF_DIR` | `/etc/traefik/waf/` |
+| `TRAEFIK_DYNAMIC_CONF` | `/etc/traefik/dynamic.toml` |
+| `BACKUP_DIR` | `/etc/traefik/waf_backup/` |
-Import Traefik middleware configurations.
+### `import_haproxy_waf.py`
-```bash
-python import_traefik_waf.py --source /path/to/traefik/config
-```
+| Env var | Default |
+|---------|---------|
+| `WAF_DIR` | `waf_patterns/haproxy` |
+| `HAPROXY_WAF_DIR` | `/etc/haproxy/waf/` |
+| `HAPROXY_CONF` | `/etc/haproxy/haproxy.cfg` |
+| `BACKUP_DIR` | `/etc/haproxy/waf_backup/` |
-### import_haproxy_waf.py
+::: warning Privileged paths
+The defaults point at system directories (`/etc/...`). Run the import scripts as root, or override every env var to point at a sandbox before running them.
+:::
-Import HAProxy ACL rules.
+## Data format
-```bash
-python import_haproxy_waf.py --source /path/to/haproxy/acl
-```
+### `owasp_rules.json`
----
-
-## Data Structures
-
-### owasp_rules.json Format
+A flat JSON array. Each item is a single rule with two required fields:
```json
[
{
- "id": "942100",
- "pattern": "(?i:union.*select)",
- "category": "sqli",
- "severity": "critical",
- "location": "request-uri",
- "description": "SQL Injection Attack Detected"
+ "category": "SQLI",
+ "pattern": "(?i:union[\\s\\S]+select)"
+ },
+ {
+ "category": "XSS",
+ "pattern": "(?i:"
+curl -I "https://example.com/?id=1' OR '1'='1"
+curl -I "https://example.com/?q="
```
## Troubleshooting
-### Configuration errors
-Always run `nginx -t` before reloading to catch syntax errors.
-
-### False positives
-If legitimate requests are being blocked, check `/var/log/nginx/error.log` and consider adding path-specific exceptions.
-
-### Performance
-The map-based approach is highly efficient. For high-traffic sites, consider enabling caching for the map variables.
+- **`nginx: configuration file test failed`** — you likely included a per-category file. Switch to `waf_maps.conf` + `waf_rules.conf`.
+- **False positives** — check `/var/log/nginx/error.log`, identify the matching `$waf_block_*` variable, then add a `location`-scoped exemption.
+- **High traffic** — the `map`-based design is already the fastest option Nginx offers; no further tuning is normally needed.
diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg
new file mode 100644
index 0000000..54f10f6
--- /dev/null
+++ b/docs/public/favicon.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/public/hero-shield.svg b/docs/public/hero-shield.svg
new file mode 100644
index 0000000..4db1cd7
--- /dev/null
+++ b/docs/public/hero-shield.svg
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/public/logo.svg b/docs/public/logo.svg
new file mode 100644
index 0000000..a5ee07e
--- /dev/null
+++ b/docs/public/logo.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/traefik.md b/docs/traefik.md
index e92b69d..02c2ff5 100644
--- a/docs/traefik.md
+++ b/docs/traefik.md
@@ -1,27 +1,21 @@
# Traefik Integration
-This guide explains how to integrate the WAF patterns with Traefik using middleware plugins.
+This guide explains how to consume the generated WAF middleware in **Traefik v2 / v3**.
-## Quick Start
+## Quick start
-1. Download `traefik_waf.zip` from [Releases](https://github.com/fabriziosalmi/patterns/releases)
-2. Extract the files
-3. Configure the middleware in your Traefik configuration
+1. Download `traefik_waf.zip` from the [latest release](https://github.com/fabriziosalmi/patterns/releases/latest).
+2. Drop the TOML files into your dynamic configuration directory.
+3. Reference the middleware from each router that should be protected.
-## Configuration Files
-
-The Traefik WAF package includes:
+## Files in the archive
| File | Purpose |
|------|---------|
-| `middleware.toml` | WAF middleware configuration |
-| `bots.toml` | Bad bot detection rules |
+| `middleware.toml` | WAF middleware definition (regex patterns per category) |
+| `bots.toml` | Bad-bot User-Agent middleware |
-## Integration with File Provider
-
-### Step 1: Enable File Provider
-
-In your `traefik.toml` or `traefik.yml`:
+## Step 1 — Enable the file provider
::: code-group
@@ -41,39 +35,31 @@ providers:
:::
-### Step 2: Copy Middleware Files
-
-Copy the WAF configuration files to your dynamic configuration directory:
+## Step 2 — Drop the TOML files in
```bash
-cp waf_patterns/traefik/*.toml /etc/traefik/dynamic/
+sudo cp waf_patterns/traefik/*.toml /etc/traefik/dynamic/
```
-### Step 3: Apply Middleware to Routes
+Traefik picks them up automatically because `watch = true`.
-Reference the middleware in your router configuration:
+## Step 3 — Reference the middleware
::: code-group
```toml [dynamic/routes.toml]
-[http.routers.my-router]
+[http.routers.app]
rule = "Host(`example.com`)"
- service = "my-service"
+ service = "app"
middlewares = ["waf-protection", "bot-blocker"]
-
-[http.middlewares.waf-protection.plugin.waf]
- # WAF configuration loaded from middleware.toml
-
-[http.middlewares.bot-blocker.plugin.botblocker]
- # Bot blocking loaded from bots.toml
```
```yaml [dynamic/routes.yml]
http:
routers:
- my-router:
+ app:
rule: "Host(`example.com`)"
- service: my-service
+ service: app
middlewares:
- waf-protection
- bot-blocker
@@ -81,35 +67,27 @@ http:
:::
-## Integration with Docker Labels
+The middleware names (`waf-protection`, `bot-blocker`) are the keys defined inside `middleware.toml` and `bots.toml`.
-For Docker-based deployments:
+## Docker labels
+
+For Docker / Compose deployments, attach the middleware via labels:
```yaml
services:
- my-app:
+ app:
image: my-app:latest
labels:
- "traefik.enable=true"
- - "traefik.http.routers.my-app.rule=Host(`example.com`)"
- - "traefik.http.routers.my-app.middlewares=waf@file"
+ - "traefik.http.routers.app.rule=Host(`example.com`)"
+ - "traefik.http.routers.app.middlewares=waf-protection@file,bot-blocker@file"
```
-## Middleware Configuration
+The `@file` suffix tells Traefik to resolve the middleware from the file provider.
-The `middleware.toml` contains regex-based blocking rules:
+## Plugin compatibility
-```toml
-[http.middlewares.waf.plugin.rewriteHeaders]
- # SQL Injection patterns
- [[http.middlewares.waf.plugin.rewriteHeaders.replacements]]
- regex = "(?i)union.*select"
- replacement = "BLOCKED"
-```
-
-## Using with Traefik Plugins
-
-For enhanced WAF capabilities, consider using community plugins:
+`middleware.toml` is generated against Traefik's built-in middleware primitives. If you prefer a dedicated WAF plugin (e.g. one of the community plugins on [Traefik Plugins](https://plugins.traefik.io/)), you can declare it side-by-side and chain both:
```yaml
experimental:
@@ -121,25 +99,24 @@ experimental:
## Customization
-### Add Custom Patterns
+### Add custom patterns
-Edit `middleware.toml` to add your own patterns:
+Edit `middleware.toml` to extend the regex set:
```toml
-[[http.middlewares.waf.plugin.rewriteHeaders.replacements]]
+[[http.middlewares.waf-protection.plugin.rewriteHeaders.replacements]]
regex = "your-custom-pattern"
replacement = "BLOCKED"
```
### Logging
-Enable access logs to monitor blocked requests:
+Enable structured access logs to track middleware decisions:
```toml
[accessLog]
filePath = "/var/log/traefik/access.log"
format = "json"
-
[accessLog.fields]
[accessLog.fields.headers]
defaultMode = "keep"
@@ -148,21 +125,12 @@ Enable access logs to monitor blocked requests:
## Testing
```bash
-# Test WAF detection
-curl -H "Host: example.com" \
- "http://localhost/?id=1' OR '1'='1"
-
-# Check Traefik logs
+curl -H "Host: example.com" "http://localhost/?id=1' OR '1'='1"
docker logs traefik 2>&1 | grep -i blocked
```
## Troubleshooting
-### Middleware not loading
-Check that the file provider is correctly configured and watching the right directory.
-
-### Routes not applying middleware
-Ensure the middleware name matches exactly between router and middleware definition.
-
-### Performance considerations
-Traefik's regex-based middleware can impact performance at high traffic. Monitor latency after enabling WAF rules.
+- **Middleware never loads** — check that the file provider directory matches and that `watch = true`. `traefik logs -f` shows hot-reload events.
+- **Router does not apply the middleware** — the middleware name must match exactly (case-sensitive) between router declaration and middleware definition.
+- **Latency** — regex middleware adds per-request overhead. Profile with `traefik` access logs and consider scoping the middleware to specific routers rather than applying globally.