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)