guard-core 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- guard_core-0.1.0/LICENSE +21 -0
- guard_core-0.1.0/PKG-INFO +55 -0
- guard_core-0.1.0/README.md +2 -0
- guard_core-0.1.0/guard_core/__init__.py +42 -0
- guard_core-0.1.0/guard_core/core/__init__.py +3 -0
- guard_core-0.1.0/guard_core/core/behavioral/__init__.py +4 -0
- guard_core-0.1.0/guard_core/core/behavioral/context.py +14 -0
- guard_core-0.1.0/guard_core/core/behavioral/processor.py +95 -0
- guard_core-0.1.0/guard_core/core/bypass/__init__.py +4 -0
- guard_core-0.1.0/guard_core/core/bypass/context.py +18 -0
- guard_core-0.1.0/guard_core/core/bypass/handler.py +52 -0
- guard_core-0.1.0/guard_core/core/checks/__init__.py +43 -0
- guard_core-0.1.0/guard_core/core/checks/base.py +48 -0
- guard_core-0.1.0/guard_core/core/checks/helpers.py +174 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/__init__.py +65 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/authentication.py +60 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/cloud_ip_refresh.py +20 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/cloud_provider.py +58 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/custom_request.py +39 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/custom_validators.py +44 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/emergency_mode.py +59 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/https_enforcement.py +62 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/ip_security.py +137 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/rate_limit.py +193 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/referrer.py +92 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/request_logging.py +14 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/request_size_content.py +103 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/required_headers.py +67 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/route_config.py +18 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/suspicious_activity.py +130 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/time_window.py +69 -0
- guard_core-0.1.0/guard_core/core/checks/implementations/user_agent.py +59 -0
- guard_core-0.1.0/guard_core/core/checks/pipeline.py +74 -0
- guard_core-0.1.0/guard_core/core/events/__init__.py +4 -0
- guard_core-0.1.0/guard_core/core/events/metrics.py +56 -0
- guard_core-0.1.0/guard_core/core/events/middleware_events.py +121 -0
- guard_core-0.1.0/guard_core/core/initialization/__init__.py +5 -0
- guard_core-0.1.0/guard_core/core/initialization/handler_initializer.py +94 -0
- guard_core-0.1.0/guard_core/core/responses/__init__.py +4 -0
- guard_core-0.1.0/guard_core/core/responses/context.py +18 -0
- guard_core-0.1.0/guard_core/core/responses/factory.py +93 -0
- guard_core-0.1.0/guard_core/core/routing/__init__.py +4 -0
- guard_core-0.1.0/guard_core/core/routing/context.py +13 -0
- guard_core-0.1.0/guard_core/core/routing/resolver.py +73 -0
- guard_core-0.1.0/guard_core/core/validation/__init__.py +4 -0
- guard_core-0.1.0/guard_core/core/validation/context.py +12 -0
- guard_core-0.1.0/guard_core/core/validation/validator.py +69 -0
- guard_core-0.1.0/guard_core/decorators/__init__.py +39 -0
- guard_core-0.1.0/guard_core/decorators/access_control.py +64 -0
- guard_core-0.1.0/guard_core/decorators/advanced.py +102 -0
- guard_core-0.1.0/guard_core/decorators/authentication.py +45 -0
- guard_core-0.1.0/guard_core/decorators/base.py +200 -0
- guard_core-0.1.0/guard_core/decorators/behavioral.py +77 -0
- guard_core-0.1.0/guard_core/decorators/content_filtering.py +59 -0
- guard_core-0.1.0/guard_core/decorators/rate_limiting.py +27 -0
- guard_core-0.1.0/guard_core/detection_engine/__init__.py +11 -0
- guard_core-0.1.0/guard_core/detection_engine/compiler.py +148 -0
- guard_core-0.1.0/guard_core/detection_engine/monitor.py +377 -0
- guard_core-0.1.0/guard_core/detection_engine/preprocessor.py +291 -0
- guard_core-0.1.0/guard_core/detection_engine/semantic.py +340 -0
- guard_core-0.1.0/guard_core/exceptions.py +9 -0
- guard_core-0.1.0/guard_core/handlers/__init__.py +21 -0
- guard_core-0.1.0/guard_core/handlers/behavior_handler.py +308 -0
- guard_core-0.1.0/guard_core/handlers/cloud_handler.py +275 -0
- guard_core-0.1.0/guard_core/handlers/dynamic_rule_handler.py +319 -0
- guard_core-0.1.0/guard_core/handlers/ipban_handler.py +125 -0
- guard_core-0.1.0/guard_core/handlers/ipinfo_handler.py +218 -0
- guard_core-0.1.0/guard_core/handlers/ratelimit_handler.py +257 -0
- guard_core-0.1.0/guard_core/handlers/redis_handler.py +258 -0
- guard_core-0.1.0/guard_core/handlers/security_headers_handler.py +462 -0
- guard_core-0.1.0/guard_core/handlers/suspatterns_handler.py +841 -0
- guard_core-0.1.0/guard_core/models.py +510 -0
- guard_core-0.1.0/guard_core/protocols/__init__.py +16 -0
- guard_core-0.1.0/guard_core/protocols/agent_protocol.py +15 -0
- guard_core-0.1.0/guard_core/protocols/geo_ip_protocol.py +14 -0
- guard_core-0.1.0/guard_core/protocols/middleware_protocol.py +38 -0
- guard_core-0.1.0/guard_core/protocols/redis_protocol.py +15 -0
- guard_core-0.1.0/guard_core/protocols/request_protocol.py +26 -0
- guard_core-0.1.0/guard_core/protocols/response_protocol.py +18 -0
- guard_core-0.1.0/guard_core/scripts/__init__.py +0 -0
- guard_core-0.1.0/guard_core/scripts/rate_lua.py +17 -0
- guard_core-0.1.0/guard_core/utils.py +639 -0
- guard_core-0.1.0/guard_core.egg-info/PKG-INFO +55 -0
- guard_core-0.1.0/guard_core.egg-info/SOURCES.txt +92 -0
- guard_core-0.1.0/guard_core.egg-info/dependency_links.txt +1 -0
- guard_core-0.1.0/guard_core.egg-info/requires.txt +32 -0
- guard_core-0.1.0/guard_core.egg-info/top_level.txt +1 -0
- guard_core-0.1.0/pyproject.toml +246 -0
- guard_core-0.1.0/setup.cfg +4 -0
- guard_core-0.1.0/setup.py +9 -0
- guard_core-0.1.0/tests/test_exceptions.py +14 -0
- guard_core-0.1.0/tests/test_models.py +215 -0
- guard_core-0.1.0/tests/test_protocols.py +53 -0
- guard_core-0.1.0/tests/test_utils.py +880 -0
guard_core-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Renzo F
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: guard-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Framework-agnostic security engine for the Guard ecosystem.
|
|
5
|
+
Author-email: Renzo Franceschini <rennf93@users.noreply.github.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rennf93/guard-core
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: <3.15,>=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: cachetools
|
|
22
|
+
Requires-Dist: httpx
|
|
23
|
+
Requires-Dist: maxminddb
|
|
24
|
+
Requires-Dist: pydantic
|
|
25
|
+
Requires-Dist: redis
|
|
26
|
+
Requires-Dist: requests
|
|
27
|
+
Requires-Dist: typing-extensions
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: bandit[toml]; extra == "dev"
|
|
30
|
+
Requires-Dist: deptry; extra == "dev"
|
|
31
|
+
Requires-Dist: fastapi-guard-agent; extra == "dev"
|
|
32
|
+
Requires-Dist: httpx; extra == "dev"
|
|
33
|
+
Requires-Dist: mkdocs; extra == "dev"
|
|
34
|
+
Requires-Dist: mkdocstrings; extra == "dev"
|
|
35
|
+
Requires-Dist: mkdocstrings-python; extra == "dev"
|
|
36
|
+
Requires-Dist: mkdocs-material; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy; extra == "dev"
|
|
38
|
+
Requires-Dist: pip-audit; extra == "dev"
|
|
39
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
40
|
+
Requires-Dist: pymarkdownlnt; extra == "dev"
|
|
41
|
+
Requires-Dist: pytest; extra == "dev"
|
|
42
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
43
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest-mock; extra == "dev"
|
|
45
|
+
Requires-Dist: radon; extra == "dev"
|
|
46
|
+
Requires-Dist: ruff; extra == "dev"
|
|
47
|
+
Requires-Dist: safety; extra == "dev"
|
|
48
|
+
Requires-Dist: types-cachetools; extra == "dev"
|
|
49
|
+
Requires-Dist: types-requests; extra == "dev"
|
|
50
|
+
Requires-Dist: vulture; extra == "dev"
|
|
51
|
+
Requires-Dist: xenon; extra == "dev"
|
|
52
|
+
Dynamic: license-file
|
|
53
|
+
|
|
54
|
+
# guard-core
|
|
55
|
+
API Guard Core - Core library for Guard Agent, FastAPI Guard, FlaskAPI Guard and DjAPI Guard
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from guard_core.decorators import RouteConfig, SecurityDecorator
|
|
2
|
+
from guard_core.handlers.behavior_handler import BehaviorRule, BehaviorTracker
|
|
3
|
+
from guard_core.handlers.cloud_handler import CloudManager, cloud_handler
|
|
4
|
+
from guard_core.handlers.ipban_handler import IPBanManager, ip_ban_manager
|
|
5
|
+
from guard_core.handlers.ipinfo_handler import IPInfoManager
|
|
6
|
+
from guard_core.handlers.ratelimit_handler import RateLimitManager, rate_limit_handler
|
|
7
|
+
from guard_core.handlers.redis_handler import RedisManager, redis_handler
|
|
8
|
+
from guard_core.handlers.security_headers_handler import (
|
|
9
|
+
SecurityHeadersManager,
|
|
10
|
+
security_headers_manager,
|
|
11
|
+
)
|
|
12
|
+
from guard_core.handlers.suspatterns_handler import sus_patterns_handler
|
|
13
|
+
from guard_core.models import SecurityConfig
|
|
14
|
+
from guard_core.protocols.geo_ip_protocol import GeoIPHandler
|
|
15
|
+
from guard_core.protocols.redis_protocol import RedisHandlerProtocol
|
|
16
|
+
from guard_core.protocols.request_protocol import GuardRequest
|
|
17
|
+
from guard_core.protocols.response_protocol import GuardResponse, GuardResponseFactory
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"SecurityConfig",
|
|
21
|
+
"SecurityDecorator",
|
|
22
|
+
"RouteConfig",
|
|
23
|
+
"BehaviorTracker",
|
|
24
|
+
"BehaviorRule",
|
|
25
|
+
"ip_ban_manager",
|
|
26
|
+
"IPBanManager",
|
|
27
|
+
"cloud_handler",
|
|
28
|
+
"CloudManager",
|
|
29
|
+
"IPInfoManager",
|
|
30
|
+
"rate_limit_handler",
|
|
31
|
+
"RateLimitManager",
|
|
32
|
+
"redis_handler",
|
|
33
|
+
"RedisManager",
|
|
34
|
+
"security_headers_manager",
|
|
35
|
+
"SecurityHeadersManager",
|
|
36
|
+
"sus_patterns_handler",
|
|
37
|
+
"GeoIPHandler",
|
|
38
|
+
"RedisHandlerProtocol",
|
|
39
|
+
"GuardRequest",
|
|
40
|
+
"GuardResponse",
|
|
41
|
+
"GuardResponseFactory",
|
|
42
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from logging import Logger
|
|
3
|
+
|
|
4
|
+
from guard_core.core.events import SecurityEventBus
|
|
5
|
+
from guard_core.decorators.base import BaseSecurityDecorator
|
|
6
|
+
from guard_core.models import SecurityConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class BehavioralContext:
|
|
11
|
+
config: SecurityConfig
|
|
12
|
+
logger: Logger
|
|
13
|
+
event_bus: SecurityEventBus
|
|
14
|
+
guard_decorator: BaseSecurityDecorator | None
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from guard_core.core.behavioral.context import BehavioralContext
|
|
2
|
+
from guard_core.decorators.base import RouteConfig
|
|
3
|
+
from guard_core.protocols.request_protocol import GuardRequest
|
|
4
|
+
from guard_core.protocols.response_protocol import GuardResponse
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BehavioralProcessor:
|
|
8
|
+
def __init__(self, context: BehavioralContext) -> None:
|
|
9
|
+
self.context = context
|
|
10
|
+
|
|
11
|
+
async def process_usage_rules(
|
|
12
|
+
self, request: GuardRequest, client_ip: str, route_config: RouteConfig
|
|
13
|
+
) -> None:
|
|
14
|
+
if not self.context.guard_decorator:
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
endpoint_id = self.get_endpoint_id(request)
|
|
18
|
+
for rule in route_config.behavior_rules:
|
|
19
|
+
if rule.rule_type in ["usage", "frequency"]:
|
|
20
|
+
behavior_tracker = self.context.guard_decorator.behavior_tracker
|
|
21
|
+
threshold_exceeded = await behavior_tracker.track_endpoint_usage(
|
|
22
|
+
endpoint_id, client_ip, rule
|
|
23
|
+
)
|
|
24
|
+
if threshold_exceeded:
|
|
25
|
+
details = f"{rule.threshold} calls in {rule.window}s"
|
|
26
|
+
message = f"Behavioral {rule.rule_type}"
|
|
27
|
+
reason = "threshold exceeded"
|
|
28
|
+
|
|
29
|
+
await self.context.event_bus.send_middleware_event(
|
|
30
|
+
event_type="decorator_violation",
|
|
31
|
+
request=request,
|
|
32
|
+
action_taken="behavioral_action_triggered",
|
|
33
|
+
reason=f"{message} {reason}: {details}",
|
|
34
|
+
decorator_type="behavioral",
|
|
35
|
+
violation_type=rule.rule_type,
|
|
36
|
+
threshold=rule.threshold,
|
|
37
|
+
window=rule.window,
|
|
38
|
+
action=rule.action,
|
|
39
|
+
endpoint_id=endpoint_id,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
await self.context.guard_decorator.behavior_tracker.apply_action(
|
|
43
|
+
rule,
|
|
44
|
+
client_ip,
|
|
45
|
+
endpoint_id,
|
|
46
|
+
f"Usage threshold exceeded: {details}",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def process_return_rules(
|
|
50
|
+
self,
|
|
51
|
+
request: GuardRequest,
|
|
52
|
+
response: GuardResponse,
|
|
53
|
+
client_ip: str,
|
|
54
|
+
route_config: RouteConfig,
|
|
55
|
+
) -> None:
|
|
56
|
+
if not self.context.guard_decorator:
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
endpoint_id = self.get_endpoint_id(request)
|
|
60
|
+
for rule in route_config.behavior_rules:
|
|
61
|
+
if rule.rule_type == "return_pattern":
|
|
62
|
+
behavior_tracker = self.context.guard_decorator.behavior_tracker
|
|
63
|
+
pattern_detected = await behavior_tracker.track_return_pattern(
|
|
64
|
+
endpoint_id, client_ip, response, rule
|
|
65
|
+
)
|
|
66
|
+
if pattern_detected:
|
|
67
|
+
details = f"{rule.threshold} for '{rule.pattern}' in {rule.window}s"
|
|
68
|
+
|
|
69
|
+
await self.context.event_bus.send_middleware_event(
|
|
70
|
+
event_type="decorator_violation",
|
|
71
|
+
request=request,
|
|
72
|
+
action_taken="behavioral_action_triggered",
|
|
73
|
+
reason=f"Return pattern threshold exceeded: {details}",
|
|
74
|
+
decorator_type="behavioral",
|
|
75
|
+
violation_type="return_pattern",
|
|
76
|
+
threshold=rule.threshold,
|
|
77
|
+
window=rule.window,
|
|
78
|
+
pattern=rule.pattern,
|
|
79
|
+
action=rule.action,
|
|
80
|
+
endpoint_id=endpoint_id,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
await self.context.guard_decorator.behavior_tracker.apply_action(
|
|
84
|
+
rule,
|
|
85
|
+
client_ip,
|
|
86
|
+
endpoint_id,
|
|
87
|
+
f"Return pattern threshold exceeded: {details}",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def get_endpoint_id(self, request: GuardRequest) -> str:
|
|
91
|
+
if hasattr(request, "scope") and "route" in request.scope:
|
|
92
|
+
route = request.scope["route"]
|
|
93
|
+
if hasattr(route, "endpoint"):
|
|
94
|
+
return f"{route.endpoint.__module__}.{route.endpoint.__qualname__}"
|
|
95
|
+
return f"{request.method}:{request.url_path}"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from logging import Logger
|
|
3
|
+
|
|
4
|
+
from guard_core.core.events import SecurityEventBus
|
|
5
|
+
from guard_core.core.responses import ErrorResponseFactory
|
|
6
|
+
from guard_core.core.routing import RouteConfigResolver
|
|
7
|
+
from guard_core.core.validation import RequestValidator
|
|
8
|
+
from guard_core.models import SecurityConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class BypassContext:
|
|
13
|
+
config: SecurityConfig
|
|
14
|
+
logger: Logger
|
|
15
|
+
event_bus: SecurityEventBus
|
|
16
|
+
route_resolver: RouteConfigResolver
|
|
17
|
+
response_factory: ErrorResponseFactory
|
|
18
|
+
validator: RequestValidator
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from collections.abc import Awaitable, Callable
|
|
2
|
+
|
|
3
|
+
from guard_core.core.bypass.context import BypassContext
|
|
4
|
+
from guard_core.decorators.base import RouteConfig
|
|
5
|
+
from guard_core.protocols.request_protocol import GuardRequest
|
|
6
|
+
from guard_core.protocols.response_protocol import GuardResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BypassHandler:
|
|
10
|
+
def __init__(self, context: BypassContext) -> None:
|
|
11
|
+
self.context = context
|
|
12
|
+
|
|
13
|
+
async def handle_passthrough(
|
|
14
|
+
self,
|
|
15
|
+
request: GuardRequest,
|
|
16
|
+
call_next: Callable[[GuardRequest], Awaitable[GuardResponse]],
|
|
17
|
+
) -> GuardResponse | None:
|
|
18
|
+
if not request.client_host:
|
|
19
|
+
response = await call_next(request)
|
|
20
|
+
return await self.context.response_factory.apply_modifier(response)
|
|
21
|
+
|
|
22
|
+
if await self.context.validator.is_path_excluded(request):
|
|
23
|
+
response = await call_next(request)
|
|
24
|
+
return await self.context.response_factory.apply_modifier(response)
|
|
25
|
+
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
async def handle_security_bypass(
|
|
29
|
+
self,
|
|
30
|
+
request: GuardRequest,
|
|
31
|
+
call_next: Callable[[GuardRequest], Awaitable[GuardResponse]],
|
|
32
|
+
route_config: RouteConfig | None,
|
|
33
|
+
) -> GuardResponse | None:
|
|
34
|
+
if not route_config or not self.context.route_resolver.should_bypass_check(
|
|
35
|
+
"all", route_config
|
|
36
|
+
):
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
await self.context.event_bus.send_middleware_event(
|
|
40
|
+
event_type="security_bypass",
|
|
41
|
+
request=request,
|
|
42
|
+
action_taken="all_checks_bypassed",
|
|
43
|
+
reason="Route configured to bypass all security checks",
|
|
44
|
+
bypassed_checks=list(route_config.bypassed_checks),
|
|
45
|
+
endpoint=str(request.url_path),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if not self.context.config.passive_mode:
|
|
49
|
+
response = await call_next(request)
|
|
50
|
+
return await self.context.response_factory.apply_modifier(response)
|
|
51
|
+
|
|
52
|
+
return None
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from guard_core.core.checks.base import SecurityCheck
|
|
2
|
+
from guard_core.core.checks.implementations import (
|
|
3
|
+
AuthenticationCheck,
|
|
4
|
+
CloudIpRefreshCheck,
|
|
5
|
+
CloudProviderCheck,
|
|
6
|
+
CustomRequestCheck,
|
|
7
|
+
CustomValidatorsCheck,
|
|
8
|
+
EmergencyModeCheck,
|
|
9
|
+
HttpsEnforcementCheck,
|
|
10
|
+
IpSecurityCheck,
|
|
11
|
+
RateLimitCheck,
|
|
12
|
+
ReferrerCheck,
|
|
13
|
+
RequestLoggingCheck,
|
|
14
|
+
RequestSizeContentCheck,
|
|
15
|
+
RequiredHeadersCheck,
|
|
16
|
+
RouteConfigCheck,
|
|
17
|
+
SuspiciousActivityCheck,
|
|
18
|
+
TimeWindowCheck,
|
|
19
|
+
UserAgentCheck,
|
|
20
|
+
)
|
|
21
|
+
from guard_core.core.checks.pipeline import SecurityCheckPipeline
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"SecurityCheck",
|
|
25
|
+
"SecurityCheckPipeline",
|
|
26
|
+
"RouteConfigCheck",
|
|
27
|
+
"EmergencyModeCheck",
|
|
28
|
+
"HttpsEnforcementCheck",
|
|
29
|
+
"RequestLoggingCheck",
|
|
30
|
+
"RequestSizeContentCheck",
|
|
31
|
+
"RequiredHeadersCheck",
|
|
32
|
+
"AuthenticationCheck",
|
|
33
|
+
"ReferrerCheck",
|
|
34
|
+
"CustomValidatorsCheck",
|
|
35
|
+
"TimeWindowCheck",
|
|
36
|
+
"CloudIpRefreshCheck",
|
|
37
|
+
"IpSecurityCheck",
|
|
38
|
+
"CloudProviderCheck",
|
|
39
|
+
"UserAgentCheck",
|
|
40
|
+
"RateLimitCheck",
|
|
41
|
+
"SuspiciousActivityCheck",
|
|
42
|
+
"CustomRequestCheck",
|
|
43
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
|
+
|
|
4
|
+
from guard_core.protocols.request_protocol import GuardRequest
|
|
5
|
+
from guard_core.protocols.response_protocol import GuardResponse
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from guard_core.protocols.middleware_protocol import GuardMiddlewareProtocol
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SecurityCheck(ABC):
|
|
12
|
+
def __init__(self, middleware: "GuardMiddlewareProtocol") -> None:
|
|
13
|
+
self.middleware = middleware
|
|
14
|
+
self.config = middleware.config
|
|
15
|
+
self.logger = middleware.logger
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
async def check(self, request: GuardRequest) -> GuardResponse | None:
|
|
19
|
+
pass # pragma: no cover
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def check_name(self) -> str:
|
|
24
|
+
pass # pragma: no cover
|
|
25
|
+
|
|
26
|
+
async def send_event(
|
|
27
|
+
self,
|
|
28
|
+
event_type: str,
|
|
29
|
+
request: GuardRequest,
|
|
30
|
+
action_taken: str,
|
|
31
|
+
reason: str,
|
|
32
|
+
**kwargs: Any,
|
|
33
|
+
) -> None:
|
|
34
|
+
await self.middleware.event_bus.send_middleware_event(
|
|
35
|
+
event_type=event_type,
|
|
36
|
+
request=request,
|
|
37
|
+
action_taken=action_taken,
|
|
38
|
+
reason=reason,
|
|
39
|
+
**kwargs,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
async def create_error_response(
|
|
43
|
+
self, status_code: int, default_message: str
|
|
44
|
+
) -> GuardResponse:
|
|
45
|
+
return await self.middleware.create_error_response(status_code, default_message)
|
|
46
|
+
|
|
47
|
+
def is_passive_mode(self) -> bool:
|
|
48
|
+
return self.config.passive_mode
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from ipaddress import ip_address, ip_network
|
|
3
|
+
from typing import Any
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
from guard_core.decorators.base import RouteConfig
|
|
7
|
+
from guard_core.models import SecurityConfig
|
|
8
|
+
from guard_core.protocols.request_protocol import GuardRequest
|
|
9
|
+
from guard_core.utils import detect_penetration_attempt
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def is_ip_in_blacklist(client_ip: str, ip_addr: object, blacklist: list[str]) -> bool:
|
|
13
|
+
for blocked in blacklist:
|
|
14
|
+
if "/" in blocked:
|
|
15
|
+
if ip_addr in ip_network(blocked, strict=False):
|
|
16
|
+
return True
|
|
17
|
+
elif client_ip == blocked:
|
|
18
|
+
return True
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def is_ip_in_whitelist(
|
|
23
|
+
client_ip: str, ip_addr: object, whitelist: list[str]
|
|
24
|
+
) -> bool | None:
|
|
25
|
+
if not whitelist:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
for allowed in whitelist:
|
|
29
|
+
if "/" in allowed:
|
|
30
|
+
if ip_addr in ip_network(allowed, strict=False):
|
|
31
|
+
return True
|
|
32
|
+
elif client_ip == allowed:
|
|
33
|
+
return True
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def check_country_access(
|
|
38
|
+
client_ip: str, route_config: RouteConfig, geo_ip_handler: Any
|
|
39
|
+
) -> bool | None:
|
|
40
|
+
if not geo_ip_handler:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
country = None
|
|
44
|
+
|
|
45
|
+
if route_config.blocked_countries:
|
|
46
|
+
country = geo_ip_handler.get_country(client_ip)
|
|
47
|
+
if country and country in route_config.blocked_countries:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
if route_config.whitelist_countries:
|
|
51
|
+
if country is None:
|
|
52
|
+
country = geo_ip_handler.get_country(client_ip)
|
|
53
|
+
|
|
54
|
+
if country:
|
|
55
|
+
return country in route_config.whitelist_countries
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _check_ip_blacklist(
|
|
62
|
+
client_ip: str, ip_addr: object, route_config: RouteConfig
|
|
63
|
+
) -> bool:
|
|
64
|
+
if not route_config.ip_blacklist:
|
|
65
|
+
return False
|
|
66
|
+
return is_ip_in_blacklist(client_ip, ip_addr, route_config.ip_blacklist)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _check_ip_whitelist(
|
|
70
|
+
client_ip: str, ip_addr: object, route_config: RouteConfig
|
|
71
|
+
) -> bool | None:
|
|
72
|
+
return is_ip_in_whitelist(client_ip, ip_addr, route_config.ip_whitelist or [])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def check_route_ip_access(
|
|
76
|
+
client_ip: str, route_config: RouteConfig, middleware: Any
|
|
77
|
+
) -> bool | None:
|
|
78
|
+
try:
|
|
79
|
+
ip_addr = ip_address(client_ip)
|
|
80
|
+
|
|
81
|
+
if _check_ip_blacklist(client_ip, ip_addr, route_config):
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
whitelist_result = _check_ip_whitelist(client_ip, ip_addr, route_config)
|
|
85
|
+
if whitelist_result is not None:
|
|
86
|
+
return whitelist_result
|
|
87
|
+
|
|
88
|
+
country_result = check_country_access(
|
|
89
|
+
client_ip, route_config, middleware.geo_ip_handler
|
|
90
|
+
)
|
|
91
|
+
if country_result is not None:
|
|
92
|
+
return country_result
|
|
93
|
+
|
|
94
|
+
return None
|
|
95
|
+
except ValueError:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def check_user_agent_allowed(
|
|
100
|
+
user_agent: str, route_config: RouteConfig | None, config: Any
|
|
101
|
+
) -> bool:
|
|
102
|
+
from guard_core.utils import is_user_agent_allowed as global_user_agent_check
|
|
103
|
+
|
|
104
|
+
if route_config and route_config.blocked_user_agents:
|
|
105
|
+
for pattern in route_config.blocked_user_agents:
|
|
106
|
+
if re.search(pattern, user_agent, re.IGNORECASE):
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
return await global_user_agent_check(user_agent, config)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def validate_auth_header(auth_header: str, auth_type: str) -> tuple[bool, str]:
|
|
113
|
+
if auth_type == "bearer":
|
|
114
|
+
if not auth_header.startswith("Bearer "):
|
|
115
|
+
return False, "Missing or invalid Bearer token"
|
|
116
|
+
elif auth_type == "basic":
|
|
117
|
+
if not auth_header.startswith("Basic "):
|
|
118
|
+
return False, "Missing or invalid Basic authentication"
|
|
119
|
+
else:
|
|
120
|
+
if not auth_header:
|
|
121
|
+
return False, f"Missing {auth_type} authentication"
|
|
122
|
+
|
|
123
|
+
return True, ""
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def is_referrer_domain_allowed(referrer: str, allowed_domains: list[str]) -> bool:
|
|
127
|
+
try:
|
|
128
|
+
referrer_domain = urlparse(referrer).netloc.lower()
|
|
129
|
+
for allowed_domain in allowed_domains:
|
|
130
|
+
if referrer_domain == allowed_domain.lower() or referrer_domain.endswith(
|
|
131
|
+
f".{allowed_domain.lower()}"
|
|
132
|
+
):
|
|
133
|
+
return True
|
|
134
|
+
return False
|
|
135
|
+
except Exception:
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _get_effective_penetration_setting(
|
|
140
|
+
config: SecurityConfig, route_config: RouteConfig | None
|
|
141
|
+
) -> tuple[bool, bool | None]:
|
|
142
|
+
route_specific_detection = None
|
|
143
|
+
penetration_enabled = config.enable_penetration_detection
|
|
144
|
+
|
|
145
|
+
if route_config and hasattr(route_config, "enable_suspicious_detection"):
|
|
146
|
+
route_specific_detection = route_config.enable_suspicious_detection
|
|
147
|
+
penetration_enabled = route_specific_detection
|
|
148
|
+
|
|
149
|
+
return penetration_enabled, route_specific_detection
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _get_detection_disabled_reason(
|
|
153
|
+
config: SecurityConfig, route_specific_detection: bool | None
|
|
154
|
+
) -> str:
|
|
155
|
+
if route_specific_detection is False and config.enable_penetration_detection:
|
|
156
|
+
return "disabled_by_decorator"
|
|
157
|
+
return "not_enabled"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async def detect_penetration_patterns(
|
|
161
|
+
request: GuardRequest,
|
|
162
|
+
route_config: RouteConfig | None,
|
|
163
|
+
config: SecurityConfig,
|
|
164
|
+
should_bypass_check_fn: Any,
|
|
165
|
+
) -> tuple[bool, str]:
|
|
166
|
+
penetration_enabled, route_specific_detection = _get_effective_penetration_setting(
|
|
167
|
+
config, route_config
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
if penetration_enabled and not should_bypass_check_fn("penetration", route_config):
|
|
171
|
+
return await detect_penetration_attempt(request)
|
|
172
|
+
|
|
173
|
+
reason = _get_detection_disabled_reason(config, route_specific_detection)
|
|
174
|
+
return False, reason
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from guard_core.core.checks.implementations.authentication import (
|
|
2
|
+
AuthenticationCheck,
|
|
3
|
+
)
|
|
4
|
+
from guard_core.core.checks.implementations.cloud_ip_refresh import (
|
|
5
|
+
CloudIpRefreshCheck,
|
|
6
|
+
)
|
|
7
|
+
from guard_core.core.checks.implementations.cloud_provider import (
|
|
8
|
+
CloudProviderCheck,
|
|
9
|
+
)
|
|
10
|
+
from guard_core.core.checks.implementations.custom_request import (
|
|
11
|
+
CustomRequestCheck,
|
|
12
|
+
)
|
|
13
|
+
from guard_core.core.checks.implementations.custom_validators import (
|
|
14
|
+
CustomValidatorsCheck,
|
|
15
|
+
)
|
|
16
|
+
from guard_core.core.checks.implementations.emergency_mode import (
|
|
17
|
+
EmergencyModeCheck,
|
|
18
|
+
)
|
|
19
|
+
from guard_core.core.checks.implementations.https_enforcement import (
|
|
20
|
+
HttpsEnforcementCheck,
|
|
21
|
+
)
|
|
22
|
+
from guard_core.core.checks.implementations.ip_security import (
|
|
23
|
+
IpSecurityCheck,
|
|
24
|
+
)
|
|
25
|
+
from guard_core.core.checks.implementations.rate_limit import RateLimitCheck
|
|
26
|
+
from guard_core.core.checks.implementations.referrer import ReferrerCheck
|
|
27
|
+
from guard_core.core.checks.implementations.request_logging import (
|
|
28
|
+
RequestLoggingCheck,
|
|
29
|
+
)
|
|
30
|
+
from guard_core.core.checks.implementations.request_size_content import (
|
|
31
|
+
RequestSizeContentCheck,
|
|
32
|
+
)
|
|
33
|
+
from guard_core.core.checks.implementations.required_headers import (
|
|
34
|
+
RequiredHeadersCheck,
|
|
35
|
+
)
|
|
36
|
+
from guard_core.core.checks.implementations.route_config import (
|
|
37
|
+
RouteConfigCheck,
|
|
38
|
+
)
|
|
39
|
+
from guard_core.core.checks.implementations.suspicious_activity import (
|
|
40
|
+
SuspiciousActivityCheck,
|
|
41
|
+
)
|
|
42
|
+
from guard_core.core.checks.implementations.time_window import (
|
|
43
|
+
TimeWindowCheck,
|
|
44
|
+
)
|
|
45
|
+
from guard_core.core.checks.implementations.user_agent import UserAgentCheck
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"AuthenticationCheck",
|
|
49
|
+
"CloudIpRefreshCheck",
|
|
50
|
+
"CloudProviderCheck",
|
|
51
|
+
"CustomRequestCheck",
|
|
52
|
+
"CustomValidatorsCheck",
|
|
53
|
+
"EmergencyModeCheck",
|
|
54
|
+
"HttpsEnforcementCheck",
|
|
55
|
+
"IpSecurityCheck",
|
|
56
|
+
"RateLimitCheck",
|
|
57
|
+
"ReferrerCheck",
|
|
58
|
+
"RequestLoggingCheck",
|
|
59
|
+
"RequestSizeContentCheck",
|
|
60
|
+
"RequiredHeadersCheck",
|
|
61
|
+
"RouteConfigCheck",
|
|
62
|
+
"SuspiciousActivityCheck",
|
|
63
|
+
"TimeWindowCheck",
|
|
64
|
+
"UserAgentCheck",
|
|
65
|
+
]
|