Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:36:21 +02:00
commit cb5be898ea
663 files changed
+82674

No files matched your search

+34
View File
@@ -0,0 +1,34 @@
"""Deterministic, deny-first policy, audit, and rate-limit primitives."""
from .audit import AuditEvent, AuditLog, AuditPage, InMemoryAuditLog, sanitize_arguments
from .engine import (
Decision,
DecisionTrace,
InvocationContext,
PolicyEngine,
PolicyRule,
PolicySimulator,
PolicyValidationError,
)
from .governance import Governance, PolicyDenied, governed_tool
from .rate_limit import RateLimitExceeded, SlidingWindowRateLimiter
__all__ = [
"AuditEvent",
"AuditLog",
"AuditPage",
"Decision",
"DecisionTrace",
"Governance",
"governed_tool",
"InMemoryAuditLog",
"InvocationContext",
"PolicyDenied",
"PolicyEngine",
"PolicyRule",
"PolicySimulator",
"PolicyValidationError",
"RateLimitExceeded",
"SlidingWindowRateLimiter",
"sanitize_arguments",
]
+165
View File
@@ -0,0 +1,165 @@
"""Sanitized append-only audit ingestion, search, retention, and export metadata."""
from __future__ import annotations
import hashlib
import json
import re
from collections.abc import Mapping
from datetime import UTC, datetime, timedelta
from typing import Any, Protocol
from uuid import UUID, uuid4
from pydantic import BaseModel, ConfigDict, Field
from .engine import DecisionTrace
_SECRET_VALUE = re.compile(r"(?:bearer\s+|eyJ[A-Za-z0-9_-]{20,}|-----BEGIN[^-]+-----)", re.I)
_SENSITIVE = re.compile(
r"(?:password|secret|token|private|authorization|cookie|credential|api[_-]?key)", re.I
)
def sanitize_arguments(value: Any, *, max_bytes: int = 16_384, _depth: int = 0) -> Any:
if _depth > 5:
return "<depth-limited>"
result: Any
if isinstance(value, Mapping):
result = {
str(key)[:80]: (
"<redacted>"
if _SENSITIVE.search(str(key))
else sanitize_arguments(item, max_bytes=max_bytes, _depth=_depth + 1)
)
for key, item in list(value.items())[:64]
}
elif isinstance(value, list | tuple):
result = [
sanitize_arguments(item, max_bytes=max_bytes, _depth=_depth + 1)
for item in list(value)[:64]
]
elif isinstance(value, str | int | float | bool) or value is None:
if isinstance(value, str) and _SECRET_VALUE.search(value):
result = "<redacted>"
else:
result = value if not isinstance(value, str) else value[:2048]
else:
result = f"<{type(value).__name__}>"
if len(json.dumps(result, sort_keys=True, default=str).encode()) > max_bytes:
return "<arguments-truncated>"
return result
class AuditEvent(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
id: UUID = Field(default_factory=uuid4)
occurred_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
trace_id: str = Field(min_length=1, max_length=128)
event_type: str = Field(default="mcp.tool.invoked", max_length=128)
workspace: str = Field(min_length=1, max_length=128)
project: str = Field(min_length=1, max_length=128)
environment: str = Field(min_length=1, max_length=32)
principal: str = Field(min_length=1, max_length=256)
client_id: UUID | None = None
tool: str = Field(min_length=1, max_length=256)
policy_decision: str
matched_rule_ids: tuple[str, ...] = ()
policy_trace: tuple[dict[str, Any], ...] = ()
arguments_hash: str = Field(pattern=r"^[0-9a-f]{64}$")
sanitized_arguments: dict[str, Any] = Field(default_factory=dict)
duration_ms: int = Field(ge=0, le=120_000, default=0)
outcome: str = Field(pattern=r"^(success|denied|error|rate_limited)$")
error_code: str | None = None
retention_until: datetime | None = None
@classmethod
def from_context(
cls,
context: Any,
trace: DecisionTrace,
*,
outcome: str,
started: float,
error_code: str | None = None,
result_summary: dict[str, Any] | None = None,
) -> AuditEvent:
import time
raw = json.dumps(dict(context.arguments), sort_keys=True, default=str).encode()
return cls(
trace_id=trace.trace_id,
workspace=context.workspace,
project=context.project,
environment=context.environment,
principal=context.principal,
client_id=context.client_id,
tool=context.tool,
policy_decision=trace.decision.value,
matched_rule_ids=trace.matched_rule_ids,
policy_trace=tuple(item.model_dump(mode="json") for item in trace.steps),
arguments_hash=hashlib.sha256(raw).hexdigest(),
sanitized_arguments=sanitize_arguments({
**dict(context.arguments),
**({"_result": result_summary} if result_summary else {}),
}),
duration_ms=min(120_000, int((time.monotonic() - started) * 1000)),
outcome=outcome,
error_code=error_code,
retention_until=datetime.now(UTC) + timedelta(days=30),
)
class AuditPage(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
items: tuple[AuditEvent, ...]
next_offset: int | None = None
class AuditLog(Protocol):
def append(self, event: AuditEvent) -> None: ...
def search(
self, *, workspace: str, project: str, limit: int = 100, offset: int = 0
) -> AuditPage: ...
def export_metadata(self, *, workspace: str, project: str) -> dict[str, Any]: ...
class InMemoryAuditLog:
"""Reference sink used by services and tests; it deliberately has no update/delete API."""
def __init__(self) -> None:
self.events: list[AuditEvent] = []
def append(self, event: AuditEvent) -> None:
if any(item.id == event.id for item in self.events):
raise ValueError("audit event IDs are immutable")
self.events.append(event)
def search(
self, *, workspace: str, project: str, limit: int = 100, offset: int = 0
) -> AuditPage:
if not 1 <= limit <= 500 or offset < 0:
raise ValueError("invalid audit pagination")
items = tuple(
item for item in self.events if item.workspace == workspace and item.project == project
)
selected = items[offset : offset + limit]
next_offset = offset + len(selected) if offset + len(selected) < len(items) else None
return AuditPage(items=selected, next_offset=next_offset)
def export_metadata(self, *, workspace: str, project: str) -> dict[str, Any]:
events = self.search(workspace=workspace, project=project, limit=500).items
digest = hashlib.sha256(
"".join(item.model_dump_json() for item in events).encode()
).hexdigest()
return {
"workspace": workspace,
"project": project,
"eventCount": len(events),
"sha256": digest,
"generatedAt": datetime.now(UTC).isoformat(),
"format": "audit-event-v1",
"access": "policy-restricted",
}
+419
View File
@@ -0,0 +1,419 @@
"""Small typed policy evaluator shared by management, router, and connectors."""
from __future__ import annotations
import fnmatch
import re
from collections.abc import Iterable, Mapping
from enum import StrEnum
from typing import Any
from uuid import UUID, uuid4
from pydantic import BaseModel, ConfigDict, Field, field_validator
class PolicyValidationError(ValueError):
"""A policy contains an unsupported or unsafe selector."""
class Decision(StrEnum):
ALLOW = "allow"
ALLOW_WITH_PREVIEW = "allow_with_preview"
ALLOW_WITH_APPROVAL = "allow_with_approval"
ALLOW_IF_CONDITIONS = "allow_if_conditions"
ENFORCE = "enforce"
DENY = "deny"
DENY_AND_ALERT = "deny_and_alert"
_IDENTIFIER = re.compile(r"^[A-Za-z0-9._:/-]{1,256}$")
_MATCH_FIELDS = {
"workspace",
"workspaceId",
"project",
"projectId",
"projects",
"projectSlugs",
"environment",
"environments",
"environmentClassifications",
"client",
"clientId",
"clientIds",
"principal",
"principalType",
"virtualServer",
"virtualServers",
"capability",
"capabilities",
"tool",
"tools",
"toolPatterns",
"resourceType",
"resourceTypes",
"connector",
"connectors",
"riskClass",
"riskClasses",
"origin",
"origins",
}
_CONDITION_FIELDS = {
"requireEnabledClient",
"requireBinding",
"requireScope",
"requireCapabilityPack",
"requireConnectorBinding",
"requireInputValidation",
"requireOutputValidation",
"requireRedaction",
"maxArgumentBytes",
"allEqual",
"requiredClaims",
"maxDefaultBytes",
"maxDefaultItems",
"redactionProfile",
}
class InvocationContext(BaseModel):
"""Validated identity and target context used at every sensitive boundary."""
model_config = ConfigDict(extra="forbid", frozen=True)
workspace: str = Field(min_length=1, max_length=128)
project: str = Field(min_length=1, max_length=128)
environment: str = Field(pattern=r"^[a-z0-9][a-z0-9-]{0,31}$")
client_id: UUID | None = None
principal: str = Field(min_length=1, max_length=256)
principal_type: str = Field(default="machine", max_length=64)
virtual_server: str | None = Field(default=None, max_length=128)
capability: str | None = Field(default=None, max_length=256)
tool: str = Field(min_length=1, max_length=256)
connector: str = Field(min_length=1, max_length=64)
risk_class: str = Field(default="R1", pattern=r"^(?:R[0-9]+|RX)$")
origin: str = Field(default="mcp", max_length=64)
resource_type: str | None = Field(default=None, max_length=64)
environment_classification: str | None = Field(default=None, max_length=64)
binding_present: bool = False
client_enabled: bool = False
arguments: Mapping[str, Any] = Field(default_factory=dict)
trace_id: str = Field(default_factory=lambda: uuid4().hex, pattern=r"^[A-Za-z0-9._:-]{1,128}$")
class PolicyRule(BaseModel):
"""A catalog or persisted rule with only typed selector primitives."""
model_config = ConfigDict(extra="forbid", frozen=True)
slug: str = Field(pattern=r"^[a-z0-9][a-z0-9-]{0,62}$")
workspace: str | None = None
project: str | None = None
priority: int = Field(ge=0, le=100000)
description: str = Field(min_length=1, max_length=1000)
match: dict[str, Any] = Field(default_factory=dict)
conditions: dict[str, Any] = Field(default_factory=dict)
decision: Decision
terminal: bool = False
source: str = Field(default="catalog", max_length=64)
enabled: bool = True
@field_validator("match", "conditions")
@classmethod
def safe_maps(cls, value: dict[str, Any]) -> dict[str, Any]:
if len(value) > 32:
raise PolicyValidationError("policy selector map is too large")
return value
@classmethod
def from_document(cls, document: Mapping[str, Any]) -> PolicyRule:
metadata = document.get("metadata") or {}
spec = document.get("spec") or {}
if not isinstance(metadata, Mapping) or not isinstance(spec, Mapping):
raise PolicyValidationError("policy document metadata and spec must be objects")
match = dict(spec.get("match") or {})
conditions = dict(spec.get("conditions") or {})
unknown_match = set(match) - _MATCH_FIELDS
unknown_conditions = set(conditions) - _CONDITION_FIELDS
if unknown_match or unknown_conditions:
raise PolicyValidationError(
f"unsupported policy fields: {sorted(unknown_match | unknown_conditions)}"
)
project = spec.get("project")
workspace = spec.get("workspace")
return cls(
slug=str(metadata.get("slug", "")),
workspace=str(workspace) if workspace is not None else None,
project=str(project) if project is not None else None,
priority=int(spec.get("priority", 0)),
description=str(spec.get("description", "")),
match=match,
conditions=conditions,
decision=Decision(str(spec.get("decision", "deny"))),
terminal=bool(spec.get("terminal", False)),
source=str(spec.get("source", "catalog")),
enabled=bool(spec.get("enabled", True)),
)
class TraceStep(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
phase: str
rule: str | None = None
matched: bool
reason: str
class DecisionTrace(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
trace_id: str
decision: Decision
reason_code: str
matched_rule_ids: tuple[str, ...] = ()
steps: tuple[TraceStep, ...] = ()
def _values(value: Any) -> tuple[str, ...]:
if isinstance(value, list | tuple | set):
return tuple(str(item) for item in value)
return (str(value),)
def _context_value(context: InvocationContext, key: str) -> tuple[str, ...]:
mapping: dict[str, Any] = {
"workspace": context.workspace,
"workspaceId": context.workspace,
"project": context.project,
"projectId": context.project,
"projectSlugs": context.project,
"projects": context.project,
"environment": context.environment,
"environments": context.environment,
"environmentClassifications": context.environment_classification or context.environment,
"resourceType": context.resource_type or "",
"resourceTypes": context.resource_type or "",
"client": str(context.client_id) if context.client_id else "",
"clientId": str(context.client_id) if context.client_id else "",
"clientIds": str(context.client_id) if context.client_id else "",
"principal": context.principal,
"principalType": context.principal_type,
"virtualServer": context.virtual_server or "",
"capability": context.capability or "",
"capabilities": context.capability or "",
"tool": context.tool,
"tools": context.tool,
"toolPatterns": context.tool,
"connector": context.connector,
"connectors": context.connector,
"riskClass": context.risk_class,
"riskClasses": context.risk_class,
"origin": context.origin,
"origins": context.origin,
}
return _values(mapping.get(key, ""))
def _rule_scope_matches(rule: PolicyRule, context: InvocationContext) -> bool:
return (rule.workspace is None or rule.workspace == context.workspace) and (
rule.project is None or rule.project == context.project
)
def _matches(rule: PolicyRule, context: InvocationContext) -> bool:
if not _rule_scope_matches(rule, context):
return False
for key, expected in rule.match.items():
if key not in _MATCH_FIELDS:
raise PolicyValidationError(f"unsupported policy match field: {key}")
actual = _context_value(context, key)
expected_values = _values(expected)
if key in {"toolPatterns"}:
if not any(
fnmatch.fnmatchcase(value, pattern)
for value in actual
for pattern in expected_values
):
return False
elif not any(value in expected_values for value in actual):
return False
return True
def _conditions_match(rule: PolicyRule, context: InvocationContext) -> bool:
for key, expected in rule.conditions.items():
if key not in _CONDITION_FIELDS:
raise PolicyValidationError(f"unsupported policy condition: {key}")
if key == "requireEnabledClient" and bool(expected) and not context.client_enabled:
return False
if key == "requireBinding" and bool(expected) and not context.binding_present:
return False
if key == "maxArgumentBytes" and len(str(dict(context.arguments)).encode()) > int(expected):
return False
if key in {
"allEqual",
"requiredClaims",
"maxDefaultBytes",
"maxDefaultItems",
"redactionProfile",
}:
return False
return True
class PolicyEngine:
"""Deterministic precedence: validation, global deny, explicit deny, allow, default deny."""
def __init__(self, policies: Iterable[PolicyRule]) -> None:
self.policies = tuple(
sorted(
(item for item in policies if item.enabled),
key=lambda item: (-item.priority, item.slug),
)
)
for policy in self.policies:
unknown = set(policy.match) - _MATCH_FIELDS
if unknown:
raise PolicyValidationError(f"unsupported policy match fields: {sorted(unknown)}")
if (
policy.decision
in {
Decision.ALLOW,
Decision.ALLOW_IF_CONDITIONS,
Decision.ALLOW_WITH_APPROVAL,
Decision.ALLOW_WITH_PREVIEW,
}
and policy.project is None
and policy.slug != "global-deny"
):
# Workspace-wide allows must not expand a project scope implicitly.
continue
def evaluate(self, context: InvocationContext) -> DecisionTrace:
steps: list[TraceStep] = []
if not context.binding_present:
return DecisionTrace(
trace_id=context.trace_id,
decision=Decision.DENY,
reason_code="MISSING_BINDING",
steps=(
TraceStep(
phase="identity", matched=False, reason="client/project binding is required"
),
),
)
for rule in self.policies:
matched = _matches(rule, context)
steps.append(
TraceStep(
phase="global-deny" if rule.project is None else "match",
rule=rule.slug,
matched=matched,
reason="selector matched" if matched else "selector did not match",
)
)
if (
matched
and rule.project is None
and rule.decision in {Decision.DENY, Decision.DENY_AND_ALERT}
and rule.terminal
):
return self._result(context, rule.decision, "GLOBAL_DENY", [rule.slug], steps)
for rule in self.policies:
if (
rule.slug == "global-deny"
or rule.decision not in {Decision.DENY, Decision.DENY_AND_ALERT}
or not _matches(rule, context)
):
continue
return self._result(context, rule.decision, "EXPLICIT_DENY", [rule.slug], steps)
for rule in self.policies:
if rule.decision not in {
Decision.ALLOW,
Decision.ALLOW_IF_CONDITIONS,
Decision.ALLOW_WITH_APPROVAL,
Decision.ALLOW_WITH_PREVIEW,
} or not _matches(rule, context):
continue
conditions = _conditions_match(rule, context)
steps.append(
TraceStep(
phase="conditions",
rule=rule.slug,
matched=conditions,
reason="conditions satisfied" if conditions else "conditions not satisfied",
)
)
if conditions:
return self._result(context, rule.decision, "ALLOW_RULE", [rule.slug], steps)
return self._result(context, Decision.DENY, "DEFAULT_DENY", [], steps)
@staticmethod
def _result(
context: InvocationContext,
decision: Decision,
reason: str,
rules: list[str],
steps: list[TraceStep],
) -> DecisionTrace:
return DecisionTrace(
trace_id=context.trace_id,
decision=decision,
reason_code=reason,
matched_rule_ids=tuple(rules),
steps=tuple(steps),
)
class PolicySimulator:
"""Runs the exact engine against a known-entity inventory without side effects."""
def __init__(
self,
engine: PolicyEngine,
*,
known_projects: Iterable[str] = (),
known_clients: Iterable[UUID] = (),
known_tools: Iterable[str] = (),
) -> None:
self.engine = engine
self.known_projects = frozenset(known_projects)
self.known_clients = frozenset(known_clients)
self.known_tools = frozenset(known_tools)
def simulate(self, context: InvocationContext) -> DecisionTrace:
if self.known_projects and context.project not in self.known_projects:
return DecisionTrace(
trace_id=context.trace_id,
decision=Decision.DENY,
reason_code="UNKNOWN_PROJECT",
steps=(
TraceStep(
phase="simulation", matched=False, reason="project is not a known entity"
),
),
)
if self.known_tools and context.tool not in self.known_tools:
return DecisionTrace(
trace_id=context.trace_id,
decision=Decision.DENY,
reason_code="UNKNOWN_TOOL",
steps=(
TraceStep(
phase="simulation", matched=False, reason="tool is not a known entity"
),
),
)
if context.client_id and self.known_clients and context.client_id not in self.known_clients:
return DecisionTrace(
trace_id=context.trace_id,
decision=Decision.DENY,
reason_code="UNKNOWN_CLIENT",
steps=(
TraceStep(
phase="simulation", matched=False, reason="client is not a known entity"
),
),
)
return self.engine.evaluate(context)
+173
View File
@@ -0,0 +1,173 @@
"""Shared decision, rate-limit, and audit lifecycle used at every invocation boundary."""
from __future__ import annotations
import inspect
import time
from collections.abc import Callable, Mapping
from functools import wraps
from typing import Any, TypeVar, cast
from .audit import AuditEvent, AuditLog
from .engine import Decision, DecisionTrace, InvocationContext, PolicyEngine
from .rate_limit import RateLimitExceeded, SlidingWindowRateLimiter
class PolicyDenied(PermissionError):
def __init__(self, trace: DecisionTrace) -> None:
super().__init__(trace.reason_code)
self.trace = trace
self.code = trace.reason_code
T = TypeVar("T", bound=Callable[..., Any])
def governed_tool(tool: str) -> Callable[[T], T]:
"""Decorate direct connector tool methods with the shared governance lifecycle."""
def decorator(method: T) -> T:
@wraps(method)
def wrapped(self: Any, context: Any, *args: Any, **kwargs: Any) -> Any:
governance = getattr(self, "governance", None)
if governance is None or getattr(self, "_governance_dispatch", False):
return method(self, context, *args, **kwargs)
bound = inspect.signature(method).bind(self, context, *args, **kwargs)
arguments = {
key: value
for key, value in bound.arguments.items()
if key not in {"self", "context"}
}
policy_context = governance.context(
workspace=str(context.workspace_id),
project=context.project_slug,
environment=context.environment,
principal=str(context.client_id or "anonymous"),
client_id=context.client_id,
binding_present=context.client_id is not None,
client_enabled=context.client_id is not None,
tool=tool,
connector=getattr(self, "connector_type", "connector"),
arguments=arguments,
)
self._governance_dispatch = True
try:
return governance.invoke(
policy_context, lambda: method(self, context, *args, **kwargs)
)
finally:
self._governance_dispatch = False
return cast(T, wrapped)
return decorator
class Governance:
def __init__(
self,
engine: PolicyEngine,
audit: AuditLog,
*,
rate_limiter: SlidingWindowRateLimiter | None = None,
) -> None:
self.engine = engine
self.audit = audit
self.rate_limiter = rate_limiter
def begin(self, context: InvocationContext) -> tuple[DecisionTrace, float]:
started = time.monotonic()
trace = self.engine.evaluate(context)
if trace.decision in {Decision.DENY, Decision.DENY_AND_ALERT}:
self.audit.append(
AuditEvent.from_context(
context, trace, outcome="denied", started=started, error_code=trace.reason_code
)
)
raise PolicyDenied(trace)
if self.rate_limiter is not None:
try:
self.rate_limiter.check(
client_id=context.client_id or context.principal,
project=context.project,
tool=context.tool,
)
except RateLimitExceeded:
self.audit.append(
AuditEvent.from_context(
context,
trace,
outcome="rate_limited",
started=started,
error_code="RATE_LIMITED",
)
)
raise
return trace, started
def finish(
self,
context: InvocationContext,
trace: DecisionTrace,
started: float,
*,
outcome: str = "success",
error_code: str | None = None,
result_summary: dict[str, Any] | None = None,
) -> None:
self.audit.append(
AuditEvent.from_context(
context, trace, outcome=outcome, started=started, error_code=error_code,
result_summary=result_summary,
)
)
def invoke(self, context: InvocationContext, operation: Callable[[], Any]) -> Any:
trace, started = self.begin(context)
try:
result = operation()
except Exception as exc:
self.finish(
context,
trace,
started,
outcome="error",
error_code=getattr(exc, "code", type(exc).__name__),
)
raise
summary = None
if context.tool == "gitea.pullrequest.merge" and isinstance(result, dict):
data = result.get("data") or {}
if isinstance(data, dict):
summary = data.get("auditEvidence")
self.finish(context, trace, started, result_summary=summary)
return result
@staticmethod
def context(
*,
workspace: str,
project: str,
environment: str,
principal: str,
tool: str,
connector: str,
arguments: Mapping[str, Any],
client_id: Any = None,
binding_present: bool = True,
client_enabled: bool = True,
**kwargs: Any,
) -> InvocationContext:
return InvocationContext(
workspace=workspace,
project=project,
environment=environment,
principal=principal,
tool=tool,
connector=connector,
arguments=dict(arguments),
client_id=client_id,
binding_present=binding_present,
client_enabled=client_enabled,
**kwargs,
)
+45
View File
@@ -0,0 +1,45 @@
"""In-process bounded sliding-window rate limiter with isolated compound keys."""
from __future__ import annotations
import time
from collections import defaultdict, deque
from threading import Lock
from uuid import UUID
class RateLimitExceeded(RuntimeError):
code = "RATE_LIMITED"
class SlidingWindowRateLimiter:
def __init__(
self, *, limit: int = 60, window_seconds: float = 60.0, clock: object = time.monotonic
) -> None:
if limit < 1 or window_seconds <= 0:
raise ValueError("rate limit must be positive")
self.limit = limit
self.window_seconds = window_seconds
self.clock = clock
self._entries: dict[tuple[str, str, str], deque[float]] = defaultdict(deque)
self._lock = Lock()
def allow(self, *, client_id: UUID | str, project: str, tool: str) -> bool:
key = (str(client_id), project, tool)
now = self.clock() # type: ignore[operator]
with self._lock:
bucket = self._entries[key]
while bucket and bucket[0] <= now - self.window_seconds:
bucket.popleft()
if len(bucket) >= self.limit:
return False
bucket.append(now)
return True
def check(self, *, client_id: UUID | str, project: str, tool: str) -> None:
if not self.allow(client_id=client_id, project=project, tool=tool):
raise RateLimitExceeded("invocation rate limit exceeded")
def clear(self) -> None:
with self._lock:
self._entries.clear()