Files
tracer-cloud--opensre/tests/interactive_shell/history/test_history_policy.py
T
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

204 lines
7.1 KiB
Python

"""Tests for history-redaction patterns, retention, and policy resolution."""
from __future__ import annotations
from pathlib import Path
import pytest
from surfaces.interactive_shell.prompt_history.policy import (
DEFAULT_MAX_ENTRIES,
DEFAULT_REDACTION_RULES,
HistoryPolicy,
RedactingFileHistory,
redact_text,
)
@pytest.mark.parametrize(
("raw", "expected_marker"),
[
("AKIAIOSFODNN7EXAMPLE", "[REDACTED:aws_key]"),
(
"aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY1",
"[REDACTED:aws_secret]",
),
("ghp_" + "a" * 36, "[REDACTED:github_pat]"),
("github_pat_" + "x" * 82, "[REDACTED:github_pat]"),
("sk-ant-" + "y" * 90, "[REDACTED:anthropic_key]"),
("sk-" + "z" * 48, "[REDACTED:openai_key]"),
("xoxb-12345-67890-abcdefghijklmn", "[REDACTED:slack_token]"),
("sk_live_" + "Q" * 24, "[REDACTED:stripe_key]"),
(
"Authorization: Bearer abc123abc123abc123abc123",
"Bearer [REDACTED]",
),
(
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3OD.SflKxwRJSMeKKF2QT4",
"[REDACTED:jwt]",
),
("psql --password=hunter2 -h db", "[REDACTED:password]"),
(
"-----BEGIN RSA PRIVATE KEY-----\nMIIEvQIBADANBgkq\n-----END RSA PRIVATE KEY-----",
"[REDACTED:private_key]",
),
],
)
def test_each_default_pattern_redacts_known_token(raw: str, expected_marker: str) -> None:
assert expected_marker in redact_text(raw)
def test_full_pem_block_is_redacted_including_body_and_footer() -> None:
pem = (
"-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDaaaa\n"
"bbbbbCCCCCdddddEEEEEfffffGGGGGhhhhhIIIIIjjjjjKKKKKlllll\n"
"mmmmmNNNNNoooooPPPPPqqqqqRRRRRsssssTTTTTuuuuuVVVVVwwwww\n"
"-----END RSA PRIVATE KEY-----"
)
redacted = redact_text(pem)
assert "[REDACTED:private_key]" in redacted
# The body and footer must be gone — no base64 or END marker can leak to disk.
assert "MIIEvQIBADANBgkq" not in redacted
assert "-----END" not in redacted
assert "-----BEGIN" not in redacted
def test_openssh_pem_block_is_redacted_end_to_end() -> None:
pem = (
"-----BEGIN OPENSSH PRIVATE KEY-----\n"
"b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFw\n"
"-----END OPENSSH PRIVATE KEY-----"
)
redacted = redact_text(pem)
assert "[REDACTED:private_key]" in redacted
assert "b3BlbnNzaC1rZXktdjEA" not in redacted
def test_pem_block_inside_history_entry_does_not_leak_to_disk(tmp_path: Path) -> None:
history_file = tmp_path / "history"
backend = RedactingFileHistory(str(history_file))
pem = (
"ssh-add - <<'EOF'\n"
"-----BEGIN EC PRIVATE KEY-----\n"
"MHcCAQEEIBcccDDDDDeeeeeFFFFFggggghhhhhIIIIIjjjjjKKKKKlllll\n"
"mmmmmNNNNNoooooPPPPPqqqqq\n"
"-----END EC PRIVATE KEY-----\n"
"EOF"
)
backend.store_string(pem)
contents = history_file.read_text(encoding="utf-8")
assert "[REDACTED:private_key]" in contents
assert "MHcCAQEEIBcccDDDDD" not in contents
assert "-----END EC PRIVATE KEY-----" not in contents
assert "-----BEGIN EC PRIVATE KEY-----" not in contents
def test_natural_language_is_left_alone() -> None:
text = "investigate api errors after the redis cluster restarted at 12:30"
assert redact_text(text) == text
def test_only_secret_segment_is_replaced() -> None:
raw = "kubectl describe pod and AKIAIOSFODNN7EXAMPLE was in the env"
out = redact_text(raw)
assert "[REDACTED:aws_key]" in out
assert out.startswith("kubectl describe pod and ")
assert out.endswith(" was in the env")
def test_redacting_file_history_writes_redacted_only(tmp_path: Path) -> None:
history_file = tmp_path / "history"
backend = RedactingFileHistory(str(history_file))
backend.store_string("AKIAIOSFODNN7EXAMPLE plus other tokens")
contents = history_file.read_text(encoding="utf-8")
assert "AKIAIOSFODNN7EXAMPLE" not in contents
assert "[REDACTED:aws_key]" in contents
def test_paused_backend_does_not_persist(tmp_path: Path) -> None:
history_file = tmp_path / "history"
backend = RedactingFileHistory(str(history_file))
backend.paused = True
backend.store_string("any string")
assert not history_file.exists() or history_file.read_text(encoding="utf-8") == ""
def test_retention_cap_drops_oldest_entries(tmp_path: Path) -> None:
history_file = tmp_path / "history"
backend = RedactingFileHistory(str(history_file), max_entries=3)
for i in range(5):
backend.store_string(f"entry-{i}")
persisted = list(reversed(list(backend.load_history_strings())))
assert persisted == ["entry-2", "entry-3", "entry-4"]
def test_zero_retention_keeps_unlimited(tmp_path: Path) -> None:
history_file = tmp_path / "history"
backend = RedactingFileHistory(str(history_file), max_entries=0)
for i in range(5):
backend.store_string(f"entry-{i}")
persisted = list(reversed(list(backend.load_history_strings())))
assert persisted == [f"entry-{i}" for i in range(5)]
def test_policy_defaults_match_documented_shape() -> None:
policy = HistoryPolicy.load()
assert policy.enabled is True
assert policy.redact is True
assert policy.max_entries == DEFAULT_MAX_ENTRIES
def test_env_var_disables_persistence(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("OPENSRE_HISTORY_ENABLED", "0")
policy = HistoryPolicy.load()
assert policy.enabled is False
def test_env_var_disables_redaction(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("OPENSRE_HISTORY_REDACT", "false")
policy = HistoryPolicy.load()
assert policy.redact is False
def test_env_var_overrides_max_entries(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("OPENSRE_HISTORY_MAX_ENTRIES", "42")
policy = HistoryPolicy.load()
assert policy.max_entries == 42
def test_env_var_garbage_falls_back_to_default(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("OPENSRE_HISTORY_MAX_ENTRIES", "not-a-number")
policy = HistoryPolicy.load()
assert policy.max_entries == DEFAULT_MAX_ENTRIES
def test_file_settings_used_when_env_absent() -> None:
policy = HistoryPolicy.load({"enabled": False, "redact": False, "max_entries": 100})
assert policy.enabled is False
assert policy.redact is False
assert policy.max_entries == 100
def test_quoted_false_like_file_settings_are_parsed_as_disabled() -> None:
policy = HistoryPolicy.load({"enabled": "false", "redact": "0"})
assert policy.enabled is False
assert policy.redact is False
def test_prune_to_cap_is_safe_when_cap_is_zero(tmp_path: Path) -> None:
history_file = tmp_path / "history"
backend = RedactingFileHistory(str(history_file), max_entries=0)
backend.store_string("entry-0")
backend._prune_to_cap()
persisted = list(reversed(list(backend.load_history_strings())))
assert persisted == ["entry-0"]
def test_default_pattern_set_size_is_stable() -> None:
# Catch accidental rule deletions in PRs.
assert len(DEFAULT_REDACTION_RULES) >= 12