fix(personalization): stop replaying exported env var values (#151)
Personalization currently captures full `export NAME=value` payloads and re-injects them through local rules into future system prompts. Narrow the environment-variable extraction to the variable name so the feature can still remember environment hints without persisting secret material. Constraint: Keep personalization's environment-hint workflow intact Rejected: Remove env_var extraction entirely | larger behavior change than needed for a first fix Confidence: high Scope-risk: narrow Reversibility: clean Directive: Do not persist raw env var values without a separate sensitivity model and tests Tested: PYTHONPATH=src pytest -q tests/test_personalization/test_extractor.py tests/test_prompts/test_claudemd.py Tested: PYTHONPATH=src ruff check src tests Not-tested: Full pytest suite in this environment (collection fails because optional pyperclip dependency is missing) Related: #149
This commit is contained in:
@@ -28,7 +28,7 @@ _FACT_PATTERNS: list[tuple[str, str, re.Pattern]] = [
|
||||
r"(https?://\S+/v\d+/?)\b"
|
||||
)),
|
||||
("env_var", "Environment variable", re.compile(
|
||||
r"export\s+([A-Z][A-Z0-9_]+=\S+)"
|
||||
r"export\s+([A-Z][A-Z0-9_]+)(?:=\S+)?"
|
||||
)),
|
||||
("git_remote", "Git remote", re.compile(
|
||||
r"(?:github|gitlab)\.com[:/](\S+?)(?:\.git)?"
|
||||
|
||||
@@ -29,7 +29,14 @@ class TestExtractFacts:
|
||||
text = 'export OPENAI_BASE_URL="https://relay.nf.video/v1"'
|
||||
facts = extract_facts_from_text(text)
|
||||
env_facts = [f for f in facts if f["type"] == "env_var"]
|
||||
assert any("OPENAI_BASE_URL" in f["value"] for f in env_facts)
|
||||
assert env_facts[0]["value"] == "OPENAI_BASE_URL"
|
||||
|
||||
def test_env_var_does_not_capture_secret_value(self):
|
||||
text = "export OPENAI_API_KEY=sk-secret-value"
|
||||
facts = extract_facts_from_text(text)
|
||||
env_facts = [f for f in facts if f["type"] == "env_var"]
|
||||
assert env_facts[0]["value"] == "OPENAI_API_KEY"
|
||||
assert "sk-secret-value" not in env_facts[0]["value"]
|
||||
|
||||
def test_extracts_api_endpoint(self):
|
||||
text = "curl https://api.minimax.chat/v1/chat/completions"
|
||||
|
||||
@@ -9,6 +9,9 @@ from openharness.config.paths import (
|
||||
get_project_issue_file,
|
||||
get_project_pr_comments_file,
|
||||
)
|
||||
from openharness.engine.messages import ConversationMessage, TextBlock
|
||||
from openharness.personalization import rules as personalization_rules
|
||||
from openharness.personalization.session_hook import update_rules_from_session
|
||||
from openharness.prompts import build_runtime_system_prompt, discover_claude_md_files, load_claude_md_prompt
|
||||
from openharness.config.settings import Settings
|
||||
|
||||
@@ -116,3 +119,29 @@ def test_build_runtime_system_prompt_skips_coordinator_context_when_disabled(tmp
|
||||
assert 'subagent_type="worker"' in prompt
|
||||
assert "/agents show TASK_ID" in prompt
|
||||
assert "Environment" in prompt
|
||||
|
||||
|
||||
def test_build_runtime_system_prompt_does_not_reinject_exported_secret_values(tmp_path: Path, monkeypatch):
|
||||
monkeypatch.setenv("OPENHARNESS_DATA_DIR", str(tmp_path / "data"))
|
||||
monkeypatch.delenv("CLAUDE_CODE_COORDINATOR_MODE", raising=False)
|
||||
repo = tmp_path / "repo"
|
||||
repo.mkdir()
|
||||
rules_dir = tmp_path / "local_rules"
|
||||
monkeypatch.setattr(personalization_rules, "_RULES_DIR", rules_dir)
|
||||
monkeypatch.setattr(personalization_rules, "_RULES_FILE", rules_dir / "rules.md")
|
||||
monkeypatch.setattr(personalization_rules, "_FACTS_FILE", rules_dir / "facts.json")
|
||||
|
||||
secret = "sk-test-secret"
|
||||
update_rules_from_session(
|
||||
[
|
||||
ConversationMessage(
|
||||
role="user",
|
||||
content=[TextBlock(text=f"export OPENAI_API_KEY={secret}")],
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
prompt = build_runtime_system_prompt(Settings(), cwd=repo, latest_user_prompt="hello")
|
||||
|
||||
assert "OPENAI_API_KEY" in prompt
|
||||
assert secret not in prompt
|
||||
|
||||
Reference in New Issue
Block a user