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
110 lines
3.8 KiB
Python
110 lines
3.8 KiB
Python
"""End-to-end tests for guardrail overlapping keyword redaction."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from platform.guardrails.engine import GuardrailEngine
|
|
from platform.guardrails.rules import GuardrailAction, GuardrailRule
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_rule(
|
|
name: str = "test",
|
|
action: str = "redact",
|
|
patterns: list[str] | None = None,
|
|
keywords: list[str] | None = None,
|
|
replacement: str = "",
|
|
) -> GuardrailRule:
|
|
compiled = tuple(re.compile(p, re.IGNORECASE) for p in (patterns or []))
|
|
kws = tuple(k.lower() for k in (keywords or []))
|
|
return GuardrailRule(
|
|
name=name,
|
|
action=GuardrailAction(action),
|
|
patterns=compiled,
|
|
keywords=kws,
|
|
replacement=replacement,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Guardrails overlapping keyword redaction — full pipeline
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestOverlappingRedactionE2E:
|
|
"""Prove that overlapping keywords are fully redacted through the complete
|
|
scan → audit → redact pipeline, not just in isolation."""
|
|
|
|
def test_secret_and_secret_key_across_rules(self) -> None:
|
|
"""Two rules, shorter keyword is a prefix of the longer one."""
|
|
engine = GuardrailEngine(
|
|
[
|
|
_make_rule(name="generic", keywords=["secret"]),
|
|
_make_rule(name="specific", keywords=["secret_key"]),
|
|
]
|
|
)
|
|
result = engine.apply("export secret_key=hunter2")
|
|
# The longer match must win — no leftover "_key"
|
|
assert "_key" not in result
|
|
assert "=hunter2" in result
|
|
assert "secret" not in result.lower().replace("[redacted:", "")
|
|
|
|
def test_api_and_api_key_single_rule(self) -> None:
|
|
"""Both keywords in the same rule."""
|
|
engine = GuardrailEngine(
|
|
[
|
|
_make_rule(name="creds", keywords=["api", "api_key"]),
|
|
]
|
|
)
|
|
result = engine.apply("set api_key=abc123")
|
|
assert "_key" not in result
|
|
assert "=abc123" in result
|
|
|
|
def test_multiple_overlapping_occurrences(self) -> None:
|
|
"""Multiple overlapping pairs in one string."""
|
|
engine = GuardrailEngine(
|
|
[
|
|
_make_rule(name="r1", keywords=["pass"]),
|
|
_make_rule(name="r2", keywords=["password"]),
|
|
]
|
|
)
|
|
text = "password=foo and pass=bar"
|
|
result = engine.apply(text)
|
|
# "password" should be fully redacted (not "word" leftover)
|
|
assert "word" not in result.split("and")[0]
|
|
# "pass" alone should also be redacted
|
|
assert "=bar" in result
|
|
|
|
def test_pattern_and_keyword_overlap(self) -> None:
|
|
"""A regex pattern and a keyword overlap on the same span."""
|
|
engine = GuardrailEngine(
|
|
[
|
|
_make_rule(name="pat", patterns=[r"AKIA[A-Z0-9]{16}"]),
|
|
_make_rule(name="kw", keywords=["akia"]),
|
|
]
|
|
)
|
|
text = "key=AKIAIOSFODNN7EXAMPLE"
|
|
result = engine.apply(text)
|
|
# The longer regex match should win
|
|
assert "AKIA" not in result
|
|
assert "key=" in result
|
|
|
|
def test_full_scan_apply_audit_cycle(self) -> None:
|
|
"""Scan, check matches, then apply — the real engine flow."""
|
|
engine = GuardrailEngine(
|
|
[
|
|
_make_rule(name="tokens", keywords=["token", "token_secret"]),
|
|
]
|
|
)
|
|
text = "auth token_secret=xyz and token=abc"
|
|
|
|
# Apply correctly redacts overlapping matches without leftovers.
|
|
result = engine.apply(text)
|
|
assert "_secret" not in result
|
|
assert "=xyz" in result
|
|
assert "=abc" in result
|