T09 · Insecure Skill Coding Practices
Warning
- Location
- llm_result_cache.py:91
- Finding
- Incomplete Default Cache Key Causes Cross-Context Result Confusion<![CDATA[ ## Vulnerability Details **File Location**: `llm_result_cache.py`, lines 91–98 **Vulnerability Type**: Insecure cache-key construction **Risk Level**: Medium ### Vulnerable Code ```python def decorator(fn: Callable) -> Callable: @wraps(fn) def wrapper(*args, **kwargs): key = key_fn(*args, **kwargs) if key_fn else str(args[0]) if args else "" hit = cache.get(key) if hit is not None: return hit result = fn(*args, **kwargs) if isinstance(result, dict): cache.set(key, result) return result ``` ### Technical Analysis When no custom `key_fn` is supplied, the decorator derives the cache key exclusively from the first positional argument. It ignores: - Additional positional arguments - All keyword arguments - Tenant or user identity - Authorization context - Model and prompt versions - Configuration options that can affect the result Consequently, semantically different function calls can resolve to the same cache entry. For example, calls such as `analyze(document, tenant="A")` and `analyze(document, tenant="B")` use an identical default key if `document` is the same. The cached value is returned immediately, so the wrapped function and any authorization-sensitive or context-sensitive processing it performs are skipped. ### Attack Path 1. An application decorates a multi-argument or keyword-sensitive function with `@cached(cache)` without providing `key_fn`. 2. An attacker or ordinary user invokes the function with a chosen first argument and attacker-controlled secondary arguments. 3. The resulting dictionary is stored under a key derived only from the first argument. 4. Another caller invokes the function with the same first argument but different tenant, permission, model, prompt, or processing options. 5. The decorator finds the previously stored value and returns it without executing the wrapped function. 6. The second caller receives a result generated for a differe ...[truncated 667 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Construct the default key from all arguments rather than only the first positional argument. 1. Canonically serialize both `args` and sorted `kwargs`. 2. Hash the serialization to produce a bounded key. 3. Include all security-relevant context, including tenant identity, user identity, model version, prompt version, and processing options. 4. Reject unsupported or non-deterministically serializable arguments instead of silently producing an incomplete key. 5. For security-sensitive functions, require an explicit `key_fn` rather than supplying an unsafe fallback. 6. Add tests proving that changes to any result-affecting argument produce distinct cache keys. Example approach: ```python import hashlib import json def default_key(args, kwargs): payload = json.dumps( {"args": args, "kwargs": kwargs}, sort_keys=True, separators=(",", ":"), default=repr, ) return hashlib.sha256(payload.encode("utf-8")).hexdigest() ``` Applications should still provide an explicit key function where identity or authorization context is not directly present in the function arguments. ]]>
