Skip to content

Commit 2c558ef

Browse files
authored
Merge branch 'master' into progressive-rendering-fix
2 parents ee282bd + 40e88da commit 2c558ef

File tree

7 files changed

+208
-20
lines changed

7 files changed

+208
-20
lines changed

.github/workflows/announce-lts-rc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
discourse-author-username: jenkins-release-bot
1818
discourse-category: 23
1919
- name: Post on mailing list
20-
uses: dawidd6/action-send-mail@6e71c855c9a091d80a519621b9fd3e8d252ca40c # v7
20+
uses: dawidd6/action-send-mail@afe978662944c6805dd197bac88b27acb0bda55a # v8
2121
with:
2222
server_address: smtp.gmail.com
2323
server_port: 465

core/src/main/java/hudson/Functions.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@
170170
import jenkins.model.details.Detail;
171171
import jenkins.model.details.DetailFactory;
172172
import jenkins.model.details.DetailGroup;
173+
import jenkins.telemetry.impl.PasswordMasking;
173174
import jenkins.util.SystemProperties;
174175
import org.apache.commons.jelly.JellyContext;
175176
import org.apache.commons.jelly.JellyTagException;
@@ -2256,32 +2257,64 @@ public String getPasswordValue(Object o) {
22562257
if (o instanceof Secret || Secret.BLANK_NONSECRET_PASSWORD_FIELDS_WITHOUT_ITEM_CONFIGURE) {
22572258
if (req != null) {
22582259
if (NON_RECURSIVE_PASSWORD_MASKING_PERMISSION_CHECK) {
2260+
List<Ancestor> ancestors = req.getAncestors();
2261+
String closestAncestor = ancestors.isEmpty() ? "unknown" :
2262+
ancestors.getLast().getObject().getClass().getName();
2263+
22592264
Item item = req.findAncestorObject(Item.class);
22602265
if (item != null && !item.hasPermission(Item.CONFIGURE)) {
2266+
PasswordMasking.recordMasking(
2267+
item.getClass().getName(),
2268+
closestAncestor,
2269+
getJellyViewsInformationForCurrentRequest()
2270+
);
22612271
return "********";
22622272
}
22632273
Computer computer = req.findAncestorObject(Computer.class);
22642274
if (computer != null && !computer.hasPermission(Computer.CONFIGURE)) {
2275+
PasswordMasking.recordMasking(
2276+
computer.getClass().getName(),
2277+
closestAncestor,
2278+
getJellyViewsInformationForCurrentRequest()
2279+
);
22652280
return "********";
22662281
}
22672282
} else {
22682283
List<Ancestor> ancestors = req.getAncestors();
2284+
String closestAncestor = ancestors.isEmpty() ? "unknown" :
2285+
ancestors.getLast().getObject().getClass().getName();
2286+
22692287
for (Ancestor ancestor : Iterators.reverse(ancestors)) {
22702288
Object type = ancestor.getObject();
22712289
if (type instanceof Item item) {
22722290
if (!item.hasPermission(Item.CONFIGURE)) {
2291+
PasswordMasking.recordMasking(
2292+
item.getClass().getName(),
2293+
closestAncestor,
2294+
getJellyViewsInformationForCurrentRequest()
2295+
);
22732296
return "********";
22742297
}
22752298
break;
22762299
}
22772300
if (type instanceof Computer computer) {
22782301
if (!computer.hasPermission(Computer.CONFIGURE)) {
2302+
PasswordMasking.recordMasking(
2303+
computer.getClass().getName(),
2304+
closestAncestor,
2305+
getJellyViewsInformationForCurrentRequest()
2306+
);
22792307
return "********";
22802308
}
22812309
break;
22822310
}
22832311
if (type instanceof View view) {
22842312
if (!view.hasPermission(View.CONFIGURE)) {
2313+
PasswordMasking.recordMasking(
2314+
view.getClass().getName(),
2315+
closestAncestor,
2316+
getJellyViewsInformationForCurrentRequest()
2317+
);
22852318
return "********";
22862319
}
22872320
break;
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2026, CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package jenkins.telemetry.impl;
26+
27+
import edu.umd.cs.findbugs.annotations.NonNull;
28+
import hudson.Extension;
29+
import java.time.LocalDate;
30+
import java.util.Map;
31+
import java.util.concurrent.ConcurrentHashMap;
32+
import java.util.concurrent.atomic.AtomicLong;
33+
import jenkins.telemetry.Telemetry;
34+
import net.sf.json.JSONArray;
35+
import net.sf.json.JSONObject;
36+
import org.kohsuke.accmod.Restricted;
37+
import org.kohsuke.accmod.restrictions.NoExternalUse;
38+
39+
/**
40+
* Telemetry implementation gathering information about password field masking.
41+
*/
42+
@Extension
43+
@Restricted(NoExternalUse.class)
44+
public class PasswordMasking extends Telemetry {
45+
46+
private static final Map<String, AtomicLong> maskingCounts = new ConcurrentHashMap<>();
47+
48+
/**
49+
* Records when password masking occurs.
50+
*
51+
* @param className the class name of the object
52+
* @param closestAncestor the closest ancestor class name
53+
* @param jellyView the Jelly view where masking occurred
54+
*/
55+
public static void recordMasking(String className, String closestAncestor, String jellyView) {
56+
if (Telemetry.isDisabled()) {
57+
return;
58+
}
59+
60+
String key = className + "|" + closestAncestor + "|" + jellyView;
61+
maskingCounts.computeIfAbsent(key, k -> new AtomicLong(0)).incrementAndGet();
62+
}
63+
64+
@NonNull
65+
@Override
66+
public String getDisplayName() {
67+
return "Password field masking";
68+
}
69+
70+
@NonNull
71+
@Override
72+
public LocalDate getStart() {
73+
return LocalDate.of(2026, 1, 26);
74+
}
75+
76+
@NonNull
77+
@Override
78+
public LocalDate getEnd() {
79+
return LocalDate.of(2026, 7, 26);
80+
}
81+
82+
@Override
83+
public JSONObject createContent() {
84+
JSONArray events = new JSONArray();
85+
for (Map.Entry<String, AtomicLong> entry : maskingCounts.entrySet()) {
86+
String[] parts = entry.getKey().split("\\|", 3);
87+
if (parts.length == 3) {
88+
String className = parts[0];
89+
String closestAncestor = parts[1];
90+
String jellyView = parts[2];
91+
long count = entry.getValue().longValue();
92+
93+
JSONObject event = new JSONObject();
94+
event.put("className", className);
95+
event.put("closestAncestor", closestAncestor);
96+
event.put("jellyView", jellyView);
97+
event.put("count", count);
98+
events.add(event);
99+
}
100+
}
101+
102+
maskingCounts.clear();
103+
104+
JSONObject payload = new JSONObject();
105+
payload.put("components", buildComponentInformation());
106+
payload.put("masking", events);
107+
108+
return payload;
109+
}
110+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!--
2+
The MIT License
3+
4+
Copyright (c) 2026 Somil Jain
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
THE SOFTWARE.
23+
-->
24+
25+
<?jelly escape-by-default='true'?>
26+
<j:jelly xmlns:j="jelly:core"
27+
xmlns:st="jelly:stapler">
28+
<st:redirect url="thirdPartyLicenses"/>
29+
</j:jelly>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?jelly escape-by-default='true'?>
2+
<j:jelly xmlns:j="jelly:core">
3+
This trial collects detailed information about password field masking when users lack Configure permission on Items, Computers, or Views:
4+
<ul>
5+
<li>The class name</li>
6+
<li>The closest ancestor class name</li>
7+
<li>The Jelly view hierarchy where masking occurred</li>
8+
<li>The frequency of each combination</li>
9+
</ul>
10+
<p>
11+
This masking behavior is a fallback that should normally be rare, as views rendering content for users without Configure permission should use read-only mode instead.
12+
</p>
13+
<p>
14+
Additionally, this trial collects the list of installed plugins, their version, and the version of Jenkins.
15+
</p>
16+
</j:jelly>

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@
2929
"@eslint/js": "9.39.2",
3030
"babel-loader": "10.0.0",
3131
"clean-webpack-plugin": "4.0.0",
32-
"css-loader": "7.1.2",
32+
"css-loader": "7.1.3",
3333
"css-minimizer-webpack-plugin": "7.0.4",
3434
"eslint": "9.39.2",
3535
"eslint-config-prettier": "10.1.8",
3636
"eslint-formatter-checkstyle": "9.0.1",
37-
"globals": "17.1.0",
37+
"globals": "17.2.0",
3838
"handlebars-loader": "1.7.3",
3939
"mini-css-extract-plugin": "2.10.0",
4040
"postcss": "8.5.6",

yarn.lock

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3237,18 +3237,18 @@ __metadata:
32373237
languageName: node
32383238
linkType: hard
32393239

3240-
"css-loader@npm:7.1.2":
3241-
version: 7.1.2
3242-
resolution: "css-loader@npm:7.1.2"
3240+
"css-loader@npm:7.1.3":
3241+
version: 7.1.3
3242+
resolution: "css-loader@npm:7.1.3"
32433243
dependencies:
32443244
icss-utils: "npm:^5.1.0"
3245-
postcss: "npm:^8.4.33"
3245+
postcss: "npm:^8.4.40"
32463246
postcss-modules-extract-imports: "npm:^3.1.0"
32473247
postcss-modules-local-by-default: "npm:^4.0.5"
32483248
postcss-modules-scope: "npm:^3.2.0"
32493249
postcss-modules-values: "npm:^4.0.0"
32503250
postcss-value-parser: "npm:^4.2.0"
3251-
semver: "npm:^7.5.4"
3251+
semver: "npm:^7.6.3"
32523252
peerDependencies:
32533253
"@rspack/core": 0.x || 1.x
32543254
webpack: ^5.27.0
@@ -3257,7 +3257,7 @@ __metadata:
32573257
optional: true
32583258
webpack:
32593259
optional: true
3260-
checksum: 10c0/edec9ed71e3c416c9c6ad41c138834c94baf7629de3b97a3337ae8cec4a45e05c57bdb7c4b4d267229fc04b8970d0d1c0734ded8dcd0ac8c7c286b36facdbbf0
3260+
checksum: 10c0/8743a8f1a0beff371d0fbeae7dbae7b079e546c734e3c936f0c5dc4603a716a576200e9261c934565584cf0e090cca444efb613efa4282ee3fcab81db4c73c7e
32613261
languageName: node
32623262
linkType: hard
32633263

@@ -4115,10 +4115,10 @@ __metadata:
41154115
languageName: node
41164116
linkType: hard
41174117

4118-
"globals@npm:17.1.0":
4119-
version: 17.1.0
4120-
resolution: "globals@npm:17.1.0"
4121-
checksum: 10c0/4a4a17847676a09f164b8bdce7df105a4f484d6d44586e374087ba9ec7089cbf4c578b8648838dee9917074199c542ce157ea3c07b266f708dfb1652010900c8
4118+
"globals@npm:17.2.0":
4119+
version: 17.2.0
4120+
resolution: "globals@npm:17.2.0"
4121+
checksum: 10c0/fd25a98b08c1d31082337cbbb5ff1b90b9980e1db69a8621fb942bdb2cdca8aa54d61466742e8bf4910578ea5fa903b8bf83bffd1b2d45551cc614830dba910b
41224122
languageName: node
41234123
linkType: hard
41244124

@@ -4545,12 +4545,12 @@ __metadata:
45454545
"@eslint/js": "npm:9.39.2"
45464546
babel-loader: "npm:10.0.0"
45474547
clean-webpack-plugin: "npm:4.0.0"
4548-
css-loader: "npm:7.1.2"
4548+
css-loader: "npm:7.1.3"
45494549
css-minimizer-webpack-plugin: "npm:7.0.4"
45504550
eslint: "npm:9.39.2"
45514551
eslint-config-prettier: "npm:10.1.8"
45524552
eslint-formatter-checkstyle: "npm:9.0.1"
4553-
globals: "npm:17.1.0"
4553+
globals: "npm:17.2.0"
45544554
handlebars: "npm:4.7.8"
45554555
handlebars-loader: "npm:1.7.3"
45564556
hotkeys-js: "npm:3.12.2"
@@ -6245,7 +6245,7 @@ __metadata:
62456245
languageName: node
62466246
linkType: hard
62476247

6248-
"postcss@npm:8.5.6, postcss@npm:^8.4.33, postcss@npm:^8.4.40, postcss@npm:^8.5.6":
6248+
"postcss@npm:8.5.6, postcss@npm:^8.4.40, postcss@npm:^8.5.6":
62496249
version: 8.5.6
62506250
resolution: "postcss@npm:8.5.6"
62516251
dependencies:
@@ -6582,12 +6582,12 @@ __metadata:
65826582
languageName: node
65836583
linkType: hard
65846584

6585-
"semver@npm:^7.3.5, semver@npm:^7.5.4, semver@npm:^7.6.2":
6586-
version: 7.7.2
6587-
resolution: "semver@npm:7.7.2"
6585+
"semver@npm:^7.3.5, semver@npm:^7.6.2, semver@npm:^7.6.3":
6586+
version: 7.7.3
6587+
resolution: "semver@npm:7.7.3"
65886588
bin:
65896589
semver: bin/semver.js
6590-
checksum: 10c0/aca305edfbf2383c22571cb7714f48cadc7ac95371b4b52362fb8eeffdfbc0de0669368b82b2b15978f8848f01d7114da65697e56cd8c37b0dab8c58e543f9ea
6590+
checksum: 10c0/4afe5c986567db82f44c8c6faef8fe9df2a9b1d98098fc1721f57c696c4c21cebd572f297fc21002f81889492345b8470473bc6f4aff5fb032a6ea59ea2bc45e
65916591
languageName: node
65926592
linkType: hard
65936593

0 commit comments

Comments
 (0)