diff --git a/backend/app/core/cache.py b/backend/app/core/cache.py index de9fce0..98b7969 100644 --- a/backend/app/core/cache.py +++ b/backend/app/core/cache.py @@ -43,8 +43,23 @@ def compute_semantic_node_hash( hasher = hashlib.sha256() hasher.update(node_type.encode("utf-8")) - # Canonicalize params: exclude transient secrets from hash - clean_params = {k: v for k, v in params.items() if not k.lower().endswith("key")} + # Canonicalize params: exclude transient secrets from hash via explicit denylist + secret_keys = { + "api_key", + "apikey", + "secret", + "secret_key", + "token", + "access_token", + "auth_token", + "password", + "bearer_token", + "app_secret", + } + clean_params = { + k: v for k, v in params.items() + if k.lower() not in secret_keys and not k.lower().endswith("_api_key") and not k.lower().endswith("_token") + } # Invariant #5: Canonical Params must include provider identity & runner version resolved_prov = provider_id or params.get("__provider") or params.get("provider") or params.get("engine_id") diff --git a/backend/tests/test_dag_cache.py b/backend/tests/test_dag_cache.py index 384aad6..7569fb4 100644 --- a/backend/tests/test_dag_cache.py +++ b/backend/tests/test_dag_cache.py @@ -162,3 +162,29 @@ async def test_cache_sqlite_error_logging(caplog): # Test clear_all_async error logging await failing_cache.clear_all_async() assert "Cache DB clear operation failed: SQLite connection lock failed" in caplog.text + + +def test_semantic_node_hash_preserves_non_secret_keys(): + """Verify parameters containing 'key' like chroma_key or animation_key are NOT stripped from hash.""" + from app.core.cache import compute_semantic_node_hash + + hash1 = compute_semantic_node_hash( + node_type="image.chroma", + params={"chroma_key": "#00FF00", "tolerance": 0.2, "api_key": "secret123"}, + input_bindings=[], + ) + hash2 = compute_semantic_node_hash( + node_type="image.chroma", + params={"chroma_key": "#0000FF", "tolerance": 0.2, "api_key": "secret123"}, + input_bindings=[], + ) + # Different chroma_key must produce different hashes (not stripped!) + assert hash1 != hash2 + + # Changing transient api_key should produce identical hashes (stripped) + hash3 = compute_semantic_node_hash( + node_type="image.chroma", + params={"chroma_key": "#00FF00", "tolerance": 0.2, "api_key": "different_secret"}, + input_bindings=[], + ) + assert hash1 == hash3