Skip to content

Commit d9d50e8

Browse files
authored
Merge commit from fork
* Replaced unbounded quantifiers with bounded quantifiers to prevent catastrophic backtracking (patterns) * Further optimizations + Replaced re with re2 + minor unrelated optimization * Revert from re2 to re * Bumping version
1 parent 1e6c287 commit d9d50e8

File tree

12 files changed

+120
-107
lines changed

12 files changed

+120
-107
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ ___
3636

3737
Environment
3838
===========
39-
- FastAPI Guard version: [e.g. 3.0.0]
39+
- FastAPI Guard version: [e.g. 3.0.1]
4040
- Python version: [e.g. 3.11.10]
4141
- FastAPI version: [e.g. 0.115.0]
4242
- OS: [e.g. Ubuntu 22.04, Windows 11, MacOS 15.4]

.mike.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ version_selector: true
22
title_switch: true
33
versions_file: docs/versions/versions.json
44
versions:
5+
- 3.0.1
56
- 3.0.0
67
- 2.1.3
78
- 2.1.2
@@ -22,4 +23,4 @@ versions:
2223
- 0.3.2
2324
- latest
2425
aliases:
25-
latest: 3.0.0
26+
latest: 3.0.1

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ Release Notes
33

44
___
55

6+
v3.0.1 (2025-07-07)
7+
-------------------
8+
9+
Security Fixes (v3.0.1)
10+
------------
11+
12+
- **IMPORTANT**: Prevented ReDoS (Regular Expression Denial of Service - CWE-1333) attacks by replacing unbounded regex quantifiers with bounded ones. (GHSA-j47q-rc62-w448)
13+
- **CVE ID**: (TBD)
14+
15+
___
16+
617
v3.0.0 (2025-06-21)
718
-------------------
819

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,6 @@ Acknowledgements
622622

