jenkinsci / jenkinsci/support-core-plugin

Case-folding mismatch in SensitiveContentFilter: NPE during bundle generation, and some names silently never anonymized

Open
#981 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Java
Stars
19
Forks
77
Avg merge
17h 53m
Merged PRs (30d)
3

Description

## Summary

`SensitiveContentFilter` builds its lookup keys with one case-folding rule and matches with another. The compiled `Pattern` uses `CASE_INSENSITIVE | UNICODE_CASE`, which compares characters via `toLowerCase(toUpperCase(c))`, while the trie words and `replacementsMap` keys are built with `String.toLowerCase(Locale.ENGLISH)`:

```java
// reload()
String lowerCaseOriginal = name.toLowerCase(Locale.ENGLISH);
trie.add(lowerCaseOriginal);
replacementsMap.putIfAbsent(lowerCaseOriginal, ...);
...
Pattern.compile("(? "ſap", which is NOT the map key "sap" => map.get() == null => NPE

// failure mode 2: the literal itself is corrupted by a length-changing lowercase
String literal = "İdalium".toLowerCase(Locale.ENGLISH); // U+0130 -> i + U+0307
Pattern q = Pattern.compile("(? " + q.matcher("x " + in + " y").find()); // all false
}
}
}
```

In the plugin, failure 1 surfaces as an aborted support bundle whenever such a character appears in filtered content; failure 2 surfaces as a name that is listed as anonymized but never actually redacted.

## Frequency

Rare. Only a handful of characters have lowercase mappings that change a string's length (U+0130 is essentially the canonical one), and the `ſ`-style divergence needs an unusual character to appear in filtered content. Reporting it because the feature accepts arbitrary Unicode names and claims to redact them, so a name it cannot match is a correctness gap regardless of frequency — and failure 2 fails *open*, which is the wrong direction for an anonymization feature.

## Environment

- support-core 1744.vf4f8e8f2b_0b_5 (the affected code path is long-standing and unchanged on `master`)
- Jenkins 2.541.3, JDK 21
- Anonymization enabled (`ContentFilters.enabled = true`)

## Fix

Deriving both the key and the lookup with the same per-code-point fold — `Character.toLowerCase(Character.toUpperCase(cp))`, which mirrors what the matcher actually does — resolves both failures and makes the `null` return impossible by construction. Verified that Java's `CASE_INSENSITIVE | UNICODE_CASE` single-character matching is exactly equality under that fold (4,000,000 random code-point pairs, 0 mismatches) and that the fold is idempotent across all 1,112,064 valid code points, so folding the pattern literal only ever broadens matching and never loses a match the current code catches.

Happy to open a PR with the fix and a regression test that fails on `master`.

Contributor guide

Open the contributing guide

Research direction

Start with SensitiveContentFilter.java and WordReplacer.java, then run the plain JDK reproducer from the issue to observe both Unicode failures. Add regression coverage for the NPE case and the silently unmatched name case, and confirm filtered bundle generation handles both without exposing the original names.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
security
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.