<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>CrowdSec Blog</title>
        <link>https://crowdsec.net/blog</link>
        <description>Latest updates and insights from CrowdSec</description>
        <lastBuildDate>Sun, 26 Jul 2026 00:25:16 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>CrowdSec Blog</title>
            <url>https://cms.crowdsec.net/wp-content/uploads/2024/10/crowdsec-logo.png</url>
            <link>https://crowdsec.net/blog</link>
        </image>
        <item>
            <title><![CDATA[WAF for Traefik with CrowdSec: Virtual Patching in Docker]]></title>
            <link>https://crowdsec.net/blog/waf-traefik-crowdsec</link>
            <guid isPermaLink="false">https://crowdsec.net/blog/waf-traefik-crowdsec</guid>
            <pubDate>Fri, 24 Jul 2026 11:49:29 GMT</pubDate>
            <description><![CDATA[<p>Add an open-source WAF to Traefik with CrowdSec. Wire the bouncer plugin, enable virtual patching, and block real attacks, step by step in Docker.</p>
]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">Traefik gives you a real front door. It terminates TLS, discovers your services as they start and stop, routes by host and path, load-balances across replicas, retries failed backends, and chains middlewares for auth, redirects, rate limits, and headers. What it does not do, out of the box, is look at what is <em>inside</em> the requests it forwards. Traefik&#8217;s job is to deliver the request; deciding whether that request is an attack was always someone else&#8217;s.</p>



<p class="wp-block-paragraph">That someone is a WAF, and on Traefik it slots in exactly where the gap is: as one more middleware in the chain. CrowdSec gives you an open-source one, and you can switch it on the safe way first. That safe way is <strong>virtual patching</strong>, which blocks the specific shapes of known exploits and known-bad paths and leaves everything else untouched. You get real protection immediately, with no tuning required.</p>



<p class="wp-block-paragraph">This guide takes a Traefik + Docker Compose stack from no request inspection to a working WAF in <strong>virtual-patching mode</strong>, the safest way to start, because it refuses attacks it can name without ever getting in your users&#8217; way.</p>



<h2 class="wp-block-heading">Why Traefik needs a WAF of its own</h2>



<p class="wp-block-paragraph">Three reasons this earns a place in your middleware chain.</p>



<p class="wp-block-paragraph"><strong>You are running code you did not write.</strong> Traefik almost never sits in front of your own application. It fronts other people&#8217;s: a self-hosted dashboard, a vendor&#8217;s container, an app built on a framework you pulled from a registry. You cannot add a security library inside software you do not maintain, and you are not going to audit all of it. Traefik is the one place every one of those requests passes through in the clear, which makes it the only practical place to inspect them. A WAF here protects the apps you depend on but do not control.</p>



<p class="wp-block-paragraph"><strong>It closes the gap between a CVE and your upgrade.</strong> When a vulnerability in a popular app goes public, mass scanning for it starts within hours, the same internet-wide spray that finds every new host within minutes of it coming online. Your realistic window to read the advisory, test the patch, and roll it out is measured in days. Virtual patching covers that gap: the moment a rule for the exploit&#8217;s shape ships, the WAF refuses that request at the edge, whether or not the app behind it is patched yet. You stop racing the scanners and buy the time to upgrade on your own schedule.</p>



<p class="wp-block-paragraph"><strong>Its rules come from a network, not just a file.</strong> A classic WAF knows exactly what its own signature set knows, and nothing more. CrowdSec&#8217;s is fed by every other engine running it: an IP caught probing someone else&#8217;s server becomes an IP yours refuses before it ever reaches your apps, and the same Traefik plugin enforces those community bans in the same pass as the WAF verdict. Your defense gets sharper because everyone else&#8217;s is running too, something a lone WAF, however good its rules, cannot do.d, first-party path is gone, and rebuilding a dynamic module against every Nginx release was never anyone&#8217;s idea of fun. ModSecurity earned its place; it is not the villain of this story. But if you are choosing a WAF for Nginx in 2026, you want something actively maintained and open source.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">What &#8220;CrowdSec&#8217;s WAF&#8221; actually is on Traefik</h2>



<p class="wp-block-paragraph">Two pieces do the work, and it helps to know which is which before you touch a config file.</p>



<ul class="wp-block-list">
<li><strong>The AppSec component</strong> is an HTTP server bundled inside the CrowdSec agent. It holds the rules and returns a verdict, <em>allow</em> or <em>block</em>, for any request it is shown. It does not sit inline with your traffic.</li>



<li><strong>The Traefik bouncer</strong> is a middleware plugin (<a href="https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" target="_blank" rel="noreferrer noopener">maxlerebourg/crowdsec-bouncer-traefik-plugin</a>), loaded through Traefik&#8217;s built-in Yaegi engine, with no separate binary and no sidecar. It intercepts each request, shows a copy to AppSec, and enforces whatever verdict comes back.</li>
</ul>



<pre><code class="language-bash">client ───▶ traefik ───▶ bouncer plugin ───▶ AppSec (crowdsec:7422)
                         │                    │
                         │          verdict ◀─┘
                         ▼
                    allow (200) / block (403)</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"><strong>Key points:</strong></p>



<ul class="wp-block-list">
<li>AppSec is the brain, the plugin is the muscle. AppSec never blocks anything itself; the plugin does.</li>



<li>The verdict round-trip stays on the Docker network, so it costs microseconds, not an internet hop.</li>



<li>The same plugin that enforces WAF verdicts also enforces IP bans from CrowdSec&#8217;s community blocklist. One middleware, two jobs.</li>



<li>The plugin speaks the same protocol to the same AppSec component that the Nginx, HAProxy, Caddy, and Envoy bouncers do, so the WAF rules and verdicts are identical whatever proxy you run.</li>
</ul>



<p class="wp-block-paragraph">Full reference: the <a href="https://docs.crowdsec.net/docs/appsec/intro" target="_blank" rel="noreferrer noopener">AppSec component docs</a>.</p>



<h2 class="wp-block-heading">Prerequisites</h2>



<ul class="wp-block-list">
<li>A host with <strong>Docker</strong> and the <strong>Compose</strong> plugin.</li>



<li>Outbound HTTPS to the CrowdSec hub (rules) and <code>api.crowdsec.net</code> (community signals).</li>
</ul>



<p class="wp-block-paragraph">That is the whole list. No database to stand up, no proxy rearchitecting; the WAF slots in as one more middleware.</p>



<h2 class="wp-block-heading">Step 1 — Stand up CrowdSec next to Traefik</h2>



<p class="wp-block-paragraph">The whole stack is one Compose file: the CrowdSec agent (which carries the WAF), Traefik, and a <code>whoami</code> container as the app to protect.<br></p>



<pre><code class="language-bash"># docker-compose.yml
services:
  crowdsec:
    image: crowdsecurity/crowdsec:v1.7.8
    container_name: crowdsec
    restart: unless-stopped
    environment:
      COLLECTIONS: "crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules"
      BOUNCER_KEY_traefik: "${BOUNCER_KEY_TRAEFIK}"
    volumes:
      - ./acquis.d:/etc/crowdsec/acquis.d
      - crowdsec-config:/etc/crowdsec        # persist config, credentials, Console enrollment
      - crowdsec-data:/var/lib/crowdsec/data
    networks: [web]

  traefik:
    image: traefik:v3.5
    container_name: traefik
    restart: unless-stopped
    depends_on: [crowdsec]
    ports: ["80:80"]
    volumes:
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - ./dynamic:/etc/traefik/dynamic:ro
      - traefik-plugins:/plugins-storage
    networks: [web]

  whoami:
    image: traefik/whoami:latest
    container_name: whoami
    restart: unless-stopped
    networks: [web]

networks:
  web:
volumes:
  crowdsec-config:
  crowdsec-data:
  traefik-plugins:</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Two lines in the <code>crowdsec</code> service do the heavy lifting. <code>COLLECTIONS</code> installs the virtual-patching rule sets on first boot. <code>BOUNCER_KEY_traefik</code> mints a fixed API key for the plugin, so it authenticates on start with no manual key copying. Pick any long random string and set it in a <code>.env</code> file next to the Compose file:</p>



<pre><code class="language-bash">echo "BOUNCER_KEY_TRAEFIK=$(openssl rand -hex 24)" > .env</code></pre>



<p class="wp-block-paragraph">Tell the AppSec component to listen, in acquis.d/appsec.yaml:</p>



<pre><code class="language-bash"># acquis.d/appsec.yaml
appsec_config: crowdsecurity/appsec-default
labels:
  type: appsec
listen_addr: 0.0.0.0:7422
source: appsec</code></pre>



<p class="wp-block-paragraph">Bring it up and confirm AppSec is listening:<br></p>



<pre><code class="language-bash">$ docker compose up -d
$ docker compose logs crowdsec | grep Appsec
crowdsec | Appsec listening on 0.0.0.0:7422
$ docker exec crowdsec cscli metrics show appsec
 Appsec Engine | Processed | Blocked
 0.0.0.0:7422/ | 0         | 0</code></pre>



<p class="wp-block-paragraph"><strong>Key points:</strong></p>



<ul class="wp-block-list">
<li>Install <strong>both</strong> collections together. <code>crowdsecurity/appsec-default</code> draws on rules from <code>appsec-virtual-patching</code> <em>and</em> <code>appsec-generic-rules</code>; the pair gives you the complete virtual-patching set.</li>



<li>AppSec binds <code>0.0.0.0:7422</code>, not <code>127.0.0.1.</code> Loopback inside the CrowdSec container is unreachable from the Traefik container; they only share the <code>web</code> network. The listener is not published to the host, so it is not exposed outside Docker.</li>



<li>Persist <strong>both</strong> volumes. <code>crowdsec-data</code> holds the decisions and alerts database; <code>crowdsec-config</code> holds <code>/etc/crowdsec</code>, where your API credentials and Console enrollment live. Leave the config volume out and a <code>docker compose down</code> drops the enrollment, so you re-enroll after every recreate.</li>



<li>At this stage CrowdSec <em>detects</em>. It does not yet <em>block</em> anything reaching Traefik; that is the plugin&#8217;s job, next.</li>
</ul>



<h2 class="wp-block-heading">Step 2: Load the plugin and wire the middleware</h2>



<p class="wp-block-paragraph">Register the plugin in Traefik&#8217;s static config, and trust the proxy hop so Traefik keeps the real client IP:</p>



<pre><code class="language-bash"># traefik.yml (static)
entryPoints:
  web:
    address: ":80"
    forwardedHeaders:
      trustedIPs:
        - "172.16.0.0/12"      # the Docker network / any LB in front of Traefik

experimental:
  plugins:
    bouncer:
      moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
      version: v1.6.0

providers:
  file:
    directory: /etc/traefik/dynamic
    watch: true</code></pre>



<p class="wp-block-paragraph">Then define the middleware (and attach it to a router) in the dynamic config:<br></p>



<pre><code class="language-bash"># dynamic/middlewares.yml
http:
  routers:
    whoami:
      rule: "PathPrefix(`/`)"
      entryPoints: [web]
      service: whoami
      middlewares: [crowdsec]        # every request is screened by the WAF

  services:
    whoami:
      loadBalancer:
        servers:
          - url: "http://whoami:80"

  middlewares:
    crowdsec:
      plugin:
        bouncer:
          enabled: true
          crowdsecMode: stream               # poll the decision list (prod default)
          updateIntervalSeconds: 10          # a fresh ban lands within this window
          crowdsecLapiScheme: http
          crowdsecLapiHost: crowdsec:8080    # LAPI, over the Docker network
          crowdsecLapiKey: "<your-bouncer-key>"   # == BOUNCER_KEY_traefik above
          crowdsecAppsecEnabled: true        # turn ON the inline WAF
          crowdsecAppsecHost: crowdsec:7422  # the AppSec listener
          forwardedHeadersTrustedIPs:
            - "172.16.0.0/12"</code></pre>



<p class="wp-block-paragraph">Reload Traefik (or let the file provider pick it up, since watch: true) and confirm the plugin registered as a bouncer:</p>



<pre><code class="language-bash">$ docker exec crowdsec cscli bouncers list
 Name    Valid  Type                             Version  Auth Type
 traefik  ✔️     Crowdsec-Bouncer-Traefik-Plugin  1.5.0    api-key
</code></pre>



<p class="wp-block-paragraph">Prove the enforcement path works before trusting the WAF with anything. Ban an IP, wait one stream interval, and present it as the forwarded client:<br></p>



<pre><code class="language-bash">$ curl -s -o /dev/null -w 'before ban: %{http_code}\n' -H 'X-Forwarded-For: 192.168.1.142' http://127.0.0.1/   # 200
$ docker exec crowdsec cscli decisions add --ip 192.168.1.142 --duration 5m --reason test
$ curl -s -o /dev/null -w 'after ban:  %{http_code}\n' -H 'X-Forwarded-For: 192.168.1.142' http://127.0.0.1/   # 403
$ curl -s -o /dev/null -w 'clean IP:   %{http_code}\n' -H 'X-Forwarded-For: 192.168.1.1'  http://127.0.0.1/   # 200
$ docker exec crowdsec cscli decisions delete --ip 192.168.1.142
</code></pre>



<p class="wp-block-paragraph"><code>200 → 403 → 200</code>, and an unrelated IP stays <code>200</code>. The plugin is live and enforcing decisions.</p>



<p class="wp-block-paragraph"><strong>Key points:</strong></p>



<ul class="wp-block-list">
<li><code>crowdsecLapiKey</code> must equal the <code>BOUNCER_KEY_traefik</code> you set in Compose. Traefik&#8217;s file provider does not expand environment variables, so this one is a literal.</li>



<li><code>crowdsecAppsecEnabled</code> defaults to <strong>false</strong>, so the WAF is off until you set it <code>true</code>. That single line is the difference between an IP-ban bouncer and a WAF.</li>



<li><code>crowdsecMode: </code>stream means the plugin polls the full decision list every <code>updateIntervalSeconds</code> and answers locally. A fresh ban lands within that window, so a ban-then<code>-curl</code> with no pause looks like a miss when it isn&#8217;t.</li>
</ul>



<style>
  .codalent-tip-box {
    background: #f7f6ff;
    border: 1px solid rgba(78, 74, 153, 0.35);
    border-left: 4px solid #4e4a99;
    border-radius: 8px;
    padding: 18px 22px;
    color: #2d2d3a;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
    max-width: 1120px;
    box-sizing: border-box;
  }

  .codalent-tip-inner {
    display: flex;
    align-items: flex-start;
    gap: 14px;
  }

  .codalent-tip-icon {
    width: 24px;
    height: 24px;
    min-width: 24px;
    border-radius: 50%;
    background: rgba(78, 74, 153, 0.12);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 1px;
  }

  .codalent-tip-icon svg {
    width: 15px;
    height: 15px;
    stroke: #4e4a99;
  }

  .codalent-tip-content {
    min-width: 0;
  }

  .codalent-tip-label {
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: #4e4a99;
    margin-bottom: 8px;
  }

  .codalent-tip-box p {
    margin: 0;
    font-size: 15px;
    line-height: 1.6;
  }

  .codalent-tip-box code {
    background: rgba(78, 74, 153, 0.1);
    border: 1px solid rgba(78, 74, 153, 0.18);
    border-radius: 4px;
    padding: 2px 5px;
    color: #277a48;
    font-family: Consolas, Monaco, "Courier New", monospace;
    font-size: 0.9em;
    overflow-wrap: anywhere;
  }

  .codalent-tip-box a {
    color: #4e4a99;
    text-decoration: underline;
    text-underline-offset: 2px;
  }

  @media (max-width: 600px) {
    .codalent-tip-box {
      padding: 16px;
    }

    .codalent-tip-inner {
      gap: 12px;
    }

    .codalent-tip-box p {
      font-size: 14px;
    }
  }
</style>

<div class="codalent-tip-box">
  <div class="codalent-tip-inner">

    <div class="codalent-tip-icon" aria-hidden="true">
      <svg viewBox="0 0 24 24" fill="none">
        <path
          d="M12 9v4"
          stroke-width="1.8"
          stroke-linecap="round">
        </path>
        <path
          d="M12 17h.01"
          stroke-width="2.2"
          stroke-linecap="round">
        </path>
        <path
          d="M10.3 4.6 3.4 16.5A2 2 0 0 0 5.1 19.5h13.8a2 2 0 0 0 1.7-3L13.7 4.6a2 2 0 0 0-3.4 0Z"
          stroke-width="1.8"
          stroke-linejoin="round">
        </path>
      </svg>
    </div>

    <div class="codalent-tip-content">
      <div class="codalent-tip-label">Note</div>

      <p>
        <strong>Keep the key out of your config in production.</strong>
        The literal <code>crowdsecLapiKey</code> is fine for getting started, but it lives in
        <code>dynamic/middlewares.yml</code>, which you probably do not want in version control.
        For anything you commit or ship, mount the key as a file instead (a Docker secret, or
        any file the container can read) and point the plugin at it with
        <code>crowdsecLapiKeyFile: /path/to/key</code> in place of
        <code>crowdsecLapiKey</code>. The
        <a href="https://docs.crowdsec.net/u/bouncers/traefik" target="_blank" rel="noopener noreferrer">official Traefik bouncer guide</a>
        walks through the secret-mounted pattern.
      </p>
    </div>

  </div>
</div>



<style>
  .codalent-tip-box {
    background: #f7f6ff;
    border: 1px solid rgba(78, 74, 153, 0.35);
    border-left: 4px solid #4e4a99;
    border-radius: 8px;
    padding: 18px 22px;
    color: #2d2d3a;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
    max-width: 1120px;
    box-sizing: border-box;
  }

  .codalent-tip-inner {
    display: flex;
    align-items: flex-start;
    gap: 14px;
  }

  .codalent-tip-icon {
    width: 24px;
    height: 24px;
    min-width: 24px;
    border-radius: 50%;
    background: rgba(78, 74, 153, 0.12);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 1px;
  }

  .codalent-tip-icon svg {
    width: 15px;
    height: 15px;
    stroke: #4e4a99;
  }

  .codalent-tip-content {
    min-width: 0;
  }

  .codalent-tip-label {
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: #4e4a99;
    margin-bottom: 8px;
  }

  .codalent-tip-box p {
    margin: 0;
    font-size: 15px;
    line-height: 1.6;
  }

  .codalent-tip-box code {
    background: rgba(78, 74, 153, 0.1);
    border: 1px solid rgba(78, 74, 153, 0.18);
    border-radius: 4px;
    padding: 2px 5px;
    color: #277a48;
    font-family: Consolas, Monaco, "Courier New", monospace;
    font-size: 0.9em;
    overflow-wrap: anywhere;
  }

  @media (max-width: 600px) {
    .codalent-tip-box {
      padding: 16px;
    }

    .codalent-tip-inner {
      gap: 12px;
    }

    .codalent-tip-box p {
      font-size: 14px;
    }
  }
</style>

<div class="codalent-tip-box">
  <div class="codalent-tip-inner">

    <div class="codalent-tip-icon" aria-hidden="true">
      <svg viewBox="0 0 24 24" fill="none">
        <path
          d="M12 9v4"
          stroke-width="1.8"
          stroke-linecap="round">
        </path>
        <path
          d="M12 17h.01"
          stroke-width="2.2"
          stroke-linecap="round">
        </path>
        <path
          d="M10.3 4.6 3.4 16.5A2 2 0 0 0 5.1 19.5h13.8a2 2 0 0 0 1.7-3L13.7 4.6a2 2 0 0 0-3.4 0Z"
          stroke-width="1.8"
          stroke-linejoin="round">
        </path>
      </svg>
    </div>

    <div class="codalent-tip-content">
      <div class="codalent-tip-label">Note</div>

      <p>
        <strong>The #1 Traefik gotcha: the real client IP.</strong>
        Traefik rewrites <code>X-Forwarded-For</code> to the immediate peer unless the
        <strong>entrypoint</strong> trusts that hop. Miss the
        <code>forwardedHeaders.trustedIPs</code> on the entrypoint and the plugin only ever
        sees the Docker gateway, so bans on the real client never match. Set it
        <strong>on the entrypoint and as the plugin&#8217;s</strong>
        <code>forwardedHeadersTrustedIPs</code>: two separate layers. And
        <strong>do not put</strong> your Docker range in the plugin&#8217;s
        <code>clientTrustedIPs</code>: anything listed there bypasses the bouncer entirely,
        and every request sails through.
      </p>
    </div>

  </div>
</div>



<h2 class="wp-block-heading">Step 3: What virtual patching is doing for you</h2>



<p class="wp-block-paragraph">The WAF is already on: crowdsecAppsecEnabled: true plus the two collections is the whole switch. Here is what it turned on.</p>



<p class="wp-block-paragraph"><strong>Virtual patching</strong> blocks requests that match <em>specific, known-bad shapes</em>: the query string of a published CVE exploit, a POST to a management endpoint for software you don&#8217;t even run, an injection payload that only a scanner would ever send. These patterns have no legitimate reason to appear in your traffic, so the false-positive risk is close to zero. You are not guessing whether a request is malicious; you are recognizing an exploit you already know by name. That is what makes it the safe first move: turn it on in blocking mode on day one and sleep fine.</p>



<h2 class="wp-block-heading">Step 4: Prove it blocks real attacks</h2>



<p class="wp-block-paragraph">Nothing exotic here. These are the exact paths scanners try on every host: a peek at a leaked <code>/.env</code>, a grab for <code>/.git/config</code>, an Exchange RCE probe. Each is a plain <code>curl</code>, so you can run them yourself.</p>



<p class="wp-block-paragraph">One wrinkle: to make CrowdSec <em>record</em> the attacks, not just answer them with a 403, the source has to look like a public IP, because CrowdSec whitelists private and loopback ranges by default. Testing from the box itself, you fake that with an <code>X-Forwarded-For</code> header (Traefik trusts it thanks to the entrypoint config from Step 2); from a separate machine you would not need to.</p>



<pre><code class="language-bash">IP=192.168.1.42   # any non-whitelisted address
curl -s -o /dev/null -w '%{http_code}  .env\n'       -H "X-Forwarded-For: $IP" http://127.0.0.1/.env
curl -s -o /dev/null -w '%{http_code}  git\n'        -H "X-Forwarded-For: $IP" http://127.0.0.1/.git/config
curl -s -o /dev/null -w '%{http_code}  exchange\n'   -H "X-Forwarded-For: $IP" 'http://127.0.0.1/autodiscover/autodiscover.json?a=powershell'
curl -s -o /dev/null -w '%{http_code}  benign\n'     -H "X-Forwarded-For: $IP" http://127.0.0.1/
</code></pre>



<pre><code class="language-bash">403  .env
403  git
403  exchange
200  benign</code></pre>



<p class="wp-block-paragraph">Three refused, one allowed. Send each bad request a few more times (scanners never knock once), then read the WAF&#8217;s own counters:</p>



<pre><code class="language-bash">$ docker exec crowdsec cscli metrics show appsec
 Appsec Engine | Processed | Blocked
 0.0.0.0:7422/ | 11        | 9

 Rule ID                             | Triggered
 crowdsecurity/vpatch-CVE-2022-41082 | 1
 crowdsecurity/vpatch-env-access     | 4
 crowdsecurity/vpatch-git-config     | 4</code></pre>



<p class="wp-block-paragraph">Each block is attributed to the exact rule that caught it. Now look at what CrowdSec recorded:</p>



<pre><code class="language-bash">$ docker exec crowdsec cscli alerts list
 value           | reason                              | kind     | decisions
 Ip:192.168.1.42 | crowdsecurity/vpatch-git-config     | waf      |
 Ip:192.168.1.42 | crowdsecurity/vpatch-env-access     | waf      |
 Ip:192.168.1.42 | crowdsecurity/appsec-vpatch         | crowdsec | ban:1
 Ip:192.168.1.42 | crowdsecurity/vpatch-CVE-2022-41082 | waf      |</code></pre>



<p class="wp-block-paragraph">Two things happened at once, and this is where CrowdSec pulls ahead of a plain WAF:</p>



<ol class="wp-block-list">
<li><code><strong>kind: waf</strong>:</code> each malicious request was refused on the spot with a 403. That is virtual patching doing its job, per request.</li>



<li><strong><code>kind: crowdsec </code>with <code>ban:1</code></strong>: because the same IP kept probing, a CrowdSec scenario aggregated the behavior and <em>banned the address outright</em>. The next request from that IP, to <em>any</em> path, is refused before the WAF even looks at it.</li>
</ol>



<pre><code class="language-bash">$ docker exec crowdsec cscli decisions list
 Source   | Scope:Value     | Reason                      | Action | Expiration
 crowdsec | Ip:192.168.1.42 | crowdsecurity/appsec-vpatch | ban    | 3h59m</code></pre>



<p class="wp-block-paragraph">And the ban is real, enforced by the same plugin:</p>



<pre><code class="language-bash">$ curl -s -o /dev/null -w '%{http_code}\n' -H 'X-Forwarded-For: 192.168.1.42' http://127.0.0.1/   # 403</code></pre>



<p class="wp-block-paragraph">A classic WAF blocks the request. CrowdSec blocks the request <em>and</em> remembers the attacker.</p>



<p class="wp-block-paragraph">There is also an official smoke test that ships with CrowdSec: requesting <code>/crowdsec-test-NtktlJHV4TfBSK3wvlhiOBnl</code> trips the <code>crowdsecurity/appsec-generic-test</code> rule so you can confirm the pipeline end to end. The full health-check flow is documented <a href="https://doc.crowdsec.net/u/getting_started/health_check" target="_blank" rel="noreferrer noopener">here</a>.</p>



<h2 class="wp-block-heading">Step 5: See it in the Console</h2>



<p class="wp-block-paragraph">Blocking attacks from the command line is satisfying, but you will want a dashboard, one place to watch alerts across every proxy and server you protect. The <strong>CrowdSec Console</strong> is that view, and it is free for the community tier.</p>



<p class="wp-block-paragraph"><br>Enroll this engine (grab a key from <a href="https://app.crowdsec.net" target="_blank" rel="noreferrer noopener">app.crowdsec.net</a> → <strong>Security Engines → Enroll</strong>):<br></p>



<pre><code class="language-bash">docker exec crowdsec cscli console enroll <YOUR-ENROLL-KEY> --name crowdsec-vm-traefik-waf
docker exec crowdsec cscli console enable context   # richer alert detail for the WAF view
docker restart crowdsec</code></pre>



<p class="wp-block-paragraph">Then <strong>accept the instance in the Console webapp</strong>. Enrollment is two-step, and nothing appears until you click Accept.<br></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="702" src="https://cms.crowdsec.net/wp-content/uploads/2026/07/image-1-1024x702.png" alt="" class="wp-image-6727" srcset="https://cms.crowdsec.net/wp-content/uploads/2026/07/image-1-1024x702.png 1024w, https://cms.crowdsec.net/wp-content/uploads/2026/07/image-1-300x206.png 300w, https://cms.crowdsec.net/wp-content/uploads/2026/07/image-1-768x526.png 768w, https://cms.crowdsec.net/wp-content/uploads/2026/07/image-1.png 1156w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><strong>Key points:</strong></p>



<ul class="wp-block-list">
<li>You enroll the <strong>engine</strong>, not the bouncer; the Traefik plugin appears in the Console <em>through</em> the engine it reports to.</li>



<li><code>cscli capi status</code> confirms the community signal loop: sharing enabled, community blocklist pulling. That blocklist is the payoff of open source: every CrowdSec user&#8217;s confirmed attackers become IPs you block before they ever reach Traefik.</li>



<li>The Console is a lens on data that already lives on your box. Turn it off and the WAF keeps working exactly the same.</li>
</ul>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="506" src="https://cms.crowdsec.net/wp-content/uploads/2026/07/Captura-de-pantalla-2026-07-25-a-las-10.23.51-1-1024x506.png" alt="" class="wp-image-6746" srcset="https://cms.crowdsec.net/wp-content/uploads/2026/07/Captura-de-pantalla-2026-07-25-a-las-10.23.51-1-1024x506.png 1024w, https://cms.crowdsec.net/wp-content/uploads/2026/07/Captura-de-pantalla-2026-07-25-a-las-10.23.51-1-300x148.png 300w, https://cms.crowdsec.net/wp-content/uploads/2026/07/Captura-de-pantalla-2026-07-25-a-las-10.23.51-1-768x380.png 768w, https://cms.crowdsec.net/wp-content/uploads/2026/07/Captura-de-pantalla-2026-07-25-a-las-10.23.51-1.png 1163w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Where this leaves you</h2>



<p class="wp-block-paragraph">In a few minutes you went from an unprotected Traefik stack to an open-source WAF that:</p>



<ul class="wp-block-list">
<li>refuses known exploit shapes on sight, with virtually no false-positive risk;</li>



<li>bans persistent attackers automatically, across every route Traefik serves;</li>



<li>pulls a community-sourced blocklist so you benefit from attacks aimed at everyone else;</li>



<li>reports into a single Console dashboard.</li>
</ul>



<p class="wp-block-paragraph">Virtual patching is the floor, not the ceiling. It only catches attacks it already knows by name. To catch the ones it doesn&#8217;t, the novel injection or the odd traversal, you bring in the <strong>OWASP Core Rule Set</strong>, and you decide whether to run it inband (block now) or out-of-band (watch first, block once you trust it). That is exactly the false-positive tradeoff virtual patching let you skip, and it is a natural next step from here.</p>



<p class="wp-block-paragraph">You built all of this with software you can read, audit, and run for free. No black box, no vendor lock, no per-request meter. Just Traefik, refusing the requests that were never meant well.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><em>Full documentation lives at </em><a href="https://docs.crowdsec.net/docs/appsec/intro" target="_blank" rel="noreferrer noopener"><em>docs.crowdsec.net/docs/appsec</em></a><em>, the Traefik plugin is open at </em><a href="https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" target="_blank" rel="noreferrer noopener"><em>github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin</em></a><em>, and the rules are on the </em><a href="https://app.crowdsec.net/hub" target="_blank" rel="noreferrer noopener"><em>CrowdSec Hub</em></a><em>.</em></p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
            <category>Integrations</category>
            <category>Proactive Cybersecurity</category>
            <enclosure url="https://cms.crowdsec.net/wp-content/uploads/2026/07/257.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Open-Source WAF for Nginx on Ubuntu with CrowdSec]]></title>
            <link>https://crowdsec.net/blog/open-source-waf-for-nginx-on-ubuntu-with-crowdsec</link>
            <guid isPermaLink="false">https://crowdsec.net/blog/open-source-waf-for-nginx-on-ubuntu-with-crowdsec</guid>
            <pubDate>Thu, 09 Jul 2026 09:05:50 GMT</pubDate>
            <description><![CDATA[<p>Deploy an open-source WAF for Nginx on Ubuntu with CrowdSec. Install the AppSec component, enable virtual patching, and block attacks step by step</p>
]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">Point a brand-new web server at the internet and start a timer. Within minutes, often seconds, the first probe lands: a request for <code>/.env</code>, hoping you left a database password in it. Next comes <code>/.git/config</code>, then the URL of a CVE exploit from 2017 that still works on every server nobody got around to patching. None of it is aimed at you personally, but it finds you anyway, because scanners sweep the whole internet looking for the same short list of mistakes.</p>



<p class="wp-block-paragraph">You can refuse that traffic on day one, without deploying a heavy ruleset that trips over your own customers. The approach is called <strong>virtual patching</strong>: block the exact request shapes of known exploits and leave everything else alone. CrowdSec gives you an open-source WAF that does exactly this, on Nginx and on whatever else you run in front of your apps.</p>



<p class="wp-block-paragraph">This is the first post in a series on running that WAF on Nginx. Here we take a single Ubuntu box from a bare apt install to a working WAF in virtual-patching mode. It&#8217;s the safest way to start, because it blocks known-bad requests without second-guessing legitimate traffic. Later posts add the OWASP Core Rule Set and the things a classic WAF can&#8217;t do.</p>



<h2 class="wp-block-heading">Why a new WAF, and why now</h2>



<p class="wp-block-paragraph">Three reasons to do this now rather than someday.</p>



<p class="wp-block-paragraph"><strong>ModSecurity&#8217;s Nginx era is ending.</strong> If you have run a WAF on Nginx before, it was probably ModSecurity. F5 ended support for the Nginx ModSecurity WAF module on <strong>March 31, 2024</strong>. The engine still runs, but the maintained, first-party path is gone, and rebuilding a dynamic module against every Nginx release was never anyone&#8217;s idea of fun. ModSecurity earned its place; it is not the villain of this story. But if you are choosing a WAF for Nginx in 2026, you want something actively maintained and open source.</p>



<p class="wp-block-paragraph"><strong>Virtual patching lets you start without fear of false positives.</strong> The usual reason people put off a WAF is exactly that fear: a broad ruleset that blocks real customers the day it goes live. Virtual patching sidesteps it. It matches the <em>specific</em> shapes of known exploits and known-bad paths, so legitimate traffic is never in question. You get real protection immediately, with almost nothing to tune. When you want broader, anomaly-based coverage, that&#8217;s the OWASP Core Rule Set, which is the subject of the next post. It comes deliberately later precisely because it <em>does</em> need tuning.</p>



<p class="wp-block-paragraph"><strong>It is not tied to Nginx.</strong> The WAF logic lives in the CrowdSec agent, not in your web server. A thin <em>bouncer</em> plugs into whatever sits in front of your apps: Nginx and OpenResty here, but equally HAProxy, Traefik, Caddy, or Envoy. Change proxies, or run several at once, and the same rules and the same dashboard follow you. You are not buying into one web server&#8217;s plugin ecosystem.</p>



<p class="wp-block-paragraph">CrowdSec is free and open source: no license key, no trial clock, and nothing metered per request. And it does something ModSecurity never did: every blocked attack becomes a signal the whole community benefits from.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">What &#8220;CrowdSec&#8217;s WAF&#8221; actually is</h2>



<p class="wp-block-paragraph">Two pieces do the work, and it helps to know which is which before you start typing commands.</p>



<ul class="wp-block-list">
<li><strong>The AppSec component</strong> is an HTTP server bundled inside the CrowdSec agent. It holds the rules and returns a verdict (<em>allow</em> or <em>block</em>) for any request it is shown. It does not sit inline with your traffic.</li>



<li><strong>The Nginx bouncer</strong> is a small Lua module inside Nginx. It intercepts each request, shows a copy to AppSec, and enforces whatever verdict comes back.</li>
</ul>



<pre><code class="language-bash">client ──► nginx ──► nginx bouncer ──► AppSec (127.0.0.1:7422)
                          │                  │
                          │      verdict ◄───┘
                          ▼
                    allow (200) / block (403)
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"><strong>Key points:</strong></p>



<ul class="wp-block-list">
<li>AppSec makes the decision and the bouncer carries it out. AppSec never blocks anything on its own; the bouncer does.</li>



<li>The verdict round-trip is local (loopback), so it costs microseconds, not a network hop.</li>



<li>The same bouncer that enforces WAF verdicts also enforces IP bans from CrowdSec&#8217;s community blocklist, so it&#8217;s one module doing two jobs.</li>



<li>We use the Nginx bouncer here. The HAProxy, Traefik, Caddy, and Envoy bouncers speak the same protocol to the same AppSec component, so from Step 3 on, the WAF rules and verdicts are identical whatever proxy you run.</li>
</ul>



<p class="wp-block-paragraph">Full reference: the <a href="https://docs.crowdsec.net/docs/appsec/intro" target="_blank" rel="noreferrer noopener">AppSec component docs</a>.</p>



<h2 class="wp-block-heading">Prerequisites</h2>



<ul class="wp-block-list">
<li>An Ubuntu server (we used 24.04/26.04) with <strong>Nginx</strong> already serving a site.</li>



<li>Root or sudo.</li>



<li>Outbound HTTPS to packagecloud.io (packages) and api.crowdsec.net (community signals).</li>
</ul>



<p class="wp-block-paragraph">That&#8217;s the whole list. You don&#8217;t need to stand up a database or rearchitect your reverse proxy.<br></p>



<h2 class="wp-block-heading">Step 1 — Install the CrowdSec engine</h2>



<p class="wp-block-paragraph">Add the official repository and install the agent:</p>



<pre><code class="language-bash">curl -s https://install.crowdsec.net | sudo sh   # adds the signed CrowdSec repo

sudo apt install crowdsec
</shadowed></code></pre>



<pre><code class="language-bash">$ sudo cscli version
version: v1.7.8-debian-pragmatic-amd64
Codename: alphaga
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">The installer runs a setup wizard that detects what is already on the box, Nginx included, and wires up log acquisition for you. You can see it did:</p>



<pre><code class="language-bash"># /etc/crowdsec/acquis.d/setup.nginx.yaml
filenames:
  - /var/log/nginx/*.log
labels:
  type: nginx
source: file
</code></pre>



<p class="wp-block-paragraph"><strong>Key points:</strong></p>



<ul class="wp-block-list">
<li>Install from the <strong>official packagecloud repo</strong>, not Ubuntu&#8217;s own crowdsec package, since the distro copy can be years behind.</li>



<li>The wizard already installed the crowdsecurity/nginx collection, so CrowdSec is parsing your access logs from the first second.</li>



<li>At this stage CrowdSec <em>detects</em> bad behavior. It does not yet <em>block</em> anything; that&#8217;s the bouncer&#8217;s job, next.</li>
</ul>



<h2 class="wp-block-heading">Step 2 — Install and wire the Nginx bouncer</h2>



<pre><code class="language-bash">sudo apt install crowdsec-nginx-bouncer
</code></pre>



<p class="wp-block-paragraph">The package registers itself with the local API and drops its Nginx config in place, with no manual key copying:</p>



<pre><code class="language-bash">$ sudo cscli bouncers list

 Name                               Valid  Auth Type

 crowdsec-nginx-bouncer-1783001915  ✔️     api-key
</code></pre>



<p class="wp-block-paragraph">Prove the enforcement path works before adding any WAF rules. Ban your own loopback for two minutes and watch Nginx start refusing you:</p>



<pre><code class="language-bash">$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/          # 200

$ sudo cscli decisions add --ip 127.0.0.1 --duration 2m --reason test

$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/          # 403

$ sudo cscli decisions delete --ip 127.0.0.1

$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/          # 200
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"><code>200 → 403 → 200</code>. The bouncer is live and enforcing decisions. Now we give it a WAF to consult.</p>



<h2 class="wp-block-heading">Step 3 — Deploy the WAF in virtual-patching mode</h2>



<p class="wp-block-paragraph">This is the core of the setup, and the reason to start here rather than with a big generic ruleset.</p>



<p class="wp-block-paragraph"><strong>Virtual patching</strong> blocks requests that match <em>specific, known-bad shapes</em>: the URL of a published CVE exploit, a request for <code>/.env</code>, an attempt to read <code>/.git/config</code>. These patterns have no legitimate reason to appear in your traffic, so the false-positive risk is close to zero. You are not guessing whether a request is malicious; you are recognizing an exploit you already know by name. That makes it the safe first move: turn it on in blocking mode on day one and sleep fine.</p>



<p class="wp-block-paragraph">Install the WAF collections:</p>



<pre><code class="language-bash">sudo cscli collections install \

    crowdsecurity/appsec-virtual-patching \

    crowdsecurity/appsec-generic-rules
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Install both together — the default AppSec config draws on rules from each, and the two collections give you the full virtual-patching set (191 rules on our box).</p>



<p class="wp-block-paragraph">Tell the AppSec component to listen, by creating <code>/etc/crowdsec/acquis.d/appsec.yaml</code>:</p>



<pre><code class="language-bash">appsec_config: crowdsecurity/appsec-default

labels:

  type: appsec

listen_addr: 127.0.0.1:7422

source: appsec
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Reload and confirm the WAF is listening:</p>



<pre><code class="language-bash">$ sudo systemctl reload crowdsec

$ sudo ss -lntp | grep 7422

LISTEN 0 4096 127.0.0.1:7422 0.0.0.0:*  users:(("crowdsec",...))
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Finally, point the Nginx bouncer at it. The bouncer ships with the WAF switched off (<code>APPSEC_URL</code> empty); one line turns it on:</p>



<pre><code class="language-bash">sudo sed -i 's|^APPSEC_URL=.*|APPSEC_URL=http://127.0.0.1:7422|' \

    /etc/crowdsec/bouncers/crowdsec-nginx-bouncer.conf

sudo systemctl reload nginx
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"><strong>Key points:</strong></p>



<ul class="wp-block-list">
<li><code>crowdsecurity/appsec-default</code> is the config that carries the virtual-patching rules <em>and</em> the health-check test rule, all in one config, ready to go.</li>



<li>The listener is loopback-only (<code>127.0.0.1</code>) because AppSec and Nginx share this host. On a distributed setup you would bind a private interface instead, which is a topic for a later post.</li>



<li>Reusing the bouncer&#8217;s existing API key for AppSec is automatic. There is no second key to create.</li>
</ul>



<h2 class="wp-block-heading">Step 4 — Prove it blocks real attacks</h2>



<p class="wp-block-paragraph">Fire a few malicious requests at the server and one honest one:</p>



<pre><code class="language-bash">.env access             -> 403

phpunit CVE-2017-9841   -> 403

git config leak         -> 403

benign homepage         -> 200
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Then read the WAF&#8217;s own counters:</p>



<pre><code class="language-bash">$ sudo cscli metrics show appsec

 Appsec Engine   | Processed | Blocked

 127.0.0.1:7422/ | 6         | 3

 Rule ID                             | Triggered

 crowdsecurity/appsec-generic-test   | 2

 crowdsecurity/vpatch-CVE-2017-9841  | 1

 crowdsecurity/vpatch-env-access     | 2
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Each block is attributed to the exact rule that caught it. Now look at what CrowdSec recorded:</p>



<pre><code class="language-bash">$ sudo cscli alerts list

 value           | reason                             | kind     | decisions

 Ip:203.0.113.42 | crowdsecurity/vpatch-git-config    | waf      |

 Ip:203.0.113.42 | crowdsecurity/CVE-2017-9841        | crowdsec | ban:1

 Ip:203.0.113.42 | crowdsecurity/appsec-vpatch        | crowdsec | ban:1

 Ip:203.0.113.42 | crowdsecurity/vpatch-CVE-2017-9841 | waf      |

 Ip:203.0.113.42 | crowdsecurity/vpatch-env-access    | waf      |
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">Two things happened at once, and this is where CrowdSec pulls ahead of a plain WAF:</p>



<ol class="wp-block-list">
<li><code><strong>kind: waf</strong>:</code> each malicious request was refused on the spot with a 403. That&#8217;s virtual patching doing its job, per request.</li>



<li><strong><code>kind: crowdsec</code> with <code>ban:1</code></strong>: because the same IP kept probing, CrowdSec&#8217;s scenarios aggregated the behavior and <em>banned the address outright</em>. The next request from that IP, to <em>any</em> path, is refused before the WAF even looks at it.</li>
</ol>



<pre><code class="language-bash">$ sudo cscli decisions list

 crowdsec | Ip:203.0.113.42 | crowdsecurity/CVE-2017-9841 | ban | FR | 3h59m
</code></pre>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">A classic WAF blocks the request. CrowdSec blocks the request <em>and</em> remembers the attacker.</p>



<style>
  .codalent-tip-box {
    background: #f7f6ff;
    border: 1px solid rgba(78, 74, 153, 0.35);
    border-left: 4px solid #4e4a99;
    border-radius: 8px;
    padding: 18px 22px;
    color: #2d2d3a;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
    max-width: 1120px;
    box-sizing: border-box;
  }

  .codalent-tip-inner {
    display: flex;
    align-items: flex-start;
    gap: 14px;
  }

  .codalent-tip-icon {
    width: 24px;
    height: 24px;
    min-width: 24px;
    border-radius: 50%;
    background: rgba(78, 74, 153, 0.12);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-top: 1px;
  }

  .codalent-tip-icon svg {
    width: 15px;
    height: 15px;
    stroke: #4e4a99;
  }

  .codalent-tip-label {
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: #4e4a99;
    margin-bottom: 8px;
  }

  .codalent-tip-box p {
    margin: 0;
    font-size: 15px;
    line-height: 1.6;
  }

  @media (max-width: 600px) {
    .codalent-tip-box {
      padding: 16px;
    }

    .codalent-tip-inner {
      gap: 12px;
    }

    .codalent-tip-box p {
      font-size: 14px;
    }
  }
</style>

<div class="codalent-tip-box">
  <div class="codalent-tip-inner">
    <div class="codalent-tip-icon" aria-hidden="true">
      <svg viewBox="0 0 24 24" fill="none">
        <path d="M12 9v4" stroke-width="1.8" stroke-linecap="round"></path>
        <path d="M12 17h.01" stroke-width="2.2" stroke-linecap="round"></path>
        <path d="M10.3 4.6 3.4 16.5A2 2 0 0 0 5.1 19.5h13.8a2 2 0 0 0 1.7-3L13.7 4.6a2 2 0 0 0-3.4 0Z" stroke-width="1.8" stroke-linejoin="round"></path>
      </svg>
    </div>

    <div class="codalent-tip-content">
      <div class="codalent-tip-label">Note</div>

      <p><b>One gotcha</b>: whitelisted source IPs CrowdSec whitelists private and loopback ranges by default, so probes from 127.0.0.1 are blocked (you still get the 403) but produce no alert. Test from a real, external IP, as we did above from 203.0.113.42, if you want to see alerts and bans appear.</p>
    </div>
  </div>
</div>



<p class="wp-block-paragraph">There is also an official smoke test that ships with CrowdSec. Requesting the path <code>/crowdsec-test-NtktlJHV4TfBSK3wvlhiOBnl</code> triggers the <code>crowdsecurity/appsec-generic-test rule</code>; the full health-check flow is documented <a href="https://doc.crowdsec.net/u/getting_started/health_check" target="_blank" rel="noreferrer noopener">here</a>.</p>



<h2 class="wp-block-heading">Step 5 — See it in the Console</h2>



<p class="wp-block-paragraph">Blocking attacks from the command line is satisfying, but you will want a dashboard, one place to watch alerts across every server you protect. The <strong>CrowdSec Console</strong> is that view, and it is free for the community tier.</p>



<p class="wp-block-paragraph">Enroll this engine (grab an enrollment key from <a href="https://app.crowdsec.net" target="_blank" rel="noreferrer noopener">app.crowdsec.net</a> → <strong>Security Engines → Enroll</strong>):</p>



<pre><code class="language-bash">sudo cscli console enroll <YOUR-ENROLL-KEY> --name crowdsec-vm-nginx-waf

sudo systemctl reload crowdsec
</code></pre>



<p class="wp-block-paragraph">Then <strong>accept the instance in the Console webapp</strong> — enrollment is two-step, and nothing shows up until you click Accept. Turn on richer alert detail while you are at it:</p>



<pre><code class="language-bash">sudo cscli console enable context

sudo systemctl reload crowdsec</code></pre>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="681" src="https://cms.crowdsec.net/wp-content/uploads/2026/07/console-engine-1-1024x681.png" alt="" class="wp-image-6711" srcset="https://cms.crowdsec.net/wp-content/uploads/2026/07/console-engine-1-1024x681.png 1024w, https://cms.crowdsec.net/wp-content/uploads/2026/07/console-engine-1-300x200.png 300w, https://cms.crowdsec.net/wp-content/uploads/2026/07/console-engine-1-768x511.png 768w, https://cms.crowdsec.net/wp-content/uploads/2026/07/console-engine-1.png 1165w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="105" src="https://cms.crowdsec.net/wp-content/uploads/2026/07/waf-alerts-1-1024x105.png" alt="" class="wp-image-6712" srcset="https://cms.crowdsec.net/wp-content/uploads/2026/07/waf-alerts-1-1024x105.png 1024w, https://cms.crowdsec.net/wp-content/uploads/2026/07/waf-alerts-1-300x31.png 300w, https://cms.crowdsec.net/wp-content/uploads/2026/07/waf-alerts-1-768x79.png 768w, https://cms.crowdsec.net/wp-content/uploads/2026/07/waf-alerts-1-1536x158.png 1536w, https://cms.crowdsec.net/wp-content/uploads/2026/07/waf-alerts-1.png 1540w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph"><strong>Key points:</strong></p>



<ul class="wp-block-list">
<li>You enroll the <strong>engine</strong>, not each bouncer; bouncers appear in the Console <em>through</em> the engine they report to.</li>



<li><code>cscli capi status</code> confirms the community signals loop: sharing enabled, community blocklist pulling. That blocklist is the payoff of open source: every CrowdSec user&#8217;s confirmed attackers become IPs you block before they ever reach you.</li>



<li>The Console is a lens on data that already lives on your box. Turn it off and your WAF keeps working exactly the same.</li>
</ul>



<h2 class="wp-block-heading">Where this leaves you</h2>



<p class="wp-block-paragraph">In a few minutes you went from a bare Nginx server to an open-source WAF that:</p>



<ul class="wp-block-list">
<li>refuses known exploit shapes on sight, with virtually no false-positive risk;</li>



<li>bans persistent attackers automatically, across your whole site;</li>



<li>pulls a community-sourced blocklist so you benefit from attacks aimed at everyone else;</li>



<li>reports into a single Console dashboard.</li>
</ul>



<p class="wp-block-paragraph">Virtual patching is a floor, not a ceiling. It only catches attacks it already knows by name. To catch the ones it doesn&#8217;t, the novel injection or the odd traversal, you bring in the <strong>OWASP Core Rule Set</strong>, and you decide whether to run it inband (block now) or out-of-band (watch first, block once you trust it). That&#8217;s exactly the false-positive tradeoff virtual patching let us skip, and it&#8217;s where the next post picks up.</p>



<p class="wp-block-paragraph">After that: the <strong>hooks</strong> that let CrowdSec&#8217;s WAF do things a signature engine can&#8217;t, and how to run the WAF and the engine as a <strong>distributed</strong> pair across many servers.</p>



<p class="wp-block-paragraph">You built all of this with software you can read, audit, and run for free. No black box, no vendor lock-in, nothing metered per request: just your server, refusing the requests that were never meant well.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><em>Next in this series: </em><strong><em>Going further — OWASP CRS inband vs out-of-band with CrowdSec.</em></strong><em> Full documentation lives at </em><a href="https://docs.crowdsec.net/docs/appsec/intro" target="_blank" rel="noreferrer noopener"><em>docs.crowdsec.net/docs/appsec</em></a><em>, and the rules are open on the </em><a href="https://app.crowdsec.net/hub" target="_blank" rel="noreferrer noopener"><em>CrowdSec Hub</em></a><em>.</em></p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
            <category>Integrations</category>
            <category>Proactive Cybersecurity</category>
            <enclosure url="https://cms.crowdsec.net/wp-content/uploads/2026/07/255-1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[CISA gives you 3 days to patch and triage. Live Exploit Tracker gives you a head start]]></title>
            <link>https://crowdsec.net/blog/cisa-bod-26-04-live-exploit-tracker</link>
            <guid isPermaLink="false">https://crowdsec.net/blog/cisa-bod-26-04-live-exploit-tracker</guid>
            <pubDate>Tue, 16 Jun 2026 13:23:19 GMT</pubDate>
            <description><![CDATA[<p>CISA BOD 26-04 mandates risk-based patching driven by real exploitation evidence. Live Exploit Tracker delivers that evidence live — per CVE, per vendor.</p>
]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph"><em>On June 10, 2026, CISA fundamentally rewrote how federal agencies must prioritize patching. The new directive assumes you can answer one question continuously, for every CVE in your environment: &#8220;Is this being exploited, right now, and by whom?&#8221; That is precisely the question Live Exploit Tracker was built to answer.</em><em><br></em><em><br></em><em>CISA&#8217;s BOD 26-04 Just Made Exploitation Evidence Mandatory. We&#8217;ve Been Collecting It Live, at Scale, for Years</em>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading"><strong>What BOD 26-04 actually changes</strong></h2>



<p class="wp-block-paragraph"><a href="https://www.cisa.gov/news-events/directives/bod-26-04-prioritizing-security-updates-based-risk" target="_blank" rel="noreferrer noopener">Binding Operational Directive 26-04, &#8220;Prioritizing Security Updates Based on Risk&#8221;</a>, supersedes both BOD 22-01 (KEV remediation) and BOD 19-02 (internet-accessible systems). The flat &#8220;<em>it&#8217;s in the KEV, patch it in 14 days</em>&#8221; era is over. In its place: a graduated model built on four binary risk variables, evaluated per vulnerability, per asset:</p>



<ol class="wp-block-list">
<li><strong>Publicly exposed</strong> — is the vulnerable asset reachable from the internet?</li>



<li><strong>Known exploitation</strong> — is the CVE in CISA&#8217;s KEV catalog?</li>



<li><strong>Automatable</strong> — can an adversary automate the full exploitation chain?</li>



<li><strong>Technical impact</strong> — does exploitation yield total or partial control?</li>
</ol>



<p class="wp-block-paragraph">The combination of answers maps to remediation timelines ranging from <strong>three calendar days with mandatory forensic triage</strong> at the top, down to &#8220;fix on next system upgrade&#8221; at the bottom. Notably, CISA no longer requires CVSS for prioritization. Severity scores are out; <em>evidence of real-world exploitation</em> is in.</p>



<p class="wp-block-paragraph">CISA is explicit about why now: AI is compressing the window between disclosure and weaponization, while remediation performance is moving the wrong way — per the 2026 Verizon DBIR figures CISA cites, only 26% of KEV vulnerabilities were fully remediated in 2025, down from 38% the year before.</p>



<p class="wp-block-paragraph">The directive&#8217;s logic is sound. Its operational demand is brutal: agencies now need <strong>continuous, current, per-CVE exploitation intelligence</strong> — not a quarterly threat report, not a static severity score. And for the highest tier, they need to determine within 72 hours whether they&#8217;ve <em>already</em> been compromised.</p>



<p class="wp-block-paragraph">This is exactly the gap<a href="https://www.crowdsec.net/live-exploit-tracker" target="_blank" rel="noreferrer noopener"> Live Exploit Tracker</a> closes.</p>



<h2 class="wp-block-heading"><strong>Live Exploit Tracker: ground truth on exploitation, from hundreds of thousands of real sensors</strong></h2>



<p class="wp-block-paragraph"><a href="https://tracker.crowdsec.net/?utm_source=website&amp;utm_medium=article&amp;utm_campaign=CISA" target="_blank" rel="noreferrer noopener">Live Exploit Tracker</a> (L.E.T.) is powered by the CrowdSec Network — the largest crowdsourced threat intelligence network in the world, with servers deployed across real production infrastructure in 180+ countries. When an attacker probes or exploits a CVE anywhere in the network, we see it. Not a honeypot guess. Not a vendor advisory. Actual exploitation attempts against actual workloads.</p>



<p class="wp-block-paragraph">For every tracked CVE, L.E.T. tells you:</p>



<ul class="wp-block-list">
<li><strong>The exploitation phase</strong> — recon, targeted exploitation, mass exploitation, or decline. Watching a CVE move from &#8220;insufficient data&#8221; to &#8220;targeted exploitation&#8221; is watching your remediation deadline accelerate in real time.</li>



<li><strong>Momentum</strong> — is this threat growing, stable, or fading? A KEV entry from 2023 that nobody exploits anymore and a CVE that gained 80 attacking IPs this week are not the same priority. BOD 26-04 finally lets agencies act on that distinction.</li>



<li><strong>Who is attacking</strong> — every observed attacker IP, with full CTI profiling: origin, hosting provider, known/unknown actor, behavioral history across the network.</li>



<li><strong>What they&#8217;re after</strong> — takeover, ransom, or exfiltration, derived from observed attacker behavior.</li>



<li><strong>Indicators of compromise</strong> — the actual HTTP patterns and payloads observed exploiting the CVE in the wild.</li>
</ul>



<h2 class="wp-block-heading"><strong>Mapping L.E.T. to the directive, variable by variable</strong></h2>



<figure class="wp-block-table"><table class="has-fixed-layout"><tbody><tr><td><strong>BOD 26-04 requirement</strong></td><td><strong>What it demands operationally</strong></td><td><strong>What Live Exploit Tracker provides</strong></td></tr><tr><td><strong>Known exploitation</strong> (variable 2)</td><td>Track KEV continuously; react the moment a CVE is added</td><td>Live exploitation telemetry that frequently precedes KEV listing — we observe in-the-wild exploitation directly, giving you a head start before the clock officially starts</td></tr><tr><td><strong>Automatable</strong> <br>(variable 3)</td><td>Assess whether exploitation can be fully automated</td><td>Direct field evidence: our targeted-vs-mass-exploitation classification distinguishes hand-driven campaigns from spray-and-pray automation, based on observed attacker behavior, not theoretical analysis</td></tr><tr><td><strong>3-day forensic triage</strong></td><td>Determine within 72h whether you were already compromised</td><td>Downloadable attacker IP lists and HTTP-level IoCs per CVE — grep your logs against the exact IPs and request patterns observed exploiting that vulnerability, going back months</td></tr><tr><td><strong>Dynamic timelines</strong></td><td>Re-prioritize the moment exploitation status changes</td><td>Phase-transition events and momentum scoring, updated continuously from live network signals, available via API for automated ingestion</td></tr><tr><td><strong>&#8220;KEV is not the only list&#8221;</strong> (CISA&#8217;s own implementation guidance)</td><td>Track exploitation beyond the KEV catalog</td><td>Coverage of actively exploited CVEs regardless of KEV status, including pre-KEV recon campaigns against specific vendors and products</td></tr></tbody></table></figure>



<p class="wp-block-paragraph">Variable 1 — knowing which of <em>your</em> assets are publicly exposed — is your asset inventory&#8217;s job (EASM). L.E.T. doesn&#8217;t scan your network; it tells you what the adversary is doing, so your exposure data plus our exploitation data equals a complete BOD 26-04 picture.</p>



<h2 class="wp-block-heading"><strong>Case in point: when CVSS says 5.3 and reality says 9/10</strong></h2>



<p class="wp-block-paragraph">CISA dropped CVSS as a mandatory prioritization input for good reason. Here&#8217;s a live example of why.</p>



<p class="wp-block-paragraph"><a href="https://tracker.crowdsec.net/cves/CVE-2025-13956?utm_source=website&amp;utm_medium=article&amp;utm_campaign=CISA" target="_blank" rel="noreferrer noopener"><strong>CVE-2025-13956</strong></a> is a missing-authorization flaw in LearnPress, a WordPress LMS plugin. CVSS score: <strong>5.3</strong>. Medium. Under severity-based triage, it sits in the backlog forever.</p>



<p class="wp-block-paragraph">Here&#8217;s what the CrowdSec Network actually observed: the CVE was published in March 2025 and stayed quiet for over a year. Then, in April 2026, we detected the first in-the-wild exploitation. By June 12, 2026, exploitation patterns shifted enough that our system reclassified it to <strong>targeted exploitation</strong> — 168 unique attacker IPs, momentum 4/5, targeting score 5/5, with 79% of attacking IPs profiled as pursuing system takeover. CrowdSec Score: <strong>9/10 — critical active campaign</strong>.</p>



<p class="wp-block-paragraph">A CVSS-driven program ignores this CVE. An exploitation-evidence-driven program — the kind BOD 26-04 now mandates — catches the phase transition the day it happens and re-prioritizes immediately. That&#8217;s the difference between a severity score assigned once at publication and a risk signal recomputed continuously from live attack data.</p>



<h2 class="wp-block-heading"><strong>The vendor view: see the campaign, not just the CVE</strong></h2>



<p class="wp-block-paragraph">Agencies don&#8217;t run CVEs; they run products. Federal networks are full of the exact appliances attackers systematically target—and if the last few years of emergency directives have taught us anything, it&#8217;s that edge devices from a handful of vendors pose disproportionate risk.</p>



<p class="wp-block-paragraph">L.E.T.&#8217;s vendor pages aggregate all observations across a vendor&#8217;s entire product line. Take our Ivanti page: 20 tracked CVEs, 3 active recon campaigns, and 12,800+ attacker IPs currently engaged — including 13,500 IPs probing Connect Secure panels <em>before any specific CVE is even in play</em>. That recon activity is your earliest warning signal: adversaries are mapping targets for the next vulnerability before it becomes publicly known.</p>



<p class="wp-block-paragraph">For a federal security team, this turns the directive&#8217;s per-CVE obligation into something manageable: one view of your actual vendor exposure, ranked by live adversary attention.</p>



<h2 class="wp-block-heading"><strong>Beyond prioritization: shrink the exposure window while you patch</strong></h2>



<p class="wp-block-paragraph">BOD 26-04 gives you three days for the worst cases. Patching an enterprise fleet in three days is hard. Patching it while the vulnerability is under active exploitation is harder.</p>



<p class="wp-block-paragraph">This is where L.E.T. goes further than any prioritization feed: every CVE and vendor page exposes <strong>dynamic, subscribable IP datasets</strong>. Point your firewall, WAF, CDN, or cloud security layer at the blocklist for a specific CVE — or an entire vendor — and the IPs actively hunting that vulnerability are preemptively blocked from ever reaching your workloads. The dataset updates as the campaign evolves; no manual list management.</p>



<p class="wp-block-paragraph">It&#8217;s not a substitute for patching — the directive is unambiguous about that, and so are we. It&#8217;s a compensating control that buys you the 72 hours you need and materially derisks the gap between &#8220;deadline starts&#8221; and &#8220;patch deployed.&#8221; Plug the holes in the hull while you&#8217;re still in the dry dock queue.</p>



<p class="wp-block-paragraph">And when the forensic triage requirement kicks in, the same data answers the other question the directive forces: pull the full attacker IP intelligence and IoC patterns for the CVE via API, run them against your logs, and you have an evidence-based answer to &#8220;were we already hit?&#8221; — within the window, not after a six-week IR engagement.</p>



<h2 class="wp-block-heading"><strong>Built for automation, because three days leave no room for manual work</strong></h2>



<p class="wp-block-paragraph">Everything in L.E.T. is API-first: CVE details, exploitation phase, scores, attacker IPs, IoCs. Feed it into your vulnerability management platform, your SOAR, your reporting pipeline. The directive expects agencies to automate KEV tracking and remediation reporting; our data is structured to drop straight into that machinery. A <code>curl</code> away:</p>



<p class="wp-block-paragraph"><code>curl -s -H "x-api-key: YOUR_API_KEY" \</code></p>



<p class="wp-block-paragraph"><code>  "https://admin.api.crowdsec.net/v1/cves/CVE-2025-13956"</code></p>



<h2 class="wp-block-heading"><strong>The bottom line</strong></h2>



<p class="wp-block-paragraph">BOD 26-04 codifies what defenders have known for years: patch what&#8217;s being exploited, defer what isn&#8217;t, and find out fast if you&#8217;ve already been breached. The directive supplies the framework. It assumes you have the evidence.</p>



<p class="wp-block-paragraph">We have the evidence — live, global, behavioral, per-CVE and per-vendor — and the protection layer to act on it before your patch lands.</p>



<p class="wp-block-paragraph"><strong>Explore Live Exploit Tracker now at </strong><a href="https://tracker.crowdsec.net/?utm_source=website&amp;utm_medium=article&amp;utm_campaign=CISA" target="_blank" rel="noreferrer noopener"><strong>tracker.crowdsec.net</strong></a> — browse CVEs, vendors, and live exploitation data freely. Federal agency or supporting a federal mission? <a href="https://www.crowdsec.net/contact-crowdsec" target="_blank" rel="noreferrer noopener">Talk to our team</a> about API access, blocklist integration, and operationalizing BOD 26-04.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
            <category>Proactive Cybersecurity</category>
            <enclosure url="https://cms.crowdsec.net/wp-content/uploads/2026/06/Blog-images-1575-×-871px-24.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[CrowdSec 1.7.8 Security Release: Fixes High-Severity WAF Bypass and LAPI DoS Vulnerabilities]]></title>
            <link>https://crowdsec.net/blog/crowdsec-1-7-8-security-release-fixes-high-severity-waf-bypass-and-lapi-dos-vulnerabilities</link>
            <guid isPermaLink="false">https://crowdsec.net/blog/crowdsec-1-7-8-security-release-fixes-high-severity-waf-bypass-and-lapi-dos-vulnerabilities</guid>
            <pubDate>Wed, 27 May 2026 13:04:16 GMT</pubDate>
            <description><![CDATA[<p>CrowdSec 1.7.8 fixes two security vulnerabilities: CVE-2026-44982, a high-severity WAF bypass, and CVE-2026-44981, a Local API denial-of-service issue. Upgrade now.</p>
]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">The 1.7.8 release of CrowdSec fixes two vulnerabilities: one of medium impact and one of high impact. We recommend that all users upgrade to the patched version as soon as possible.</p>



<p class="wp-block-paragraph">If you are using the nginx (or OpenResty) remediation component, you will also need to upgrade it to its latest version to fully address CVE-2026-44982.</p>



<h2 class="wp-block-heading">CVE-2026-44982: Partial CrowdSec WAF Bypass (high severity)</h2>



<p class="wp-block-paragraph">The AppSec datasource failed to read the request body for any request whose Content-Length was not positive.</p>



<p class="wp-block-paragraph">In practice, this means HTTP/1.1 requests using <code>Transfer-Encoding: chunked</code> and HTTP/2 requests sent without a <code>content-length</code> header were evaluated against an empty body, silently bypassing any WAF rule targeting body content.</p>



<p class="wp-block-paragraph">Headers-only and URI-only rules are not affected.</p>



<h2 class="wp-block-heading">CVE-2026-44981: Local API Denial of Service</h2>



<p class="wp-block-paragraph">LAPI did not enforce a maximum decompressed body size on incoming gzip-compressed requests.</p>



<p class="wp-block-paragraph">A small compressed payload could decompress into hundreds of megabytes of valid JSON, and the unauthenticated <code>/v1/watchers</code> and <code>/v1/watchers/login</code> endpoints made this reachable without credentials.</p>



<p class="wp-block-paragraph">Sending enough concurrent requests causes LAPI to exhaust memory and be killed by the OS.</p>



<p class="wp-block-paragraph">By default, LAPI only listens on localhost, so this is not remotely exploitable.</p>



<p class="wp-block-paragraph">In a distributed setup, any attacker who can reach LAPI can exploit it. Until you upgrade, restrict LAPI access to trusted IPs at the network or reverse proxy layer.</p>



<p class="wp-block-paragraph">The impact is limited to the availability of new decisions: bouncers continue to enforce existing decisions, but new alerts cannot be processed, and new decisions cannot be distributed while LAPI is down.</p>
]]></content:encoded>
            <category>Product Updates</category>
            <enclosure url="https://cms.crowdsec.net/wp-content/uploads/2026/05/Blog-images-1575-×-871px-22.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Edge is the new endpoint: How to respond when edge CVEs go hot]]></title>
            <link>https://crowdsec.net/blog/edge-is-the-new-endpoint-how-to-respond-when-edge-cves-go-hot</link>
            <guid isPermaLink="false">https://crowdsec.net/blog/edge-is-the-new-endpoint-how-to-respond-when-edge-cves-go-hot</guid>
            <pubDate>Thu, 14 May 2026 11:14:12 GMT</pubDate>
            <description><![CDATA[<p>Edge CVEs move fast from disclosure to exploitation. Learn a practical 60-minute response framework to assess exposure, reduce risk, deploy mitigations, and communicate clearly during edge vulnerability incidents.</p>
]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong><em>Disclaimer</em></strong><em>: This series of articles was created in collaboration with CISOs and SOC Managers from our user community. They aim to describe situations and processes in place within their respective companies and inspire readers to examine their own.</em></p>



<p class="wp-block-paragraph">–</p>



<p class="wp-block-paragraph">Edge systems (VPN gateways, firewalls, WAFs, reverse proxies, load balancers, IAM appliances, and remote access portals) are inherently exposed. They sit in front of identity, internal apps, and admin surfaces. They are also difficult to manage, and most organizations have more of them than they realize. Subsidiaries have their own stacks. Teams spin up temporary machines in cloud accounts. Old stuff lingers because “we’ll clean that up later.”</p>



<p class="wp-block-paragraph">Attackers love “later”.</p>



<p class="wp-block-paragraph">Every time an edge <a href="https://tracker.crowdsec.net/" target="_blank" rel="noreferrer noopener">CVE goes public</a>, teams fall into the same loop. Read the advisory, argue about CVSS, wait for a catalog update, and then decide whether the change window is worth it. That is understandable. Emergency changes can break production. But it is also risky. By the time you have a consensus, exploitation may already be underway.</p>



<p class="wp-block-paragraph">The better approach is simple. Start with reality, then move to exposure, then mitigate, then patch. You can do this without drama if your workflow is clear.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading"><strong>Why do edge vulnerabilities become incidents?</strong></h2>



<p class="wp-block-paragraph">Edge issues quickly become major incidents because they create a direct path to valuable outcomes:</p>



<ul class="wp-block-list">
<li>Remote code execution on an internet-facing system often leads to internal access.</li>



<li>Auth bypass can turn into an admin takeover.</li>



<li>SSRF on a gateway can expose cloud metadata, internal APIs, or secrets.</li>



<li>Weaknesses in edge products are often chainable. One bug is enough to get a foothold, then another gets privilege.</li>
</ul>



<p class="wp-block-paragraph">There is also a timing problem. Attackers move faster than most change processes. Within hours of a PoC being public, you can expect scanning at scale. Within days, you can expect mass exploitation attempts that are more reliable and more automated. Sometimes it is even faster than that.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading"><strong>Scanning is constant, exploitation is specific</strong></h2>



<p class="wp-block-paragraph">Your logs will fill with noise after a CVE drops. That does not mean you are compromised. It means you are on a target list.</p>



<p class="wp-block-paragraph">The distinction matters because it drives decisions. If you treat every scan spike like an incident, you will burn out your team and break production more often than you should. If you dismiss scans as noise, you may miss the point at which scanning becomes a compromise.</p>



<p class="wp-block-paragraph">Scanning tends to look like:</p>



<ul class="wp-block-list">
<li>Quick version checks</li>



<li>Broad requests across many paths</li>



<li>High volume from distributed sources</li>



<li>Low “intent” in payloads</li>
</ul>



<p class="wp-block-paragraph">Exploitation tends to look like:</p>



<ul class="wp-block-list">
<li>Repeated requests to a small set of paths tied to vulnerability</li>



<li>Payloads that attempt code execution, file writes, or auth bypass</li>



<li>Follow-on behavior, like attempts to log in, create sessions, or pull configs</li>



<li>Patterns that align with known indicators for a given exploit family</li>
</ul>



<p class="wp-block-paragraph">For a SOC, your goal is not perfection. It is to be fast and correct enough to prioritize action.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading"><strong>Start with the question that cuts through the noise</strong></h2>



<p class="wp-block-paragraph">When an edge CVE hits, ask two questions in this order:</p>



<ol class="wp-block-list">
<li>Is exploitation observed in the wild?</li>



<li>Are we exposed?</li>
</ol>



<p class="wp-block-paragraph">If exploitation is observed and you are exposed, you act immediately. You reduce exposure and add friction, even before you patch, because you need to buy time.</p>



<p class="wp-block-paragraph">If exploitation is not observed, you still do the exposure work and patch planning, but you avoid panic changes.</p>



<p class="wp-block-paragraph"><strong><a href="https://tracker.crowdsec.net/" target="_blank" rel="noreferrer noopener">CrowdSec Live Exploit Tracker</a></strong> is mandatory here because it provides a reality check. It is not a replacement for advisories or catalog entries. It is a way to answer “Is this happening now?” without guessing.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading"><strong>The 60-minute response pattern</strong></h2>



<p class="wp-block-paragraph">Here is a practical way to run the first hour. This works for both CISO-level decision making and SOC-level execution.</p>



<h3 class="wp-block-heading"><strong>Minutes 0 to 10: confirm reality</strong></h3>



<ul class="wp-block-list">
<li>Confirm whether exploitation is observed in the wild.</li>



<li>If exploitation is observed, decide to mitigate now and patch as soon as feasible.</li>



<li>If exploitation is not observed, proceed with exposure assessment and prepare mitigations, but avoid disruptive changes until you have a reason.</li>
</ul>



<p class="wp-block-paragraph">This is where many teams lose time. They debate the severity instead of determining whether the attacker&#8217;s activity is real.</p>



<h3 class="wp-block-heading"><strong>Minutes 10 to 25: determine exposure</strong></h3>



<p class="wp-block-paragraph">Do not rely on one inventory source. Use multiple angles:</p>



<ul class="wp-block-list">
<li>public IP and DNS review</li>



<li>cloud security posture data</li>



<li>network scans of ingress points</li>



<li>configuration management records for edge products</li>
</ul>



<p class="wp-block-paragraph">Make sure you include:</p>



<ul class="wp-block-list">
<li>HA pairs and standby nodes</li>



<li>systems in regional sites</li>



<li>test systems that accidentally became public</li>



<li>legacy devices that were “temporary.”</li>
</ul>



<p class="wp-block-paragraph">Then answer:</p>



<ul class="wp-block-list">
<li>Is the vulnerable interface reachable from the internet?</li>



<li>Is it reachable from partner networks?</li>



<li>Is it reachable from user segments?</li>
</ul>



<p class="wp-block-paragraph">Exposure drives urgency. You can have a severe bug with low exposure. You can also have a moderate bug with high exposure that becomes your biggest risk.</p>



<h3 class="wp-block-heading"><strong>Minutes 25 to 45: reduce exposure and add friction</strong></h3>



<p class="wp-block-paragraph">If exploitation is observed and you have exposure, focus on making quick, safe changes that reduce risk.</p>



<p class="wp-block-paragraph">Start with exposure reduction:</p>



<ul class="wp-block-list">
<li>Restrict management interfaces to allowlists. Do it now.</li>



<li>Move admin access behind a VPN or a jump host.</li>



<li>Remove direct internet access where possible.</li>
</ul>



<p class="wp-block-paragraph">Then add friction:</p>



<ul class="wp-block-list">
<li>Apply WAF rules or reverse-proxy filtering if traffic passes through them.</li>



<li>Add IPS signatures or firewall blocklists where available.</li>



<li>Rate limit endpoints that attackers hammer during exploitation.</li>



<li>Tighten ACLs around the edge service and its management plane.</li>
</ul>



<p class="wp-block-paragraph">CrowdSec provides both <strong>WAF virtual patching rules</strong> and <strong>real-time-updated blocklists</strong> to address the latest vulnerabilities. These mitigation assets could be pulled automatically by your edge devices to respond instantly.</p>



<h3 class="wp-block-heading"><strong>Minutes 45 to 60: verify and watch</strong></h3>



<p class="wp-block-paragraph">Mitigation without verification is just hope.</p>



<ul class="wp-block-list">
<li>Confirm that logging is enabled and that it is shipped to your SIEM.</li>



<li>Create targeted detections for the known exploit paths and payload patterns.</li>



<li>Watch for post-exploitation indicators:
<ul class="wp-block-list">
<li>New admin sessions</li>



<li>New users or API tokens</li>



<li>Config changes</li>



<li>Unusual outbound connections from the appliance</li>



<li>Unexpected access to internal services</li>
</ul>
</li>
</ul>



<p class="wp-block-paragraph">This last step matters because some exploit chains succeed quickly and quietly. Your mitigations might be late for one asset but still protect the rest.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading"><strong>How to communicate without drama</strong></h2>



<p class="wp-block-paragraph">CISOs get pulled into edge events because the risk is easy to explain and the consequences are high.</p>



<p class="wp-block-paragraph">A good update to leadership avoids raw CVE lists and vague reassurance. It includes:</p>



<ul class="wp-block-list">
<li>Exploitation status: observed or not observed</li>



<li>Exposure status: which assets are internet-facing and vulnerable</li>



<li>Mitigations deployed: what changed in the last hour</li>



<li>Patch plan: when and how, including constraints</li>



<li>Evidence: what the SOC sees after mitigation</li>
</ul>



<p class="wp-block-paragraph">This keeps the conversation grounded. It also helps you defend emergency actions later if you need to explain why a change freeze was overridden.</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading"><strong>Key takeaways</strong></h2>



<ul class="wp-block-list">
<li>Edge systems are high value because they are exposed and privileged.</li>



<li>Scanning is expected. Exploitation is what changes your response.</li>



<li>Start with “is exploitation observed” and “are we exposed.”</li>



<li>Reduce exposure first, then add filtering, then patch.</li>



<li>Report progress on exposure and controls, not on CV</li>
</ul>
]]></content:encoded>
            <category>CrowdSec</category>
            <category>Proactive Cybersecurity</category>
            <enclosure url="https://cms.crowdsec.net/wp-content/uploads/2026/04/Blog-images-1575-×-871px-19.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[ButanGas Enhances Cybersecurity with CrowdSec to Protect LPG Distribution]]></title>
            <link>https://crowdsec.net/blog/securing-lpg-distribution-butangas-and-crowdsec</link>
            <guid isPermaLink="false">https://crowdsec.net/blog/securing-lpg-distribution-butangas-and-crowdsec</guid>
            <pubDate>Tue, 07 Apr 2026 09:35:42 GMT</pubDate>
            <description><![CDATA[<p>ButanGas strengthens its IT security using CrowdSec’s real-time threat intelligence &#038; blocklists, ensuring secure, uninterrupted LPG distribution across Italy.</p>
]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph"><strong>ButanGas, a key player in the Italian energy sector, strengthens its cybersecurity posture with CrowdSec’s real-time threat intelligence and Platinum Blocklists, ensuring safe and uninterrupted liquified petroleum gas (LPG) distribution across the country.</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="wp-block-paragraph"><a href="https://butangas.it/en/" target="_blank" rel="noreferrer noopener">ButanGas</a> operates in the energy sector, specializing in liquified petroleum gas (LPG) storage and distribution. Its main mission is to guarantee a safe, reliable, and continuous supply throughout Italy. As part of the Dragan Group, a multinational energy organization active in seven countries, ButanGas maintains a strong national presence with more than <strong>20 offices and 9 LPG storage and bottling depots</strong>.</p>



<p class="wp-block-paragraph">Behind these operations lies a complex and extended digital infrastructure. Supporting over <strong>700 users across multiple countries</strong>, ButanGas <span style="box-sizing: border-box; margin: 0px; padding: 0px;">manages<strong> IT</strong></span><strong> and cybersecurity centrally for Italy and additional legal entities abroad</strong>. They require consistent protection, centralized visibility, and scalable security controls across geographies.</p>



<p class="wp-block-paragraph">At the helm of this strategy is the organization’s CIO and CISO, Riccardo Morandotti. He is responsible for IT strategy, cybersecurity governance, risk management, and digital transformation initiatives across the company.&nbsp;</p>



<h2 class="wp-block-heading"><strong>The challenge: Too much noise, not enough insight</strong></h2>



<p class="wp-block-paragraph">Like many <a href="https://www.crowdsec.net/solutions/oil-and-energy" target="_blank" rel="noreferrer noopener">companies in the energy sector</a>, ButanGas faced a constant stream of malicious activity targeting its network perimeter.</p>



<p class="wp-block-paragraph"><strong>Hundreds of suspicious connections were hitting their firewalls every day</strong>, generating significant noise but little actionable intelligence. While Sophos firewalls were already in place, the team lacked clear visibility into what was actually happening within their logs.</p>



<p class="wp-block-paragraph">They needed more than just protection; they needed clarity. The volume of alerts made it difficult to distinguish meaningful threats from <a href="https://www.crowdsec.net/glossary/internet-background-noise" target="_blank" rel="noreferrer noopener">background noise</a>, and the lack of enriched data limited their ability to take informed action. At the same time, ButanGas had a clear strategic priority: <strong>adopting European cybersecurity solutions without compromising on the quality of threat intelligence</strong>. </p>



<h2 class="wp-block-heading"><strong>Why CrowdSec: European roots, global intelligence</strong></h2>



<p class="wp-block-paragraph">After evaluating several threat intelligence providers, Riccardo identified CrowdSec as the right fit. He explains, “What stood out was that CrowdSec is a European-based company, which aligned perfectly with our strategic goal of using European security tools whenever possible. At the same time, CrowdSec collects data globally and provides insights that few other vendors can match.”&nbsp;&nbsp;</p>



<p class="wp-block-paragraph">Equally important was the ease of deployment. Within just one hour, CrowdSec was integrated into their existing environment, and results followed almost immediately. From day one, <strong>hundreds of malicious connections were blocked daily, with a threefold increase in detected suspicious traffic.</strong> At the same time, <strong>false positives remained extremely low, below 1%, ensuring legitimate traffic was never disrupted.</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>CrowdSec is simple to deploy, operate, and integrate, letting our team focus on response rather than tool management. Its reliable threat intelligence and accurate detections give us high‑confidence security decisions with minimal noise, making it a truly dependable part of our security architecture.</em></p>



<p class="wp-block-paragraph">&#8211; Riccardo Morandotti, CIO and CISO of ButanGas</p>
</blockquote>



<h2 class="wp-block-heading"><strong>A smarter, more proactive firewall</strong></h2>



<p class="wp-block-paragraph">ButanGas initially deployed CrowdSec on its most critical firewall, with plans to extend coverage to ten more. Even this first step delivered a significant impact.</p>



<p class="wp-block-paragraph">All malicious traffic is now blocked before reaching the network, drastically reducing exposure. <strong>Firewall logs have also become far more readable and actionable, with a clear distinction between legitimate and malicious traffic</strong>, eliminating the confusion that previously slowed down analysis.</p>



<p class="wp-block-paragraph">The real transformation, however, goes beyond inbound protection.</p>



<p class="wp-block-paragraph"><a href="https://doc.crowdsec.net/u/console/blocklists/integrations/firewall/" target="_blank" rel="noreferrer noopener">Through the integration between CrowdSec, Sophos Firewall, and their MDR service</a>, ButanGas now benefits from <strong>proactive outbound control</strong> as well. Internal endpoints are prevented from establishing connections with known malicious IPs, and any suspicious outbound attempt is immediately blocked. In more critical cases, <strong>compromised machines can be automatically isolated, helping contain threats before they spread or escalate</strong>.</p>



<p class="wp-block-paragraph">This capability has proven essential in limiting <a href="https://www.crowdsec.net/glossary/lateral-movement">lateral movement</a> and preventing potential data exfiltration, <strong>turning the firewall into an active enforcement point rather than a passive filter</strong>.</p>



<h2 class="wp-block-heading"><strong>From blocking threats to understanding them</strong></h2>



<p class="wp-block-paragraph">While automated blocking significantly improved their security posture, the most meaningful shift came from <strong>enhanced visibility</strong>.</p>



<p class="wp-block-paragraph">With <a href="https://www.crowdsec.net/cyber-threat-intelligence" target="_blank" rel="noreferrer noopener">CrowdSec’s IP enrichment capabilities</a>, ButanGas now gains contextual intelligence on every threat, <strong>ranging from geographic origin to behavior patterns and risk scoring</strong>. This allows their team to understand not just that an IP is malicious, but why.</p>



<p class="wp-block-paragraph">For a European organization, this is particularly valuable. They benefit from strong visibility into threats impacting European countries, while still leveraging <a href="https://www.crowdsec.net/our-data" target="_blank" rel="noreferrer noopener">globally collected intelligence</a> to stay ahead of emerging risks.</p>



<h2 class="wp-block-heading"><strong>Building a scalable security model</strong></h2>



<p class="wp-block-paragraph">With CrowdSec in place, ButanGas has transformed its firewall into a <strong>proactive, intelligence-driven security layer</strong>.</p>



<p class="wp-block-paragraph">Malicious traffic is stopped before it becomes a risk, visibility has improved dramatically, and the operational burden on the IT team has been reduced. All of this has been achieved without disrupting legitimate business activity.</p>



<p class="wp-block-paragraph">As deployments grow across more <a href="https://www.crowdsec.net/glossary/what-does-a-firewall-do" target="_blank" rel="noreferrer noopener">firewalls</a>, ButanGas is creating a scalable, unified security model. It protects a <strong>complex, multinational infrastructure while supporting the company’s mission to deliver safe, reliable energy</strong>.</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
            <category>Success Story</category>
            <enclosure url="https://cms.crowdsec.net/wp-content/uploads/2026/03/245.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Protecting Your Web Applications with OWASP CRS and CrowdSec]]></title>
            <link>https://crowdsec.net/blog/protecting-your-web-applications-with-owasp-crs-and-crowdsec</link>
            <guid isPermaLink="false">https://crowdsec.net/blog/protecting-your-web-applications-with-owasp-crs-and-crowdsec</guid>
            <pubDate>Mon, 23 Mar 2026 12:59:05 GMT</pubDate>
            <description><![CDATA[<p>Deploy the OWASP Core Rule Set (CRS) with CrowdSec to detect SQL injection, XSS, and other attack patterns. Learn how to install, tune, and enable blocking.</p>
]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">If you&#8217;re already running <a href="https://www.crowdsec.net/solutions/application-security" target="_blank" rel="noreferrer noopener">CrowdSec&#8217;s AppSec</a> component with virtual patching rules, you&#8217;re well protected against known CVEs. But what about the attacks that don&#8217;t target a specific vulnerability: <strong>the SQL injections, cross-site scripting attempts, and command injections that probe your applications every day?</strong></p>



<p class="wp-block-paragraph">That&#8217;s where the OWASP Core Rule Set (CRS) comes in. <a href="https://www.crowdsec.net/blog/crowdsec-waf-the-collaborative-future-of-web-application-security" target="_blank" rel="noreferrer noopener">CrowdSec supports CRS natively</a> through the <a href="https://github.com/corazawaf/coraza" target="_blank" rel="noreferrer noopener">Coraza</a> WAF engine, giving you broad attack pattern detection alongside your existing virtual patching setup.</p>



<p class="wp-block-paragraph">In this post, we&#8217;ll walk through what CRS offers, how to deploy it, and how to tune it for your applications.</p>



<h2 class="wp-block-heading">What is OWASP Core Rule Set (CRS)?</h2>



<p class="wp-block-paragraph">The <a href="https://coreruleset.org/" target="_blank" rel="noreferrer noopener">OWASP Core Rule Set</a> is among the most widely deployed sets of WAF rules worldwide. Actively maintained by the OWASP community, it provides generic attack detection against:</p>



<ul class="wp-block-list">
<li>SQL injection</li>



<li>Cross-site scripting</li>



<li>Command injection</li>



<li>Path traversal</li>



<li>And more</li>
</ul>



<p class="wp-block-paragraph">Unlike virtual patching, which targets specific known CVEs, the CRS uses a more generic approach to detect exploitation of still-unknown vulnerabilities.</p>



<p class="wp-block-paragraph">This comes at a cost: <strong>a higher chance of false positives depending on the application.</strong></p>



<p class="wp-block-paragraph">Using both CRS and virtual patching rules is complementary: virtual patching rules for precise targeting of known vulnerabilities without the need for any configuration, and CRS to catch everything else.</p>



<h2 class="wp-block-heading">Using the CRS with CrowdSec</h2>



<p class="wp-block-paragraph">CrowdSec offers two deployment modes for CRS: <strong>out-of-band or in-band.</strong></p>



<h3 class="wp-block-heading">Out-of-band (non-blocking)</h3>



<p class="wp-block-paragraph">In this mode, CRS rules analyze your traffic asynchronously. No request is ever blocked by a single rule match. Instead, events are generated and fed into <a href="https://app.crowdsec.net/hub/scenarios" target="_blank" rel="noreferrer noopener">CrowdSec&#8217;s scenario engine</a>. If an IP triggers too many different rule violations in a short timespan, <strong>it gets banned</strong>.</p>



<p class="wp-block-paragraph">This is the recommended starting point because it lets you:</p>



<ul class="wp-block-list">
<li>See what CRS detects in your traffic without disrupting legitimate users</li>



<li>Identify false positives and tune the CRS accordingly</li>
</ul>



<h3 class="wp-block-heading">In-band (blocking)</h3>



<p class="wp-block-paragraph">In blocking mode, requests that reach the CRS threshold for blocking are <strong>dropped immediately</strong>. Alerts are generated by default, <strong>giving you visibility into what is blocked</strong>.</p>



<h2 class="wp-block-heading">Getting Started: Installation</h2>



<h3 class="wp-block-heading">Prerequisites</h3>



<p class="wp-block-paragraph">You need a working CrowdSec setup with a WAF-capable remediation component. This includes the Nginx/OpenResty bouncer, Traefik bouncer, HAProxy bouncer, or any other bouncer (also known as <a href="https://doc.crowdsec.net/u/bouncers/intro" target="_blank" rel="noreferrer noopener">Remediation Component</a>) that supports the AppSec protocol.</p>



<h3 class="wp-block-heading">Step 1: Install the CRS collection</h3>



<p class="wp-block-paragraph">For non-blocking mode:</p>



<pre><code class="language-bash">cscli collections install crowdsecurity/appsec-crs</code></pre>



<p class="wp-block-paragraph">This installs:</p>



<ul class="wp-block-list">
<li><code>crowdsecurity/crs</code>: the configuration that loads CRS rules in out-of-band mode</li>



<li><code>crowdsecurity/crowdsec-appsec-outofband</code>: a scenario that bans IPs after 5+ out-of-band rule violations to offer a base level of protection, even if the rules themselves do not block anything directly.</li>
</ul>



<h3 class="wp-block-heading">Step 2: Configure the acquisition</h3>



<p class="wp-block-paragraph">Add the CRS config to your acquisition file (e.g., <code>/etc/crowdsec/acquis.d/waf.yaml</code>):</p>



<pre><code class="language-yaml">source: appsec
appsec-configs:
  - crowdsecurity/crs
labels:
  type: appsec</code></pre>



<p class="wp-block-paragraph">If you already have an acquisition file for AppSec with other configs (like virtual patching), simply add <code>crowdsecurity/crs</code> to the existing <code>appsec-configs</code> list.</p>



<h3 class="wp-block-heading">Step 3: Enable per-match alerting</h3>



<p class="wp-block-paragraph">By default, non-blocking mode doesn&#8217;t generate an alert for every individual rule match. If you want visibility into each match (useful during the tuning phase), create <code>/etc/crowdsec/appsec-configs/crs-alerting.yaml</code>:</p>



<pre><code class="language-yaml">name: custom/crs-alerting
on_match:
  - filter: IsOutBand == true
    apply:
      - SendAlert()
      - CancelEvent()
</code></pre>



<p class="wp-block-paragraph">The <code>CancelEvent()</code> directive is optional: if set, no event is generated for the scenario engine, meaning CrowdSec won&#8217;t take automated decisions based on these matches. This is useful if you want alerts for monitoring but don&#8217;t want any automated bans yet.</p>



<p class="wp-block-paragraph">Then add it to your acquisition:</p>



<pre><code class="language-yaml">source: appsec
appsec-configs:
  - crowdsecurity/crs
  - custom/crs-alerting
labels:
  type: appsec
</code></pre>



<p class="wp-block-paragraph">Even though an alert is generated, because the rules are loaded out-of-band, nothing will be blocked, but you will still know exactly what is matching.</p>



<h3 class="wp-block-heading">Step 4: Restart CrowdSec</h3>



<pre><code class="language-bash">sudo systemctl restart crowdsec</code></pre>



<h3 class="wp-block-heading">Step 5: Confirm it&#8217;s working</h3>



<p class="wp-block-paragraph">Now, we can see if everything is working as it should. Let&#8217;s exploit a (fake) SQL injection:</p>



<pre><code class="language-bash">$ curl "localhost/?a='+OR+'1'='1" -i
HTTP/1.1 200 OK
...
</code></pre>



<p class="wp-block-paragraph">Our request was not blocked: it&#8217;s expected as the CRS is configured in non-blocking mode.</p>



<p class="wp-block-paragraph">If we look at the list of alerts in crowdsec with <code>cscli alerts list</code> and <code>cscli alerts inspectd -d</code>, We can indeed see the request was detected by the CRS:</p>



<pre><code class="language-bash">root@instance-20240401-2335:~# cscli alerts inspect -d 105901

################################################################################################

 - ID           : 105901
 - Date         : 2026-03-12T09:44:32Z
 - Machine      : 717704f5186c4314928c2060bf5169f32E1tgFLtep049JcD
 - Simulation   : false
 - Remediation  : false
 - Reason       : anomaly score out-of-band: sql_injection: 10, anomaly: 10,
 - Events Count : 4
 - Scope:Value  : Ip:127.0.0.1
 - Country      :
 - AS           :
 - Begin        : 2026-03-12T09:44:32Z
 - End          : 2026-03-12T09:44:32Z
 - UUID         : 8cf71673-81d7-4ba0-b211-0d415a0f51fd


 - Context  :
╭───────────────┬─────────────────────────────────────────────────────╮
│      Key      │                        Value                        │
├───────────────┼─────────────────────────────────────────────────────┤
│ ja4h          │ ge11nn020000_cf69e1861692_000000000000_000000000000 │
│ matched_zones │ ARGS.a                                              │
│ method        │ GET                                                 │
│ msg           │ SQL Injection Attack Detected via libinjection      │
│ name          │ native_rule:942100                                  │
│ target_uri    │ /?a='+OR+'1'='1                                     │
│ user_agent    │ curl/7.81.0                                         │
╰───────────────┴─────────────────────────────────────────────────────╯

 - Events  :

- Date: 2026-03-12 09:44:32 +0000 UTC
╭───────────────┬──────────────────────────╮
│      Key      │           Value          │
├───────────────┼──────────────────────────┤
│ data          │                          │
│ matched_zones │ REQBODY_PROCESSOR        │
│ message       │ Enabling body inspection │
│ rule_name     │ native_rule:901340       │
│ target_fqdn   │ localhost                │
│ uri           │ /?a='+OR+'1'='1          │
╰───────────────┴──────────────────────────╯

- Date: 2026-03-12 09:44:32 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────────╮
│      Key      │                         Value                        │
├───────────────┼──────────────────────────────────────────────────────┤
│ data          │ Matched Data: s&sos found within ARGS:a: ' OR '1'='1 │
│ matched_zones │ ARGS.a,ARGS.a                                        │
│ message       │ SQL Injection Attack Detected via libinjection       │
│ rule_name     │ native_rule:942100                                   │
│ target_fqdn   │ localhost                                            │
│ uri           │ /?a='+OR+'1'='1                                      │
╰───────────────┴──────────────────────────────────────────────────────╯

- Date: 2026-03-12 09:44:32 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────╮
│      Key      │                       Value                      │
├───────────────┼──────────────────────────────────────────────────┤
│ data          │                                                  │
│ matched_zones │ TX.blocking_inbound_anomaly_score                │
│ message       │ Inbound Anomaly Score Exceeded (Total Score: 10) │
│ rule_name     │ native_rule:949110                               │
│ target_fqdn   │ localhost                                        │
│ uri           │ /?a='+OR+'1'='1                                  │
╰───────────────┴──────────────────────────────────────────────────╯

- Date: 2026-03-12 09:44:32 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────────────────╮
│      Key      │                             Value                            │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ data          │                                                              │
│ matched_zones │ UNKNOWN                                                      │
│ message       │ Anomaly Scores: (Inbound Scores: blocking=10, detection=10,  │
│               │ per_pl=10-0-0-0, threshold=5) - (Outbound Scores:            │
│               │ blocking=0, detection=0, per_pl=0-0-0-0, threshold=4) -      │
│               │ (SQLI=10, XSS=0, RFI=0, LFI=0, RCE=0, PHPI=0, HTTP=0,        │
│               │ SESS=0, COMBINED_SCORE=10)                                   │
│ rule_name     │ native_rule:980170                                           │
│ target_fqdn   │ localhost                                                    │
│ uri           │ /?a='+OR+'1'='1                                              │
╰───────────────┴──────────────────────────────────────────────────────────────╯
</code></pre>



<p class="wp-block-paragraph">A note about the reason given for the block: CRS works with a threshold system. Each rule that matches on the request will increase by some value a global score, and if the global score exceeds a preconfigured threshold (5 by default), the request will be blocked.</p>



<p class="wp-block-paragraph">This is why the reason appears as <code>anomaly score out-of-band: sql_injection: 10, anomaly: 10,.</code></p>



<p class="wp-block-paragraph">Crowdsec automatically extracts the scoring information from the CRS to give you, at first glance, details about what was wrong with the request.</p>



<h3 class="wp-block-heading">Switching to blocking mode</h3>



<p class="wp-block-paragraph">Once you&#8217;re confident in your CRS tuning, switch to blocking mode:</p>



<pre><code class="language-bash">cscli collections install crowdsecurity/appsec-crs-inband</code></pre>



<p class="wp-block-paragraph">Update your acquisition:</p>



<pre><code class="language-yaml">source: appsec
appsec-configs:
  - crowdsecurity/crs-inband
labels:
  type: appsec
</code></pre>



<p class="wp-block-paragraph">In this mode, any request that reaches the default CRS threshold is dropped, and alerts are generated automatically.</p>



<p class="wp-block-paragraph">Let&#8217;s retry the same request as previously:</p>



<pre><code class="language-bash">$ curl "localhost/?a='+OR+'1'='1" -i
HTTP/1.1 403 Forbidden
...
</code></pre>



<p class="wp-block-paragraph">This time, the request actually got blocked!</p>



<p class="wp-block-paragraph">Again, we can inspect the alert:</p>



<pre><code class="language-bash">root@instance-20240401-2335:~# cscli alerts inspect -d 105902

################################################################################################

 - ID           : 105902
 - Date         : 2026-03-12T09:49:04Z
 - Machine      : 717704f5186c4314928c2060bf5169f32E1tgFLtep049JcD
 - Simulation   : false
 - Remediation  : false
 - Reason       : anomaly score block: sql_injection: 10, anomaly: 10,
 - Events Count : 4
 - Scope:Value  : Ip:127.0.0.1
 - Country      :
 - AS           :
 - Begin        : 2026-03-12T09:49:03Z
 - End          : 2026-03-12T09:49:03Z
 - UUID         : 7ef38529-5e7e-4d2e-9e21-24ec7a89a1ee


 - Context  :
╭───────────────┬─────────────────────────────────────────────────────╮
│      Key      │                        Value                        │
├───────────────┼─────────────────────────────────────────────────────┤
│ ja4h          │ ge11nn020000_cf69e1861692_000000000000_000000000000 │
│ matched_zones │ ARGS.a                                              │
│ method        │ GET                                                 │
│ msg           │ SQL Injection Attack Detected via libinjection      │
│ name          │ native_rule:942100                                  │
│ target_uri    │ /?a='+OR+'1'='1                                     │
│ user_agent    │ curl/7.81.0                                         │
╰───────────────┴─────────────────────────────────────────────────────╯

 - Events  :

- Date: 2026-03-12 09:49:03 +0000 UTC
╭───────────────┬──────────────────────────╮
│      Key      │           Value          │
├───────────────┼──────────────────────────┤
│ data          │                          │
│ matched_zones │ REQBODY_PROCESSOR        │
│ message       │ Enabling body inspection │
│ rule_name     │ native_rule:901340       │
│ target_fqdn   │ localhost                │
│ uri           │ /?a='+OR+'1'='1          │
╰───────────────┴──────────────────────────╯

- Date: 2026-03-12 09:49:03 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────────╮
│      Key      │                         Value                        │
├───────────────┼──────────────────────────────────────────────────────┤
│ data          │ Matched Data: s&sos found within ARGS:a: ' OR '1'='1 │
│ matched_zones │ ARGS.a,ARGS.a                                        │
│ message       │ SQL Injection Attack Detected via libinjection       │
│ rule_name     │ native_rule:942100                                   │
│ target_fqdn   │ localhost                                            │
│ uri           │ /?a='+OR+'1'='1                                      │
╰───────────────┴──────────────────────────────────────────────────────╯

- Date: 2026-03-12 09:49:03 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────╮
│      Key      │                       Value                      │
├───────────────┼──────────────────────────────────────────────────┤
│ data          │                                                  │
│ matched_zones │ TX.blocking_inbound_anomaly_score                │
│ message       │ Inbound Anomaly Score Exceeded (Total Score: 10) │
│ rule_name     │ native_rule:949110                               │
│ target_fqdn   │ localhost                                        │
│ uri           │ /?a='+OR+'1'='1                                  │
╰───────────────┴──────────────────────────────────────────────────╯

- Date: 2026-03-12 09:49:03 +0000 UTC
╭───────────────┬──────────────────────────────────────────────────────────────╮
│      Key      │                             Value                            │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ data          │                                                              │
│ matched_zones │ UNKNOWN                                                      │
│ message       │ Anomaly Scores: (Inbound Scores: blocking=10, detection=10,  │
│               │ per_pl=10-0-0-0, threshold=5) - (Outbound Scores:            │
│               │ blocking=0, detection=0, per_pl=0-0-0-0, threshold=4) -      │
│               │ (SQLI=10, XSS=0, RFI=0, LFI=0, RCE=0, PHPI=0, HTTP=0,        │
│               │ SESS=0, COMBINED_SCORE=10)                                   │
│ rule_name     │ native_rule:980170                                           │
│ target_fqdn   │ localhost                                                    │
│ uri           │ /?a='+OR+'1'='1                                              │
╰───────────────┴──────────────────────────────────────────────────────────────╯
</code></pre>



<p class="wp-block-paragraph">It&#8217;s almost the same, except for the reason: <code>anomaly score block: sql_injection: 10, anomaly: 10,.</code></p>



<p class="wp-block-paragraph">The presence of <code>block</code> means the request was actually blocked and never reached the target application.</p>



<h2 class="wp-block-heading">Customizing CRS for Your Applications</h2>



<p class="wp-block-paragraph">Out of the box, CRS is tuned for broad coverage. For any moderately complex application, you&#8217;ll likely need some customization to eliminate false positives. <a href="https://www.crowdsec.net/blog/crowdsec-waf-in-action-real-world-use-cases" target="_blank" rel="noreferrer noopener">CrowdSec uses the CRS plugin system for all customizations</a>, keeping your changes separate from the CRS rules themselves.</p>



<h3 class="wp-block-heading">CRS Plugins for Popular Applications</h3>



<p class="wp-block-paragraph">The CRS community maintains exclusion plugins for popular web applications. These plugins contain pre-built rule exclusions that prevent common false positives for each application.</p>



<p class="wp-block-paragraph">The following plugins are available directly from the <a href="https://app.crowdsec.net/hub" target="_blank" rel="noreferrer noopener">CrowdSec Hub</a>:</p>



<ul class="wp-block-list">
<li>WordPress</li>



<li>NextCloud</li>



<li>PHPMyAdmin</li>



<li>CPanel</li>



<li>Drupal</li>



<li>PHPBB</li>



<li>DokuWiki</li>



<li>Xenforo</li>
</ul>



<h3 class="wp-block-heading">Installing a plugin</h3>



<pre><code class="language-bash">cscli collections install crowdsecurity/appsec-crs-exclusion-plugin-<application-name>
</code></pre>



<p class="wp-block-paragraph">For example, to install the WordPress plugin:</p>



<pre><code class="language-yaml">cscli collections install crowdsecurity/appsec-crs-exclusion-plugin-wordpress</code></pre>



<p class="wp-block-paragraph">Once installed, the plugin loads automatically after a CrowdSec restart.</p>



<p class="wp-block-paragraph">The plugins are updated automatically, like any other Hub items, so you will receive upstream changes automatically.</p>



<p class="wp-block-paragraph">You can also manually deploy custom plugins; refer to our <a href="https://docs.crowdsec.net/docs/next/appsec/crs/plugin_support#manually-installing-a-plugin" target="_blank" rel="noreferrer noopener">documentation</a>.</p>



<h2 class="wp-block-heading">Putting It All Together: Defense in Depth</h2>



<p class="wp-block-paragraph">Here&#8217;s how a complete AppSec configuration might look, combining virtual patching and CRS:</p>



<pre><code class="language-yaml"># /etc/crowdsec/acquis.d/waf.yaml
source: appsec
appsec-configs:
  - crowdsecurity/appsec-default # Virtual patching (in-band)
  - crowdsecurity/crs # OWASP CRS (out-of-band)
labels:
  type: appsec
</code></pre>



<p class="wp-block-paragraph">With this setup:</p>



<ol class="wp-block-list">
<li><strong>Virtual patching rules</strong> run in-band, immediately blocking requests that match known CVE patterns</li>



<li><strong>CRS rules</strong> run out-of-band, detecting broad attack patterns and feeding events into CrowdSec&#8217;s scenario engine</li>



<li><strong>IPs that trigger multiple CRS violations</strong> get banned automatically through the scenario system</li>



<li><strong>CrowdSec&#8217;s threat intelligence</strong> adds another layer, sharing decisions across the community</li>
</ol>



<p class="wp-block-paragraph">Once you&#8217;ve tuned CRS and are confident in your configuration, you can switch to the in-band CRS collection for immediate blocking with the following configuration:</p>



<pre><code class="language-yaml"># /etc/crowdsec/acquis.d/waf.yaml
source: appsec
appsec-configs:
  - crowdsecurity/appsec-default # Virtual patching (in-band)
  - crowdsecurity/crs-inband # OWASP CRS (in-band)
labels:
  type: appsec
</code></pre>



<h2 class="wp-block-heading">Next Steps</h2>



<p class="wp-block-paragraph">Now that you have deployed CRS with CrowdSec, here are some more resources and tools you can use to better protect your web applications:&nbsp;&nbsp;</p>



<ul class="wp-block-list">
<li>Browse the <a href="https://docs.crowdsec.net/docs/appsec/crs/intro" target="_blank" rel="noreferrer noopener">CRS documentation</a> for detailed reference</li>



<li>Search the <a href="https://app.crowdsec.net/hub" target="_blank" rel="noreferrer noopener">CrowdSec Hub</a> for available CRS plugins</li>



<li>Check out the <a href="https://docs.crowdsec.net/docs/appsec/intro" target="_blank" rel="noreferrer noopener">CrowdSec WAF quickstart guides</a> if you haven&#8217;t set up the AppSec component yet</li>



<li>Join the <a href="https://discourse.crowdsec.net/" target="_blank" rel="noreferrer noopener">CrowdSec community</a> to share your CRS tuning experiences</li>
</ul>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
            <category>Tutorial</category>
            <enclosure url="https://cms.crowdsec.net/wp-content/uploads/2026/03/242-1.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[The future of CrowdSec support in Kubernetes]]></title>
            <link>https://crowdsec.net/blog/crowdsec-support-kubernetes-ingress-nginx</link>
            <guid isPermaLink="false">https://crowdsec.net/blog/crowdsec-support-kubernetes-ingress-nginx</guid>
            <pubDate>Tue, 17 Mar 2026 11:04:34 GMT</pubDate>
            <description><![CDATA[<p>Explore CrowdSec support for Kubernetes, Ingress-NGINX deprecation, and how to migrate to Gateway API using Traefik, HAProxy, and Envoy.</p>
]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph"><a href="https://kubernetes.io/" target="_blank" rel="noreferrer noopener">Kubernetes</a> networking is evolving quickly, and with it, the way security integrations are deployed. One <span style="box-sizing: border-box; margin: 0px; padding: 0px;">change currently affecting many users is the announced <strong>deprecation of Ingress-NGINX</strong></span>. Because a large portion of Kubernetes deployments rely on it today, <strong>we want to clarify how CrowdSec support will evolve and what users can expect</strong>.</p>



<h2 class="wp-block-heading">Supporting Ingress-NGINX during its final lifecycle</h2>



<p class="wp-block-paragraph"><a href="https://www.crowdsec.net/blog/kubernetes-crowdsec-integration" target="_blank" rel="noreferrer noopener">CrowdSec currently integrates with Ingress-NGINX through a dedicated image that embeds CrowdSec remediation capabilities</a>. This integration will remain available for the last supported versions of Ingress-NGINX.</p>



<p class="wp-block-paragraph">First, we need to underline that CrowdSec requires a dedicated image. CrowdSec remediation in Ingress-NGINX relies on Lua to execute the blocking logic. Lua support was removed from the official Ingress-NGINX image, which means CrowdSec cannot run on the upstream image anymore. As a result, the CrowdSec integration requires an alternate image that restores the Lua capability needed by the remediation engine. This extended support should be viewed as a transitional measure, providing ample time for a smooth migration to more future-proof and robust architectures.</p>



<p class="wp-block-paragraph">For users evaluating alternatives today, it is also worth noting that <a href="https://www.crowdsec.net/search?prod_crowdsec%5Bquery%5D=kuberne" target="_blank" rel="noreferrer noopener">CrowdSec already supports Traefik and HAProxy as ingress controllers</a>.</p>



<p class="wp-block-paragraph">While third parties sometimes provide support for this in an open-source capacity, we remain fully committed to the CrowdSec components and features, ensuring their continued support and the introduction of new functionality over time. We also maintain and develop <a href="https://doc.crowdsec.net/" target="_blank" rel="noreferrer noopener">documentation</a> for the tools used by the community.</p>



<h2 class="wp-block-heading">The shift from Ingress-NGINX to Gateway API in Kubernetes</h2>



<p class="wp-block-paragraph">The <a href="https://gateway-api.sigs.k8s.io/guides/getting-started/migrating-from-ingress/" target="_blank" rel="noreferrer noopener">Kubernetes shift toward Gateway API</a> beyond the lifecycle of Ingress-NGINX, Kubernetes itself is signalling a broader shift in how north-south traffic should be handled.</p>



<p class="wp-block-paragraph">The Kubernetes ecosystem is increasingly converging around the Gateway API specification. This specification introduces the Gateway API as a more flexible and extensible way to manage traffic entering a cluster.</p>



<p class="wp-block-paragraph">Several implementations of the Gateway API specification already exist, and a number of them are built on <a href="https://app.crowdsec.net/hub/remediation-components" target="_blank" rel="noreferrer noopener">technologies that already support CrowdSec remediation mechanisms</a>. Because of this, we see the Gateway API ecosystem as a natural evolution path for CrowdSec users running Kubernetes clusters. Today, solutions such as <strong>Traefik, HAProxy, and Envoy can already operate with CrowdSec remediation, meaning that when they are used as Gateway API implementations</strong>. They can immediately benefit from CrowdSec to help secure infrastructure.</p>



<p class="wp-block-paragraph">We are committed to supporting both established and emerging API gateway solutions with meaningful adoption. Traefik and <a href="https://www.crowdsec.net/blog/the-haproxy-bouncer-is-out" target="_blank" rel="noreferrer noopener">HAProxy</a> are strong examples of this today, and multiple Envoy-based community implementations are also evolving in this space. We closely monitor these projects as they mature toward production readiness, so that CrowdSec users can rely on them with confidence.</p>



<h2 class="wp-block-heading">Next Steps for Kubernetes: Transitioning to Gateway API with CrowdSec</h2>



<p class="wp-block-paragraph">Our goal is to help users navigate this transition as smoothly as possible.</p>



<p class="wp-block-paragraph"><a href="https://www.crowdsec.net/integrations" target="_blank" rel="noreferrer noopener">Several Gateway API integrations</a> already exist, many of them developed by the community. When the underlying technology supports CrowdSec remediation, as is the case with Traefik, integration is straightforward. Our focus is now on validating these solutions and providing the necessary testing and documentation so they can be easily adopted and reliably used by the community.</p>



<p class="wp-block-paragraph">In parallel, we are committed to producing documentation that will guide users through the transition from traditional ingress controllers to API Gateway-based architectures.</p>



<p class="wp-block-paragraph">This will include practical migration guidance, configuration examples, and recommendations on which Gateway implementations work well with CrowdSec.&nbsp;</p>



<h2 class="wp-block-heading">Looking forward</h2>



<p class="wp-block-paragraph">Ingress controllers played a key role in the Kubernetes ecosystem for many years, but the landscape is evolving. As Kubernetes provides a new Gateway API, users can run CrowdSec alongside it.</p>



<p class="wp-block-paragraph">In the meantime, <strong>users relying on Ingress-NGINX can continue using CrowdSec remediation with the latest supported versions</strong>. However, teams planning new deployments or long-term architectures are encouraged to start considering alternatives, such as adopting the Gateway API or using another supported ingress controller, since Ingress-NGINX is the only controller being gradually phased out.</p>



<p class="wp-block-paragraph">We are closely monitoring the Gateway API ecosystem and its evolution. Our goal is to support solutions with a strong, growing user base, ensuring that <strong>CrowdSec integrates where it can deliver the most value</strong>.&nbsp;</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
            <category>Inside CrowdSec</category>
            <category>Integrations</category>
            <enclosure url="https://cms.crowdsec.net/wp-content/uploads/2026/03/243.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Vulnerability 101: Understanding Security Weaknesses]]></title>
            <link>https://crowdsec.net/blog/vulnerability-101-understanding-security-weaknesses</link>
            <guid isPermaLink="false">https://crowdsec.net/blog/vulnerability-101-understanding-security-weaknesses</guid>
            <pubDate>Thu, 05 Mar 2026 08:44:54 GMT</pubDate>
            <description><![CDATA[<p>Learn the difference between vulnerabilities, threats, and risks, and how understanding their lifecycle helps prevent security incidents.</p>
]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">Around security discussions, you&#8217;ve probably noticed the words &#8220;vulnerability,&#8221; &#8220;threat,&#8221; and &#8220;risk&#8221; being used more or less interchangeably. They&#8217;re not. Mixing them up isn&#8217;t just imprecise; it leads to bad prioritization, wasted effort, and the occasional 3 AM incident that could have been avoided.&nbsp;</p>



<p class="wp-block-paragraph">This article breaks down what each term actually means, how vulnerabilities move through their lifecycle, and what that means for you as a developer or maintainer.</p>



<h2 class="wp-block-heading">What even is a vulnerability?</h2>



<p class="wp-block-paragraph">At its core, a <strong>vulnerability is a weakness in a system that someone could exploit</strong>. <a href="https://www.crowdsec.net/blog/5-common-vulnerability-myths" target="_blank" rel="noreferrer noopener">It doesn&#8217;t have to be a dramatic zero-day in a cryptography library.</a> It can be something simply mundane, like a forgotten admin endpoint with default credentials, or a dependency that hasn&#8217;t been updated in two years.</p>



<p class="wp-block-paragraph">Vulnerabilities tend to fall into a few buckets:</p>



<ul class="wp-block-list">
<li><strong>Software bugs:</strong> Logic errors, memory issues, improper input handling, the kinds of things that slip through code review and only reveal themselves under the right (wrong) conditions.</li>



<li><strong>Misconfiguration: </strong><a href="https://www.crowdsec.net/glossary/port-security" target="_blank" rel="noreferrer noopener">Open ports that shouldn&#8217;t be open</a>, overly permissive IAM roles, debug endpoints left on in production. Config issues are responsible for a huge proportion of real-world breaches.</li>



<li><strong>Human factors: </strong>Weak passwords, phishing susceptibility, and the developer who commits an API key because they were in a hurry. This category is frustratingly durable.</li>
</ul>



<p class="wp-block-paragraph">The key thing to understand: a vulnerability sitting quietly in your codebase isn&#8217;t actively hurting anyone. It only becomes a problem when someone finds it and does something with it.</p>



<h2 class="wp-block-heading">Vulnerability vs. threat vs. risk</h2>



<p class="wp-block-paragraph">These three concepts form a chain, and understanding the chain is what lets you make sensible decisions about where to spend your time.</p>



<ul class="wp-block-list">
<li>Vulnerability is the weakness (an unpatched library, a misconfigured firewall rule).</li>



<li>A threat is the actor or mechanism that could exploit it (an attacker, an automated scanner sweeping the internet).</li>



<li>Risk is what you&#8217;re actually trying to manage: the likelihood that the threat exploits the vulnerability, multiplied by the impact if it does.</li>
</ul>



<p class="wp-block-paragraph">A concrete example: say your app has an admin panel exposed to the internet with default credentials still set. That&#8217;s your vulnerability. The threat is anyone running credential-stuffing tools against it, and there are plenty of those running 24/7. The risk is high because both the likelihood and the potential impact (full admin access) are significant.</p>



<p class="wp-block-paragraph">Contrast that with a theoretical XSS in an internal tool used by two people on your team. Still a vulnerability, much lower risk. This framing is how you avoid treating every <a href="https://tracker.crowdsec.net/cves" target="_blank" rel="noreferrer noopener">CVE</a> as an alarm fire.</p>



<h2 class="wp-block-heading">The vulnerability lifecycle</h2>



<p class="wp-block-paragraph">Vulnerabilities don&#8217;t just appear and disappear. They go through a lifecycle, and where you are in that lifecycle dramatically affects how much danger you&#8217;re in.</p>



<h3 class="wp-block-heading">Discovery</h3>



<p class="wp-block-paragraph">Someone finds the vulnerability: a security researcher, a developer doing a code review, a bug bounty hunter, or an attacker. The discoverer&#8217;s intentions shape everything that happens next.</p>



<h3 class="wp-block-heading">Disclosure</h3>



<p class="wp-block-paragraph">How the vulnerability gets reported matters a lot. There are three main patterns in the wild:</p>



<ul class="wp-block-list">
<li><strong>Responsible disclosure: </strong>The researcher contacts the vendor privately and gives them time to fix it before going public.</li>



<li><strong>Coordinated disclosure:</strong> A deadline is set, typically 90 days, after which the details go public regardless of whether a patch exists. This is now the dominant approach, popularized by Google Project Zero and CERT/CC. It puts real pressure on vendors to actually ship fixes rather than quietly hoping nobody notices.</li>



<li><strong>Full disclosure:</strong> Everything goes public immediately. Controversial, but the argument is that it forces faster action and gives defenders the information they need without waiting on vendor timelines.</li>
</ul>



<h3 class="wp-block-heading">The exploitation window</h3>



<p class="wp-block-paragraph">This is where things get uncomfortable. Between when a <a href="https://www.crowdsec.net/vulntracking-report" target="_blank" rel="noreferrer noopener">vulnerability is discovered</a> (or disclosed) and when it&#8217;s actually patched across affected systems, there&#8217;s a window of exposure. For zero-days, vulnerabilities being actively exploited before any patch exists, that window starts the moment the attacker finds it.</p>



<p class="wp-block-paragraph">The exploitation window is why patch speed matters so much. It&#8217;s not theoretical. <strong>Once a CVE drops with a working proof-of-concept attached, mass exploitation often starts within hours.</strong></p>



<h3 class="wp-block-heading">Patch and remediation</h3>



<p class="wp-block-paragraph">The vendor ships a fix. In the open source world, this usually means a PR gets merged, a new version gets tagged, and a CVE gets assigned with a CVSS score that tells you how severe the issue is considered to be. CVSS isn&#8217;t perfect; it doesn&#8217;t account for your specific environment, but it&#8217;s a useful first filter for prioritization.</p>



<h3 class="wp-block-heading">Resolution and monitoring</h3>



<p class="wp-block-paragraph">Applying the patch is not the end of the story. <strong>You need to verify it&#8217;s actually deployed everywhere it needs to be,</strong> and keep watching for signs that exploitation happened before you patched. Logs, alerts, anomaly detection, and the operational work that actually catches incidents.</p>



<h2 class="wp-block-heading">How vulnerabilities get found</h2>



<p class="wp-block-paragraph">In practice, most vulnerabilities surface through one of these channels:</p>



<ul class="wp-block-list">
<li><strong>Security audits and code review: </strong>Organizations <a href="https://www.crowdsec.net/glossary/what-is-proactive-cybersecurity" target="_blank" rel="noreferrer noopener">proactively scan</a> their own systems and codebases for weaknesses.</li>



<li><strong>Bug bounty programs:</strong> External researchers are incentivized to find and responsibly report vulnerabilities.</li>



<li><strong>Accidental discovery:</strong> Users or developers notice unexpected behavior that points to a weakness.</li>



<li><strong>Malicious discovery:</strong> Attackers actively probe systems to find exploitable vulnerabilities.</li>
</ul>



<h2 class="wp-block-heading">When a vulnerability becomes an incident</h2>



<p class="wp-block-paragraph">Most vulnerabilities never turn into incidents. The ones that do tend to share a common thread: <strong>they were known, there was a fix available, and nobody got around to applying it.</strong></p>



<p class="wp-block-paragraph">An incident happens when a vulnerability gets actively exploited and causes real harm, data exfiltration, services taken down, and credentials compromised. At that point, you&#8217;re no longer in vulnerability management territory; you&#8217;re in incident response, which is a different and much more stressful.</p>



<p class="wp-block-paragraph">Staying on the right side of that line comes down to a few practical habits: patch quickly (especially anything with a high CVSS score or active exploitation reports), <a href="https://www.crowdsec.net/blog/am-i-under-attack" target="_blank" rel="noreferrer noopener">monitor for anomalous behavior</a>, and treat your <a href="https://www.crowdsec.net/live-exploit-tracker" target="_blank" rel="noreferrer noopener">CVE feed as something worth actually reading</a>.</p>



<h2 class="wp-block-heading">Why does this matter if you&#8217;re building or maintaining software</h2>



<p class="wp-block-paragraph">If you&#8217;re a developer, the decisions you make, which dependencies you pull in, how you configure your deployment, and whether you have a process for responding to vulnerability disclosures in your own project, have downstream consequences for everyone using your software.</p>



<p class="wp-block-paragraph">Open source maintainers carry a particular version of this responsibility. When a vulnerability is found in a widely-used package, the blast radius can be enormous. <strong>Having a clear disclosure policy, staying reachable, and shipping fixes promptly are part of what makes open source software trustworthy</strong>.</p>



<p class="wp-block-paragraph">Security doesn&#8217;t have a finish line. Understanding <a href="https://www.crowdsec.net/blog/5-common-vulnerability-myths" target="_blank" rel="noreferrer noopener">how vulnerabilities work</a>, how they get discovered, and how they move from disclosure to exploitation is the foundation for making better decisions across the whole chain, from the code you write to the alerts you triage at midnight.</p>
]]></content:encoded>
            <category>Proactive Cybersecurity</category>
            <category>Vulnerabilities</category>
            <enclosure url="https://cms.crowdsec.net/wp-content/uploads/2026/03/239.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[CrowdSec MCP: Life Is Too Short for YAML]]></title>
            <link>https://crowdsec.net/blog/crowdsec-mcp-use-ai-to-write-waf-rules-automatically</link>
            <guid isPermaLink="false">https://crowdsec.net/blog/crowdsec-mcp-use-ai-to-write-waf-rules-automatically</guid>
            <pubDate>Wed, 25 Feb 2026 08:57:03 GMT</pubDate>
            <description><![CDATA[<p>Learn how the CrowdSec Model Context Protocol (MCP) helps you leverage LLMs to automatically write reliable WAF rules &#038; scenarios without writing complex YAML.</p>
]]></description>
            <content:encoded><![CDATA[
<p class="wp-block-paragraph">Writing <a href="https://www.crowdsec.net/glossary/web-application-firewall" target="_blank" rel="noreferrer noopener">Web Application Firewall (WAF)</a> rules can be tedious and complex, especially when dealing with convoluted YAML syntax. To solve this, we recently published a <a href="https://github.com/crowdsecurity/crowdsec-local-mcp" target="_blank" rel="noreferrer noopener">Model Context Protocol (MCP) for CrowdSec</a>, specifically focused on helping users leverage AI to automatically write scenarios and WAF rules for the <a href="https://www.crowdsec.net/security-engine" target="_blank" rel="noreferrer noopener">CrowdSec Security Engine</a>.</p>



<p class="wp-block-paragraph">First of all, an <strong>MCP (Model Context Protocol)</strong> is an open-standard specification that enables Large Language Models to securely and consistently connect to external data sources and local tools, essentially acting as a &#8220;USB port&#8221; for AI to interact with your files, databases, and APIs.</p>



<p class="wp-block-paragraph">This extract from our internal tooling has drastically improved our ability to create virtual patching rules for the WAF and internal detection rules for our data lake. In our context, the CrowdSec MCP allows a user to simply instruct their favourite LLM, like ChatGPT, Claude, Gemini, and others, and say, “<em>Hey, look up this blog post and create a WAF rule for it</em>”, and end up with a highly accurate result.</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="508" src="https://cms.crowdsec.net/wp-content/uploads/2026/02/image-12-1024x508.png" alt="" class="wp-image-6478" srcset="https://cms.crowdsec.net/wp-content/uploads/2026/02/image-12-1024x508.png 1024w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-12-300x149.png 300w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-12-768x381.png 768w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-12-1536x762.png 1536w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-12.png 1600w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="465" src="https://cms.crowdsec.net/wp-content/uploads/2026/02/image-10-1024x465.png" alt="" class="wp-image-6476" srcset="https://cms.crowdsec.net/wp-content/uploads/2026/02/image-10-1024x465.png 1024w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-10-300x136.png 300w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-10-768x348.png 768w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-10-1536x697.png 1536w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-10.png 1598w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">In the era of rapidly progressing LLMs, this might sound like a normal Monday morning. But achieving consistent, production-ready <a href="https://www.crowdsec.net/blog/crowdsec-waf-from-first-steps-to-advanced-deployments" target="_blank" rel="noreferrer noopener">WAF</a> rules takes a few tricks; let’s not forget, there is no magic. Here is a walk-through of the process that brought us here and the lessons we learned along the way.</p>



<h2 class="wp-block-heading">Why Automate WAF Rules with AI?</h2>



<p class="wp-block-paragraph">Let’s cut to the chase: writing WAF rules is tedious, and life is too short to write YAML. At CrowdSec, we track about <strong>100 new vulnerabilities every month</strong>, which means writing just as many <a href="https://github.com/crowdsecurity/hub/pull/1691" target="_blank" rel="noreferrer noopener">YAML WAF rules with accompanying tests</a>, often with convoluted syntax. The initial need was clear: we wanted to increase our ability to produce reliable WAF rules while reducing as much as possible the human bandwidth spent writing WAF rules or scenarios.</p>



<p class="wp-block-paragraph">We already leveraged LLMs to industrialise those processes and increase their velocity, but the way MCP interacts with LLM allowed us to take it one step further.</p>



<h2 class="wp-block-heading">Why We Built a Model Context Protocol (MCP) for CrowdSec?</h2>



<p class="wp-block-paragraph">In our first iterations, we exploited <a href="http://openai.com/" target="_blank" rel="noreferrer noopener">OpenAI</a> APIs directly with some custom prompts, including examples, instructions, etc. However, even if you can set the temperature of the LLM when using the API, the results aren’t consistent, as you might know. As a result, the generated rules are sometimes correct and sometimes incorrect.</p>



<p class="wp-block-paragraph">When things went right, everybody was happy, but when things went wrong (hallucinations, misunderstandings from the LLM), the human had to take over and start over. This was both a source of frustration and a waste of time, with every iteration providing results of varying quality. We quickly identified that a feedback loop was needed.</p>



<p class="wp-block-paragraph">Last but not least, previously developed tooling had strong adherence to some input formats &#8211; such as nuclei templates &#8211; and we wanted natural language understanding to achieve the same result from less structured sources, such as blog posts or write-ups.</p>



<p class="wp-block-paragraph">Both these reasons make a good case for experimenting with MCPs.</p>



<h2 class="wp-block-heading">Overcoming LLM Limitations in WAF Rule Generation</h2>



<p class="wp-block-paragraph">On tasks that sound rather simple, an LLM might fail spectacularly, especially if it involves a lot of small steps to follow meticulously. Providing the LLM with tools (such as linting, syntax validation, etc.) is a great way to introduce feedback loops, giving it a chance to identify its mistakes and correct itself, and that’s what makes MCP attractive in this use case.</p>



<h2 class="wp-block-heading">Feedback Loop is Key</h2>



<p class="wp-block-paragraph">In the context of an MCP, given that you provide enough tools, it will correct itself over time when it starts hallucinating. As a concrete example, the first steps of the MCP usage by the LLM to generate a WAF rule look like:</p>



<ol class="wp-block-list">
<li>Get “WAF rules syntax” prompt from MCP: it covers the syntax of the WAF rules, with some do’s and don’ts.</li>



<li>Submit the generated WAF rule to the “syntax validation” tool: it will validate the generated YAML based on our public YAML schemas.</li>
</ol>



<p class="wp-block-paragraph">Simply introducing this 2nd step allows the LLM to understand that it generated an incorrect YAML document, go back to the 1st step, make another attempt, and go back to the 2nd step until it gets proper validation. Providing the LLM with specific steps to follow enables iterative refinement, significantly improving the overall quality of generated results.</p>



<p class="wp-block-paragraph">In the example above, we see self-correction via a feedback loop: upon getting the prompt “WAF rule challenge” that aims at identifying common mistakes or suboptimal patterns, the LLM corrects itself:&nbsp;</p>



<p class="wp-block-paragraph">&gt; Actually, since the guidelines say only use OR or AND (not both), and the paths differ, but the injection pattern is the same, I&#8217;ll create a single rule using a URI regex to match both paths with the AND condition on the injection:</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="474" src="https://cms.crowdsec.net/wp-content/uploads/2026/02/image-11-1024x474.png" alt="" class="wp-image-6477" srcset="https://cms.crowdsec.net/wp-content/uploads/2026/02/image-11-1024x474.png 1024w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-11-300x139.png 300w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-11-768x356.png 768w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-11-1536x711.png 1536w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-11.png 1600w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">Let’s not forget about consistency</h2>



<p class="wp-block-paragraph">Another key advantage of MCP integration is consistency. As one might know, while LLMs excel at a lot of things, consistency isn’t their forte. By exposing the right tools and prompts to the LLM, we can strongly mitigate the issue:</p>



<ul class="wp-block-list">
<li>Sometimes, a few lines of code will succeed where a smarter LLM might erratically fail. Identifying those choke points and exposing the tools at the right time allows us to get around the LLM&#8217;s inconsistencies.</li>



<li>Exposing clear steps to the LLM with various subprompts helps ensure that validation steps aren’t skipped (which might otherwise happen). Typically, forcing the LLM to analyse previously generated data with a different prompt enables it to perform <strong>self-correction</strong> and pushes consistency one step further.</li>
</ul>



<p class="wp-block-paragraph">Thus, MCPs sometimes allow for filling gaps in the LLM’s ability. In the example below, the LLM generated a syntaxically correct rule, but upon testing the WAF rule against an actual exploit, it realizes its own mistake:</p>



<figure class="wp-block-image aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="171" src="https://cms.crowdsec.net/wp-content/uploads/2026/02/image-9-1024x171.png" alt="" class="wp-image-6475" srcset="https://cms.crowdsec.net/wp-content/uploads/2026/02/image-9-1024x171.png 1024w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-9-300x50.png 300w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-9-768x128.png 768w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-9-1536x256.png 1536w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-9.png 1600w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">More tools for better results</h2>



<p class="wp-block-paragraph">However, to obtain a correct <a href="https://www.crowdsec.net/blog/strengthen-security-with-crowdsec-open-source-waf" target="_blank" rel="noreferrer noopener">WAF</a> rule, it isn’t enough to produce a syntaxically correct document, as there are many pitfalls:</p>



<ul class="wp-block-list">
<li>The rule might be too broad or too narrow.</li>



<li>The rule might simply not match the exploit.</li>



<li>The rule isn’t following best practices.</li>



<li>The rule needs to include a proper test case for regression and false-positive detection.</li>
</ul>



<p class="wp-block-paragraph">To achieve all this, we provide the MCP with <strong>more than 20 tools that will help it at the various steps of the process</strong>, so that the overall workflow looks like:</p>



<figure class="wp-block-image aligncenter size-full"><img loading="lazy" decoding="async" width="589" height="477" src="https://cms.crowdsec.net/wp-content/uploads/2026/02/image-8.png" alt="" class="wp-image-6474" srcset="https://cms.crowdsec.net/wp-content/uploads/2026/02/image-8.png 589w, https://cms.crowdsec.net/wp-content/uploads/2026/02/image-8-300x243.png 300w" sizes="auto, (max-width: 589px) 100vw, 589px" /></figure>



<h2 class="wp-block-heading">Identified limitations</h2>



<p class="wp-block-paragraph">While we’re overall very satisfied with the current results produced by the LLM and how it drastically allowed us to reduce production time of rules while increasing the consistency of the results, there are a few key points that need to be kept in mind when designing such systems:</p>



<ul class="wp-block-list">
<li>LLMs get overwhelmed when there is too much data: On other features that implied interacting with verbose APIs, we repeatedly observed the LLMs getting confused or lost when dealing with MCP outputs. You often need to find bypasses or shortcuts to avoid exposing the LLM to significant volumes of data, or risk inconsistent results &#8211; most likely due to context size.</li>



<li>LLMs lack consistency: sometimes, we had to expose or provide tools that might seem overly basic, simply because the LLM repeatedly failed on steps that seemed trivial. It was specifically true when you step away from creative tasks, and instead ask LLM to verify a document against a yaml/JSON schema.</li>



<li>Prompt engineering: while it might sound really stupid, we observed very different output quality depending on <strong>how</strong> the prompt was formatted, rather than what was inside.</li>
</ul>



<div class="inline-cta-small">
    <a href="https://github.com/crowdsecurity/crowdsec-local-mcp" target="_blank" rel="noopener noreferrer">Get your hands on our open-source MCP, and give it a shot!</a>
</div>
]]></content:encoded>
            <category>Inside CrowdSec</category>
            <enclosure url="https://cms.crowdsec.net/wp-content/uploads/2026/02/237.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>