mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-22 06:19:47 +08:00
Integrate Z3-based formal verification for sandbox policies as openshell.prover. Detects data exfiltration paths, write bypass attacks, L7 enforcement gaps, overpermissive methods, and privilege escalation via binary capability chains. Closes #699 Signed-off-by: Alexander Watson <zredlined@gmail.com>
142 lines
4.2 KiB
Python
142 lines
4.2 KiB
Python
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
"""Parse credential descriptors and API capability registries."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import TYPE_CHECKING
|
|
|
|
import yaml
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
@dataclass
|
|
class Credential:
|
|
name: str
|
|
type: str
|
|
scopes: list[str] = field(default_factory=list)
|
|
injected_via: str = ""
|
|
target_hosts: list[str] = field(default_factory=list)
|
|
|
|
|
|
@dataclass
|
|
class ApiAction:
|
|
method: str
|
|
path: str
|
|
action: str
|
|
|
|
|
|
@dataclass
|
|
class ApiCapability:
|
|
api: str
|
|
host: str
|
|
port: int = 443
|
|
credential_type: str = ""
|
|
scope_capabilities: dict[str, list[ApiAction]] = field(default_factory=dict)
|
|
action_risk: dict[str, str] = field(default_factory=dict)
|
|
|
|
def actions_for_scopes(self, scopes: list[str]) -> list[ApiAction]:
|
|
"""Return all API actions enabled by the given scopes."""
|
|
result = []
|
|
for scope in scopes:
|
|
result.extend(self.scope_capabilities.get(scope, []))
|
|
return result
|
|
|
|
def write_actions_for_scopes(self, scopes: list[str]) -> list[ApiAction]:
|
|
"""Return API actions that involve writing (POST, PUT, PATCH, DELETE)."""
|
|
return [
|
|
a
|
|
for a in self.actions_for_scopes(scopes)
|
|
if a.method.upper() in {"POST", "PUT", "PATCH", "DELETE"}
|
|
]
|
|
|
|
def destructive_actions_for_scopes(self, scopes: list[str]) -> list[ApiAction]:
|
|
"""Return actions classified as high or critical risk."""
|
|
actions = self.actions_for_scopes(scopes)
|
|
return [
|
|
a
|
|
for a in actions
|
|
if self.action_risk.get(a.action, "low") in {"high", "critical"}
|
|
]
|
|
|
|
|
|
@dataclass
|
|
class CredentialSet:
|
|
credentials: list[Credential] = field(default_factory=list)
|
|
api_registries: dict[str, ApiCapability] = field(default_factory=dict)
|
|
|
|
def credentials_for_host(self, host: str) -> list[Credential]:
|
|
return [c for c in self.credentials if host in c.target_hosts]
|
|
|
|
def api_for_host(self, host: str) -> ApiCapability | None:
|
|
for api in self.api_registries.values():
|
|
if api.host == host:
|
|
return api
|
|
return None
|
|
|
|
|
|
def load_credentials(path: Path) -> list[Credential]:
|
|
"""Load credential descriptors from YAML."""
|
|
with open(path) as f: # noqa: PTH123
|
|
raw = yaml.safe_load(f)
|
|
|
|
credentials = []
|
|
for c_raw in raw.get("credentials", []):
|
|
credentials.append(
|
|
Credential(
|
|
name=c_raw.get("name", ""),
|
|
type=c_raw.get("type", ""),
|
|
scopes=c_raw.get("scopes", []),
|
|
injected_via=c_raw.get("injected_via", ""),
|
|
target_hosts=c_raw.get("target_hosts", []),
|
|
)
|
|
)
|
|
return credentials
|
|
|
|
|
|
def load_api_registry(path: Path) -> ApiCapability:
|
|
"""Load an API capability registry from YAML."""
|
|
with open(path) as f: # noqa: PTH123
|
|
raw = yaml.safe_load(f)
|
|
|
|
scope_caps = {}
|
|
for scope, actions_raw in (raw.get("scope_capabilities") or {}).items():
|
|
scope_caps[scope] = [
|
|
ApiAction(
|
|
method=a.get("method", ""),
|
|
path=a.get("path", ""),
|
|
action=a.get("action", ""),
|
|
)
|
|
for a in actions_raw
|
|
]
|
|
|
|
return ApiCapability(
|
|
api=raw.get("api", ""),
|
|
host=raw.get("host", ""),
|
|
port=raw.get("port", 443),
|
|
credential_type=raw.get("credential_type", ""),
|
|
scope_capabilities=scope_caps,
|
|
action_risk=raw.get("action_risk", {}),
|
|
)
|
|
|
|
|
|
def load_credential_set(
|
|
credentials_path: Path,
|
|
registry_dir: Path,
|
|
) -> CredentialSet:
|
|
"""Load credentials and all API registries from a registry directory."""
|
|
creds = load_credentials(credentials_path)
|
|
|
|
api_registries = {}
|
|
apis_dir = registry_dir / "apis"
|
|
if apis_dir.is_dir():
|
|
for api_file in apis_dir.glob("*.yaml"):
|
|
api = load_api_registry(api_file)
|
|
api_registries[api.api] = api
|
|
|
|
return CredentialSet(credentials=creds, api_registries=api_registries)
|