diff --git a/studio/backend/core/inference/tools.py b/studio/backend/core/inference/tools.py index b07893df40..ef5c799cd0 100644 --- a/studio/backend/core/inference/tools.py +++ b/studio/backend/core/inference/tools.py @@ -1364,46 +1364,33 @@ def _check_signal_escape_patterns(code: str): if alias.name in _PATHLIB_PATH_CLASSES_PREPASS: path_class_aliases_prepass.add(alias.asname or alias.name) - # Cheap hint set used to bias ``string_bindings`` toward the most - # sensitive value of a name when multiple literals are assigned. - # Substring match against the full set of credential / process-state - # root tokens (kept intentionally loose; the real per-spec match - # still runs downstream in the file-read / shutil / bash gates). - _SENSITIVE_HINTS = ( - "/etc/", - "/proc/", - "/var/spool/", - "/root/", - ".ssh/", - ".aws/", - ".gnupg", - ".kube", - ".docker", - ".config/gcloud", - ".pypirc", - ".npmrc", - ".netrc", - ".cargo/credentials", - ".password-store", - "credentials", - "id_rsa", - "id_dsa", - "id_ecdsa", - "id_ed25519", - "shadow", - "sudoers", - ) - def _looks_sensitive(value: str) -> bool: + """True if *value* matches any host-credential / process-state + path that the bash / file gates already flag. Uses the + authoritative ``_find_sensitive_paths`` so the bias distinguishes + ``/etc/shadow`` (sensitive) from ``/etc/hosts`` (allow-listed) -- + a substring hint set conflates them and admits a chained- + reassignment bypass ``p='/etc/hosts'; p='/etc/shadow'``.""" if not value: return False - low = value.lower() - return any(hint in low for hint in _SENSITIVE_HINTS) + return bool(_find_sensitive_paths(value)) def _record_string_binding(name: str, value: str) -> None: """Append ``value`` to ``string_bindings_all[name]`` and update - ``string_bindings[name]`` to favour a sensitive-shaped value - when one exists. Resists the second-assignment bypass.""" + ``string_bindings[name]`` so the gate sees the most sensitive + value the variable could carry at runtime. The selection rule + mirrors Python's last-wins semantics for sensitive values: + + * If the new value is sensitive, it always wins (even if the + current is also sensitive) -- a later sensitive assignment + is at least as concerning as an earlier one, and the chain + ``p='/etc/hosts'; p='/etc/shadow'`` must surface the shadow. + * If the new value is benign and the current sensitive, keep + the sensitive value (Python would last-wins to benign, but + statically we cannot prove the new value executes and we + err on the side of blocking the path the attacker reached + for). + * If both are benign, latest seen wins.""" bucket = string_bindings_all.setdefault(name, []) if value not in bucket: bucket.append(value) @@ -1411,8 +1398,12 @@ def _check_signal_escape_patterns(code: str): if cur is None: string_bindings[name] = value return - if _looks_sensitive(value) and not _looks_sensitive(cur): + if _looks_sensitive(value): string_bindings[name] = value + return + if _looks_sensitive(cur): + return + string_bindings[name] = value def _extract_string_literal(node, _depth = 0): """Strict literal-string extraction: no name binding lookup,