From 2fad269da68dfc8d902af235898d3290499421d4 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:34:29 +0000 Subject: [PATCH] Fix parameter default expressions taint analysis leak Evaluate default expressions with TaintState.INTEGRAL instead of function_taint during parameter seeding to avoid false negative trust laundering when omitted parameters evaluate to untrusted defaults on @trusted functions. Co-authored-by: tachyon-beep <544926+tachyon-beep@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ src/wardline/scanner/taint/variable_level.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index a48a87f5..2136765f 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -7,3 +7,8 @@ **Vulnerability:** The static analyzer was missing `yaml.unsafe_load` and `yaml.full_load` in its `_SERIALISATION_SINKS` mapping, potentially leading to false negatives when tracking untrusted data flowing into these dangerous deserialization functions. **Learning:** Even if functions are listed in rule specifications (like `_SINK_SPECS`), they also need to be properly categorized in the core taint propagation logic (`_SERIALISATION_SINKS`) to ensure the analyzer correctly sheds validation provenance (converting output to `UNKNOWN_RAW`). **Prevention:** When adding new sinks to rule definitions, always verify if they need to be added to core propagation mappings like `_SERIALISATION_SINKS` or `_PROPAGATING_BUILTINS`. + +## 2026-06-21 - Evaluate Default Argument Expressions with Clean State +**Vulnerability:** Default parameter expressions in Python functions were being evaluated using the function's overall `function_taint` instead of a clean, baseline state. +**Learning:** For a taint analysis engine, parameter defaults must be evaluated independently of the current call context's `function_taint`. If an untrusted default expression uses `function_taint` (e.g. `ASSURED`), it can inadvertently launder untrusted data by marking it as safe simply because the enclosing function is trusted. +**Prevention:** In `_seed_parameters` within `src/wardline/scanner/taint/variable_level.py`, compute the taint of default arguments using `TaintState.INTEGRAL` rather than passing `function_taint` down as the base. diff --git a/src/wardline/scanner/taint/variable_level.py b/src/wardline/scanner/taint/variable_level.py index a51a6989..be712597 100644 --- a/src/wardline/scanner/taint/variable_level.py +++ b/src/wardline/scanner/taint/variable_level.py @@ -767,12 +767,12 @@ def _seed_parameters( param_idx = total_pos_args - num_defaults + i if 0 <= param_idx < len(all_pos_params): param_name = all_pos_params[param_idx].arg - default_taints[param_name] = _resolve_expr(default_expr, function_taint, taint_map or {}, {}) + default_taints[param_name] = _resolve_expr(default_expr, TaintState.INTEGRAL, taint_map or {}, {}) # Map keyword-only parameter defaults for param, kw_default_expr in zip(args.kwonlyargs, args.kw_defaults, strict=True): if kw_default_expr is not None: - default_taints[param.arg] = _resolve_expr(kw_default_expr, function_taint, taint_map or {}, {}) + default_taints[param.arg] = _resolve_expr(kw_default_expr, TaintState.INTEGRAL, taint_map or {}, {}) def handle_arg(arg: ast.arg) -> None: fallback = default_taints.get(arg.arg, function_taint)