authgent-server 0.2.2__tar.gz → 0.2.4__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.
- {authgent_server-0.2.2 → authgent_server-0.2.4}/.env.production +5 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/PKG-INFO +1 -1
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/__init__.py +1 -1
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/__init__.py +2 -0
- authgent_server-0.2.4/authgent_server/endpoints/scan.py +117 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/wellknown.py +2 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/pyproject.toml +1 -1
- authgent_server-0.2.4/tests/test_scan_endpoint.py +101 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/.env.example +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/.gitignore +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/Dockerfile +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/README.md +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/app.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/cli.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/config.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/crypto.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/db.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/dependencies.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/agents.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/audit.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/authorize.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/device.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/health.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/introspect.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/register.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/revoke.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/stepup.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/token.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/token_check.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/endpoints/token_inspect.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/errors.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/logging.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/middleware/__init__.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/middleware/cors.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/middleware/error_handler.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/middleware/rate_limit.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/middleware/request_id.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/middleware/sanitize.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/__init__.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/agent.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/audit_log.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/authorization_code.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/base.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/consent.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/delegation_receipt.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/device_code.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/oauth_client.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/refresh_token.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/signing_key.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/stepup_request.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/token_blocklist.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/user.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/providers/__init__.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/providers/attestation.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/providers/events.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/providers/hitl.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/providers/keys.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/providers/policy.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/providers/protocols.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/scanner.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/schemas/__init__.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/schemas/agent.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/schemas/client.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/schemas/common.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/schemas/token.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/__init__.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/agent_service.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/audit_service.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/chaining_verifier.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/claims_transcription.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/client_service.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/consent_service.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/delegation_service.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/dpop_service.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/external_oidc.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/jwks_service.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/stepup_service.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/token_service.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/templates/consent.html +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/utils.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/docker-compose.yml +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/migrations/alembic.ini +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/migrations/env.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/migrations/versions/.gitkeep +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/migrations/versions/001_initial_schema.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/migrations/versions/002_dcr_software_fields.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/__init__.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/adversarial_live_test.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/agent_to_agent_simulation.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/conftest.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/live_discovery_simulation.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/simulation_test.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_agent_discovery.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_agents.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_audit_endpoint.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_authorize.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_cli_phase0.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_crypto.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_delegation.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_device.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_dpop.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_dpop_integration.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_error_handler.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_external_oidc.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_health.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_hitl_provider.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_identity_chaining.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_integration_workflows.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_introspect.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_log_redaction.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_real_world_agents.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_register.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_registration_policy.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_revoke.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_scanner.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_security.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_stepup.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_token.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_token_advanced.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_token_check.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_token_expiry.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_token_inspect.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_transaction_tokens.py +0 -0
- {authgent_server-0.2.2 → authgent_server-0.2.4}/tests/test_wellknown.py +0 -0
|
@@ -14,6 +14,11 @@ AUTHGENT_DATABASE_URL=sqlite+aiosqlite:////tmp/demo/authgent.db
|
|
|
14
14
|
# Strict CORS — only the deployed frontend + local dev origins.
|
|
15
15
|
AUTHGENT_CORS_ORIGINS=["https://authgent.github.io", "http://localhost:3000", "http://localhost:3001"]
|
|
16
16
|
|
|
17
|
+
# Public origin used for issuer claim, RFC 9207 iss=, and SEP-2351 path-suffix
|
|
18
|
+
# metadata. Without this, the AS advertises localhost:8000 in production and
|
|
19
|
+
# every issuer-comparison check at the MCP client fails.
|
|
20
|
+
AUTHGENT_SERVER_URL=https://authgent-demo.dhruvagnihotri.com
|
|
21
|
+
|
|
17
22
|
AUTHGENT_REGISTRATION_POLICY=open
|
|
18
23
|
AUTHGENT_CONSENT_MODE=auto_approve
|
|
19
24
|
AUTHGENT_HOST=0.0.0.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: authgent-server
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Open-source reference implementation of the IETF agent-identity stack — OAuth 2.1 + identity-chaining + transaction-tokens + DPoP + MCP
|
|
5
5
|
Project-URL: Homepage, https://github.com/authgent/authgent
|
|
6
6
|
Project-URL: Documentation, https://github.com/authgent/authgent/tree/main/server
|
|
@@ -10,6 +10,7 @@ from authgent_server.endpoints.health import router as health_router
|
|
|
10
10
|
from authgent_server.endpoints.introspect import router as introspect_router
|
|
11
11
|
from authgent_server.endpoints.register import router as register_router
|
|
12
12
|
from authgent_server.endpoints.revoke import router as revoke_router
|
|
13
|
+
from authgent_server.endpoints.scan import router as scan_router
|
|
13
14
|
from authgent_server.endpoints.stepup import router as stepup_router
|
|
14
15
|
from authgent_server.endpoints.token import router as token_router
|
|
15
16
|
from authgent_server.endpoints.token_check import router as token_check_router
|
|
@@ -30,3 +31,4 @@ api_router.include_router(audit_router)
|
|
|
30
31
|
api_router.include_router(token_inspect_router)
|
|
31
32
|
api_router.include_router(wellknown_router)
|
|
32
33
|
api_router.include_router(health_router)
|
|
34
|
+
api_router.include_router(scan_router)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""GET /api/scan — public MCP-OAuth conformance scanner endpoint.
|
|
2
|
+
|
|
3
|
+
Wraps :func:`authgent_server.scanner.scan` so the static GitHub Pages
|
|
4
|
+
``/scan`` page can call it without bundling the scanner. Stateless;
|
|
5
|
+
no DB writes; safe to expose publicly. Per-IP rate limiting is provided
|
|
6
|
+
by the existing :class:`RateLimitMiddleware`.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import asdict
|
|
12
|
+
from typing import Literal
|
|
13
|
+
from urllib.parse import urlparse
|
|
14
|
+
|
|
15
|
+
from fastapi import APIRouter, HTTPException, Query
|
|
16
|
+
from pydantic import BaseModel
|
|
17
|
+
|
|
18
|
+
from authgent_server.scanner import (
|
|
19
|
+
Finding,
|
|
20
|
+
has_blocking,
|
|
21
|
+
scan,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
router = APIRouter(tags=["scan"])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ScanResponse(BaseModel):
|
|
28
|
+
target: str
|
|
29
|
+
grade: Literal["A", "B", "C", "D", "F"]
|
|
30
|
+
score: int # 0-100
|
|
31
|
+
blocking: bool
|
|
32
|
+
finding_count: int
|
|
33
|
+
findings: list[dict]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _grade(findings: list[Finding]) -> tuple[str, int]:
|
|
37
|
+
"""Compute a letter grade and 0-100 score from findings."""
|
|
38
|
+
weight = {"info": 1, "warning": 5, "critical": 25, "error": 15}
|
|
39
|
+
penalty = sum(weight.get(f.severity, 5) for f in findings)
|
|
40
|
+
score = max(0, 100 - penalty)
|
|
41
|
+
if score >= 95:
|
|
42
|
+
return "A", score
|
|
43
|
+
if score >= 85:
|
|
44
|
+
return "B", score
|
|
45
|
+
if score >= 70:
|
|
46
|
+
return "C", score
|
|
47
|
+
if score >= 50:
|
|
48
|
+
return "D", score
|
|
49
|
+
return "F", score
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _is_safe_url(url: str) -> bool:
|
|
53
|
+
"""Reject URLs that target the Oracle host's own internal network.
|
|
54
|
+
|
|
55
|
+
The scanner makes outbound HTTP. If a visitor pastes
|
|
56
|
+
``http://localhost`` / ``127.0.0.1`` / a private IP, the scanner would
|
|
57
|
+
probe authgent's own internal services. Block those.
|
|
58
|
+
"""
|
|
59
|
+
parsed = urlparse(url)
|
|
60
|
+
if parsed.scheme not in ("http", "https"):
|
|
61
|
+
return False
|
|
62
|
+
host = (parsed.hostname or "").lower()
|
|
63
|
+
if not host:
|
|
64
|
+
return False
|
|
65
|
+
blocked_hosts = {"localhost", "0.0.0.0", "::1"}
|
|
66
|
+
if host in blocked_hosts:
|
|
67
|
+
return False
|
|
68
|
+
if host.startswith("127.") or host.startswith("169.254."):
|
|
69
|
+
return False
|
|
70
|
+
if host.startswith("10.") or host.startswith("192.168."):
|
|
71
|
+
return False
|
|
72
|
+
if host.startswith("172.") and len(host.split(".")) > 1:
|
|
73
|
+
try:
|
|
74
|
+
second = int(host.split(".")[1])
|
|
75
|
+
if 16 <= second <= 31:
|
|
76
|
+
return False
|
|
77
|
+
except ValueError:
|
|
78
|
+
pass
|
|
79
|
+
if host.endswith(".internal") or host.endswith(".local"):
|
|
80
|
+
return False
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@router.get("/api/scan", response_model=ScanResponse)
|
|
85
|
+
async def scan_endpoint(
|
|
86
|
+
url: str = Query(
|
|
87
|
+
...,
|
|
88
|
+
description="MCP server base URL to audit, e.g. https://mcp.example.com",
|
|
89
|
+
max_length=2048,
|
|
90
|
+
),
|
|
91
|
+
) -> ScanResponse:
|
|
92
|
+
"""Run the MCP-OAuth conformance scanner against ``url`` and return JSON."""
|
|
93
|
+
if not _is_safe_url(url):
|
|
94
|
+
raise HTTPException(
|
|
95
|
+
status_code=400,
|
|
96
|
+
detail=(
|
|
97
|
+
"url must be an http(s) URL pointing at a public host. "
|
|
98
|
+
"Loopback, link-local, and RFC 1918 addresses are blocked."
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
try:
|
|
102
|
+
findings = await scan(url)
|
|
103
|
+
except Exception as exc: # noqa: BLE001 — surface a friendly error
|
|
104
|
+
raise HTTPException(
|
|
105
|
+
status_code=502,
|
|
106
|
+
detail=f"Scanner failed: {exc}",
|
|
107
|
+
) from exc
|
|
108
|
+
|
|
109
|
+
grade, score = _grade(findings)
|
|
110
|
+
return ScanResponse(
|
|
111
|
+
target=url,
|
|
112
|
+
grade=grade, # type: ignore[arg-type]
|
|
113
|
+
score=score,
|
|
114
|
+
blocking=has_blocking(findings),
|
|
115
|
+
finding_count=len(findings),
|
|
116
|
+
findings=[asdict(f) for f in findings],
|
|
117
|
+
)
|
|
@@ -73,6 +73,8 @@ async def oauth_server_metadata(
|
|
|
73
73
|
"scopes_supported": scopes,
|
|
74
74
|
"resource_indicators_supported": True,
|
|
75
75
|
"dpop_signing_alg_values_supported": ["ES256"],
|
|
76
|
+
# RFC 9207 / MCP SEP-2468 — we emit iss= on every /authorize redirect.
|
|
77
|
+
"authorization_response_iss_parameter_supported": True,
|
|
76
78
|
"service_documentation": "https://authgent.dev/docs",
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "authgent-server"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "Open-source reference implementation of the IETF agent-identity stack — OAuth 2.1 + identity-chaining + transaction-tokens + DPoP + MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Tests for the public ``/api/scan`` endpoint.
|
|
2
|
+
|
|
3
|
+
The endpoint must (a) refuse loopback / RFC-1918 / link-local targets so
|
|
4
|
+
the scanner can't probe the host's own internal network, and (b) return
|
|
5
|
+
a structured grade + findings for safe URLs.
|
|
6
|
+
|
|
7
|
+
The actual scan is patched out — the routing + safety guards are what
|
|
8
|
+
matters here. ``test_scanner.py`` covers the scanner internals.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
from authgent_server.endpoints.scan import _grade, _is_safe_url
|
|
16
|
+
from authgent_server.scanner import Finding
|
|
17
|
+
|
|
18
|
+
# --- safety guard ----------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.parametrize(
|
|
22
|
+
"url",
|
|
23
|
+
[
|
|
24
|
+
"http://localhost/",
|
|
25
|
+
"http://127.0.0.1:8000",
|
|
26
|
+
"https://10.0.0.5",
|
|
27
|
+
"http://192.168.1.1",
|
|
28
|
+
"http://172.16.0.1",
|
|
29
|
+
"http://172.31.255.255",
|
|
30
|
+
"http://169.254.169.254", # AWS metadata
|
|
31
|
+
"http://thing.internal",
|
|
32
|
+
"http://thing.local",
|
|
33
|
+
"ftp://example.com",
|
|
34
|
+
"javascript:alert(1)",
|
|
35
|
+
"",
|
|
36
|
+
],
|
|
37
|
+
)
|
|
38
|
+
def test_unsafe_urls_rejected(url: str):
|
|
39
|
+
assert _is_safe_url(url) is False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest.mark.parametrize(
|
|
43
|
+
"url",
|
|
44
|
+
[
|
|
45
|
+
"https://mcp.example.com",
|
|
46
|
+
"https://mcp.example.com/sub",
|
|
47
|
+
"http://198.51.100.1", # TEST-NET-2 (public-routed for examples)
|
|
48
|
+
"https://api.descope.com",
|
|
49
|
+
],
|
|
50
|
+
)
|
|
51
|
+
def test_safe_urls_accepted(url: str):
|
|
52
|
+
assert _is_safe_url(url) is True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# --- grading ---------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _f(severity):
|
|
59
|
+
return Finding(
|
|
60
|
+
check_id="X",
|
|
61
|
+
severity=severity,
|
|
62
|
+
title="t",
|
|
63
|
+
detail="d",
|
|
64
|
+
spec_link="https://example",
|
|
65
|
+
remediation="r",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_grade_clean_is_a():
|
|
70
|
+
grade, score = _grade([])
|
|
71
|
+
assert grade == "A"
|
|
72
|
+
assert score == 100
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_grade_one_critical_drops_to_c_or_lower():
|
|
76
|
+
grade, _ = _grade([_f("critical")])
|
|
77
|
+
assert grade in ("C", "D", "F", "B") # depends on weights; never A
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_grade_three_criticals_is_failing():
|
|
81
|
+
grade, _ = _grade([_f("critical")] * 3)
|
|
82
|
+
assert grade in ("D", "F")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# --- HTTP --------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_scan_endpoint_rejects_loopback(test_client):
|
|
89
|
+
resp = test_client.get("/api/scan", params={"url": "http://localhost"})
|
|
90
|
+
assert resp.status_code == 400
|
|
91
|
+
assert "blocked" in resp.json()["detail"].lower()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_scan_endpoint_rejects_private_ip(test_client):
|
|
95
|
+
resp = test_client.get("/api/scan", params={"url": "http://10.0.0.1"})
|
|
96
|
+
assert resp.status_code == 400
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_scan_endpoint_rejects_non_http(test_client):
|
|
100
|
+
resp = test_client.get("/api/scan", params={"url": "ftp://example.com"})
|
|
101
|
+
assert resp.status_code == 400
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/authorization_code.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/models/delegation_receipt.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/chaining_verifier.py
RENAMED
|
File without changes
|
{authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/claims_transcription.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{authgent_server-0.2.2 → authgent_server-0.2.4}/authgent_server/services/delegation_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{authgent_server-0.2.2 → authgent_server-0.2.4}/migrations/versions/002_dcr_software_fields.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|