4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
156 lines
5.0 KiB
Python
156 lines
5.0 KiB
Python
"""Masking policy — configurable via environment variables.
|
|
|
|
A ``MaskingPolicy`` decides which kinds of sensitive infrastructure
|
|
identifiers are masked and what extra regex patterns apply. It is built
|
|
fresh per investigation, so env-var changes between investigations are
|
|
picked up. No module-level singleton.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
from typing import ClassVar, Literal
|
|
|
|
from pydantic import Field, field_validator
|
|
|
|
from config.strict_config import StrictConfigModel
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
IdentifierKind = Literal[
|
|
"pod",
|
|
"namespace",
|
|
"cluster",
|
|
"hostname",
|
|
"account_id",
|
|
"ip_address",
|
|
"email",
|
|
"service_name",
|
|
]
|
|
|
|
ALL_KINDS: tuple[IdentifierKind, ...] = (
|
|
"pod",
|
|
"namespace",
|
|
"cluster",
|
|
"hostname",
|
|
"account_id",
|
|
"ip_address",
|
|
"email",
|
|
"service_name",
|
|
)
|
|
|
|
|
|
class MaskingPolicy(StrictConfigModel):
|
|
"""Configuration that drives what gets masked before LLM calls."""
|
|
|
|
enabled: bool = False
|
|
kinds: tuple[IdentifierKind, ...] = ALL_KINDS
|
|
extra_patterns: dict[str, str] = Field(default_factory=dict)
|
|
|
|
_ENV_ENABLED: ClassVar[str] = "OPENSRE_MASK_ENABLED"
|
|
_ENV_KINDS: ClassVar[str] = "OPENSRE_MASK_KINDS"
|
|
_ENV_EXTRA_REGEX: ClassVar[str] = "OPENSRE_MASK_EXTRA_REGEX"
|
|
|
|
@field_validator("kinds", mode="before")
|
|
@classmethod
|
|
def _coerce_kinds(cls, value: object) -> tuple[IdentifierKind, ...]:
|
|
if value is None or value == "":
|
|
return ALL_KINDS
|
|
if isinstance(value, str):
|
|
parts = tuple(p.strip() for p in value.split(",") if p.strip())
|
|
return cls._filter_valid_kinds(parts)
|
|
if isinstance(value, list | tuple):
|
|
parts = tuple(str(p).strip() for p in value if str(p).strip())
|
|
return cls._filter_valid_kinds(parts)
|
|
raise ValueError(f"kinds must be a string or list, got {type(value).__name__}")
|
|
|
|
@classmethod
|
|
def _filter_valid_kinds(cls, parts: tuple[str, ...]) -> tuple[IdentifierKind, ...]:
|
|
valid: list[IdentifierKind] = []
|
|
for p in parts:
|
|
if p in ALL_KINDS:
|
|
valid.append(p) # type: ignore[arg-type]
|
|
else:
|
|
logger.warning("[masking] ignoring unknown identifier kind: %r", p)
|
|
return tuple(valid) if valid else ALL_KINDS
|
|
|
|
@field_validator("extra_patterns")
|
|
@classmethod
|
|
def _validate_extra_patterns(cls, value: dict[str, str]) -> dict[str, str]:
|
|
for label, pattern in value.items():
|
|
try:
|
|
re.compile(pattern)
|
|
except re.error as exc:
|
|
raise ValueError(f"extra_patterns[{label!r}] is not a valid regex: {exc}") from exc
|
|
return value
|
|
|
|
@classmethod
|
|
def from_env(cls, env: dict[str, str] | None = None) -> MaskingPolicy:
|
|
"""Build a policy from the current environment (or an injected dict)."""
|
|
source = env if env is not None else os.environ
|
|
enabled = _parse_bool(source.get(cls._ENV_ENABLED, ""))
|
|
kinds_raw = source.get(cls._ENV_KINDS, "") or ""
|
|
extra_raw = source.get(cls._ENV_EXTRA_REGEX, "") or ""
|
|
|
|
extra_patterns: dict[str, str] = {}
|
|
if extra_raw.strip():
|
|
try:
|
|
parsed = json.loads(extra_raw)
|
|
if isinstance(parsed, dict):
|
|
extra_patterns = {str(k): str(v) for k, v in parsed.items()}
|
|
else:
|
|
logger.warning(
|
|
"[masking] %s must be a JSON object, got %s; ignoring",
|
|
cls._ENV_EXTRA_REGEX,
|
|
type(parsed).__name__,
|
|
)
|
|
except json.JSONDecodeError as exc:
|
|
logger.warning(
|
|
"[masking] failed to parse %s as JSON: %s; ignoring",
|
|
cls._ENV_EXTRA_REGEX,
|
|
exc,
|
|
)
|
|
|
|
return cls.model_validate(
|
|
{
|
|
"enabled": enabled,
|
|
"kinds": kinds_raw,
|
|
"extra_patterns": extra_patterns,
|
|
}
|
|
)
|
|
|
|
def is_kind_enabled(self, kind: IdentifierKind) -> bool:
|
|
return self.enabled and kind in self.kinds
|
|
|
|
|
|
def _parse_bool(value: str | None) -> bool:
|
|
if value is None:
|
|
return False
|
|
return value.strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
def compile_extra_patterns(policy: MaskingPolicy) -> dict[str, re.Pattern[str]]:
|
|
"""Compile a policy's extra regex patterns into a label→Pattern dict.
|
|
|
|
Public helper so callers (e.g. MaskingContext) can compile once per
|
|
investigation rather than on every mask call.
|
|
"""
|
|
compiled: dict[str, re.Pattern[str]] = {}
|
|
for label, pattern in policy.extra_patterns.items():
|
|
try:
|
|
compiled[label] = re.compile(pattern)
|
|
except re.error as exc:
|
|
logger.warning("[masking] skipping extra pattern %r (invalid regex): %s", label, exc)
|
|
return compiled
|
|
|
|
|
|
__all__ = [
|
|
"ALL_KINDS",
|
|
"IdentifierKind",
|
|
"MaskingPolicy",
|
|
"compile_extra_patterns",
|
|
]
|