623623
- [FastAPI](https://fastapi.tiangolo.com/)
624624
- [IPInfo](https://ipinfo.io/)
625-
- [aiohttp](https://docs.aiohttp.org/)
626625
- [cachetools](https://cachetools.readthedocs.io/)
627626
- [requests](https://docs.python-requests.org/)
628627
- [Redis](https://redis.io/)

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ You can also download the example app as a Docker container from [GitHub Contain
7575
docker pull ghcr.io/rennf93/fastapi-guard-example:latest
7676

7777
# Or pull a specific version (matches library releases)
78-
docker pull ghcr.io/rennf93/fastapi-guard-example:v3.0.0
78+
docker pull ghcr.io/rennf93/fastapi-guard-example:v3.0.1
7979
```
8080

8181
___

docs/release-notes.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,17 @@ Release Notes
1010

1111
___
1212

13+
v3.0.1 (2025-07-07)
14+
-------------------
15+
16+
Security Fixes (v3.0.1)
17+
------------
18+
19+
- **IMPORTANT**: Prevented ReDoS (Regular Expression Denial of Service - CWE-1333) attacks by replacing unbounded regex quantifiers with bounded ones. (GHSA-j47q-rc62-w448)
20+
- **CVE ID**: (TBD)
21+
22+
___
23+
1324
v3.0.0 (2025-06-21)
1425
-------------------
1526

docs/versions/versions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"3.0.1": "3.0.1",
23
"3.0.0": "3.0.0",
34
"2.1.3": "2.1.3",
45
"2.1.2": "2.1.2",
@@ -18,5 +19,5 @@
1819
"0.3.4": "0.3.4",
1920
"0.3.3": "0.3.3",
2021
"0.3.2": "0.3.2",
21-
"latest": "3.0.0"
22+
"latest": "3.0.1"
2223
}

guard/handlers/cloud_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def fetch_azure_ip_ranges() -> set[ipaddress.IPv4Network | ipaddress.IPv6Network
5353
response.raise_for_status()
5454

5555
decoded_html = html.unescape(response.text)
56-
pattern = r'href=["\'](https://download\.microsoft\.com/' r'.*?\.json)["\']'
56+
pattern = r'href=["\'](https://download\.microsoft\.com/.{1,500}?\.json)["\']'
5757
match = re.search(pattern, decoded_html)
5858

5959
if not match:

guard/handlers/ipinfo_handler.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55
from typing import Any
66

7-
import aiohttp
7+
import httpx
88
import maxminddb
99
from maxminddb import Reader
1010

@@ -76,24 +76,24 @@ async def _download_database(self) -> None:
7676
retries = 3
7777
backoff = 1
7878

79-
async with aiohttp.ClientSession() as session:
79+
async with httpx.AsyncClient() as session:
8080
for attempt in range(retries):
8181
try:
82-
async with session.get(url) as response:
83-
response.raise_for_status()
84-
with open(self.db_path, "wb") as f:
85-
f.write(await response.read())
86-
87-
if self.redis_handler is not None:
88-
with open(self.db_path, "rb") as f:
89-
db_content = f.read().decode("latin-1")
90-
await self.redis_handler.set_key(
91-
"ipinfo",
92-
"database",
93-
db_content,
94-
ttl=86400, # 24 hours
95-
)
96-
return
82+
response = await session.get(url)
83+
response.raise_for_status()
84+
with open(self.db_path, "wb") as f:
85+
f.write(response.content)
86+
87+
if self.redis_handler is not None:
88+
with open(self.db_path, "rb") as f:
89+
db_content = f.read().decode("latin-1")
90+
await self.redis_handler.set_key(
91+
"ipinfo",
92+
"database",
93+
db_content,
94+
ttl=86400, # 24 hours
95+
)
96+
return
9797
except Exception:
9898
if attempt == retries - 1:
9999
raise

guard/handlers/suspatterns_handler.py

Lines changed: 60 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -18,98 +18,98 @@ class SusPatternsManager:
1818
custom_patterns: set[str] = set()
1919

2020
patterns: list[str] = [
21-
# XSS - Enhanced patterns
22-
r"<script[^>]*>[^<]*<\/script\s*>", # Basic script tag
23-
r"javascript:\s*[^\s]+", # javascript: protocol
21+
# XSS
22+
r"<script[^>]{0,100}>[^<]{0,1000}<\/script\s{0,10}>", # Basic script tag
23+
r"javascript:\s{0,10}[^\s]{1,200}", # javascript: protocol
2424
# Event handlers
2525
r"(?:on(?:error|load|click|mouseover|submit|mouse|unload|change|focus|"
26-
r"blur|drag))=[\"\']?[^\"\'>\s]+",
26+
r"blur|drag))=(?:[\"'][^\"']{1,100}[\"']|[^\s>]{1,100})",
2727
# Malicious attributes
28-
r"(?:<[^>]*\s+(?:href|src|data|action)\s*=[\s\"\']*(?:javascript|"
28+
r"(?:<[^>]{1,200}\s{1,20}(?:href|src|data|action)\s{0,10}=[\s\"\']{0,3}(?:javascript|"
2929
r"vbscript|data):)",
3030
# CSS expressions
31-
r"(?:<[^>]*\s+style\s*=[\s\"\']*[^>]*(?:expression|behavior|url)\s*\("
32-
r"[^)]*\))",
33-
r"(?:<object[^>]*>[\s\S]*?<\/object\s*>)", # Suspicious objects
34-
r"(?:<embed[^>]*>[\s\S]*?<\/embed\s*>)", # Suspicious embeds
35-
r"(?:<applet[^>]*>[\s\S]*?<\/applet\s*>)", # Java applets
36-
# SQL Injection - Enhanced patterns
31+
r"(?:<[^>]{1,200}style\s{0,10}=[\s\"\']{0,3}[^>\"\']{1,200}(?:expression|behavior|url)\s{0,10}\("
32+
r"[^)]{1,200}\))",
33+
r"(?:<object[^>]{1,200}>[\s\S]{1,1000}<\/object\s{0,10}>)", # Suspicious obj
34+
r"(?:<embed[^>]{1,200}>[\s\S]{1,1000}<\/embed\s{0,10}>)", # Suspicious embeds
35+
r"(?:<applet[^>]{1,200}>[\s\S]{1,1000}<\/applet\s{0,10}>)", # Java applets
36+
# SQL Injection
3737
# Basic SELECT statements
38-
r"(?i)SELECT\s+[\w\s,\*]+\s+FROM\s+[\w\s\._]+",
38+
r"(?i)SELECT\s{1,20}[\w\s,\*]{1,200}\s{1,20}FROM\s{1,20}[\w\s\._]{1,100}",
3939
# UNION-based queries
40-
r"(?i)UNION\s+(?:ALL\s+)?SELECT",
40+
r"(?i)UNION\s{1,20}(?:ALL\s{1,20})?SELECT",
4141
# Logic-based
42-
r"(?i)('\s*(?:OR|AND)\s*[\(\s]*'?[\d\w]+\s*(?:=|LIKE|<|>|<=|>=)\s*"
43-
r"[\(\s]*'?[\d\w]+)",
44-
# UNION-based (original pattern)
45-
r"(?i)(UNION\s+(?:ALL\s+)?SELECT\s+(?:NULL[,\s]*)+|\(\s*SELECT\s+"
42+
r"(?i)('\s{0,5}(?:OR|AND)\s{0,5}[\(\s]{0,5}'?[\d\w]{1,50}\s{0,5}(?:=|LIKE|<|>|<=|>=)\s{0,5}"
43+
r"[\(\s]{0,5}'?[\d\w]{1,50})",
44+
# UNION-based
45+
r"(?i)(UNION\s{1,20}(?:ALL\s{1,20})?SELECT\s{1,20}(?:NULL[,\s]{0,10}){1,20}|\(\s{0,10}SELECT\s{1,20}"
4646
r"(?:@@|VERSION))",
47-
r"(?i)(?:INTO\s+(?:OUTFILE|DUMPFILE)\s+'[^']+')", # File operations
48-
r"(?i)(?:LOAD_FILE\s*\([^)]+\))", # File reading
49-
r"(?i)(?:BENCHMARK\s*\(\s*\d+\s*,)", # Time-based
50-
r"(?i)(?:SLEEP\s*\(\s*\d+\s*\))", # Time-based
47+
r"(?i)(?:INTO\s{1,20}(?:OUTFILE|DUMPFILE)\s{1,20}'[^']{1,200}')", # File ops
48+
r"(?i)(?:LOAD_FILE\s{0,10}\([^)]{1,200}\))", # File reading
49+
r"(?i)(?:BENCHMARK\s{0,10}\(\s{0,10}\d{1,10}\s{0,10},)", # Time-based
50+
r"(?i)(?:SLEEP\s{0,10}\(\s{0,10}\d{1,10}\s{0,10}\))", # Time-based
5151
# Comment-based
52-
r"(?i)(?:\/\*![0-9]*\s*(?:OR|AND|UNION|SELECT|INSERT|DELETE|DROP|"
52+
r"(?i)(?:\/\*![0-9]{0,10}\s{0,10}(?:OR|AND|UNION|SELECT|INSERT|DELETE|DROP|"
5353
r"CONCAT|CHAR|UPDATE)\b)",
54-
# Directory Traversal - Enhanced patterns
55-
r"(?:\.\./|\.\\/){2,}", # Multiple traversal
54+
# Directory Traversal
55+
r"(?:\.\./|\.\\/){2,10}", # Multiple traversal
5656
# Sensitive files
5757
r"(?:/etc/(?:passwd|shadow|group|hosts|motd|issue|mysql/my.cnf|ssh/"
5858
r"ssh_config)$)",
59-
r"(?:boot\.ini|win\.ini|system\.ini|config\.sys)\s*$", # Windows files
59+
r"(?:boot\.ini|win\.ini|system\.ini|config\.sys)\s{0,10}$", # Windows files
6060
r"(?:\/proc\/self\/environ$)", # Process information
61-
r"(?:\/var\/log\/[^\/]+$)", # Log files
62-
# Command Injection - Enhanced patterns
61+
r"(?:\/var\/log\/[^\/]{1,100}$)", # Log files
62+
# Command Injection
6363
# Basic commands
64-
r";\s*(?:ls|cat|rm|chmod|chown|wget|curl|nc|netcat|ping|telnet)\s+"
65-
r"-[a-zA-Z]+\s+",
64+
r";\s{0,10}(?:ls|cat|rm|chmod|chown|wget|curl|nc|netcat|ping|telnet)\s{1,20}"
65+
r"-[a-zA-Z]{1,20}\s{1,20}",
6666
# Download commands
67-
r"\|\s*(?:wget|curl|fetch|lwp-download|lynx|links|GET)\s+",
67+
r"\|\s{0,10}(?:wget|curl|fetch|lwp-download|lynx|links|GET)\s{1,20}",
6868
# Command substitution
69-
r"(?:[;&|`]\s*(?:\$\([^)]+\)|\$\{[^}]+\}))",
69+
r"(?:[;&|`]\s{0,10}(?:\$\([^)]{1,100}\)|\$\{[^}]{1,100}\}))",
7070
# Shell execution
71-
r"(?:^|;)\s*(?:bash|sh|ksh|csh|tsch|zsh|ash)\s+-[a-zA-Z]+",
71+
r"(?:^|;)\s{0,10}(?:bash|sh|ksh|csh|tsch|zsh|ash)\s{1,20}-[a-zA-Z]{1,20}",
7272
# PHP functions
73-
r"\b(?:eval|system|exec|shell_exec|passthru|popen|proc_open)\s*\(",
74-
# File Inclusion - Enhanced patterns
73+
r"\b(?:eval|system|exec|shell_exec|passthru|popen|proc_open)\s{0,10}\(",
74+
# File Inclusion
7575
# Protocols
7676
r"(?:php|data|zip|rar|file|glob|expect|input|phpinfo|zlib|phar|ssh2|"
77-
r"rar|ogg|expect)://[^\s]+",
77+
r"rar|ogg|expect)://[^\s]{1,200}",
7878
# URLs
79-
r"(?:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(?:\/?)(?:"
80-
r"[a-zA-Z0-9\-\.\?,'/\\\+&amp;%\$#_]*)?)",
81-
# LDAP Injection - Enhanced patterns
82-
r"\(\s*[|&]\s*\(\s*[^)]+=[*]", # Wildcards
83-
r"(?:\*(?:[\s\d\w]+\s*=|=\s*[\d\w\s]+))", # Attribute matching
84-
r"(?:\(\s*[&|]\s*)", # Logic operations
85-
# XML Injection - Enhanced patterns
86-
r"<!(?:ENTITY|DOCTYPE)[^>]+SYSTEM[^>]+>", # XXE
87-
r"(?:<!\[CDATA\[.*?\]\]>)", # CDATA sections
88-
r"(?:<\?xml.*?\?>)", # XML declarations
89-
# SSRF - Enhanced patterns
79+
r"(?:\/\/[0-9a-zA-Z]([-.\w]{0,50}[0-9a-zA-Z]){0,10}(:[0-9]{0,10}){0,1}(?:\/?)(?:"
80+
r"[a-zA-Z0-9\-\.\?,'/\\\+&amp;%\$#_]{0,500})?)",
81+
# LDAP Injection
82+
r"\(\s{0,10}[|&]\s{0,10}\(\s{0,10}[^)]{1,100}=[*]", # Wildcards
83+
r"(?:\*(?:[\s\d\w]{1,50}\s{0,10}=|=\s{0,10}[\d\w\s]{1,50}))", # Attribute match
84+
r"(?:\(\s{0,10}[&|]\s{0,10})", # Logic operations
85+
# XML Injection
86+
r"<!(?:ENTITY|DOCTYPE)[^>]{1,200}SYSTEM[^>]{1,200}>", # XXE
87+
r"(?:<!\[CDATA\[.{0,1000}?\]\]>)", # CDATA sections
88+
r"(?:<\?xml.{0,200}?\?>)", # XML declarations
89+
# SSRF
9090
# Local addresses
91-
r"(?:^|\s|/)(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::(?:\d*)\]|(?:169\.254|192\.168|10\.|"
92-
r"172\.(?:1[6-9]|2[0-9]|3[01]))\.\d+)(?:\s|$|/)",
93-
r"(?:file|dict|gopher|jar|tftp)://[^\s]+", # Dangerous protocols
94-
# NoSQL Injection - Enhanced patterns
91+
r"(?:^|\s|/)(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::(?:\d{0,10})\]|(?:169\.254|192\.168|10\.|"
92+
r"172\.(?:1[6-9]|2[0-9]|3[01]))\.\d{1,3})(?:\s|$|/)",
93+
r"(?:file|dict|gopher|jar|tftp)://[^\s]{1,200}", # Dangerous protocols
94+
# NoSQL Injection
9595
# MongoDB
96-
r"\{\s*\$(?:where|gt|lt|ne|eq|regex|in|nin|all|size|exists|type|mod|"
96+
r"\{\s{0,10}\$(?:where|gt|lt|ne|eq|regex|in|nin|all|size|exists|type|mod|"
9797
r"options):",
98-
r"(?:\{\s*\$[a-zA-Z]+\s*:\s*(?:\{|\[))", # Nested operators
99-
# File Upload - Enhanced patterns
100-
r"(?i)filename=[\"'].*?\.(?:php\d*|phar|phtml|exe|jsp|asp|aspx|sh|"
98+
r"(?:\{\s{0,10}\$[a-zA-Z]{1,20}\s{0,10}:\s{0,10}(?:\{|\[))", # Nested operators
99+
# File Upload
100+
r"(?i)filename=[\"'].{0,200}?\.(?:php\d{0,5}|phar|phtml|exe|jsp|asp|aspx|sh|"
101101
r"bash|rb|py|pl|cgi|com|bat|cmd|vbs|vbe|js|ws|wsf|msi|hta)[\"\']",
102-
# Path Traversal - Enhanced patterns
102+
# Path Traversal
103103
# Encoded traversal
104104
r"(?:%2e%2e|%252e%252e|%uff0e%uff0e|%c0%ae%c0%ae|%e0%40%ae|%c0%ae"
105105
r"%e0%80%ae|%25c0%25ae)/",
106-
# Template Injection - New category
106+
# Template Injection
107107
# Basic template injection
108-
r"\{\{\s*[^\}]*(?:system|exec|popen|eval|require|include)\s*\}\}",
108+
r"\{\{\s{0,10}[^\}]{1,200}(?:system|exec|popen|eval|require|include)\s{0,10}\}\}",
109109
# Alternative syntax
110-
r"\{\%\s*[^\%]*(?:system|exec|popen|eval|require|include)\s*\%\}",
111-
# HTTP Response Splitting - New category
112-
r"[\r\n]\s*(?:HTTP\/[0-9.]+|Location:|Set-Cookie:)",
110+
r"\{\%\s{0,10}[^\%]{1,200}(?:system|exec|popen|eval|require|include)\s{0,10}\%\}",
111+
# HTTP Response Splitting
112+
r"[\r\n]\s{0,10}(?:HTTP\/[0-9.]{1,10}|Location:|Set-Cookie:)",
113113
]
114114

115115
compiled_patterns: list[re.Pattern]

0 commit comments

Comments
 (0)