[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: sandrogauci, 0xInfection\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nCommand that reproduces the issue. e.g. `wafw00f http://example.org -a -vv`\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. Windows, Linux]\n - OS version, distribution: \n - Python version: [e.g. python 3.2]\n\n**Debug output**\nPaste the output that you get when passing `-vv` to wafw00f. Example:\n\n```\n[*] Checking http://www.example.com\nINFO:wafw00f:starting wafw00f on http://www.example.com\nINFO:wafw00f:Request Succeeded\nINFO:wafw00f:Request Succeeded\nINFO:wafw00f:Checking for ACE XML Gateway (Cisco)\nINFO:wafw00f:Checking for aeSecure (aeSecure)\nINFO:wafw00f:Checking for AireeCDN (Airee)\nINFO:wafw00f:Checking for Airlock (Phion/Ergon)\nINFO:wafw00f:Checking for Alert Logic (Alert Logic)\nINFO:wafw00f:Checking for AliYunDun (Alibaba Cloud Computing)\nINFO:wafw00f:Checking for Anquanbao (Anquanbao)\nINFO:wafw00f:Checking for AnYu (AnYu Technologies)\nINFO:wafw00f:Checking for Approach (Approach)\nINFO:wafw00f:Checking for AppWall (Radware)\nINFO:wafw00f:Checking for Armor Defense (Armor)\nINFO:wafw00f:Checking for ArvanCloud (ArvanCloud)\nINFO:wafw00f:Checking for ASP.NET Generic (Microsoft)\nINFO:wafw00f:Checking for ASPA Firewall (ASPA Engineering Co.)\nINFO:wafw00f:Checking for Astra (Czar Securities)\nINFO:wafw00f:Checking for AzionCDN (AzionCDN)\nINFO:wafw00f:Checking for Barikode (Ethic Ninja)\nINFO:wafw00f:Checking for Barracuda (Barracuda Networks)\nINFO:wafw00f:Checking for Bekchy (Faydata Technologies Inc.)\nINFO:wafw00f:Checking for Beluga CDN (Beluga)\nINFO:wafw00f:Checking for BIG-IP Local Traffic Manager (F5 Networks)\nINFO:wafw00f:Checking for BinarySec (BinarySec)\nINFO:wafw00f:Checking for BitNinja (BitNinja)\nINFO:wafw00f:Checking for BlockDoS (BlockDoS)\nINFO:wafw00f:Checking for Bluedon (Bluedon IST)\nINFO:wafw00f:Checking for BulletProof Security Pro (AITpro Security)\nINFO:wafw00f:Checking for CacheWall (Varnish)\nINFO:wafw00f:Checking for CacheFly CDN (CacheFly)\nINFO:wafw00f:Checking for Comodo cWatch (Comodo CyberSecurity)\nINFO:wafw00f:Checking for CdnNS Application Gateway (CdnNs/WdidcNet)\nINFO:wafw00f:Checking for ChinaCache Load Balancer (ChinaCache)\nINFO:wafw00f:Checking for Chuang Yu Shield (Yunaq)\nINFO:wafw00f:Checking for Cloudbric (Penta Security)\nINFO:wafw00f:Checking for Cloudflare (Cloudflare Inc.)\nINFO:wafw00f:Checking for Cloudfloor (Cloudfloor DNS)\nINFO:wafw00f:Checking for Cloudfront (Amazon)\nINFO:wafw00f:Checking for CrawlProtect (Jean-Denis Brun)\nINFO:wafw00f:Checking for DataPower (IBM)\nINFO:wafw00f:Checking for DenyALL (Rohde & Schwarz CyberSecurity)\nINFO:wafw00f:Checking for Distil (Distil Networks)\nINFO:wafw00f:Checking for DOSarrest (DOSarrest Internet Security)\nINFO:wafw00f:Checking for DotDefender (Applicure Technologies)\nINFO:wafw00f:Checking for DynamicWeb Injection Check (DynamicWeb)\nINFO:wafw00f:Checking for Edgecast (Verizon Digital Media)\nINFO:wafw00f:Identified WAF: ['Edgecast (Verizon Digital Media)']\n[+] The site http://www.example.com is behind Edgecast (Verizon Digital Media) WAF.\n[~] Number of requests: 2\n```\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "#### Which category is this pull request?\n<!-- Check the boxes with 'x' like '[x]' -->\n- [ ] A new feature/enhancement.\n- [ ] Fix an issue/feature-request.\n- [ ] An improvement to existing modules.\n- [ ] Other (Please mention below).\n\n#### Where has this been tested?\n<!-- Check the boxes with 'x' like '[x]' -->\n- Python Version\n    - [ ] v3.x\n    - [ ] v2.x\n- Operating System:\n    - [ ] Linux (Please specify distro)\n    - [ ] Windows\n    - [ ] MacOS\n\n#### Does this close any currently open issues? \n[Mention any issue which this PR closes]\n\n#### Does this add any new dependency?\n[Mention if this PR includes any new library]\n\n#### Does this add any new command line switch/argument?\n[Mention if the changes add any new arguments like `--arg`]\n\n#### Any other comments you would like to make?\n[Anything more you'd want the reviewer to know]\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*.swp\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbin/\nbuild/\ndevelop-eggs/\ndist/\neggs/\ninclude/\nlocal/\nlib64/\nman/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.cache\nnosetests.xml\ncoverage.xml\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n\n# Rope\n.ropeproject\n\n# Django stuff:\n*.log\n*.pot\n\n# Sphinx documentation\ndocs/_build/\n\n*.csv\n*.json\n.idea/*\n.vscode/*\n.pypirc\n.venv/"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\nWAFW00F is a Web Application Firewall (WAF) fingerprinting and detection tool written in Python. It identifies WAF products protecting web applications through HTTP response analysis and behavioral testing.\n\n## Development Commands\n\n### Testing\n```bash\n# Run all tests\npytest\n\n# Run tests with verbose output\npytest -v\n\n# Run specific test file\npytest tests/test_evillib.py\n\n# Run specific test\npytest tests/test_evillib.py::TestWafToolsEngine::test_default_timeout\n\n# Run tests with coverage\npytest --cov=wafw00f --cov-report=term-missing\n```\n\n### Linting\n```bash\n# Run linter (prospector)\nprospector wafw00f --strictness veryhigh\n```\n\n### Building and Installing\n```bash\n# Install in development mode with dev dependencies\npip install -e .[dev,docs]\n\n# Build distribution packages\npython setup.py sdist bdist_wheel\n\n# Clean build artifacts\nmake clean\n```\n\n### Publishing New Release\n```bash\n# 1. Update version in wafw00f/__init__.py\n# 2. Update version in README.md (badge and ASCII art examples - 3 places)\n# 3. Run tests\npytest\n\n# 4. Build package\npython setup.py sdist bdist_wheel\n\n# 5. Upload to PyPI\ntwine upload dist/*\n\n# 6. Create GitHub release\ngh release create vX.Y.Z --title \"vX.Y.Z\" --notes \"Release notes here\"\n\n# 7. Commit version changes\ngit add wafw00f/__init__.py README.md\ngit commit -m \"Bump version to X.Y.Z\"\ngit push\n```\n\n## Architecture\n\n### Core Components\n\n**main.py** - Entry point and orchestration\n- `WAFW00F` class extends `waftoolsengine` and orchestrates the detection workflow\n- Contains attack payload definitions (XSS, SQLi, LFI, XXE, RCE)\n- Implements detection methods: `matchHeader()`, `matchStatus()`, `matchCookie()`, `matchContent()`, `matchReason()`\n- Provides attack request generators: `normalRequest()`, `xssAttack()`, `sqliAttack()`, `centralAttack()`, etc.\n- Two detection modes:\n  - `identwaf()`: Plugin-based detection using WAF-specific signatures\n  - `genericdetect()`: Behavioral detection when no plugin matches\n\n**manager.py** - Plugin loader\n- Dynamically discovers and loads all Python files from `wafw00f/plugins/`\n- Uses `importlib.util` for runtime module loading\n- Returns dictionary: `{plugin_name: plugin_module}`\n\n**wafprio.py** - Detection priority\n- Ordered list of 182 WAF names defining which plugins to check first\n- Optimization: fast header/cookie checks before complex logic\n- Plugins not in the list are still checked but after prioritized ones\n\n**evillib.py** - HTTP request engine\n- `waftoolsengine` class wraps the `requests` library\n- Enforces 100KB max response size to prevent hanging on streaming responses\n- Enforces timeout during response body reading (not just connection)\n- Default timeout: 7 seconds (configurable)\n- Disables SSL warnings for testing self-signed certificates\n- Streams responses in 8KB chunks\n\n### Detection Flow\n\n1. **Normal request**: Baseline HTTP request to establish normal behavior\n2. **Attack request**: `centralAttack()` sends combined XSS+SQLi+LFI payload\n3. **Plugin detection**: Iterate through prioritized plugins, each calling `is_waf(self)`\n4. **Generic detection**: If no plugin matches, analyze behavioral differences (status codes, headers, blocking)\n\n### Plugin System\n\nPlugins are minimal Python modules in `wafw00f/plugins/` with exactly 2 requirements:\n\n```python\nNAME = 'WAF Name (Manufacturer)'\n\ndef is_waf(self):\n    # 'self' is the WAFW00F instance\n    # Access: self.rq (normal response), self.attackres (attack response)\n    # Available methods: matchHeader, matchCookie, matchContent, matchStatus, matchReason\n    if self.matchHeader(('server', 'cloudflare')):\n        return True\n    return False\n```\n\n**Common detection patterns:**\n\n1. **Simple single-check** (e.g., `cloudflare.py`):\n   - Check for specific header, cookie, or content pattern\n\n2. **Multiple checks** (e.g., `incapsula.py`):\n   - Try several different signatures (OR logic)\n\n3. **Schema-based** (e.g., `modsecurity.py`):\n   - Multiple helper functions checking combinations of conditions (AND logic)\n   - Example: `check_schema_02()` requires both 403 status AND \"ModSecurity Action\" reason\n\n**Detection methods available to plugins:**\n- `matchHeader((name, pattern), attack=False)` - Regex match on header\n- `matchCookie(pattern, attack=False)` - Shortcut for Set-Cookie header\n- `matchContent(regex, attack=True)` - Regex match on response body\n- `matchStatus(code, attack=True)` - Match HTTP status code\n- `matchReason(phrase, attack=True)` - Match HTTP reason phrase\n\n## Adding New WAF Detection\n\n1. Create `wafw00f/plugins/newwaf.py`\n2. Define `NAME` constant with \"WAF Name (Manufacturer)\" format\n3. Define `is_waf(self)` function returning True/False\n4. Optionally add WAF name to `wafprio.py` for priority detection\n5. Add test to `tests/test_detection.py`:\n   ```python\n   @responses.activate\n   def test_detect_newwaf_by_header(self):\n       responses.add(responses.GET, 'https://example.com',\n                     headers={'Server': 'NewWAF'}, status=200)\n       engine = WAFW00F('https://example.com')\n       assert 'NewWAF' in engine.identwaf()\n   ```\n\n## Important Notes for Development\n\n### Timeout Handling (Issue #246)\nThe timeout parameter must be enforced during both:\n1. Connection establishment (handled by requests library)\n2. Response body reading (enforced manually in `evillib.py`)\n\nWhen modifying request logic, ensure timeouts are respected during streaming to prevent hangs on slow servers.\n\n### Response Size Limiting\nAlways use `stream=True` with requests and enforce `MAX_RESPONSE_SIZE` (100KB) to prevent memory issues and hanging on:\n- Streaming media servers (audio/video)\n- Infinite response generators\n- Large file downloads\n\n### Version Updates\nWhen bumping version, update **3 locations**:\n1. `wafw00f/__init__.py` - `__version__` variable\n2. `README.md` - Badge (line 18)\n3. `README.md` - ASCII art examples (lines 53 and 253)\n\n### Commit Messages\nFollow conventional format:\n- \"Fix X\" for bug fixes\n- \"Add X\" for new features\n- \"Update X\" for enhancements\n- Include issue references: \"Fix timeout enforcement (issue #246)\"\n\n### Testing WAF Plugins\nWhen testing plugin detection:\n- Use `@responses.activate` decorator\n- Mock HTTP responses with specific headers/content/status\n- Test both positive (WAF detected) and negative (not detected) cases\n- Check against actual attack responses when possible\n\n### Git Workflow\n- Main branch: `master`\n- Always run tests before committing\n- Push releases to both GitHub and PyPI\n- Create GitHub releases using `gh release create`\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\n advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\n address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at code@enablesecurity.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CREDITS.txt",
    "content": "===================\nTHE WAFW00F PROJECT\n===================\n\n$ AUTHORS\n  =======\n\n* Current Maintainers :-\n    - Sandro Gauci <sandro [at] enablesecurity [dot] com>\n    - Pinaki Mondal <0xinfection [at] gmail [dot] com>\n\n* Original Code by :-\n    - Sandro Gauci <sandro [at] enablesecurity [dot] com>\n    - Wendel G. Henrique <whenrique [at] trustwave [dot] com>\n\n$ CONTRIBUTORS\n  ============\n\nA number of people contributed in the past (in no particular order):\n\n- Sebastien Gioria <https://github.com/SPoint42>\n- W3AF (or Andres Riancho) <https://github.com/andresriancho>\n- Charlie Campbell <https://github.com/sinnur>\n- @j0eMcCray <https://github.com/joemccray>\n- Mathieu Dessus\n- David S. Langlands\n- Nmap's http-waf-fingerprint.nse / Hani Benhabiles\n- Denis Kolegov <https://github.com/dnkolegov>\n- kun a <https://github.com/akun>\n- Louis-Philippe Huberdeau <https://github.com/lphuberdeau>\n- Brendan Coles <https://github.com/bcoles>\n- Matt Foster <https://github.com/mattfoster>\n- g0tmi1k (?) <https://github.com/g0tmi1k>\n- MyKings <https://github.com/MyKings>\n\nIf you did contribute and somehow I didn't put your name in there, please do\nlet me know at: <sandro [at] enablesecurity [dot] com>.\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM python:3.11.9-alpine\nWORKDIR /usr/src/app\nCOPY . .\nRUN pip install .\nENTRYPOINT [ \"wafw00f\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2009-2026, WAFW00F Developers\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\n* Neither the name of the {organization} nor the names of its\n  contributors may be used to endorse or promote products derived from\n  this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include CREDITS.txt\ninclude LICENSE\ninclude MANIFEST.in\ninclude README.md\ninclude wafw00f/__init__.py\ninclude wafw00f/bin/wafw00f\n"
  },
  {
    "path": "Makefile",
    "content": "SRC_DIR = wafw00f\nDOC_DIR = docs\nMAKE = make\n\nall:\n\tmake install\n\tmake test\n\tmake html\n\tmake clean\n\ninstall:\n\tpip install -q -e .[dev,docs]\n\ntest:\n\tpytest\n\ntest-verbose:\n\tpytest -v --tb=short\n\ntest-coverage:\n\tpytest --cov=$(SRC_DIR) --cov-report=term-missing\n\nlint:\n\tprospector $(SRC_DIR) --strictness veryhigh\n\ntestall:\n\ttox\n\nhtml:\n\tcd $(DOC_DIR) && $(MAKE) html\n\nclean:\n\trm -rf *.egg-info build dist .coverage\n\tfind $(SRC_DIR) -name \"*.pyc\" | xargs rm -rf\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">\n  <a href=\"https://github.com/enablesecurity/wafw00f\"><img src=\"https://i.imgur.com/uAgp49o.png\" alt=\"wafw00f\"/></a>\n  <br>\n  WAFW00F\n</h1>\n<p align=\"center\">\n  <b>The Web Application Firewall Fingerprinting Tool.</b>\n  <br>\n  <b>\n    &mdash; From <a href=\"https://enablesecurity.com\">Enable Security</a>\n  </b>\n</p>\n<p align=\"center\">\n  <a href=\"https://docs.python.org/3/download.html\">\n    <img src=\"https://img.shields.io/badge/Python-3.10+-green.svg\">\n  </a>\n  <a href=\"https://github.com/EnableSecurity/wafw00f/releases\">\n    <img src=\"https://img.shields.io/badge/Version-2.4.2%20(stable)-blue.svg\">\n  </a>\n  <a href=\"https://github.com/EnableSecurity/wafw00f/blob/master/LICENSE\">\n    <img src=\"https://img.shields.io/badge/License-BSD%203%20Clause-orange.svg\">\n  </a>\n</p>\n\n## How does it work?\n\nTo do its magic, WAFW00F does the following:\n\n- Sends a _normal_ HTTP request and analyses the response; this identifies a\n  number of WAF solutions.\n- If that is not successful, it sends a number of (potentially malicious) HTTP\n  requests and uses simple logic to deduce which WAF it is.\n- If that is also not successful, it analyses the responses previously\n  returned and uses another simple algorithm to guess if a WAF or security\n  solution is actively responding to our attacks.\n\nFor further details, check out the source code on our [main repository](https://github.com/EnableSecurity/wafw00f).\n\n## What does it detect?\n\nWAFW00F can detect a number of firewalls, a list of which is as below:\n\n```\n$ wafw00f -l\n\n\n                  ?              ,.   (   .      )        .      \"\n          __        ??          (\"     )  )'     ,'        )  . (`     '`\n    (___()'`;   ???          .; )  ' (( (\" )    ;(,     ((  (  ;)  \"  )\")\n    /,___ /`                 _\"., ,._'_.,)_(..,( . )_  _' )_') (. _..( ' )\n    \\\\   \\\\                 |____|____|____|____|____|____|____|____|____|\n\n                                ~ WAFW00F : v2.4.2 ~\n                  ~ Sniffing Web Application Firewalls since 2009 ~\n\n[+] Can test for these WAFs:\n\n  WAF Name                        Manufacturer\n  --------                        ------------\n\n  360PanYun                        360 Technologies\n  360WangZhanBao                   360 Technologies\n  ACE XML Gateway                  Cisco\n  ASP.NET Generic                  Microsoft\n  ASPA Firewall                    ASPA Engineering Co.\n  AWS Elastic Load Balancer        Amazon\n  AireeCDN                         Airee\n  Airlock                          Phion/Ergon\n  Alert Logic                      Alert Logic\n  AliYunDun                        Alibaba Cloud Computing\n  AnYu                             AnYu Technologies\n  Anquanbao                        Anquanbao\n  Anubis                           Techaro\n  AppWall                          Radware\n  Approach                         Approach\n  Armor Defense                    Armor\n  ArvanCloud                       ArvanCloud\n  Astra                            Czar Securities\n  Azion Edge Firewall              Azion\n  Azure Application Gateway        Microsoft\n  Azure Front Door                 Microsoft\n  BIG-IP AP Manager                F5 Networks\n  BIG-IP AppSec Manager            F5 Networks\n  BIG-IP Local Traffic Manager     F5 Networks\n  Barikode                         Ethic Ninja\n  Barracuda                        Barracuda Networks\n  Baffin Bay                       Mastercard\n  Bekchy                           Faydata Technologies Inc.\n  Beluga CDN                       Beluga\n  BinarySec                        BinarySec\n  BitNinja                         BitNinja\n  BlockDoS                         BlockDoS\n  Bluedon                          Bluedon IST\n  BulletProof Security Pro         AITpro Security\n  CacheFly CDN                     CacheFly\n  CacheWall                        Varnish\n  CdnNS Application Gateway        CdnNs/WdidcNet\n  ChinaCache Load Balancer         ChinaCache\n  Chuang Yu Shield                 Yunaq\n  Cloud Protector                  Rohde & Schwarz CyberSecurity\n  Cloudbric                        Penta Security\n  Cloudflare                       Cloudflare Inc.\n  Cloudfloor                       Cloudfloor DNS\n  Cloudfront                       Amazon\n  Comodo cWatch                    Comodo CyberSecurity\n  CrawlProtect                     Jean-Denis Brun\n  DDoS-GUARD                       DDOS-GUARD CORP.\n  DOSarrest                        DOSarrest Internet Security\n  DataPower                        IBM\n  DenyALL                          Rohde & Schwarz CyberSecurity\n  Distil                           Distil Networks\n  DotDefender                      Applicure Technologies\n  DynamicWeb Injection Check       DynamicWeb\n  Edgecast                         Verizon Digital Media\n  Eisoo Cloud Firewall             Eisoo\n  Envoy                            EnvoyProxy\n  Expression Engine                EllisLab\n  Fastly                           Fastly CDN\n  FirePass                         F5 Networks\n  FortiGate                        Fortinet\n  FortiGuard                       Fortinet\n  FortiWeb                         Fortinet\n  GoDaddy Website Protection       GoDaddy\n  Google Cloud App Armor           Google Cloud\n  Greywizard                       Grey Wizard\n  Huawei Cloud Firewall            Huawei\n  HyperGuard                       Art of Defense\n  ISA Server                       Microsoft\n  Imunify360                       CloudLinux\n  Incapsula                        Imperva Inc.\n  IndusGuard                       Indusface\n  Instart DX                       Instart Logic\n  Janusec Application Gateway      Janusec\n  Jiasule                          Jiasule\n  KS-WAF                           KnownSec\n  Kemp LoadMaster                  Progress Software\n  KeyCDN                           KeyCDN\n  Kona SiteDefender                Akamai\n  LimeLight CDN                    LimeLight\n  Link11 WAAP                      Link11\n  LiteSpeed                        LiteSpeed Technologies\n  Malcare                          Inactiv\n  MaxCDN                           MaxCDN\n  Mission Control Shield           Mission Control\n  ModSecurity                      SpiderLabs\n  NAXSI                            NBS Systems\n  NSFocus                          NSFocus Global Inc.\n  Nemesida                         PentestIt\n  NetContinuum                     Barracuda Networks\n  NetScaler AppFirewall            Citrix Systems\n  NevisProxy                       AdNovum\n  Newdefend                        NewDefend\n  NexusGuard Firewall              NexusGuard\n  NinjaFirewall                    NinTechNet\n  NullDDoS Protection              NullDDoS\n  OnMessage Shield                 BlackBaud\n  Open-Resty Lua Nginx             FLOSS\n  Oracle Cloud                     Oracle\n  PT Application Firewall          Positive Technologies\n  Palo Alto Next Gen Firewall      Palo Alto Networks\n  PentaWAF                         Global Network Services\n  PerimeterX                       PerimeterX\n  PowerCDN                         PowerCDN\n  Profense                         ArmorLogic\n  Puhui                            Puhui\n  Qcloud                           Tencent Cloud\n  Qiniu                            Qiniu CDN\n  Qrator                           Qrator\n  RSFirewall                       RSJoomla!\n  RayWAF                           WebRay Solutions\n  Reblaze                          Reblaze\n  Reflected Networks               Reflected Networks\n  RequestValidationMode            Microsoft\n  SEnginx                          Neusoft\n  Sabre Firewall                   Sabre\n  Safe3 Web Firewall               Safe3\n  Safedog                          SafeDog\n  Safeline                         Chaitin Tech.\n  Scutum                           Secure Sky Technology Inc.\n  SecKing                          SecKing\n  SecuPress WP Security            SecuPress\n  Secure Entry                     United Security Providers\n  SecureSphere                     Imperva Inc.\n  ServerDefender VP                Port80 Software\n  Shadow Daemon                    Zecure\n  Shield Security                  One Dollar Plugin\n  SiteGround                       SiteGround\n  SiteGuard                        EG Secure Solutions Inc.\n  Sitelock                         TrueShield\n  SonicWall                        Dell\n  Squarespace                      Squarespace\n  SquidProxy IDS                   SquidProxy\n  StackPath                        StackPath\n  Sucuri CloudProxy                Sucuri Inc.\n  Tencent Cloud Firewall           Tencent Technologies\n  Teros                            Citrix Systems\n  ThreatX                          A10 Networks\n  Trafficshield                    F5 Networks\n  TransIP Web Firewall             TransIP\n  UEWaf                            UCloud\n  URLMaster SecurityCheck          iFinity/DotNetNuke\n  URLScan                          Microsoft\n  UTM Web Protection               Sophos\n  Variti                           Variti\n  Varnish                          OWASP\n  Vercel WAF                       Vercel\n  Viettel                          Cloudrity\n  VirusDie                         VirusDie LLC\n  WP Cerber Security               Cerber Tech\n  WTS-WAF                          WTS\n  Wallarm                          Wallarm Inc.\n  WatchGuard                       WatchGuard Technologies\n  WebARX                           WebARX Security Solutions\n  WebKnight                        AQTRONIX\n  WebLand                          WebLand\n  WebSEAL                          IBM\n  WebTotem                         WebTotem\n  West263 CDN                      West263CDN\n  Wordfence                        Defiant\n  XLabs Security WAF               XLabs\n  Xuanwudun                        Xuanwudun\n  YXLink                           YxLink Technologies\n  Yundun                           Yundun\n  Yunjiasu                         Baidu Cloud Computing\n  Yunsuo                           Yunsuo\n  ZScaler                          Accenture\n  Zenedge                          Zenedge\n  aeSecure                         aeSecure\n  eEye SecureIIS                   BeyondTrust\n  pkSecurity IDS                   pkSec\n  wpmudev WAF                      Incsub\n  Shieldon Firewall                Shieldon.io\n```\n\n## How do I use it?\n\nFirst, install the tools as described [here](#how-do-i-install-it).\n\nFor help you can make use of the `--help` option. The basic usage is to pass\nan URL as an argument. Example:\n```\n$   wafw00f https://example.org\n\n                   ______\n                  /      \\\n                 (  Woof! )\n                  \\  ____/                      )\n                  ,,                           ) (_\n             .-. -    _______                 ( |__|\n            ()``; |==|_______)                .)|__|\n            / ('        /|\\                  (  |__|\n        (  /  )        / | \\                  . |__|\n         \\(_)_))      /  |  \\                   |__|\n\n                    ~ WAFW00F : v2.4.2 ~\n    The Web Application Firewall Fingerprinting Toolkit\n\n[*] Checking https://example.org\n[+] The site https://example.org is behind Edgecast (Verizon Digital Media) WAF.\n[~] Number of requests: 2\n```\n\n## How do I install it?\n\n### Install from PyPI (recommended)\nRun:\n```\npython3 -m pip install wafw00f\n```\nor\n```\npip3 install wafw00f\n```\n\n### Via Docker\nIt is also possible to run it within a docker container. Clone this repository first and build the Docker image using:\n```\ndocker build . -t wafw00f\n```\nNow you can run:\n```\ndocker run --rm -it wafw00f https://example.com\n```\n\n### From source\n> NOTE: Be careful to not break your system packages while installing wafw00f. Use venv as and when required.\n\nClone the repository:\n```\ngit clone https://github.com/enablesecurity/wafw00f.git\n```\nThen:\n```\ncd wafw00f/\npython3 -m pip install .\n```\n\nOr, by using pipx directly:\n```\npipx install git+https://github.com/EnableSecurity/wafw00f.git\n```\n\n## Final Words\n\n__Questions?__ Pull up an [issue on GitHub Issue Tracker](https://github.com/enablesecurity/wafw00f/issues/new) or contact [me](mailto:sandro@enablesecurity.com).\n[Pull requests](https://github.com/enablesecurity/wafw00f/pulls), [ideas and issues](https://github.com/enablesecurity/wafw00f/issues) are highly welcome.\n\nSome useful links:\n\n- [Documentation/Wiki](https://github.com/enablesecurity/wafw00f/wiki/)\n- [Pypi Package Repository](https://pypi.org/project/wafw00f)\n\nPresently being developed and maintained by:\n\n- Sandro Gauci ([@SandroGauci](https://twitter.com/sandrogauci))\n- Pinaki Mondal ([@0xInfection](https://twitter.com/0xinfection))\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/wafw00f.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/wafw00f.qhc\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/wafw00f\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/wafw00f\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# wafw00f documentation build configuration file, created by\n# sphinx-quickstart on Thu May 15 20:04:22 2014.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport os\nimport sys\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))\nsys.path.insert(0, BASE_DIR)\nfrom wafw00f import __version__\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#sys.path.insert(0, os.path.abspath('.'))\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = []\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix of source filenames.\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = u'wafw00f'\ncopyright = u'2020, WAFW00F Developers'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = __version__\n# The full version, including alpha/beta/rc tags.\nrelease = __version__\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#language = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'default'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\n#html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n#html_extra_path = []\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'wafw00fdoc'\n\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n  ('index', 'wafw00f.tex', u'wafw00f Documentation',\n   u'sandrogauci', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    ('index', 'wafw00f', u'wafw00f Documentation',\n     [u'sandrogauci'], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n  ('index', 'wafw00f', u'wafw00f Documentation',\n   u'sandrogauci', 'wafw00f', 'One line description of project.',\n   'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. wafw00f documentation master file, created by\n   sphinx-quickstart on Thu May 15 20:04:22 2014.\n   You can adapt this file completely to your liking, but it should at least\n   contain the root `toctree` directive.\n\nWelcome to wafw00f's documentation!\n===================================\n\nContents:\n\n.. toctree::\n   :maxdepth: 2\n\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n\n"
  },
  {
    "path": "docs/wafw00f.8",
    "content": ".TH WAFW00F \"8\" \"October 2020\" \"wafw00f \" \"User Commands\"\n.SH NAME\nWAFW00F \\- Identify and fingerprint Web Application Firewall products\n.SH SYNOPSIS\n.B wafw00f \\fI\\,url1 \\/\\fR[\\fI\\,url2 \\/\\fR[\\fI\\,url3 \\/\\fR... ]]\n.SH DESCRIPTION\n.TP\nThe Web Application Firewall Identification and Fingerprinting Tool.\n.TP\n.TP\nTo do its magic, WAFW00F does the following:\nSends a normal HTTP request and analyses the response; this identifies a number of WAF solutions.\nIf that is not successful, it sends a number of (potentially malicious) HTTP requests and uses simple logic to deduce which WAF it is.\nIf that is also not successful, it analyses the responses previously returned and uses another simple algorithm to guess if a WAF or security solution is active>\n.SH OPTIONS\n.TP\n\\fB\\-h\\fR, \\fB\\-\\-help\\fR\nShow available options.\n.TP\n\\fB\\-v\\fR, \\fB\\-\\-verbose\\fR\nEnable verbosity \\- multiple \\fB\\-v\\fR options increase verbosity.\n.TP\n\\fB\\-a\\fR, \\fB\\-\\-findall\\fR\nFind all WAFs, do not stop testing on the first one.\n.TP\n\\fB\\-r\\fR, \\fB\\-\\-noredirect\\fR\nDo not follow redirections given by 3xx responses.\n.TP\n\\fB\\-t\\fR WAF, \\fB\\-\\-test\\fR=\\fI\\,WAF\\/\\fR\nTest for one specific WAF product.\n.TP\n\\fB\\-o\\fR OUTPUT, \\fB\\-\\-output\\fR=\\fI\\,OUTPUT\\/\\fR\nWrite output to csv, json or text file depending on file extension. For stdout, specify - as filename.\n.TP\n\\fB\\-f\\fR, \\fB\\-\\-format\\fR=\\fI\\,FORMAT\\/\\fR\nForce output format to csv, json or text.\n.TP\n\\fB\\-i\\fR INPUT, \\fB\\-\\-input\\fR=\\fI\\,INPUT\\/\\fR\nRead targets from a file. Input format can be csv, json or text. For csv and json, a `url` column name or element is required.\n.TP\n\\fB\\-l\\fR, \\fB\\-\\-list\\fR\nList all the WAFs that WAFW00F is able to detect.\n.TP\n\\fB\\-p\\fR PROXY, \\fB\\-\\-proxy\\fR=\\fI\\,PROXY\\/\\fR\nUse an HTTP proxy to perform requests, example: http://hostname:8080, socks5://hostname:1080.\n.TP\n\\fB\\-V\\fR, \\fB\\-\\-version\\fR\nPrint out the version.\n.TP\n\\fB\\-H\\fR FILE, \\fB\\-\\-headers\\fR=\\fI\\,FILE\\/\\fR\nPass custom headers, for example to overwrite the default user\\-agent string.\n.TP\n\\fB\\-T\\fR TIMEOUT, \\fB\\-\\-timeout\\fR=\\fI\\,TIMEOUT\\/\\fR\nSet the timeout for the requests.\n.TP\n\\fB\\-\\-no\\-colors\\fR\nDisable ANSI colors in output.\n.SH AUTHORS\nSandro Gauci (@SandroGauci)\n.br\nPinaki Mondal (@0xInfection)\n.SH REPORTING BUGS\nYou can report bugs at the project homepage issue tracker: <https://github.com/EnableSecurity/wafw00f/issues/>.\n.SH COPYRIGHT\nCopyright (C) 2009-2022 WAFW00F Developers. License: BSD 3-Clause <https://opensource.org/licenses/BSD-3-Clause>.\n.br\nThis is free software: you are free to modify and distribute under the terms as permitted by the license provided alongwith.\n.SH SEE ALSO\nFull documentation is available at: <https://github.com/EnableSecurity/wafw00f/wiki/>.\n.PP\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"setuptools>=64\", \"wheel\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[project]\nname = \"wafw00f\"\ndynamic = [\"version\"]\ndescription = \"The Web Application Firewall Fingerprinting Toolkit\"\nreadme = \"README.md\"\nlicense = \"BSD-3-Clause\"\nauthors = [\n    {name = \"Sandro Gauci\", email = \"sandro@enablesecurity.com\"}\n]\nkeywords = [\"waf\", \"firewall\", \"detector\", \"fingerprint\"]\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: System Administrators\",\n    \"Intended Audience :: Information Technology\",\n    \"Topic :: Internet\",\n    \"Topic :: Security\",\n    \"Topic :: System :: Networking :: Firewalls\",\n    \"Programming Language :: Python :: 3\",\n    \"Operating System :: OS Independent\",\n]\nrequires-python = \">=3.10\"\ndependencies = [\n    \"requests\",\n    \"requests[socks]\",\n]\n\n[project.optional-dependencies]\ndev = [\"prospector\", \"pytest\", \"pytest-mock\", \"responses\"]\ndocs = [\"Sphinx\"]\n\n[project.urls]\nHomepage = \"https://github.com/enablesecurity/wafw00f\"\n\"Bug Tracker\" = \"https://github.com/EnableSecurity/wafw00f/issues\"\nDocumentation = \"https://github.com/EnableSecurity/wafw00f/wiki\"\n\"Source Code\" = \"https://github.com/EnableSecurity/wafw00f/tree/master\"\n\n[project.scripts]\nwafw00f = \"wafw00f.main:main\"\n\n[tool.setuptools.dynamic]\nversion = {attr = \"wafw00f.__version__\"}\n\n[tool.setuptools.packages.find]\ninclude = [\"wafw00f*\"]\n\n[tool.pytest.ini_options]\ntestpaths = [\"tests\"]\npython_files = [\"test_*.py\"]\npython_functions = [\"test_*\"]\naddopts = \"-v\"\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "# wafw00f tests\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "\"\"\"Shared pytest fixtures for wafw00f tests.\"\"\"\n\nimport pytest\nfrom unittest.mock import MagicMock\n\n\nclass MockResponse:\n    \"\"\"Mock HTTP response for testing.\"\"\"\n\n    def __init__(self, status_code=200, headers=None, text='', reason='OK'):\n        self.status_code = status_code\n        self.headers = headers or {}\n        self.text = text\n        self.reason = reason\n        self._content = text.encode('utf-8') if isinstance(text, str) else text\n\n    @property\n    def content(self):\n        return self._content\n\n\n@pytest.fixture\ndef mock_response():\n    \"\"\"Factory fixture to create mock responses.\"\"\"\n    def _make_response(status_code=200, headers=None, text='', reason='OK'):\n        return MockResponse(status_code, headers, text, reason)\n    return _make_response\n\n\n@pytest.fixture\ndef wafw00f_instance():\n    \"\"\"Create a WAFW00F instance for testing.\"\"\"\n    from wafw00f.main import WAFW00F\n    return WAFW00F(target='https://example.com')\n"
  },
  {
    "path": "tests/test_detection.py",
    "content": "\"\"\"Integration tests for WAF detection.\"\"\"\n\nimport pytest\nimport responses\nfrom wafw00f.main import WAFW00F\n\n\nclass TestCloudflareDetection:\n    \"\"\"Tests for Cloudflare WAF detection.\"\"\"\n\n    @responses.activate\n    def test_detect_cloudflare_by_header(self):\n        \"\"\"Test detecting Cloudflare by server header.\"\"\"\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'Server': 'cloudflare'},\n            status=200\n        )\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'Server': 'cloudflare'},\n            status=200\n        )\n\n        waf = WAFW00F(target='https://example.com')\n        waf.rq = waf.Request()\n        waf.attackres = waf.Request()\n\n        from wafw00f.plugins import cloudflare\n        assert cloudflare.is_waf(waf)\n\n    @responses.activate\n    def test_detect_cloudflare_by_cf_ray(self):\n        \"\"\"Test detecting Cloudflare by CF-RAY header.\"\"\"\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'CF-RAY': '1234567890abcdef-LAX'},\n            status=200\n        )\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'CF-RAY': '1234567890abcdef-LAX'},\n            status=200\n        )\n\n        waf = WAFW00F(target='https://example.com')\n        waf.rq = waf.Request()\n        waf.attackres = waf.Request()\n\n        from wafw00f.plugins import cloudflare\n        assert cloudflare.is_waf(waf)\n\n\nclass TestFastlyDetection:\n    \"\"\"Tests for Fastly detection.\"\"\"\n\n    @responses.activate\n    def test_detect_fastly_by_request_id(self):\n        \"\"\"Test detecting Fastly by X-Fastly-Request-ID header.\"\"\"\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'X-Fastly-Request-ID': 'abc123def456'},\n            status=200\n        )\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'X-Fastly-Request-ID': 'abc123def456'},\n            status=200\n        )\n\n        waf = WAFW00F(target='https://example.com')\n        waf.rq = waf.Request()\n        waf.attackres = waf.Request()\n\n        from wafw00f.plugins import fastly\n        assert fastly.is_waf(waf)\n\n    @responses.activate\n    def test_detect_fastly_by_served_by(self):\n        \"\"\"Test detecting Fastly by X-Served-By header.\"\"\"\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'X-Served-By': 'cache-sjc10049-SJC'},\n            status=200\n        )\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'X-Served-By': 'cache-sjc10049-SJC'},\n            status=200\n        )\n\n        waf = WAFW00F(target='https://example.com')\n        waf.rq = waf.Request()\n        waf.attackres = waf.Request()\n\n        from wafw00f.plugins import fastly\n        assert fastly.is_waf(waf)\n\n\nclass TestAWSWAFDetection:\n    \"\"\"Tests for AWS WAF detection.\"\"\"\n\n    @responses.activate\n    def test_detect_awswaf_by_header(self):\n        \"\"\"Test detecting AWS WAF by X-AMZ-ID header.\"\"\"\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'X-AMZ-ID': 'abc123xyz'},\n            status=200\n        )\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'X-AMZ-ID': 'abc123xyz'},\n            status=200\n        )\n\n        waf = WAFW00F(target='https://example.com')\n        waf.rq = waf.Request()\n        waf.attackres = waf.Request()\n\n        from wafw00f.plugins import awswaf\n        assert awswaf.is_waf(waf)\n\n\nclass TestNoWAFDetection:\n    \"\"\"Tests for when no WAF is detected.\"\"\"\n\n    @responses.activate\n    def test_no_waf_plain_response(self):\n        \"\"\"Test that plain response doesn't trigger false positive.\"\"\"\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'Server': 'nginx'},\n            body='<html><body>Hello World</body></html>',\n            status=200\n        )\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'Server': 'nginx'},\n            body='<html><body>Hello World</body></html>',\n            status=200\n        )\n\n        waf = WAFW00F(target='https://example.com')\n        waf.rq = waf.Request()\n        waf.attackres = waf.Request()\n\n        # Test some common WAF plugins don't trigger\n        from wafw00f.plugins import cloudflare, fastly, awswaf\n        assert not cloudflare.is_waf(waf)\n        assert not fastly.is_waf(waf)\n        assert not awswaf.is_waf(waf)\n\n\nclass TestAnubisDetection:\n    \"\"\"Tests for Anubis bot protection detection.\"\"\"\n\n    @responses.activate\n    def test_detect_anubis_by_cookie(self):\n        \"\"\"Test detecting Anubis by cookie pattern.\"\"\"\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'Set-Cookie': 'site-anubis-auth=token123; path=/'},\n            status=200\n        )\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            headers={'Set-Cookie': 'site-anubis-auth=token123; path=/'},\n            status=200\n        )\n\n        waf = WAFW00F(target='https://example.com')\n        waf.rq = waf.Request()\n        waf.attackres = waf.Request()\n\n        from wafw00f.plugins import anubis\n        assert anubis.is_waf(waf)\n\n    @responses.activate\n    def test_detect_anubis_by_content(self):\n        \"\"\"Test detecting Anubis by page content.\"\"\"\n        anubis_page = '''\n        <html>\n        <head><script id=\"anubis_version\">v1.0</script></head>\n        <body>Protected by Anubis From Techaro</body>\n        </html>\n        '''\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            body=anubis_page,\n            status=200\n        )\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            body=anubis_page,\n            status=200\n        )\n\n        waf = WAFW00F(target='https://example.com')\n        waf.rq = waf.Request()\n        waf.attackres = waf.Request()\n\n        from wafw00f.plugins import anubis\n        assert anubis.is_waf(waf)\n"
  },
  {
    "path": "tests/test_evillib.py",
    "content": "\"\"\"Tests for the evillib module.\"\"\"\n\nimport pytest\nimport responses\nfrom wafw00f.lib.evillib import waftoolsengine, MAX_RESPONSE_SIZE\n\n\nclass TestWafToolsEngine:\n    \"\"\"Tests for the waftoolsengine class.\"\"\"\n\n    def test_default_timeout(self):\n        \"\"\"Test default timeout is set.\"\"\"\n        engine = waftoolsengine(target='https://example.com')\n        assert engine.timeout == 7\n\n    def test_custom_timeout(self):\n        \"\"\"Test custom timeout can be set.\"\"\"\n        engine = waftoolsengine(target='https://example.com', timeout=30)\n        assert engine.timeout == 30\n\n    def test_default_headers(self):\n        \"\"\"Test default headers are set.\"\"\"\n        engine = waftoolsengine(target='https://example.com')\n        assert 'User-Agent' in engine.headers\n        assert 'Accept' in engine.headers\n\n    def test_custom_headers(self):\n        \"\"\"Test custom headers can be set.\"\"\"\n        custom = {'X-Custom': 'value'}\n        engine = waftoolsengine(target='https://example.com', head=custom)\n        assert engine.headers == custom\n\n    @responses.activate\n    def test_request_success(self):\n        \"\"\"Test successful request.\"\"\"\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            body='Hello World',\n            status=200\n        )\n\n        engine = waftoolsengine(target='https://example.com')\n        resp = engine.Request()\n\n        assert resp is not None\n        assert resp.status_code == 200\n        assert engine.requestnumber == 1\n\n    @responses.activate\n    def test_request_increments_counter(self):\n        \"\"\"Test request counter increments.\"\"\"\n        responses.add(responses.GET, 'https://example.com', status=200)\n        responses.add(responses.GET, 'https://example.com', status=200)\n\n        engine = waftoolsengine(target='https://example.com')\n        engine.Request()\n        engine.Request()\n\n        assert engine.requestnumber == 2\n\n    @responses.activate\n    def test_response_content_accessible(self):\n        \"\"\"Test response content is accessible after streaming read.\"\"\"\n        test_content = 'This is test content for WAF detection'\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            body=test_content,\n            status=200\n        )\n\n        engine = waftoolsengine(target='https://example.com')\n        resp = engine.Request()\n\n        assert resp.content == test_content.encode('utf-8')\n        assert resp.text == test_content\n\n\nclass TestResponseSizeLimit:\n    \"\"\"Tests for the response size limiting feature.\"\"\"\n\n    def test_max_response_size_defined(self):\n        \"\"\"Test MAX_RESPONSE_SIZE is defined.\"\"\"\n        assert MAX_RESPONSE_SIZE > 0\n        assert MAX_RESPONSE_SIZE == 100 * 1024  # 100KB\n\n    @responses.activate\n    def test_small_response_fully_read(self):\n        \"\"\"Test small responses are fully read.\"\"\"\n        small_content = 'x' * 1000  # 1KB\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            body=small_content,\n            status=200\n        )\n\n        engine = waftoolsengine(target='https://example.com')\n        resp = engine.Request()\n\n        assert len(resp.content) == 1000\n\n    @responses.activate\n    def test_large_response_truncated(self):\n        \"\"\"Test large responses are truncated to MAX_RESPONSE_SIZE.\"\"\"\n        large_content = 'x' * (MAX_RESPONSE_SIZE + 50000)  # 150KB\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            body=large_content,\n            status=200\n        )\n\n        engine = waftoolsengine(target='https://example.com')\n        resp = engine.Request()\n\n        # Should be truncated to around MAX_RESPONSE_SIZE (may be slightly over due to chunk size)\n        assert len(resp.content) <= MAX_RESPONSE_SIZE + 8192\n        assert len(resp.content) >= MAX_RESPONSE_SIZE\n\n\nclass TestTimeoutEnforcement:\n    \"\"\"Tests for timeout enforcement during response reading.\"\"\"\n\n    @responses.activate\n    def test_timeout_attribute_used(self):\n        \"\"\"Test timeout is properly configured and accessible.\n\n        Note: Testing actual timeout enforcement during slow streaming\n        requires integration tests with real servers, as the responses\n        mock library doesn't support time-based streaming simulation.\n        The timeout enforcement logic in Request() will break out of\n        the chunk reading loop if time.time() - start_time > self.timeout.\n        \"\"\"\n        responses.add(\n            responses.GET,\n            'https://example.com',\n            body='test',\n            status=200\n        )\n\n        engine = waftoolsengine(target='https://example.com', timeout=5)\n        resp = engine.Request()\n\n        # Verify the engine has timeout configured\n        assert engine.timeout == 5\n        assert resp is not None\n\n\nclass TestPathPreservation:\n    \"\"\"Tests that request paths are not normalized.\"\"\"\n\n    @responses.activate\n    def test_path_traversal_not_normalized(self):\n        \"\"\"Test path traversal sequences are preserved.\"\"\"\n        responses.add(responses.GET, 'https://example.com/../../etc/passwd', status=200)\n        engine = waftoolsengine(target='https://example.com')\n        engine.Request(path='../../etc/passwd')\n        assert '../../etc/passwd' in responses.calls[0].request.url\n\n    @responses.activate\n    def test_path_traversal_with_params(self):\n        \"\"\"Test path traversal is preserved when query params are present.\"\"\"\n        responses.add(responses.GET, 'https://example.com/../../etc/passwd', status=200)\n        engine = waftoolsengine(target='https://example.com')\n        engine.Request(path='../../etc/passwd', params={'key': 'val'})\n        url = responses.calls[0].request.url\n        assert '../../etc/passwd' in url\n        assert 'key=val' in url\n"
  },
  {
    "path": "tests/test_manager.py",
    "content": "\"\"\"Tests for the plugin manager.\"\"\"\n\nimport pytest\nfrom wafw00f.manager import load_plugins\n\n\nclass TestLoadPlugins:\n    \"\"\"Tests for the load_plugins function.\"\"\"\n\n    def test_load_plugins_returns_dict(self):\n        \"\"\"Verify load_plugins returns a dictionary.\"\"\"\n        plugins = load_plugins()\n        assert isinstance(plugins, dict)\n\n    def test_load_plugins_not_empty(self):\n        \"\"\"Verify plugins are loaded.\"\"\"\n        plugins = load_plugins()\n        assert len(plugins) > 0\n\n    def test_plugins_have_name_attribute(self):\n        \"\"\"Verify each plugin has a NAME attribute.\"\"\"\n        plugins = load_plugins()\n        for name, plugin in plugins.items():\n            assert hasattr(plugin, 'NAME'), f\"Plugin {name} missing NAME attribute\"\n\n    def test_plugins_have_is_waf_function(self):\n        \"\"\"Verify each plugin has an is_waf function.\"\"\"\n        plugins = load_plugins()\n        for name, plugin in plugins.items():\n            assert hasattr(plugin, 'is_waf'), f\"Plugin {name} missing is_waf function\"\n            assert callable(plugin.is_waf), f\"Plugin {name} is_waf is not callable\"\n\n    def test_known_plugins_loaded(self):\n        \"\"\"Verify some known plugins are loaded.\"\"\"\n        plugins = load_plugins()\n        known_plugins = ['cloudflare', 'fastly', 'awswaf', 'anubis']\n        for plugin_name in known_plugins:\n            assert plugin_name in plugins, f\"Expected plugin {plugin_name} not found\"\n\n    def test_plugin_names_are_strings(self):\n        \"\"\"Verify plugin keys are strings.\"\"\"\n        plugins = load_plugins()\n        for name in plugins.keys():\n            assert isinstance(name, str)\n"
  },
  {
    "path": "tests/test_matching.py",
    "content": "\"\"\"Tests for the WAFW00F matching functions.\"\"\"\n\nimport pytest\nfrom wafw00f.main import WAFW00F\n\n\nclass TestMatchHeader:\n    \"\"\"Tests for the matchHeader function.\"\"\"\n\n    def test_match_header_exact(self, wafw00f_instance, mock_response):\n        \"\"\"Test matching an exact header value.\"\"\"\n        wafw00f_instance.rq = mock_response(headers={'Server': 'cloudflare'})\n        assert wafw00f_instance.matchHeader(('Server', 'cloudflare'), attack=False)\n\n    def test_match_header_regex(self, wafw00f_instance, mock_response):\n        \"\"\"Test matching header with regex.\"\"\"\n        wafw00f_instance.rq = mock_response(headers={'Server': 'cloudflare-nginx'})\n        assert wafw00f_instance.matchHeader(('Server', r'cloudflare.*'), attack=False)\n\n    def test_match_header_case_insensitive(self, wafw00f_instance, mock_response):\n        \"\"\"Test that header matching is case insensitive.\"\"\"\n        wafw00f_instance.rq = mock_response(headers={'Server': 'CLOUDFLARE'})\n        assert wafw00f_instance.matchHeader(('Server', 'cloudflare'), attack=False)\n\n    def test_match_header_not_found(self, wafw00f_instance, mock_response):\n        \"\"\"Test when header doesn't match.\"\"\"\n        wafw00f_instance.rq = mock_response(headers={'Server': 'nginx'})\n        assert not wafw00f_instance.matchHeader(('Server', 'cloudflare'), attack=False)\n\n    def test_match_header_missing(self, wafw00f_instance, mock_response):\n        \"\"\"Test when header is missing.\"\"\"\n        wafw00f_instance.rq = mock_response(headers={})\n        assert not wafw00f_instance.matchHeader(('Server', 'cloudflare'), attack=False)\n\n    def test_match_header_none_response(self, wafw00f_instance):\n        \"\"\"Test when response is None.\"\"\"\n        wafw00f_instance.rq = None\n        result = wafw00f_instance.matchHeader(('Server', 'cloudflare'), attack=False)\n        assert result is None\n\n\nclass TestMatchContent:\n    \"\"\"Tests for the matchContent function.\"\"\"\n\n    def test_match_content_exact(self, wafw00f_instance, mock_response):\n        \"\"\"Test matching exact content.\"\"\"\n        wafw00f_instance.attackres = mock_response(text='Access Denied by Cloudflare')\n        assert wafw00f_instance.matchContent('Access Denied')\n\n    def test_match_content_regex(self, wafw00f_instance, mock_response):\n        \"\"\"Test matching content with regex.\"\"\"\n        wafw00f_instance.attackres = mock_response(text='Error 403: Forbidden by WAF')\n        assert wafw00f_instance.matchContent(r'Error \\d+:.*WAF')\n\n    def test_match_content_case_insensitive(self, wafw00f_instance, mock_response):\n        \"\"\"Test that content matching is case insensitive.\"\"\"\n        wafw00f_instance.attackres = mock_response(text='ACCESS DENIED')\n        assert wafw00f_instance.matchContent('access denied')\n\n    def test_match_content_not_found(self, wafw00f_instance, mock_response):\n        \"\"\"Test when content doesn't match.\"\"\"\n        wafw00f_instance.attackres = mock_response(text='Welcome to our website')\n        assert not wafw00f_instance.matchContent('Access Denied')\n\n    def test_match_content_none_response(self, wafw00f_instance):\n        \"\"\"Test when response is None.\"\"\"\n        wafw00f_instance.attackres = None\n        result = wafw00f_instance.matchContent('test')\n        assert result is None\n\n\nclass TestMatchCookie:\n    \"\"\"Tests for the matchCookie function.\"\"\"\n\n    def test_match_cookie(self, wafw00f_instance, mock_response):\n        \"\"\"Test matching a cookie.\"\"\"\n        wafw00f_instance.rq = mock_response(\n            headers={'Set-Cookie': '__cfduid=abc123; path=/'}\n        )\n        assert wafw00f_instance.matchCookie('__cfduid', attack=False)\n\n    def test_match_cookie_regex(self, wafw00f_instance, mock_response):\n        \"\"\"Test matching cookie with regex.\"\"\"\n        wafw00f_instance.rq = mock_response(\n            headers={'Set-Cookie': 'session_id=xyz789; path=/'}\n        )\n        assert wafw00f_instance.matchCookie(r'session_id=\\w+', attack=False)\n\n    def test_match_cookie_not_found(self, wafw00f_instance, mock_response):\n        \"\"\"Test when cookie doesn't match.\"\"\"\n        wafw00f_instance.rq = mock_response(\n            headers={'Set-Cookie': 'other_cookie=value'}\n        )\n        assert not wafw00f_instance.matchCookie('__cfduid', attack=False)\n\n\nclass TestMatchStatus:\n    \"\"\"Tests for the matchStatus function.\"\"\"\n\n    def test_match_status_200(self, wafw00f_instance, mock_response):\n        \"\"\"Test matching 200 status code.\"\"\"\n        wafw00f_instance.attackres = mock_response(status_code=200)\n        assert wafw00f_instance.matchStatus(200)\n\n    def test_match_status_403(self, wafw00f_instance, mock_response):\n        \"\"\"Test matching 403 status code.\"\"\"\n        wafw00f_instance.attackres = mock_response(status_code=403)\n        assert wafw00f_instance.matchStatus(403)\n\n    def test_match_status_mismatch(self, wafw00f_instance, mock_response):\n        \"\"\"Test when status code doesn't match.\"\"\"\n        wafw00f_instance.attackres = mock_response(status_code=200)\n        assert not wafw00f_instance.matchStatus(403)\n\n    def test_match_status_none_response(self, wafw00f_instance):\n        \"\"\"Test when response is None.\"\"\"\n        wafw00f_instance.attackres = None\n        result = wafw00f_instance.matchStatus(200)\n        assert result is None\n\n\nclass TestMatchReason:\n    \"\"\"Tests for the matchReason function.\"\"\"\n\n    def test_match_reason_ok(self, wafw00f_instance, mock_response):\n        \"\"\"Test matching OK reason.\"\"\"\n        wafw00f_instance.attackres = mock_response(reason='OK')\n        assert wafw00f_instance.matchReason('OK')\n\n    def test_match_reason_forbidden(self, wafw00f_instance, mock_response):\n        \"\"\"Test matching Forbidden reason.\"\"\"\n        wafw00f_instance.attackres = mock_response(reason='Forbidden')\n        assert wafw00f_instance.matchReason('Forbidden')\n\n    def test_match_reason_mismatch(self, wafw00f_instance, mock_response):\n        \"\"\"Test when reason doesn't match.\"\"\"\n        wafw00f_instance.attackres = mock_response(reason='OK')\n        assert not wafw00f_instance.matchReason('Forbidden')\n"
  },
  {
    "path": "wafw00f/__init__.py",
    "content": "#!/usr/bin/env python3\n\n__version__ = '2.4.2'\n__license__ = 'BSD 3-Clause'\n"
  },
  {
    "path": "wafw00f/lib/__init__.py",
    "content": ""
  },
  {
    "path": "wafw00f/lib/asciiarts.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nfrom dataclasses import dataclass\nfrom random import randint\n\nfrom wafw00f import __version__\n\n\n@dataclass\nclass Color:\n    \"\"\"ANSI colors.\"\"\"\n    W: str = '\\033[1;97m'\n    Y: str = '\\033[1;93m'\n    G: str = '\\033[1;92m'\n    R: str = '\\033[1;91m'\n    B: str = '\\033[1;94m'\n    C: str = '\\033[1;96m'\n    E: str = '\\033[0m'\n\n    @classmethod\n    def disable(cls):\n        \"\"\"Disables all colors.\"\"\"\n        cls.W = ''\n        cls.Y = ''\n        cls.G = ''\n        cls.R = ''\n        cls.B = ''\n        cls.C = ''\n        cls.E = ''\n\n    @classmethod\n    def unpack(cls):\n        \"\"\"Unpacks and returns the color values.\n        Useful for brevity, e.g.:\n        (W,Y,G,R,B,C,E) = Color.unpack()\n        \"\"\"\n        return (\n            cls.W,\n            cls.Y,\n            cls.G,\n            cls.R,\n            cls.B,\n            cls.C,\n            cls.E\n        )\n\n\ndef randomArt():\n    # Colors for terminal\n\n    (W,Y,G,R,B,C,E) = Color.unpack()\n\n    woof = '''\n                   '''+W+'''______\n                  '''+W+'''/      \\\\\n                 '''+W+'''(  Woof! )\n                  '''+W+r'''\\  ____/                      '''+R+''')\n                  '''+W+''',,                           '''+R+''') ('''+Y+'''_\n             '''+Y+'''.-. '''+W+'''-    '''+G+'''_______                 '''+R+'''( '''+Y+'''|__|\n            '''+Y+'''()``; '''+G+'''|==|_______)                '''+R+'''.)'''+Y+'''|__|\n            '''+Y+'''/ ('        '''+G+r'''/|\\                  '''+R+'''(  '''+Y+'''|__|\n        '''+Y+'''(  /  )       '''+G+r''' / | \\                  '''+R+'''. '''+Y+'''|__|\n         '''+Y+r'''\\(_)_))      '''+G+r'''/  |  \\                   '''+Y+'''|__|'''+E+'''\n\n                    '''+C+'~ WAFW00F : '+B+'v'+__version__+''' ~'''+W+'''\n    The Web Application Firewall Fingerprinting Toolkit\n    '''+E\n\n    w00f = '''\n                '''+W+'''______\n               '''+W+'''/      \\\\\n              '''+W+'''(  W00f! )\n               '''+W+r'''\\  ____/\n               '''+W+''',,    '''+G+'''__            '''+Y+'''404 Hack Not Found\n           '''+C+'''|`-.__   '''+G+'''/ /                     '''+R+''' __     __\n           '''+C+'''/\"  _/  '''+G+'''/_/                       '''+R+r'''\\ \\   / /\n          '''+B+'''*===*    '''+G+'''/                          '''+R+r'''\\ \\_/ /  '''+Y+'''405 Not Allowed\n         '''+C+'''/     )__//                           '''+R+r'''\\   /\n    '''+C+'''/|  /     /---`                        '''+Y+'''403 Forbidden\n    '''+C+r'''\\\\/`   \\ |                                 '''+R+'''/ _ \\\\\n    '''+C+r'''`\\    /_\\\\_              '''+Y+'''502 Bad Gateway  '''+R+r'''/ / \\ \\  '''+Y+'''500 Internal Error\n      '''+C+'''`_____``-`                             '''+R+r'''/_/   \\_\\\\\n\n                        '''+C+'~ WAFW00F : '+B+'v'+__version__+''' ~'''+W+'''\n        The Web Application Firewall Fingerprinting Toolkit\n    '''+E\n\n    wo0f = r'''\n                 ?              ,.   (   .      )        .      \"\n         __        ??          (\"     )  )'     ,'        )  . (`     '`\n    (___()'`;   ???          .; )  ' (( (\" )    ;(,     ((  (  ;)  \"  )\")\n    /,___ /`                 _\"., ,._'_.,)_(..,( . )_  _' )_') (. _..( ' )\n    \\\\   \\\\                 |____|____|____|____|____|____|____|____|____|\n\n                                ~ WAFW00F : v'''+__version__+''' ~\n                    ~ Sniffing Web Application Firewalls since 2009 ~\n'''\n\n    arts = [woof, w00f, wo0f]\n    return arts[randint(0, len(arts)-1)]\n"
  },
  {
    "path": "wafw00f/lib/evillib.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nimport time\nimport logging\nfrom copy import copy\nfrom urllib.parse import urlparse\n\nimport requests\nimport urllib3\n\n# For requests < 2.16, this should be used.\n# requests.packages.urllib3.disable_warnings(InsecureRequestWarning)\n# For requests >= 2.16, this is the convention\nurllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)\n\ndef_headers = {\n    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',\n    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:130.0) Gecko/20100101 Firefox/130.0',\n    'Accept-Language': 'en-US,en;q=0.5',\n    'Upgrade-Insecure-Requests': '1',\n    'Sec-Fetch-Dest': 'document',\n    'Sec-Fetch-Mode': 'navigate',\n    'Sec-Fetch-Site': 'cross-site',\n    'Priority': 'u=0, i',\n    'DNT': '1',\n}\nproxies = {}\n\n# Maximum response body size to read (100KB should be plenty for WAF detection)\nMAX_RESPONSE_SIZE = 100 * 1024\n\nclass waftoolsengine:\n    def __init__(\n        self, target='https://example.com', debuglevel=0,\n        path='/', proxies=None, redir=True, head=None, timeout=7\n    ):\n        self.target = target\n        self.debuglevel = debuglevel\n        self.requestnumber = 0\n        self.path = path\n        self.redirectno = 0\n        self.allowredir = redir\n        self.proxies = proxies\n        self.log = logging.getLogger('wafw00f')\n        self.timeout = timeout\n        if head:\n            self.headers = head\n        else:\n            self.headers = copy(def_headers) #copy object by value not reference. Fix issue #90\n\n    def Request(self, headers=None, path=None, params={}, delay=0):\n        try:\n            time.sleep(delay)\n            if not headers:\n                h = self.headers\n            else: h = headers\n\n            # Create the url manually to avoid path normalization\n            url = self.target if path is None else self.target.rstrip('/') + '/' + path.lstrip('/')\n            prepared = requests.Request('GET', url, headers=h,\n                                        params=params or {}).prepare()\n\n            parsed_url = urlparse(prepared.url)\n\n            # Ensuring trailing slash does not disappear\n            trailing_slash = parsed_url.path.endswith('/')\n            if trailing_slash and not url.endswith('/'):\n                url += '/'\n\n            # Preserve the original path (e.g. ../../etc/passwd)\n            if params:\n                prepared.url = url + '?' + parsed_url.query\n            else:\n                prepared.url = url\n\n            req = requests.Session().send(prepared, proxies=self.proxies, timeout=self.timeout,\n                    allow_redirects=self.allowredir, verify=False, stream=True)\n\n            # Read only up to MAX_RESPONSE_SIZE to avoid hanging on streaming responses\n            # (e.g., audio streams) - see issue #246\n            # Also enforce timeout during reading to handle slow streaming servers\n            chunks = []\n            bytes_read = 0\n            start_time = time.time()\n            for chunk in req.iter_content(chunk_size=8192):\n                chunks.append(chunk)\n                bytes_read += len(chunk)\n                if bytes_read >= MAX_RESPONSE_SIZE:\n                    break\n                # Check if we've exceeded the timeout during reading\n                if time.time() - start_time > self.timeout:\n                    self.log.debug('Timeout reached during response body reading')\n                    break\n            req._content = b''.join(chunks)\n            self.log.info('Request Succeeded')\n            self.log.debug('Headers: %s\\n' % req.headers)\n            self.log.debug('Content: %s\\n' % req.content)\n            self.requestnumber += 1\n            return req\n        except requests.exceptions.RequestException as e:\n            self.log.error('Something went wrong %s' % (e.__str__()))\n"
  },
  {
    "path": "wafw00f/main.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nimport csv\nimport io\nimport json\nimport logging\nimport os\nimport random\nimport re\nimport sys\nimport string\nimport urllib.parse\nfrom collections import defaultdict\nfrom optparse import OptionParser\n\nfrom wafw00f import __license__, __version__\nfrom wafw00f.lib.asciiarts import Color, randomArt\nfrom wafw00f.lib.evillib import waftoolsengine\nfrom wafw00f.manager import load_plugins\nfrom wafw00f.wafprio import wafdetectionsprio\n\n\nclass WAFW00F(waftoolsengine):\n\n    xsstring = r'<script>alert(\"XSS\");</script>'\n    sqlistring = r'UNION SELECT ALL FROM information_schema AND \" or SLEEP(5) or \"'\n    lfistring = r'../../etc/passwd'\n    rcestring = r'/bin/cat /etc/passwd; ping 127.0.0.1; curl google.com'\n    xxestring = r'<!ENTITY xxe SYSTEM \"file:///etc/shadow\">]><pwn>&hack;</pwn>'\n\n    def __init__(self, target='www.example.com', debuglevel=0, path='/',\n                 followredirect=True, extraheaders={}, proxies=None, timeout=7):\n\n        self.log = logging.getLogger('wafw00f')\n        self.attackres = None\n        waftoolsengine.__init__(self, target, debuglevel, path, proxies, followredirect, extraheaders, timeout)\n        self.knowledge = {\n            'generic': {\n                'found': False,\n                'reason': ''\n            },\n            'wafname': []\n        }\n        self.rq = self.normalRequest()\n\n    def normalRequest(self):\n        return self.Request()\n\n    def customRequest(self, headers=None):\n        return self.Request(\n            headers=headers\n        )\n\n    def nonExistent(self):\n        return self.Request(\n            path=self.path + str(random.randrange(100, 999)) + '.html'\n        )\n\n    def xssAttack(self):\n        return self.Request(\n            path=self.path,\n            params={\n                create_random_param_name(): self.xsstring\n            }\n        )\n\n    def xxeAttack(self):\n        return self.Request(\n            path=self.path,\n            params={\n                create_random_param_name(): self.xxestring\n            }\n        )\n\n    def lfiAttack(self):\n        return self.Request(\n            path=self.path + self.lfistring\n        )\n\n    def centralAttack(self):\n        return self.Request(\n            path=self.path,\n            params={\n                create_random_param_name(): self.xsstring,\n                create_random_param_name(): self.sqlistring,\n                create_random_param_name(): self.lfistring\n            }\n        )\n\n    def sqliAttack(self):\n        return self.Request(\n            path=self.path,\n            params={\n                create_random_param_name(): self.sqlistring\n            }\n        )\n\n    def osciAttack(self):\n        return self.Request(\n            path=self.path,\n            params= {\n                create_random_param_name(): self.rcestring\n            }\n        )\n\n    def performCheck(self, request_method):\n        r = request_method()\n        if r is None:\n            raise RequestBlocked()\n        return r, r.url\n\n    # Most common attacks used to detect WAFs\n    attcom = [xssAttack, sqliAttack, lfiAttack]\n    attacks = [xssAttack, xxeAttack, lfiAttack, sqliAttack, osciAttack]\n\n    def genericdetect(self):\n        reason = ''\n        reasons = ['Blocking is being done at connection/packet level.',\n                   'The server header is different when an attack is detected.',\n                   'The server returns a different response code when an attack string is used.',\n                   'It closed the connection for a normal request.',\n                   'The response was different when the request wasn\\'t made from a browser.'\n                ]\n        try:\n            # Testing for no user-agent response. Detects almost all WAFs out there.\n            resp1, _ = self.performCheck(self.normalRequest)\n            if 'User-Agent' in self.headers:\n                self.headers.pop('User-Agent')  # Deleting the user-agent key from object not dict.\n            resp3 = self.customRequest(headers=self.headers)\n            if resp3 is not None and resp1 is not None:\n                if resp1.status_code != resp3.status_code:\n                    self.log.info('Server returned a different response when request didn\\'t contain the User-Agent header.')\n                    reason = reasons[4]\n                    reason += '\\r\\n'\n                    reason += 'Normal response code is \"%s\",' % resp1.status_code\n                    reason += ' while the response code to a modified request is \"%s\"' % resp3.status_code\n                    self.knowledge['generic']['reason'] = reason\n                    self.knowledge['generic']['found'] = True\n                    return True\n\n            # Testing the status code upon sending a xss attack\n            resp2, xss_url = self.performCheck(self.xssAttack)\n            if resp1.status_code != resp2.status_code:\n                self.log.info('Server returned a different response when a XSS attack vector was tried.')\n                reason = reasons[2]\n                reason += '\\r\\n'\n                reason += 'Normal response code is \"%s\",' % resp1.status_code\n                reason += ' while the response code to cross-site scripting attack is \"%s\"' % resp2.status_code\n                self.knowledge['generic']['reason'] = reason\n                self.knowledge['generic']['found'] = True\n                return xss_url\n\n            # Testing the status code upon sending a lfi attack\n            resp2, lfi_url = self.performCheck(self.lfiAttack)\n            if resp1.status_code != resp2.status_code:\n                self.log.info('Server returned a different response when a directory traversal was attempted.')\n                reason = reasons[2]\n                reason += '\\r\\n'\n                reason += 'Normal response code is \"%s\",' % resp1.status_code\n                reason += ' while the response code to a file inclusion attack is \"%s\"' % resp2.status_code\n                self.knowledge['generic']['reason'] = reason\n                self.knowledge['generic']['found'] = True\n                return lfi_url\n\n            # Testing the status code upon sending a sqli attack\n            resp2, sqli_url = self.performCheck(self.sqliAttack)\n            if resp1.status_code != resp2.status_code:\n                self.log.info('Server returned a different response when a SQLi was attempted.')\n                reason = reasons[2]\n                reason += '\\r\\n'\n                reason += 'Normal response code is \"%s\",' % resp1.status_code\n                reason += ' while the response code to a SQL injection attack is \"%s\"' % resp2.status_code\n                self.knowledge['generic']['reason'] = reason\n                self.knowledge['generic']['found'] = True\n                return sqli_url\n\n            # Checking for the Server header after sending malicious requests\n            normalserver, attackresponse_server = '', ''\n            response = self.attackres\n            if 'server' in resp1.headers:\n                normalserver = resp1.headers.get('Server')\n            if response is not None and 'server' in response.headers:\n                attackresponse_server = response.headers.get('Server')\n            if attackresponse_server != normalserver:\n                self.log.info('Server header changed, WAF possibly detected')\n                self.log.debug('Attack response: %s' % attackresponse_server)\n                self.log.debug('Normal response: %s' % normalserver)\n                reason = reasons[1]\n                reason += '\\r\\nThe server header for a normal response is \"%s\",' % normalserver\n                reason += ' while the server header a response to an attack is \"%s\",' % attackresponse_server\n                self.knowledge['generic']['reason'] = reason\n                self.knowledge['generic']['found'] = True\n                return True\n\n        # If at all request doesn't go, press F\n        except RequestBlocked:\n            self.knowledge['generic']['reason'] = reasons[0]\n            self.knowledge['generic']['found'] = True\n            return True\n        return False\n\n    def matchHeader(self, headermatch, attack=False):\n        if attack:\n            r = self.attackres\n        else:\n            r = self.rq\n        if r is None:\n            return\n\n        header, match = headermatch\n        headerval = r.headers.get(header)\n        if headerval:\n            # set-cookie can have multiple headers, python gives it to us\n            # concatinated with a comma\n            if header == 'Set-Cookie':\n                headervals = headerval.split(', ')\n            else:\n                headervals = [headerval]\n            for headerval in headervals:\n                if re.search(match, headerval, re.I):\n                    return True\n        return False\n\n    def matchStatus(self, statuscode, attack=True):\n        if attack:\n            r = self.attackres\n        else:\n            r = self.rq\n        if r is None:\n            return\n        if r.status_code == statuscode:\n            return True\n        return False\n\n    def matchCookie(self, match, attack=False):\n        return self.matchHeader(('Set-Cookie', match), attack=attack)\n\n    def matchReason(self, reasoncode, attack=True):\n        if attack:\n            r = self.attackres\n        else:\n            r = self.rq\n        if r is None:\n            return\n        # We may need to match multiline context in response body\n        if str(r.reason) == reasoncode:\n            return True\n        return False\n\n    def matchContent(self, regex, attack=True):\n        if attack:\n            r = self.attackres\n        else:\n            r = self.rq\n        if r is None:\n            return\n        # We may need to match multiline context in response body\n        if re.search(regex, r.text, re.I):\n            return True\n        return False\n\n    wafdetections = dict()\n\n    plugin_dict = load_plugins()\n    result_dict = {}\n    for plugin_module in plugin_dict.values():\n        wafdetections[plugin_module.NAME] = plugin_module.is_waf\n    # Check for prioritized ones first, then check those added externally\n    checklist = wafdetectionsprio\n    checklist += list(set(wafdetections.keys()) - set(checklist))\n\n    def identwaf(self, findall=False):\n        detected = list()\n        try:\n            self.attackres, xurl = self.performCheck(self.centralAttack)\n        except RequestBlocked:\n            return detected, None\n        for wafvendor in self.checklist:\n            self.log.info('Checking for %s' % wafvendor)\n            if self.wafdetections[wafvendor](self):\n                detected.append(wafvendor)\n                if not findall:\n                    break\n        self.knowledge['wafname'] = detected\n        return detected, xurl\n\ndef calclogginglevel(verbosity):\n    default = 40  # errors are printed out\n    level = default - (verbosity * 10)\n    if level < 0:\n        level = 0\n    return level\n\ndef buildResultRecord(url, waf, evil_url=None):\n    result = {}\n    result['url'] = url\n    if waf:\n        result['detected'] = True\n        if waf == 'generic':\n            result['trigger_url'] = evil_url\n            result['firewall'] = 'Generic'\n            result['manufacturer'] = 'Unknown'\n        else:\n            result['trigger_url'] = evil_url\n            result['firewall'] = waf.split('(')[0].strip()\n            result['manufacturer'] = waf.split('(')[1].replace(')', '').strip()\n    else:\n        result['trigger_url'] = evil_url\n        result['detected'] = False\n        result['firewall'] = 'None'\n        result['manufacturer'] = 'None'\n    return result\n\ndef getTextResults(res=[]):\n    # leaving out some space for future possibilities of newer columns\n    # newer columns can be added to this tuple below\n    keys = ('detected')\n    res = [({key: ba[key] for key in ba if key not in keys}) for ba in res]\n    rows = []\n    for dk in res:\n        p = [str(x) for _, x in dk.items()]\n        rows.append(p)\n    for m in rows:\n        m[1] = '%s (%s)' % (m[1], m[2])\n        m.pop()\n    defgen = [\n        (max([len(str(row[i])) for row in rows]) + 3)\n        for i in range(len(rows[0]))\n    ]\n    rwfmt = ''.join(['{:>'+str(dank)+'}' for dank in defgen])\n    textresults = []\n    for row in rows:\n        textresults.append(rwfmt.format(*row))\n    return textresults\n\ndef create_random_param_name(size=8, chars=string.ascii_lowercase):\n    return ''.join(random.choice(chars) for _ in range(size))\n\ndef disableStdOut():\n    sys.stdout = None\n\ndef enableStdOut():\n    sys.stdout = sys.__stdout__\n\ndef getheaders(fn):\n    headers = {}\n    if not os.path.exists(fn):\n        logging.getLogger('wafw00f').critical('Headers file \"%s\" does not exist!' % fn)\n        return\n    with io.open(fn, 'r', encoding='utf-8') as f:\n        for line in f.readlines():\n            _t = line.split(':', 2)\n            if len(_t) == 2:\n                h, v = map(lambda x: x.strip(), _t)\n                headers[h] = v\n    return headers\n\nclass RequestBlocked(Exception):\n    pass\n\ndef main():\n    parser = OptionParser(usage='%prog url1 [url2 [url3 ... ]]\\r\\nexample: %prog http://www.victim.org/')\n    parser.add_option('-v', '--verbose', action='count', dest='verbose', default=0,\n                      help='Enable verbosity, multiple -v options increase verbosity')\n    parser.add_option('-a', '--findall', action='store_true', dest='findall', default=False,\n                      help='Find all WAFs which match the signatures, do not stop testing on the first one')\n    parser.add_option('-r', '--noredirect', action='store_false', dest='followredirect',\n                      default=True, help='Do not follow redirections given by 3xx responses')\n    parser.add_option('-t', '--test', dest='test', help='Test for one specific WAF (use --list to get names, quote names with spaces e.g. \"AireeCDN (Airee)\")')\n    parser.add_option('-o', '--output', dest='output', help='Write output to csv, json or text file depending on file extension. For stdout, specify - as filename.',\n                      default=None)\n    parser.add_option('-f', '--format', dest='format', help='Force output format to csv, json or text.',\n                      default=None)\n    parser.add_option('-i', '--input-file', dest='input', help='Read targets from a file. Input format can be csv, json or text. For csv and json, a `url` column name or element is required.',\n                      default=None)\n    parser.add_option('-l', '--list', dest='list', action='store_true',\n                      default=False, help='List all WAFs that WAFW00F is able to detect')\n    parser.add_option('-p', '--proxy', dest='proxy', default=None,\n                      help='Use an HTTP proxy to perform requests, examples: http://hostname:8080, socks5://hostname:1080, http://user:pass@hostname:8080')\n    parser.add_option('--version', '-V', dest='version', action='store_true',\n                      default=False, help='Print out the current version of WafW00f and exit.')\n    parser.add_option('--headers', '-H', dest='headers', action='store', default=None,\n                      help='Pass custom headers via a text file to overwrite the default header set.')\n    parser.add_option('-T', '--timeout', dest='timeout', action='store', default=7, type=int,\n                      help='Set the timeout for the requests.')\n    parser.add_option('--no-colors', dest='colors', action='store_false',\n                      default=True, help='Disable ANSI colors in output.')\n\n    options, args = parser.parse_args()\n\n    logging.basicConfig(level=calclogginglevel(options.verbose))\n    log = logging.getLogger('wafw00f')\n    if options.output == '-':\n        disableStdOut()\n\n    # Windows based systems do not support ANSI sequences,\n    # hence not displaying them.\n    if not options.colors or 'win' in sys.platform:\n        Color.disable()\n\n    print(randomArt())\n    (W,Y,G,R,B,C,E) = Color.unpack()\n\n    if options.list:\n        print('[+] Can test for these WAFs:\\r\\n')\n        try:\n            m = [i.replace(')', '').split(' (') for i in wafdetectionsprio]\n            print(R+'  WAF Name'+' '*24+'Manufacturer\\n  '+'-'*8+' '*24+'-'*12+'\\n')\n            max_len = max(len(str(x)) for k in m for x in k)\n            for inner in m:\n                first = True\n                for elem in inner:\n                    if first:\n                        text = Y+'  {:<{}} '.format(elem, max_len+2)\n                        first = False\n                    else:\n                        text = W+'{:<{}} '.format(elem, max_len+2)\n                    print(text, E, end='')\n                print()\n            sys.exit(0)\n        except Exception:\n            return\n    if options.version:\n        print('[+] The version of WAFW00F you have is %sv%s%s' % (B, __version__, E))\n        print('[+] WAFW00F is provided under the %s%s%s license.' % (C, __license__, E))\n        return\n    extraheaders = {}\n    if options.headers:\n        log.info('Getting extra headers from %s' % options.headers)\n        extraheaders = getheaders(options.headers)\n        if extraheaders is None:\n            parser.error('Please provide a headers file with colon delimited header names and values')\n    if len(args) == 0 and not options.input:\n        parser.error('No test target specified.')\n    #check if input file is present\n    if options.input:\n        log.debug('Loading file \"%s\"' % options.input)\n        try:\n            if options.input.endswith('.json'):\n                with open(options.input) as f:\n                    try:\n                        urls = json.loads(f.read())\n                    except json.decoder.JSONDecodeError:\n                        log.critical('JSON file %s did not contain well-formed JSON', options.input)\n                        sys.exit(1)\n                log.info('Found: %s urls to check.' %(len(urls)))\n                targets = [ item['url'] for item in urls ]\n            elif options.input.endswith('.csv'):\n                columns = defaultdict(list)\n                with open(options.input) as f:\n                    reader = csv.DictReader(f)\n                    for row in reader:\n                        for (k,v) in row.items():\n                            columns[k].append(v)\n                targets = columns['url']\n            else:\n                with open(options.input) as f:\n                    targets = [x for x in f.read().splitlines()]\n        except FileNotFoundError:\n            log.error('File %s could not be read. No targets loaded.', options.input)\n            sys.exit(1)\n    else:\n        targets = args\n    results = []\n    for target in targets:\n        if not target.startswith('http'):\n            log.info('The url %s should start with http:// or https:// .. fixing (might make this unusable)' % target)\n            target = 'https://' + target\n        print('[*] Checking %s' % target)\n        pret = urllib.parse.urlparse(target)\n        if pret is None:\n            log.critical('The url %s is not well formed' % target)\n            sys.exit(1)\n        log.info('starting wafw00f on %s' % target)\n        proxies = dict()\n        if options.proxy:\n            proxies = {\n                'http': options.proxy,\n                'https': options.proxy,\n            }\n        attacker = WAFW00F(target, debuglevel=options.verbose, path=pret.path,\n                    followredirect=options.followredirect, extraheaders=extraheaders,\n                        proxies=proxies, timeout=options.timeout)\n        if attacker.rq is None:\n            log.error('Site %s appears to be down' % pret.hostname)\n            continue\n        if options.test:\n            if options.test in attacker.wafdetections:\n                waf = attacker.wafdetections[options.test](attacker)\n                if waf:\n                    print('[+] The site %s%s%s is behind %s%s%s WAF.' % (B, target, E, C, options.test, E))\n                else:\n                    print('[-] WAF %s was not detected on %s' % (options.test, target))\n            else:\n                print('[-] WAF %s was not found in our list\\r\\nUse the --list option to see what is available' % options.test)\n            return\n        waf, xurl = attacker.identwaf(options.findall)\n        log.info('Identified WAF: %s' % waf)\n        if len(waf) > 0:\n            for i in waf:\n                results.append(buildResultRecord(target, i, xurl))\n            print('[+] The site %s%s%s is behind %s%s%s WAF.' % (B, target, E, C, (E+' and/or '+C).join(waf), E))\n        if (options.findall) or len(waf) == 0:\n            print('[+] Generic Detection results:')\n            generic_url = attacker.genericdetect()\n            if generic_url:\n                log.info('Generic Detection: %s' % attacker.knowledge['generic']['reason'])\n                print('[*] The site %s seems to be behind a WAF or some sort of security solution' % target)\n                print('[~] Reason: %s' % attacker.knowledge['generic']['reason'])\n                results.append(buildResultRecord(target, 'generic', generic_url))\n            else:\n                print('[-] No WAF detected by the generic detection')\n                results.append(buildResultRecord(target, None, None))\n        print('[~] Number of requests: %s' % attacker.requestnumber)\n    #print table of results\n    if len(results) > 0:\n        log.info('Found: %s matches.' % (len(results)))\n    if options.output:\n        if options.output == '-':\n            enableStdOut()\n            if options.format == 'json':\n                json.dump(results, sys.stdout, indent=2, sort_keys=True)\n            elif options.format == 'csv':\n                csvwriter = csv.writer(sys.stdout, delimiter=',', quotechar='\"',\n                    quoting=csv.QUOTE_MINIMAL)\n                count = 0\n                for result in results:\n                    if count == 0:\n                        header = result.keys()\n                        csvwriter.writerow(header)\n                        count += 1\n                    csvwriter.writerow(result.values())\n            else:\n                print(os.linesep.join(getTextResults(results)))\n        elif options.output.endswith('.json'):\n            log.debug('Exporting data in json format to file: %s' % (options.output))\n            with open(options.output, 'w') as outfile:\n                json.dump(results, outfile, indent=2, sort_keys=True)\n        elif options.output.endswith('.csv'):\n            log.debug('Exporting data in csv format to file: %s' % (options.output))\n            with open(options.output, 'w') as outfile:\n                csvwriter = csv.writer(outfile, delimiter=',', quotechar='\"',\n                    quoting=csv.QUOTE_MINIMAL)\n                count = 0\n                for result in results:\n                    if count == 0:\n                        header = result.keys()\n                        csvwriter.writerow(header)\n                        count += 1\n                    csvwriter.writerow(result.values())\n        else:\n            log.debug('Exporting data in text format to file: %s' % (options.output))\n            if options.format == 'json':\n                with open(options.output, 'w') as outfile:\n                    json.dump(results, outfile, indent=2, sort_keys=True)\n            elif options.format == 'csv':\n                with open(options.output, 'w') as outfile:\n                    csvwriter = csv.writer(outfile, delimiter=',', quotechar='\"',\n                        quoting=csv.QUOTE_MINIMAL)\n                    count = 0\n                    for result in results:\n                        if count == 0:\n                            header = result.keys()\n                            csvwriter.writerow(header)\n                            count += 1\n                        csvwriter.writerow(result.values())\n            else:\n                with open(options.output, 'w') as outfile:\n                    outfile.write(os.linesep.join(getTextResults(results)))\n\nif __name__ == '__main__':\n    version_info = sys.version_info\n    if version_info.major < 3 or (version_info.major == 3 and version_info.minor < 6):\n        sys.stderr.write('Your version of python is way too old... please update to 3.6 or later\\r\\n')\n    main()\n"
  },
  {
    "path": "wafw00f/manager.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nimport os\nimport importlib.util\n\ndef load_plugins():\n    here = os.path.abspath(os.path.dirname(__file__))\n    plugin_dir = os.path.join(here, 'plugins')\n\n    plugin_dict = {}\n\n    # Iterate over all files in the plugin directory\n    for filename in os.listdir(plugin_dir):\n        if filename.endswith('.py') and filename != '__init__.py':\n            plugin_name = filename[:-3]  # Remove the .py extension\n            plugin_path = os.path.join(plugin_dir, filename)\n\n            # Load the plugin module\n            spec = importlib.util.spec_from_file_location(plugin_name, plugin_path)\n            plugin_module = importlib.util.module_from_spec(spec)\n            spec.loader.exec_module(plugin_module)\n\n            # Store the loaded plugin in the dictionary\n            plugin_dict[plugin_name] = plugin_module\n\n    return plugin_dict\n"
  },
  {
    "path": "wafw00f/plugins/__init__.py",
    "content": ""
  },
  {
    "path": "wafw00f/plugins/aesecure.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'aeSecure (aeSecure)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('aeSecure-code', '.+?')):\n        return True\n\n    if self.matchContent(r'aesecure_denied\\.png'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/airee.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'AireeCDN (Airee)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'Airee')):\n        return True\n\n    if self.matchHeader(('X-Cache', r'(\\w+\\.)?airee\\.cloud')):\n        return True\n\n    if self.matchContent(r'airee\\.cloud'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/airlock.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Airlock (Phion/Ergon)'\n\n\ndef is_waf(self):\n    # This method of detection is old (though most reliable), so we check it first\n    if self.matchCookie(r'^al[_-]?(sess|lb)='):\n        return True\n\n    if self.matchContent(r'server detected a syntax error in your request'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/alertlogic.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Alert Logic (Alert Logic)'\n\n\ndef is_waf(self):\n    if not self.matchContent(r'<(title|h\\d{1})>requested url cannot be found'):\n        return False\n\n    if not self.matchContent(r'we are sorry.{0,10}?but the page you are looking for cannot be found'):\n        return False\n\n    if not self.matchContent(r'back to previous page'):\n        return False\n\n    if not self.matchContent(r'proceed to homepage'):\n        return False\n\n    if not self.matchContent(r'reference id'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/aliyundun.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'AliYunDun (Alibaba Cloud Computing)'\n\n\ndef is_waf(self):\n    if not self.matchContent(r'error(s)?\\.aliyun(dun)?\\.(com|net)?'):\n        return False\n\n    if not self.matchContent(r'alicdn\\.com\\/sd\\-base\\/static\\/\\d{1,2}\\.\\d{1,2}\\.\\d{1,2}\\/image\\/405\\.png'):\n        return False\n\n    if not self.matchContent(r'Sorry, your request has been blocked as it may cause potential threats to the server\\'s security.'):\n        return False\n\n    if not self.matchStatus(405):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/anquanbao.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Anquanbao (Anquanbao)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Powered-By-Anquanbao', '.+?')):\n        return True\n\n    if self.matchContent(r'aqb_cc/error/'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/anubis.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Anubis (Techaro)'\n\n\ndef is_waf(self) -> bool:\n    if self.matchCookie(r'-anubis-auth='):\n        return True\n\n    if self.matchContent(r'/\\.within\\.website/x/cmd/anubis/'):\n        return True\n\n    if self.matchContent(r'<script id=\"anubis_version\"'):\n        return True\n\n    if self.matchContent(r'<script id=\"anubis_challenge\"'):\n        return True\n\n    if self.matchContent(r'Protected by.*Anubis.*From.*Techaro'):\n        return True\n\n    if self.matchContent(r'github\\.com/TecharoHQ/anubis'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/anyu.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'AnYu (AnYu Technologies)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'anyu.{0,10}?the green channel'):\n        return True\n\n    if self.matchContent(r'your access has been intercepted by anyu'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/applicationgateway.py",
    "content": "#!/usr/bin/env python\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Azure Application Gateway (Microsoft)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'<center>Microsoft-Azure-Application-Gateway/v2</center>') and \\\n        self.matchContent(r'<h1>403 Forbidden</h1>'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/approach.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Approach (Approach)'\n\n\ndef is_waf(self):\n    # This method of detection is old (though most reliable), so we check it first\n    if self.matchContent(r'approach.{0,10}?web application (firewall|filtering)'):\n        return True\n\n    if self.matchContent(r'approach.{0,10}?infrastructure team'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/armor.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Armor Defense (Armor)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'blocked by website protection from armor'):\n        return True\n\n    if self.matchContent(r'please create an armor support ticket'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/arvancloud.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'ArvanCloud (ArvanCloud)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'ArvanCloud')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/aspa.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'ASPA Firewall (ASPA Engineering Co.)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'ASPA[\\-_]?WAF')):\n        return True\n\n    if self.matchHeader(('ASPA-Cache-Status', r'.+?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/aspnetgen.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'ASP.NET Generic (Microsoft)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'iis (\\d+.)+?detailed error'):\n        return True\n\n    if self.matchContent(r'potentially dangerous request querystring'):\n        return True\n\n    if self.matchContent(r'application error from being viewed remotely (for security reasons)?'):\n        return True\n\n    if self.matchContent(r'An application error occurred on the server'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/astra.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Astra (Czar Securities)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^cz_astra_csrf_cookie'):\n        return True\n\n    if self.matchContent(r'astrawebsecurity\\.freshdesk\\.com'):\n        return True\n\n    if self.matchContent(r'www\\.getastra\\.com/assets/images'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/awswaf.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'AWS Elastic Load Balancer (Amazon)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-AMZ-ID', '.+?')):\n        return True\n\n    if self.matchHeader(('X-AMZ-Request-ID', '.+?')):\n        return True\n\n    if self.matchCookie(r'^aws.?alb='):\n        return True\n\n    if self.matchHeader(('Server', r'aws.?elb'), attack=True):\n        return True\n\n    if self.matchHeader(('X-Blocked-By-WAF', 'Blocked_by_custom_response_for_AWSManagedRules.*')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/azion.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Azion Edge Firewall (Azion)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('x-azion-edge-pop', r'.+?')):\n        return True\n\n    if self.matchHeader(('x-azion-request-id', r'.+?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/baffinbay.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Baffin Bay (Mastercard)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('server', 'baffin-bay-inlet')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/baidu.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Yunjiasu (Baidu Cloud Computing)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'yunjiasu.*')):\n        return True\n\n    if self.matchContent(r'href=\"/.well-known/yunjiasu-cgi/'):\n        return True\n\n    if self.matchContent(r\"document.cookie='yjs_use_ob=0\"):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/barikode.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Barikode (Ethic Ninja)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'<strong>barikode<.strong>'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/barracuda.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Barracuda (Barracuda Networks)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^barra_counter_session='):\n        return True\n\n    if self.matchCookie(r'^BNI__BARRACUDA_LB_COOKIE='):\n        return True\n\n    if self.matchCookie(r'^BNI_persistence='):\n        return True\n\n    if self.matchCookie(r'^BN[IE]S_.*?='):\n        return True\n\n    if self.matchContent(r'Barracuda.Networks'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/bekchy.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Bekchy (Faydata Technologies Inc.)'\n\n\ndef is_waf(self):\n    # Both signatures are contained within response, so checking for any one of them\n    # Sometimes I observed that there is an XHR request being being made to submit the\n    # report data automatically upon page load. In those cases a missing https is causing\n    # false negatives.\n    if self.matchContent(r'Bekchy.{0,10}?Access Denied'):\n        return True\n\n    if self.matchContent(r'bekchy\\.com/report'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/beluga.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Beluga CDN (Beluga)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'Beluga')):\n        return True\n\n    if self.matchCookie(r'^beluga_request_trail='):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/binarysec.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'BinarySec (BinarySec)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'BinarySec')):\n        return True\n\n    if self.matchHeader(('x-binarysec-via', '.+')):\n        return True\n\n    if self.matchHeader(('x-binarysec-nocache', '.+')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/bitninja.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'BitNinja (BitNinja)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'Security check by BitNinja'):\n        return True\n\n    if self.matchContent(r'Visitor anti-robot validation'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/blockdos.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'BlockDoS (BlockDoS)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'blockdos\\.net')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/bluedon.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Bluedon (Bluedon IST)'\n\n\ndef is_waf(self):\n    # Found sample servers returning 'Server: BDWAF/2.0'\n    if self.matchHeader(('Server', r'BDWAF')):\n        return True\n\n    if self.matchContent(r'bluedon web application firewall'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/bulletproof.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'BulletProof Security Pro (AITpro Security)'\n\n\ndef is_waf(self):\n    if not self.matchContent(r'\\+?bpsMessage'):\n        return False\n\n    if not self.matchContent(r'403 Forbidden Error Page'):\n        return False\n\n    if not self.matchContent(r'If you arrived here due to a search'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/cachefly.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'CacheFly CDN (CacheFly)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('BestCDN', r'Cachefly')):\n        return True\n\n    if self.matchCookie(r'^cfly_req.*='):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/cachewall.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'CacheWall (Varnish)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'Varnish')):\n        return True\n\n    if self.matchHeader(('X-Varnish', '.+')):\n        return True\n\n    if self.matchHeader(('X-Cachewall-Action', '.+?')):\n        return True\n\n    if self.matchHeader(('X-Cachewall-Reason', '.+?')):\n        return True\n\n    if self.matchContent(r'security by cachewall'):\n        return True\n\n    if self.matchContent(r'403 naughty.{0,10}?not nice!'):\n        return True\n\n    if self.matchContent(r'varnish cache server'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/cdnns.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'CdnNS Application Gateway (CdnNs/WdidcNet)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'cdnnswaf application gateway'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/cerber.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'WP Cerber Security (Cerber Tech)'\n\n\ndef is_waf(self):\n    if not self.matchContent(r'your request looks suspicious or similar to automated'):\n        return False\n\n    if not self.matchContent(r'our server stopped processing your request'):\n        return False\n\n    if not self.matchContent(r'We.re sorry.{0,10}?you are not allowed to proceed'):\n        return False\n\n    if not self.matchContent(r'requests from spam posting software'):\n        return False\n\n    if not self.matchContent(r'<title>403 Access Forbidden'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/chinacache.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'ChinaCache Load Balancer (ChinaCache)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Powered-By-ChinaCache', '.+')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/chuangyu.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Chuang Yu Shield (Yunaq)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'www\\.365cyd\\.com'):\n        return True\n\n    if self.matchContent(r'help\\.365cyd\\.com/cyd\\-error\\-help.html\\?code=403'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/ciscoacexml.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'ACE XML Gateway (Cisco)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'ACE XML Gateway')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/cloudbric.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Cloudbric (Penta Security)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'<title>Cloudbric.{0,5}?ERROR!'):\n        return True\n\n    if self.matchContent(r'Your request was blocked by Cloudbric'):\n        return True\n\n    if self.matchContent(r'please contact Cloudbric Support'):\n        return True\n\n    if self.matchContent(r'cloudbric\\.zendesk\\.com'):\n        return True\n\n    if self.matchContent(r'Cloudbric Help Center'):\n        return True\n\n    if self.matchContent(r'malformed request syntax.{0,4}?invalid request message framing.{0,4}?or deceptive request routing'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/cloudflare.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Cloudflare (Cloudflare Inc.)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('server', 'cloudflare')):\n        return True\n\n    if self.matchHeader(('server', r'cloudflare[-_]nginx')):\n        return True\n\n    if self.matchHeader(('cf-ray', r'.+?')):\n        return True\n\n    if self.matchCookie('__cfduid'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/cloudfloordns.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Cloudfloor (Cloudfloor DNS)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'CloudfloorDNS(.WAF)?')):\n        return True\n\n    if self.matchContent(r'<(title|h\\d{1})>CloudfloorDNS.{0,6}?Web Application Firewall Error'):\n        return True\n\n    if self.matchContent(r'www\\.cloudfloordns\\.com/contact'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/cloudfront.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Cloudfront (Amazon)'\n\n\ndef is_waf(self):\n    # This is standard detection schema, checking the server header\n    if self.matchHeader(('Server', 'Cloudfront')):\n        return True\n\n    # Found samples returning 'Via: 1.1 58bfg7h6fg76h8fg7jhdf2.cloudfront.net (CloudFront)'\n    if self.matchHeader(('Via', r'([0-9\\.]+?)? \\w+?\\.cloudfront\\.net \\(Cloudfront\\)')):\n        return True\n\n    # The request token is sent along with this header, eg:\n    # X-Amz-Cf-Id: sX5QSkbAzSwd-xx3RbJmxYHL3iVNNyXa1UIebDNCshQbHxCjVcWDww==\n    if self.matchHeader(('X-Amz-Cf-Id', '.+?'), attack=True):\n        return True\n\n    # This is another reliable fingerprint found on headers\n    if self.matchHeader(('X-Cache', 'Error from Cloudfront'), attack=True):\n        return True\n\n    # These fingerprints are found on the blockpage itself\n    if self.matchContent(r'Generated by cloudfront \\(CloudFront\\)'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/cloudprotector.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Cloud Protector (Rohde & Schwarz CyberSecurity)'\n\ndef is_waf(self):\n    if self.matchContent(r'Cloud Protector.*?by Rohde.{3,8}?Schwarz Cybersecurity'):\n        return True\n\n    if self.matchContent(r\"<a href='https?:\\/\\/(?:www\\.)?cloudprotector\\.com\\/'>R.{1,6}?S.Cloud Protector\"):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/comodo.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Comodo cWatch (Comodo CyberSecurity)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'Protected by COMODO WAF(.+)?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/crawlprotect.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'CrawlProtect (Jean-Denis Brun)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^crawlprotecttag='):\n        return True\n\n    if self.matchContent(r'<title>crawlprotect'):\n        return True\n\n    if self.matchContent(r'this site is protected by crawlprotect'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/ddosguard.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'DDoS-GUARD (DDOS-GUARD CORP.)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^__ddg1.*?='):\n        return True\n\n    if self.matchCookie(r'^__ddg2.*?='):\n        return True\n\n    if self.matchCookie(r'^__ddgid.*?='):\n        return True\n\n    if self.matchCookie(r'^__ddgmark.*?='):\n        return True\n\n    if self.matchHeader(('Server', 'ddos-guard')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/denyall.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'DenyALL (Rohde & Schwarz CyberSecurity)'\n\n\ndef is_waf(self):\n    if not self.matchStatus(200):\n        return False\n\n    if not self.matchReason('Condition Intercepted'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/distil.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Distil (Distil Networks)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'cdn\\.distilnetworks\\.com/images/anomaly\\.detected\\.png'):\n        return True\n\n    if self.matchContent(r'distilCaptchaForm'):\n        return True\n\n    if self.matchContent(r'distilCallbackGuard'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/dosarrest.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'DOSarrest (DOSarrest Internet Security)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-DIS-Request-ID', '.+')):\n        return True\n\n    # Found samples of DOSArrest returning 'Server: DoSArrest/3.5'\n    if self.matchHeader(('Server', r'DOSarrest(.*)?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/dotdefender.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'DotDefender (Applicure Technologies)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-dotDefender-denied', r'.+?'), attack=True):\n        return True\n\n    if self.matchContent(r'dotdefender blocked your request'):\n        return True\n\n    if self.matchContent(r'Applicure is the leading provider of web application security'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/dynamicweb.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'DynamicWeb Injection Check (DynamicWeb)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-403-Status-By', r'dw.inj.check'), attack=True):\n        return True\n\n    if self.matchContent(r'by dynamic check(.{0,10}?module)?'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/edgecast.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Edgecast (Verizon Digital Media)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'^ECD(.+)?')):\n        return True\n\n    if self.matchHeader(('Server', r'^ECS(.*)?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/eisoo.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Eisoo Cloud Firewall (Eisoo)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'EisooWAF(\\-AZURE)?/?')):\n        return True\n\n    if self.matchContent(r'<link.{0,10}?href=\\\"/eisoo\\-firewall\\-block\\.css'):\n        return True\n\n    if self.matchContent(r'www\\.eisoo\\.com'):\n        return True\n\n    if self.matchContent(r'&copy; \\d{4} Eisoo Inc'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/envoy.py",
    "content": "#!/usr/bin/env python\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Envoy (EnvoyProxy)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('server', 'envoy')):\n        return True\n\n    if self.matchHeader(('x-envoy-upstream-service-time', '.+')):\n        return True\n\n    if self.matchHeader(('x-envoy-downstream-service-cluster', '.+')):\n        return True\n\n    if self.matchHeader(('x-envoy-downstream-service-node', '.+')):\n        return True\n\n    if self.matchHeader(('x-envoy-external-address', '.+')):\n        return True\n\n    if self.matchHeader(('x-envoy-force-trace', '.+')):\n        return True\n\n    if self.matchHeader(('x-envoy-internal', '.+')):\n        return True\n\n    if self.matchHeader(('x-envoy-original-dst-host', '.+')):\n        return True\n\n    if self.matchHeader(('x-envoy-original-path', '.+')):\n        return True\n\n    if self.matchHeader(('x-envoy-local-overloaded', '.+')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/expressionengine.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Expression Engine (EllisLab)'\n\n\ndef is_waf(self):\n    # I have seen some sites use a tracking header and sets a cookie upon authentication\n    # 'Set-Cookie: _exp_tracking=rufyhweiuitefgcxyniercyft5-6dctuxeygfr'\n    if self.matchCookie(r'^exp_track.+?='):\n        return True\n\n    # There are traces found where cookie is returning values like:\n    # Set-Cookie: exp_last_query=834y8d73y94d8g983u4shn8u4shr3uh3\n    # Set-Cookie: exp_last_id=b342b432b1a876r8\n    if self.matchCookie(r'^exp_last_.+?=', attack=True):\n        return True\n\n    # In-page fingerprints vary a lot in different sites. Hence these are not quite reliable.\n    if self.matchContent(r'invalid get data'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/f5bigipapm.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'BIG-IP AP Manager (F5 Networks)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    if check_schema_03(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if not self.matchCookie('^LastMRH_Session'):\n        return False\n\n    if not self.matchCookie('^MRHSession'):\n        return False\n\n    return True\n\n\ndef check_schema_02(self):\n    if not self.matchCookie('^MRHSession'):\n        return False\n\n    if not self.matchHeader(('Server', r'Big([-_])?IP'), attack=True):\n        return False\n\n    return True\n\n\ndef check_schema_03(self):\n    if self.matchCookie('^F5_fullWT'):\n        return True\n\n    if self.matchCookie('^F5_fullWT'):\n        return True\n\n    if self.matchCookie('^F5_HT_shrinked'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/f5bigipasm.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'BIG-IP AppSec Manager (F5 Networks)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    # ASM ≥ 11.4.0 → eight hex digits after “TS” - https://my.f5.com/manage/s/article/K6850\n    if self.matchCookie(r'TS[a-fA-F0-9]{8}=.+'):\n        return True\n    \n    # ASM 10.0.0 – 11.3.0 → six hex digits after “TS” - https://my.f5.com/manage/s/article/K6850\n    if self.matchCookie(r'TS[a-fA-F0-9]{6}=.+'):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if not self.matchContent('the requested url was rejected'):\n        return False\n\n    if not self.matchContent('please consult with your administrator'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/f5bigipltm.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'BIG-IP Local Traffic Manager (F5 Networks)'\n\n\ndef is_waf(self):\n    if self.matchCookie('^bigipserver'):\n        return True\n\n    if self.matchHeader(('X-Cnection', 'close'), attack=True):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/f5firepass.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'FirePass (F5 Networks)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if not self.matchCookie('^VHOST'):\n        return False\n\n    if not self.matchHeader(('Location', r'\\/my\\.logon\\.php3')):\n        return False\n\n    return True\n\n\ndef check_schema_02(self):\n    if not self.matchCookie(r'^F5_fire.+?'):\n        return False\n\n    if not self.matchCookie('^F5_passid_shrinked'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/f5trafficshield.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Trafficshield (F5 Networks)'\n\n\ndef is_waf(self):\n    if self.matchCookie('^ASINFO='):\n        return True\n\n    if self.matchHeader(('Server', 'F5-TrafficShield')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/fastly.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Fastly (Fastly CDN)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Fastly-Request-ID', r'\\w+')):\n        return True\n\n    if self.matchHeader(('X-Served-By', r'^cache-[a-z]{3}\\d+-[A-Z]{3}')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/fortigate.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'FortiGate (Fortinet)'\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\ndef check_schema_01(self):\n    if not self.matchContent('//globalurl.fortinet.net'):\n        return False\n\n    if not self.matchContent('FortiGate Application Control'):\n        return False\n\n    return True\n\ndef check_schema_02(self):\n    if not self.matchContent('Web Application Firewall'):\n        return False\n\n    if not self.matchContent('Event ID'):\n        return False\n\n    if not self.matchContent('//globalurl.fortinet.net'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/fortiguard.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'FortiGuard (Fortinet)'\n\ndef is_waf(self):\n    if check_schema(self):\n        return True\n\n    return False\n\ndef check_schema(self):\n    # Use OR logic with strong signatures containing explicit FortiGuard/Fortinet branding\n    # Generic \"Web Filter\" strings are avoided to prevent false positives\n    if self.matchContent('FortiGuard Intrusion Prevention'):\n        return True\n\n    if self.matchContent('//globalurl.fortinet.net'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/fortiweb.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'FortiWeb (Fortinet)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if self.matchCookie(r'^FORTIWAFSID='):\n        return True\n\n    if self.matchContent('.fgd_icon'):\n        return True\n\n    return False\n\n\ndef check_schema_02(self):\n    if not self.matchContent('fgd_icon'):\n        return False\n\n    if not self.matchContent('web.page.blocked'):\n        return False\n\n    if not self.matchContent('url'):\n        return False\n\n    if not self.matchContent('attack.id'):\n        return False\n\n    if not self.matchContent('message.id'):\n        return False\n\n    if not self.matchContent('client.ip'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/frontdoor.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Azure Front Door (Microsoft)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Azure-Ref', '.+?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/gcparmor.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Google Cloud App Armor (Google Cloud)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Via', '1.1 google')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/godaddy.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'GoDaddy Website Protection (GoDaddy)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'GoDaddy (security|website firewall)'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/greywizard.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Greywizard (Grey Wizard)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'greywizard')):\n        return True\n\n    if self.matchContent(r'<(title|h\\d{1})>Grey Wizard'):\n        return True\n\n    if self.matchContent(r'contact the website owner or Grey Wizard'):\n        return True\n\n    if self.matchContent(r'We.ve detected attempted attack or non standard traffic from your ip address'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/huaweicloud.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Huawei Cloud Firewall (Huawei)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^HWWAFSESID='):\n        return True\n\n    if self.matchHeader(('Server', r'HuaweiCloudWAF')):\n        return True\n\n    if self.matchContent(r'hwclouds\\.com'):\n        return True\n\n    if self.matchContent(r'hws_security@'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/hyperguard.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'HyperGuard (Art of Defense)'\n\n\ndef is_waf(self):\n    if self.matchCookie('^WODSESSION='):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/ibmdatapower.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'DataPower (IBM)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Backside-Transport', r'(OK|FAIL)')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/imunify360.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Imunify360 (CloudLinux)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'imunify360.{0,10}?')):\n        return True\n\n    if self.matchContent(r'protected.by.{0,10}?imunify360'):\n        return True\n\n    if self.matchContent(r'powered.by.{0,10}?imunify360'):\n        return True\n\n    if self.matchContent(r'imunify360.preloader'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/incapsula.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Incapsula (Imperva Inc.)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^incap_ses.*?='):\n        return True\n\n    if self.matchCookie(r'^visid_incap.*?='):\n        return True\n\n    if self.matchContent(r'incapsula incident id'):\n        return True\n\n    if self.matchContent(r'powered by incapsula'):\n        return True\n\n    if self.matchContent(r'/_Incapsula_Resource'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/indusguard.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'IndusGuard (Indusface)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'IF_WAF')):\n        return True\n\n    if self.matchContent(r'This website is secured against online attacks. Your request was blocked'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/instartdx.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Instart DX (Instart Logic)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if self.matchHeader(('X-Instart-Request-ID', '.+')):\n        return True\n\n    if self.matchHeader(('X-Instart-Cache', '.+')):\n        return True\n\n    if self.matchHeader(('X-Instart-WL', '.+')):\n        return True\n\n    return False\n\n\ndef check_schema_02(self):\n    if not self.matchContent(r'the requested url was rejected'):\n        return False\n\n    if not self.matchContent(r'please consult with your administrator'):\n        return False\n\n    if not self.matchContent(r'your support id is'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/isaserver.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'ISA Server (Microsoft)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'The.{0,10}?(isa.)?server.{0,10}?denied the specified uniform resource locator \\(url\\)'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/janusec.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Janusec Application Gateway (Janusec)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'janusec application gateway'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/jiasule.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Jiasule (Jiasule)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'jiasule\\-waf')):\n        return True\n\n    if self.matchCookie(r'^jsl_tracking(.+)?='):\n        return True\n\n    if self.matchCookie(r'__jsluid='):\n        return True\n\n    if self.matchContent(r'notice\\-jiasule'):\n        return True\n\n    if self.matchContent(r'static\\.jiasule\\.com'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/kemp.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Kemp LoadMaster (Progress Software)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-ServedBy', 'KEMP-LM')) and \\\n        self.matchStatus(403) and \\\n        self.matchContent(r'<title>403 Forbidden</title>'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/keycdn.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'KeyCDN (KeyCDN)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'KeyCDN')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/knownsec.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'KS-WAF (KnownSec)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'/ks[-_]waf[-_]error\\.png'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/kona.py",
    "content": "#!/usr/bin/env python3\n\n\n#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Kona SiteDefender (Akamai)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'AkamaiGHost')):\n        return True\n\n    if self.matchHeader(('Server', 'AkamaiGHost'), attack=True)        :\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/limelight.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'LimeLight CDN (LimeLight)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^limelight'):\n        return True\n\n    if self.matchCookie(r'^l[mg]_sessid='):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/link11.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Link11 WAAP (Link11)'\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if self.matchHeader(('server', 'rhino-core-shield')):\n        return True\n\n    return False\n  \n"
  },
  {
    "path": "wafw00f/plugins/litespeed.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'LiteSpeed (LiteSpeed Technologies)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if not self.matchHeader(('Server', 'LiteSpeed')):\n        return False\n\n    if not self.matchStatus(403):\n        return False\n\n    return True\n\n\ndef check_schema_02(self):\n    if self.matchContent(r'Proudly powered by litespeed web server'):\n        return True\n\n    if self.matchContent(r'www\\.litespeedtech\\.com/error\\-page'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/malcare.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Malcare (Inactiv)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'firewall.{0,15}?powered.by.{0,15}?malcare.{0,15}?pro'):\n        return True\n\n    if self.matchContent('blocked because of malicious activities'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/maxcdn.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'MaxCDN (MaxCDN)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-CDN', r'maxcdn')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/missioncontrol.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Mission Control Shield (Mission Control)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'Mission Control Application Shield')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/modsecurity.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'ModSecurity (SpiderLabs)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    if check_schema_03(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if self.matchHeader(('Server', r'(mod_security|Mod_Security|NOYB)')):\n        return True\n\n    if self.matchContent(r'This error was generated by Mod.?Security'):\n        return True\n\n    if self.matchContent(r'rules of the mod.security.module'):\n        return True\n\n    if self.matchContent(r'mod.security.rules triggered'):\n        return True\n\n    if self.matchContent(r'Protected by Mod.?Security'):\n        return True\n\n    if self.matchContent(r'/modsecurity[\\-_]errorpage/'):\n        return True\n\n    if self.matchContent(r'modsecurity iis'):\n        return True\n\n    return False\n\n\ndef check_schema_02(self):\n    if not self.matchReason('ModSecurity Action'):\n        return False\n\n    if not self.matchStatus(403):\n        return False\n\n    return True\n\n\ndef check_schema_03(self):\n    if not self.matchReason('ModSecurity Action'):\n        return False\n\n    if not self.matchStatus(406):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/naxsi.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'NAXSI (NBS Systems)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Data-Origin', r'^naxsi(.+)?')):\n        return True\n\n    if self.matchHeader(('Server', r'naxsi(.+)?')):\n        return True\n\n    if self.matchContent(r'blocked by naxsi'):\n        return True\n\n    if self.matchContent(r'naxsi blocked information'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/nemesida.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Nemesida (PentestIt)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'@?nemesida(\\-security)?\\.com'):\n        return True\n\n    if self.matchContent(r'Suspicious activity detected.{0,10}?Access to the site is blocked'):\n        return True\n\n    if self.matchContent(r'nwaf@'):\n        return True\n\n    if self.matchStatus(222):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/netcontinuum.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'NetContinuum (Barracuda Networks)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^NCI__SessionId='):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/netscaler.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'NetScaler AppFirewall (Citrix Systems)'\n\n\ndef is_waf(self):\n    # This header can be obtained without attack mode\n    if self.matchHeader(('Via', r'NS\\-CACHE')):\n        return True\n\n    # Cookies are set only when someone is authenticated.\n    # Not much reliable since wafw00f isn't authenticating.\n    if self.matchCookie(r'^(ns_af=|citrix_ns_id|NSC_)'):\n        return True\n\n    if self.matchContent(r'(NS Transaction|AppFW Session) id'):\n        return True\n\n    if self.matchContent(r'Violation Category.{0,5}?APPFW_'):\n        return True\n\n    if self.matchContent(r'Citrix\\|NetScaler'):\n        return True\n\n    # Reliable but not all servers return this header\n    if self.matchHeader(('Cneonction', r'^(keep alive|close)'), attack=True):\n        return True\n\n    if self.matchHeader(('nnCoection', r'^(keep alive|close)'), attack=True):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/nevisproxy.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'NevisProxy (AdNovum)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^Navajo'):\n        return True\n\n    if self.matchCookie(r'^NP_ID'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/newdefend.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Newdefend (NewDefend)'\n\n\ndef is_waf(self):\n    # This header can be obtained without attack mode\n    # Most reliable fingerprint\n    if self.matchHeader(('Server', 'Newdefend')):\n        return True\n\n    # Reliable ones within blockpage\n    if self.matchContent(r'www\\.newdefend\\.com/feedback'):\n        return True\n\n    if self.matchContent(r'/nd\\-block/'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/nexusguard.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'NexusGuard Firewall (NexusGuard)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'Powered by Nexusguard'):\n        return True\n\n    if self.matchContent(r'nexusguard\\.com/wafpage/.+#\\d{3};'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/ninja.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'NinjaFirewall (NinTechNet)'\n\n\ndef is_waf(self):\n    if not self.matchContent(r'<title>NinjaFirewall.{0,10}?\\d{3}.forbidden'):\n        return False\n\n    if not self.matchContent(r'For security reasons?.{0,10}?it was blocked and logged'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/nsfocus.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'NSFocus (NSFocus Global Inc.)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'NSFocus')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/nullddos.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'NullDDoS Protection (NullDDoS)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'NullDDoS(.System)?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/onmessage.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'OnMessage Shield (BlackBaud)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Engine', 'onMessage Shield')):\n        return True\n\n    if self.matchContent(r'Blackbaud K\\-12 conducts routine maintenance'):\n        return True\n\n    if self.matchContent(r'onMessage SHEILD'):\n        return True\n\n    if self.matchContent(r'maintenance\\.blackbaud\\.com'):\n        return True\n\n    if self.matchContent(r'status\\.blackbaud\\.com'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/openresty.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Open-Resty Lua Nginx (FLOSS)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if not self.matchHeader(('Server', r'^openresty/[0-9\\.]+?')):\n        return False\n\n    if not self.matchStatus(403):\n        return False\n\n    return True\n\n\ndef check_schema_02(self):\n    if not self.matchContent(r'openresty/[0-9\\.]+?'):\n        return False\n\n    if not self.matchStatus(406):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/oraclecloud.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Oracle Cloud (Oracle)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'<title>fw_error_www'):\n        return True\n\n    if self.matchContent(r'src=\\\"/oralogo_small\\.gif\\\"'):\n        return True\n\n    if self.matchContent(r'www\\.oracleimg\\.com/us/assets/metrics/ora_ocom\\.js'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/paloalto.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Palo Alto Next Gen Firewall (Palo Alto Networks)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'Download of virus.spyware blocked'):\n        return True\n\n    if self.matchContent(r'Palo Alto Next Generation Security Platform'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/panyun360.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = '360PanYun (360 Technologies)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'panyun')):\n        return True\n    if self.matchHeader(('X-Panyun-Request-ID', r'.+?'), attack=True):\n        return True\n    if self.matchHeader(('X-Panyun-Error-Reason', r'.+?'), attack=True):\n        return True\n    if self.matchHeader(('X-Panyun-Error-Step', r'.+?'), attack=True):\n        return True\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/pentawaf.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'PentaWAF (Global Network Services)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'PentaWaf(/[0-9\\.]+)?')):\n        return True\n\n    if self.matchContent(r'Penta.?Waf/[0-9\\.]+?.server'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/perimeterx.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'PerimeterX (PerimeterX)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'www\\.perimeterx\\.(com|net)/whywasiblocked'):\n        return True\n\n    if self.matchContent(r'client\\.perimeterx\\.(net|com)'):\n        return True\n\n    if self.matchContent(r'denied because we believe you are using automation tools'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/pksec.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'pkSecurity IDS (pkSec)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if self.matchContent(r'pk.?Security.?Module'):\n        return True\n\n    if self.matchContent(r'Security.Alert'):\n        return True\n\n    return False\n\n\ndef check_schema_02(self):\n    if not self.matchContent(r'As this could be a potential hack attack'):\n        return False\n\n    if not self.matchContent(r'A safety critical (call|request) was (detected|discovered) and blocked'):\n        return False\n\n    if not self.matchContent(r'maximum number of reloads per minute and prevented access'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/powercdn.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'PowerCDN (PowerCDN)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Via', r'(.*)?powercdn.com(.*)?')):\n        return True\n\n    if self.matchHeader(('X-Cache', r'(.*)?powercdn.com(.*)?')):\n        return True\n\n    if self.matchHeader(('X-CDN', r'PowerCDN')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/profense.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Profense (ArmorLogic)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'Profense')):\n        return True\n\n    if self.matchCookie(r'^PLBSID='):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/ptaf.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'PT Application Firewall (Positive Technologies)'\n\n\ndef is_waf(self):\n    if not self.matchContent(r'<h1.{0,10}?Forbidden'):\n        return False\n\n    if not self.matchContent(r'<pre>Request.ID:.{0,10}?\\d{4}\\-(\\d{2})+.{0,35}?pre>'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/puhui.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Puhui (Puhui)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'Puhui[\\-_]?WAF')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/qcloud.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Qcloud (Tencent Cloud)'\n\n\ndef is_waf(self):\n    if not self.matchContent(r'腾讯云Web应用防火墙'):\n        return False\n\n    if not self.matchStatus(403):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/qiniu.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Qiniu (Qiniu CDN)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Qiniu-CDN', r'\\d+?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/qrator.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Qrator (Qrator)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'QRATOR')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/radware.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'AppWall (Radware)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if self.matchContent(r'CloudWebSec\\.radware\\.com'):\n        return True\n\n    if self.matchHeader(('X-SL-CompState', '.+')):\n        return True\n\n    return False\n\n\ndef check_schema_02(self):\n    if not self.matchContent(r'because we have detected unauthorized activity'):\n        return False\n\n    if not self.matchContent(r'<title>Unauthorized Request Blocked'):\n        return False\n\n    if not self.matchContent(r'if you believe that there has been some mistake'):\n        return False\n\n    if not self.matchContent(r'\\?Subject=Security Page.{0,10}?Case Number'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/reblaze.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Reblaze (Reblaze)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if self.matchCookie(r'^rbzid'):\n        return True\n\n    if self.matchHeader(('Server', 'Reblaze Secure Web Gateway')):\n        return True\n\n    return False\n\n\ndef check_schema_02(self):\n    if not self.matchContent(r'current session has been terminated'):\n        return False\n\n    if not self.matchContent(r'do not hesitate to contact us'):\n        return False\n\n    if not self.matchContent(r'access denied \\(\\d{3}\\)'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/reflected.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Reflected Networks (Reflected Networks)'\n\n\ndef is_waf(self):\n    if self.matchContent('<img class=\"logo loader\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAbgAAABHCAIAAAD6G8WcAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw') and \\\n        self.matchStatus(403) and \\\n        self.matchContent(r'content=\"Request is denied\"') and \\\n        self.matchContent(r'<title>Forbidden</title>'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/rsfirewall.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'RSFirewall (RSJoomla!)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'com_rsfirewall_(\\d{3}_forbidden|event)?'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/rvmode.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'RequestValidationMode (Microsoft)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'Request Validation has detected a potentially dangerous client input'):\n        return True\n\n    if self.matchContent(r'ASP\\.NET has detected data in the request'):\n        return True\n\n    if self.matchContent(r'HttpRequestValidationException'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/sabre.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Sabre Firewall (Sabre)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'dxsupport\\.sabre\\.com'):\n        return True\n\n    if check_schema_01(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if not self.matchContent(r'<title>Application Firewall Error'):\n        return False\n\n    if not self.matchContent(r'add some important details to the email for us to investigate'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/safe3.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Safe3 Web Firewall (Safe3)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'Safe3 Web Firewall')):\n        return True\n\n    if self.matchHeader(('X-Powered-By', r'Safe3WAF/[\\.0-9]+?')):\n        return True\n\n    if self.matchContent(r'Safe3waf/[0-9\\.]+?'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/safedog.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Safedog (SafeDog)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^safedog\\-flow\\-item='):\n        return True\n\n    if self.matchHeader(('Server', 'Safedog')):\n        return True\n\n    if self.matchContent(r'safedogsite/broswer_logo\\.jpg'):\n        return True\n\n    if self.matchContent(r'404\\.safedog\\.cn/sitedog_stat.html'):\n        return True\n\n    if self.matchContent(r'404\\.safedog\\.cn/images/safedogsite/head\\.png'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/safeline.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Safeline (Chaitin Tech.)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'safeline|<!\\-\\-\\sevent id:'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/scutum.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n\"\"\"\n\nNAME = \"Scutum (Secure Sky Technology Inc.)\"\n\n\ndef is_waf(self):\n    if self.matchHeader((\"Server\", \"Scutum\")):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/secking.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'SecKing (SecKing)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'secking(.?waf)?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/secupress.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'SecuPress WP Security (SecuPress)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'<(title|h\\d{1})>SecuPress'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/secureentry.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Secure Entry (United Security Providers)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'Secure Entry Server')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/secureiis.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'eEye SecureIIS (BeyondTrust)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'SecureIIS is an internet security application'):\n        return True\n\n    if self.matchContent(r'Download SecureIIS Personal Edition'):\n        return True\n\n    if self.matchContent(r'https?://www\\.eeye\\.com/Secure\\-?IIS'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/securesphere.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'SecureSphere (Imperva Inc.)'\n\n\ndef is_waf(self):\n    if not self.matchContent(r'<(title|h2)>Error'):\n        return False\n\n    if not self.matchContent(r'The incident ID is'):\n        return False\n\n    if not self.matchContent(r\"This page can't be displayed\"):\n        return False\n\n    if not self.matchContent(r'Contact support for additional information'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/senginx.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'SEnginx (Neusoft)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'SENGINX\\-ROBOT\\-MITIGATION'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/serverdefender.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'ServerDefender VP (Port80 Software)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Pint', r'p(ort\\-)?80')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/shadowd.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Shadow Daemon (Zecure)'\n\n\ndef is_waf(self):\n    if not self.matchContent(r\"<h\\d{1}>\\d{3}.forbidden<.h\\d{1}>\"):\n        return False\n\n    if not self.matchContent(r\"request forbidden by administrative rules\"):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/shieldon.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Shieldon Firewall (Shieldon.io)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    if check_schema_03(self):\n        return True\n\n    if self.matchHeader((r'[Xx]-[Pp]rotected-[Bb]y', 'shieldon.io')):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if not self.matchContent('Please solve CAPTCHA'):\n        return False\n\n    if not self.matchContent('shieldon_captcha'):\n        return False\n\n    if not self.matchContent('Unusual behavior detected'):\n        return False\n\n    if not self.matchContent('status-user-info'):\n        return False\n\n    return True\n\n\ndef check_schema_02(self):\n    if not self.matchContent('Access denied'):\n        return False\n\n    if not self.matchContent('The IP address you are using has been blocked.'):\n        return False\n\n    if not self.matchContent('status-user-info'):\n        return False\n\n    return True\n\n\ndef check_schema_03(self):\n    if not self.matchContent('Please line up'):\n        return False\n\n    if not self.matchContent('This page is limiting the number of people online. Please wait a moment.'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/shieldsecurity.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Shield Security (One Dollar Plugin)'\n\n\ndef is_waf(self):\n    if self.matchContent(r\"You were blocked by the Shield\"):\n        return True\n\n    if self.matchContent(r\"remaining transgression\\(s\\) against this site\"):\n        return True\n\n    if self.matchContent(r\"Something in the URL.{0,5}?Form or Cookie data wasn\\'t appropriate\"):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/siteground.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'SiteGround (SiteGround)'\n\n\ndef is_waf(self):\n    if self.matchContent(r\"Our system thinks you might be a robot!\"):\n        return True\n\n    if self.matchContent(r'access is restricted due to a security rule'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/siteguard.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'SiteGuard (EG Secure Solutions Inc.)'\n\n\ndef is_waf(self):\n    if self.matchContent(r\"Powered by SiteGuard\"):\n        return True\n\n    if self.matchContent(r'The server refuse to browse the page'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/sitelock.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Sitelock (TrueShield)'\n\n# Well this is confusing, Sitelock itself uses Incapsula from Imperva\n# So the fingerprints obtained on blockpage are similar to those of Incapsula.\n\ndef is_waf(self):\n    if self.matchContent(r\"SiteLock will remember you\"):\n        return True\n\n    if self.matchContent(r\"Sitelock is leader in Business Website Security Services\"):\n        return True\n\n    if self.matchContent(r\"sitelock[_\\-]shield([_\\-]logo|[\\-_]badge)?\"):\n        return True\n\n    if self.matchContent(r'SiteLock incident ID'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/sonicwall.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'SonicWall (Dell)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'SonicWALL')):\n        return True\n\n    if self.matchContent(r\"<(title|h\\d{1})>Web Site Blocked\"):\n        return True\n\n    if self.matchContent(r'\\+?nsa_banner'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/sophos.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'UTM Web Protection (Sophos)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if self.matchContent(r'www\\.sophos\\.com'):\n        return True\n\n    if self.matchContent(r'Powered by.?(Sophos)? UTM Web Protection'):\n        return True\n\n    return False\n\n\ndef check_schema_02(self):\n    if not self.matchContent(r'<title>Access to the requested URL was blocked'):\n        return False\n\n    if not self.matchContent(r'Access to the requested URL was blocked'):\n        return False\n\n    if not self.matchContent(r'incident was logged with the following log identifier'):\n        return False\n\n    if not self.matchContent(r'Inbound Anomaly Score exceeded'):\n        return False\n\n    if not self.matchContent(r'Your cache administrator is'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/squarespace.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Squarespace (Squarespace)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'Squarespace')):\n        return True\n\n    if self.matchCookie(r'^SS_ANALYTICS_ID='):\n        return True\n\n    if self.matchCookie(r'^SS_MATTR='):\n        return True\n\n    if self.matchCookie(r'^SS_MID='):\n        return True\n\n    if self.matchCookie(r'SS_CVT='):\n        return True\n\n    if self.matchContent(r'status\\.squarespace\\.com'):\n        return True\n\n    if self.matchContent(r'BRICK\\-\\d{2}'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/squidproxy.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'SquidProxy IDS (SquidProxy)'\n\n\ndef is_waf(self):\n    if not self.matchHeader(('Server', r'squid(/[0-9\\.]+)?')):\n        return False\n\n    if not self.matchContent(r'Access control configuration prevents your request'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/stackpath.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'StackPath (StackPath)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if self.matchContent(r'<title>StackPath[^<]+</title>'):\n        return True\n\n    if self.matchContent(r'Protected by <a href=\"https?:\\/\\/(?:www\\.)?stackpath\\.com\\/\"[^>]+>StackPath'):\n        return True\n\n    return False\n\n\ndef check_schema_02(self):\n    if not self.matchContent(r\"is using a security service for protection against online attacks\"):\n        return False\n\n    if not self.matchContent(r'An action has triggered the service and blocked your request'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/sucuri.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Sucuri CloudProxy (Sucuri Inc.)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Sucuri-ID', r'.+?')):\n        return True\n\n    if self.matchHeader(('X-Sucuri-Cache', r'.+?')):\n        return True\n\n    if self.matchHeader(('Server', r'Sucuri(\\-Cloudproxy)?')):\n        return True\n\n    if self.matchHeader(('X-Sucuri-Block', r'.+?'), attack=True):\n        return True\n\n    if self.matchContent(r\"Access Denied.{0,6}?Sucuri Website Firewall\"):\n        return True\n\n    if self.matchContent(r\"<title>Sucuri WebSite Firewall.{0,6}?(CloudProxy)?.{0,6}?Access Denied\"):\n        return True\n\n    if self.matchContent(r\"sucuri\\.net/privacy\\-policy\"):\n        return True\n\n    if self.matchContent(r\"cdn\\.sucuri\\.net/sucuri[-_]firewall[-_]block\\.css\"):\n        return True\n\n    if self.matchContent(r'cloudproxy@sucuri\\.net'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/tencent.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Tencent Cloud Firewall (Tencent Technologies)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'waf\\.tencent\\-?cloud\\.com/'):\n        return True\n\n    if self.matchContent(r'window\\.location\\.href.{1,3}?https?://waf.tencent(?:-?cloud)?.com/(?:403|501)page\\.html'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/teros.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Teros (Citrix Systems)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^st8id='):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/threatx.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'ThreatX (A10 Networks)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Request-Id', r'.*')) and \\\n       self.matchContent(r\"^Forbidden - ID: ([a-fA-F0-9]{32})$\") and \\\n       self.matchStatus(403):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/transip.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'TransIP Web Firewall (TransIP)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-TransIP-Backend', '.+')):\n        return True\n\n    if self.matchHeader(('X-TransIP-Balancer', '.+')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/uewaf.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'UEWaf (UCloud)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'uewaf(/[0-9\\.]+)?')):\n        return True\n\n    if self.matchContent(r'/uewaf_deny_pages/default/img/'):\n        return True\n\n    if self.matchContent(r'ucloud\\.cn'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/urlmaster.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'URLMaster SecurityCheck (iFinity/DotNetNuke)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if self.matchHeader(('X-UrlMaster-Debug', '.+')):\n        return True\n\n    if self.matchHeader(('X-UrlMaster-Ex', '.+')):\n        return True\n\n    return False\n\n\ndef check_schema_02(self):\n    if not self.matchContent(r\"Ur[li]RewriteModule\"):\n        return False\n\n    if not self.matchContent(r'SecurityCheck'):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/urlscan.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'URLScan (Microsoft)'\n\n\ndef is_waf(self):\n    if self.matchContent(r\"Rejected[-_]By[_-]UrlScan\"):\n        return True\n\n    if self.matchContent(r'A custom filter or module.{0,4}?such as URLScan'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/variti.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Variti (Variti)'\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'Variti(?:\\/[a-z0-9\\.\\-]+)?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/varnish.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Varnish (OWASP)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'Request rejected by xVarnish\\-WAF'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/vercel.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Vercel WAF (Vercel)'\n\n\ndef is_waf(self):\n    if self.matchContent(r'<title>Vercel Security Checkpoint</title>'):\n        return True\n    if self.matchContent(r'/vercel/security/'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/viettel.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Viettel (Cloudrity)'\n\n\ndef is_waf(self):\n    if self.matchContent(r\"Access Denied.{0,10}?Viettel WAF\"):\n        return True\n\n    if self.matchContent(r\"cloudrity\\.com\\.(vn)?/\"):\n        return True\n\n    if self.matchContent(r\"Viettel WAF System\"):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/virusdie.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'VirusDie (VirusDie LLC)'\n\n\ndef is_waf(self):\n    if self.matchContent(r\"cdn\\.virusdie\\.ru/splash/firewallstop\\.png\"):\n        return True\n\n    if self.matchContent(r'copy.{0,10}?Virusdie\\.ru'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/wallarm.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Wallarm (Wallarm Inc.)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'nginx[\\-_]wallarm')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/watchguard.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'WatchGuard (WatchGuard Technologies)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'WatchGuard')):\n        return True\n\n    if self.matchContent(r\"Request denied by WatchGuard Firewall\"):\n        return True\n\n    if self.matchContent(r'WatchGuard Technologies Inc\\.'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/webarx.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'WebARX (WebARX Security Solutions)'\n\n\ndef is_waf(self):\n    if self.matchContent(r\"WebARX.{0,10}?Web Application Firewall\"):\n        return True\n\n    if self.matchContent(r\"www\\.webarxsecurity\\.com\"):\n        return True\n\n    if self.matchContent(r'/wp\\-content/plugins/webarx/includes/'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/webknight.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'WebKnight (AQTRONIX)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    if check_schema_03(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if not self.matchStatus(999):\n        return False\n\n    if not self.matchReason('No Hacking'):\n        return False\n\n    return True\n\n\ndef check_schema_02(self):\n    if not self.matchStatus(404):\n        return False\n\n    if not self.matchReason('Hack Not Found'):\n        return False\n\n    return True\n\n\ndef check_schema_03(self):\n    if self.matchContent(r'WebKnight Application Firewall Alert'):\n        return True\n\n    if self.matchContent(r'What is webknight\\?'):\n        return True\n\n    if self.matchContent(r'AQTRONIX WebKnight is an application firewall'):\n        return True\n\n    if self.matchContent(r'WebKnight will take over and protect'):\n        return True\n\n    if self.matchContent(r'aqtronix\\.com/WebKnight'):\n        return True\n\n    if self.matchContent(r'AQTRONIX.{0,10}?WebKnight'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/webland.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'WebLand (WebLand)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'protected by webland')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/webray.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'RayWAF (WebRay Solutions)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'WebRay\\-WAF')):\n        return True\n\n    if self.matchHeader(('DrivedBy', r'RaySrv.RayEng/[0-9\\.]+?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/webseal.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'WebSEAL (IBM)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'WebSEAL')):\n        return True\n\n    if self.matchContent(r\"This is a WebSEAL error message template file\"):\n        return True\n\n    if self.matchContent(r\"WebSEAL server received an invalid HTTP request\"):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/webtotem.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'WebTotem (WebTotem)'\n\n\ndef is_waf(self):\n    if self.matchContent(r\"The current request was blocked.{0,8}?>WebTotem\"):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/west263cdn.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'West263 CDN (West263CDN)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-Cache', r'WS?T263CDN')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/wordfence.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Wordfence (Defiant)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'wf[_\\-]?WAF')):\n        return True\n\n    if self.matchContent(r\"Generated by Wordfence\"):\n        return True\n\n    if self.matchContent(r'broke one of (the )?Wordfence (advanced )?blocking rules'):\n        return True\n\n    if self.matchContent(r\"/plugins/wordfence\"):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/wpmudev.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'wpmudev WAF (Incsub)'\n\n\ndef is_waf(self):\n    if check_schema_01(self):\n        return True\n\n    if check_schema_02(self):\n        return True\n\n    return False\n\n\ndef check_schema_01(self):\n    if not self.matchContent(r'href=\"http(s)?.\\/\\/wpmudev.com\\/.{0,15}?'):\n        return False\n\n    if not self.matchContent(r'Click on the Logs tab, then the WAF Log.'):\n        return False\n\n    if not self.matchContent(r'Choose your site from the list'):\n        return False\n\n    if not self.matchStatus(403):\n        return False\n\n    return True\n\n\ndef check_schema_02(self):\n    if not self.matchContent(r'<h1>Whoops, this request has been blocked!'):\n        return False\n\n    if not self.matchContent(r'This request has been deemed suspicious'):\n        return False\n\n    if not self.matchContent(r'possible attack on our servers.'):\n        return False\n\n    if not self.matchStatus(403):\n        return False\n\n    return True\n"
  },
  {
    "path": "wafw00f/plugins/wts.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'WTS-WAF (WTS)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'wts/[0-9\\.]+?')):\n        return True\n\n    if self.matchContent(r\"<(title|h\\d{1})>WTS\\-WAF\"):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/wzb360.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = '360WangZhanBao (360 Technologies)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'qianxin\\-waf')):\n        return True\n\n    if self.matchHeader(('WZWS-Ray', r'.+?')):\n        return True\n\n    if self.matchHeader(('X-Powered-By-360WZB', r'.+?')):\n        return True\n\n    if self.matchContent(r'wzws\\-waf\\-cgi/'):\n        return True\n\n    if self.matchContent(r'wangshan\\.360\\.cn'):\n        return True\n\n    if self.matchStatus(493):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/xlabssecuritywaf.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'XLabs Security WAF (XLabs)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('X-CDN', r'XLabs Security')):\n        return True\n\n    if self.matchHeader(('Secured', r'^By XLabs Security')):\n        return True\n\n    if self.matchHeader(('Server', r'XLabs[-_]?.?WAF'), attack=True):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/xuanwudun.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Xuanwudun (Xuanwudun)'\n\n\ndef is_waf(self):\n    if self.matchContent(r\"admin\\.dbappwaf\\.cn/(index\\.php/Admin/ClientMisinform/)?\"):\n        return True\n\n    if self.matchContent(r'class=.(db[\\-_]?)?waf(.)?([\\-_]?row)?>'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/yundun.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Yundun (Yundun)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'YUNDUN')):\n        return True\n\n    if self.matchHeader(('X-Cache', 'YUNDUN')):\n        return True\n\n    if self.matchCookie(r'^yd_cookie='):\n        return True\n\n    if self.matchContent(r'Blocked by YUNDUN Cloud WAF'):\n        return True\n\n    if self.matchContent(r'yundun\\.com/yd[-_]http[_-]error/'):\n        return True\n\n    if self.matchContent(r'www\\.yundun\\.com/(static/js/fingerprint\\d{1}?\\.js)?'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/yunsuo.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Yunsuo (Yunsuo)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^yunsuo_session='):\n        return True\n\n    if self.matchContent(r'class=\\\"yunsuologo\\\"'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/yxlink.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'YXLink (YxLink Technologies)'\n\n\ndef is_waf(self):\n    if self.matchCookie(r'^yx_ci_session='):\n        return True\n\n    if self.matchCookie(r'^yx_language='):\n        return True\n\n    if self.matchHeader(('Server', r'Yxlink([\\-_]?WAF)?')):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/zenedge.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'Zenedge (Zenedge)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', 'ZENEDGE')):\n        return True\n\n    if self.matchHeader(('X-Zen-Fury', r'.+?')):\n        return True\n\n    if self.matchContent(r'/__zenedge/'):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/plugins/zscaler.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\nNAME = 'ZScaler (Accenture)'\n\n\ndef is_waf(self):\n    if self.matchHeader(('Server', r'ZScaler')):\n        return True\n\n    if self.matchContent(r\"Access Denied.{0,10}?Accenture Policy\"):\n        return True\n\n    if self.matchContent(r'policies\\.accenture\\.com'):\n        return True\n\n    if self.matchContent(r'login\\.zscloud\\.net/img_logo_new1\\.png'):\n        return True\n\n    if self.matchContent(r'Zscaler to protect you from internet threats'):\n        return True\n\n    if self.matchContent(r\"Internet Security by ZScaler\"):\n        return True\n\n    if self.matchContent(r\"Accenture.{0,10}?webfilters indicate that the site likely contains\"):\n        return True\n\n    return False\n"
  },
  {
    "path": "wafw00f/wafprio.py",
    "content": "#!/usr/bin/env python3\n'''\nCopyright (C) 2026, WAFW00F Developers.\nSee the LICENSE file for copying permission.\n'''\n\n# NOTE: this priority list is used so that each check can be prioritized,\n# so that the quick checks are done first and ones that require more\n# requests, are done later\n\n\nwafdetectionsprio = [\n\t'360PanYun (360 Technologies)',\n\t'360WangZhanBao (360 Technologies)',\n\t'ACE XML Gateway (Cisco)',\n\t'ASP.NET Generic (Microsoft)',\n\t'ASPA Firewall (ASPA Engineering Co.)',\n\t'AWS Elastic Load Balancer (Amazon)',\n\t'AireeCDN (Airee)',\n\t'Airlock (Phion/Ergon)',\n\t'Alert Logic (Alert Logic)',\n\t'AliYunDun (Alibaba Cloud Computing)',\n\t'AnYu (AnYu Technologies)',\n\t'Anquanbao (Anquanbao)',\n\t'Anubis (Techaro)',\n\t'AppWall (Radware)',\n\t'Approach (Approach)',\n\t'Armor Defense (Armor)',\n\t'ArvanCloud (ArvanCloud)',\n\t'Astra (Czar Securities)',\n\t'Azion Edge Firewall (Azion)',\n\t'Azure Application Gateway (Microsoft)',\n\t'Azure Front Door (Microsoft)',\n\t'Baffin Bay (Mastercard)',\n\t'BIG-IP AP Manager (F5 Networks)',\n\t'BIG-IP AppSec Manager (F5 Networks)',\n\t'BIG-IP Local Traffic Manager (F5 Networks)',\n\t'Barikode (Ethic Ninja)',\n\t'Barracuda (Barracuda Networks)',\n\t'Bekchy (Faydata Technologies Inc.)',\n\t'Beluga CDN (Beluga)',\n\t'BinarySec (BinarySec)',\n\t'BitNinja (BitNinja)',\n\t'BlockDoS (BlockDoS)',\n\t'Bluedon (Bluedon IST)',\n\t'BulletProof Security Pro (AITpro Security)',\n\t'CacheFly CDN (CacheFly)',\n\t'CacheWall (Varnish)',\n\t'CdnNS Application Gateway (CdnNs/WdidcNet)',\n\t'ChinaCache Load Balancer (ChinaCache)',\n\t'Chuang Yu Shield (Yunaq)',\n\t'Cloud Protector (Rohde & Schwarz CyberSecurity)',\n\t'Cloudbric (Penta Security)',\n\t'Cloudflare (Cloudflare Inc.)',\n\t'Cloudfloor (Cloudfloor DNS)',\n\t'Cloudfront (Amazon)',\n\t'Comodo cWatch (Comodo CyberSecurity)',\n\t'CrawlProtect (Jean-Denis Brun)',\n\t'DDoS-GUARD (DDOS-GUARD CORP.)',\n\t'DOSarrest (DOSarrest Internet Security)',\n\t'DataPower (IBM)',\n\t'DenyALL (Rohde & Schwarz CyberSecurity)',\n\t'Distil (Distil Networks)',\n\t'DotDefender (Applicure Technologies)',\n\t'DynamicWeb Injection Check (DynamicWeb)',\n\t'Edgecast (Verizon Digital Media)',\n\t'Eisoo Cloud Firewall (Eisoo)',\n\t'Envoy (EnvoyProxy)',\n\t'Expression Engine (EllisLab)',\n\t'Fastly (Fastly CDN)',\n\t'FirePass (F5 Networks)',\n\t'FortiGate (Fortinet)',\n\t'FortiGuard (Fortinet)',\n\t'FortiWeb (Fortinet)',\n\t'GoDaddy Website Protection (GoDaddy)',\n\t'Google Cloud App Armor (Google Cloud)',\n\t'Greywizard (Grey Wizard)',\n\t'Huawei Cloud Firewall (Huawei)',\n\t'HyperGuard (Art of Defense)',\n\t'ISA Server (Microsoft)',\n\t'Imunify360 (CloudLinux)',\n\t'Incapsula (Imperva Inc.)',\n\t'IndusGuard (Indusface)',\n\t'Instart DX (Instart Logic)',\n\t'Janusec Application Gateway (Janusec)',\n\t'Jiasule (Jiasule)',\n\t'KS-WAF (KnownSec)',\n\t'Kemp LoadMaster (Progress Software)',\n\t'KeyCDN (KeyCDN)',\n\t'Kona SiteDefender (Akamai)',\n\t'LimeLight CDN (LimeLight)',\n\t'Link11 WAAP (Link11)',\n\t'LiteSpeed (LiteSpeed Technologies)',\n\t'Malcare (Inactiv)',\n\t'MaxCDN (MaxCDN)',\n\t'Mission Control Shield (Mission Control)',\n\t'ModSecurity (SpiderLabs)',\n\t'NAXSI (NBS Systems)',\n\t'NSFocus (NSFocus Global Inc.)',\n\t'Nemesida (PentestIt)',\n\t'NetContinuum (Barracuda Networks)',\n\t'NetScaler AppFirewall (Citrix Systems)',\n\t'NevisProxy (AdNovum)',\n\t'Newdefend (NewDefend)',\n\t'NexusGuard Firewall (NexusGuard)',\n\t'NinjaFirewall (NinTechNet)',\n\t'NullDDoS Protection (NullDDoS)',\n\t'OnMessage Shield (BlackBaud)',\n\t'Open-Resty Lua Nginx (FLOSS)',\n\t'Oracle Cloud (Oracle)',\n\t'PT Application Firewall (Positive Technologies)',\n\t'Palo Alto Next Gen Firewall (Palo Alto Networks)',\n\t'PentaWAF (Global Network Services)',\n\t'PerimeterX (PerimeterX)',\n\t'PowerCDN (PowerCDN)',\n\t'Profense (ArmorLogic)',\n\t'Puhui (Puhui)',\n\t'Qcloud (Tencent Cloud)',\n\t'Qiniu (Qiniu CDN)',\n\t'Qrator (Qrator)',\n\t'RSFirewall (RSJoomla!)',\n\t'RayWAF (WebRay Solutions)',\n\t'Reblaze (Reblaze)',\n\t'Reflected Networks (Reflected Networks)',\n\t'RequestValidationMode (Microsoft)',\n\t'SEnginx (Neusoft)',\n\t'Sabre Firewall (Sabre)',\n\t'Safe3 Web Firewall (Safe3)',\n\t'Safedog (SafeDog)',\n\t'Safeline (Chaitin Tech.)',\n\t'Scutum (Secure Sky Technology Inc.)',\n\t'SecKing (SecKing)',\n\t'SecuPress WP Security (SecuPress)',\n\t'Secure Entry (United Security Providers)',\n\t'SecureSphere (Imperva Inc.)',\n\t'ServerDefender VP (Port80 Software)',\n\t'Shadow Daemon (Zecure)',\n\t'Shield Security (One Dollar Plugin)',\n\t'Shieldon Firewall (Shieldon.io)',\n\t'SiteGround (SiteGround)',\n\t'SiteGuard (EG Secure Solutions Inc.)',\n\t'Sitelock (TrueShield)',\n\t'SonicWall (Dell)',\n\t'Squarespace (Squarespace)',\n\t'SquidProxy IDS (SquidProxy)',\n\t'StackPath (StackPath)',\n\t'Sucuri CloudProxy (Sucuri Inc.)',\n\t'Tencent Cloud Firewall (Tencent Technologies)',\n\t'Teros (Citrix Systems)',\n\t'ThreatX (A10 Networks)',\n\t'Trafficshield (F5 Networks)',\n\t'TransIP Web Firewall (TransIP)',\n\t'UEWaf (UCloud)',\n\t'URLMaster SecurityCheck (iFinity/DotNetNuke)',\n\t'URLScan (Microsoft)',\n\t'UTM Web Protection (Sophos)',\n\t'Variti (Variti)',\n\t'Varnish (OWASP)',\n\t'Vercel WAF (Vercel)',\n\t'Viettel (Cloudrity)',\n\t'VirusDie (VirusDie LLC)',\n\t'WP Cerber Security (Cerber Tech)',\n\t'WTS-WAF (WTS)',\n\t'Wallarm (Wallarm Inc.)',\n\t'WatchGuard (WatchGuard Technologies)',\n\t'WebARX (WebARX Security Solutions)',\n\t'WebKnight (AQTRONIX)',\n\t'WebLand (WebLand)',\n\t'WebSEAL (IBM)',\n\t'WebTotem (WebTotem)',\n\t'West263 CDN (West263CDN)',\n\t'Wordfence (Defiant)',\n\t'XLabs Security WAF (XLabs)',\n\t'Xuanwudun (Xuanwudun)',\n\t'YXLink (YxLink Technologies)',\n\t'Yundun (Yundun)',\n\t'Yunjiasu (Baidu Cloud Computing)',\n\t'Yunsuo (Yunsuo)',\n\t'ZScaler (Accenture)',\n\t'Zenedge (Zenedge)',\n\t'aeSecure (aeSecure)',\n\t'eEye SecureIIS (BeyondTrust)',\n\t'pkSecurity IDS (pkSec)',\n\t'wpmudev WAF (Incsub)',\n]\n"
  }
]