Skip to content

Tuning & False Positives

Understanding Regex Patterns

Regex Delimiter Requirement

IMPORTANT: All regex patterns MUST include delimiters. The firewall validates regex patterns and will reject patterns without proper delimiters.

Valid patterns:

- path@regex:#^/test\.php$#        # Using # delimiter
- path@regex:/test\.php$/          # Using / delimiter
- path@regex:@test\.php$@          # Using @ delimiter
- path@regex:~test\.php$~          # Using ~ delimiter

Invalid patterns (will be rejected):

- path@regex:^/test\.php$          # Missing delimiters - INVALID
- path@regex:test\.php             # Missing delimiters - INVALID

Choosing Delimiters

Choose a delimiter that doesn't appear in your pattern to avoid excessive escaping: - Use # for paths with slashes: #^/test/# - Use / for simple patterns: /test/ - Use @ if pattern contains / and #: @test@

Understanding the Generic PHP Block

The malicious-urls.yml preset includes this powerful catch-all rule:

- path@regex:#(?<!index)\.php(\?.*)?$#

This blocks any PHP file except index.php. This is useful for: - Preventing execution of uploaded malicious PHP files - Blocking common backdoor file names - Stopping unknown exploits

When to Use This Rule

Use when: - Running a CMS (WordPress, Drupal) where all requests go through index.php - You have a front-controller pattern (Laravel, Symfony) - Maximum security is required

Don't use when: - You have legitimate PHP files at root level (contact.php, api.php) - Your application doesn't use a front-controller pattern - You need to access various PHP files directly

Adapting the Generic PHP Block

If you need to allow specific PHP files, add an allow entry to your plugins: array:

plugins:
  - plugin: "Kanopi\\Firewall\\Plugins\\Url"
    response: allow
    weight: -200
    enable: true
    config:
      - path:/contact.php
      - path:/api.php
      - path:/webhook.php
      - path@regex:#^/api/.*\.php$#  # Allow all files in /api/ directory

Or modify the preset to exclude specific patterns:

# Allow PHP files in /api/ and /public/ directories
- type: AND
  rules:
    - path@regex:(?<!index)\.php(\?.*)?$
    - path@not_contains:/api/
    - path@not_contains:/public/

Common False Positives

Legitimate PHP Files Blocked

Problem: Your application has legitimate PHP files that are blocked.

Solution: Add an allow entry for those specific files:

plugins:
  - plugin: "Kanopi\\Firewall\\Plugins\\Url"
    response: allow
    weight: -200
    enable: true
    config:
      - path:/my-file.php

WordPress Plugin/Theme Files Blocked

Problem: WordPress plugins or themes have PHP files that are being blocked.

Solution: The preset already allows /wp-content/plugins/ and /wp-content/themes/. If you're still having issues:

plugins:
  - plugin: "Kanopi\\Firewall\\Plugins\\Url"
    response: allow
    weight: -200
    enable: true
    config:
      - path@starts_with:/wp-content/plugins/
      - path@starts_with:/wp-content/themes/

API Endpoints Blocked

Problem: Your REST API endpoints are being blocked.

Solution: Add specific allow entries for API routes:

plugins:
  - plugin: "Kanopi\\Firewall\\Plugins\\Url"
    response: allow
    weight: -200
    enable: true
    config:
      - path@starts_with:/api/
      - path@starts_with:/wp-json/

Drupal false positives

/user/<id> versus /user/login. drupal-admin.yml names the authentication routes individually rather than blocking the /user prefix, because public user profiles live at /user/123 on a great many sites. If yours has no public profiles, blocking path@starts_with:/user is tighter — but check first.

Language prefixes. A multilingual site serves /es/user/login as well as /user/login. The preset covers a two-letter code with an optional region (/pt-br/user/login); a site using a longer or non-standard prefix needs its own rule.

Drupal 7 query routing is deliberately absent. ?q=admin and ?q=user/login are not blocked, for two reasons found by testing rather than reasoning. q is the parameter a great many search forms use, so ?q=admin is a visitor searching for the word "admin" at least as often as it is someone reaching the back end. And the rule would not work anyway: the separator arrives percent-encoded (q=admin%2Fcontent), so a q=admin/ pattern never matches. If you need it, match the encoded form and accept the search false positive.

/core/ is not blocked wholesale. Core serves its CSS and JavaScript from /core/misc/ and /core/assets/, so only named files and the scripts, tests, and module-test directories are blocked. Adding path@starts_with:/core/ will unstyle the site.

Uploaded media stays reachable. Only executable extensions under /sites/*/files/ are blocked. Blocking the directory outright takes every image and document with it.

/admin prefix matching. path@starts_with:/admin/ plus an exact path:/admin is deliberate — it means /administrative-services and /news/admin-appointed keep working, which a bare path@contains:/admin would break.

Search crawler allow: what to check

The scope is the safety. search-bots.yml is only defensible because the allow does not cover the admin surface. If you copy it and drop the path exclusion, you have published a firewall bypass that anyone can use with curl -A Googlebot.

Add your own admin path. The exclusion knows about WordPress and Drupal. A custom admin route, a headless CMS endpoint, or an API you do not want crawled needs adding to the same rule.

Watch the -Extended names. Applebot and Applebot-Extended are different crawlers that mean opposite things, and one contains the other as a substring. The preset excludes -Extended explicitly for this reason. If you add a crawler to the list by hand, check whether its name is a prefix of something you do not want to allow.

Google-Extended is not Googlebot. Blocking Googlebot deindexes the site; blocking Google-Extended only opts out of training collection. They are on different lists here on purpose.