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:
Junghwan
2026-04-17 19:06:12 +09:00
committed by GitHub
parent 59017e0988
commit 3b395500a2
3 changed files with 38 additions and 2 deletions
+1 -1
View File
@@ -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)?"
+8 -1
View File
@@ -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"
+29
View File
@@ -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