Skip to content

Commit b641f88

Browse files
authored
Fix CspBuilder not applying fallback to uninitialized directive (#23855)
* Fix CspBuilder not applying fallback to uninitialized directive * Improve test further --------- Co-authored-by: Daniel Beck <daniel-beck@users.noreply.github.com>
1 parent 72d084c commit b641f88

File tree

2 files changed

+35
-26
lines changed

2 files changed

+35
-26
lines changed

core/src/main/java/jenkins/security/csp/CspBuilder.java

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -185,43 +185,37 @@ public CspBuilder initialize(FetchDirective fetchDirective, String... values) {
185185
public List<Directive> getMergedDirectives() {
186186
List<Directive> result = new ArrayList<>();
187187
for (Map.Entry<String, Set<String>> entry : directives.entrySet()) {
188-
Set<String> effectiveValues = new HashSet<>();
189188
final String name = entry.getKey();
190189

191-
// Calculate inherited values from fallback chain
192190
if (FetchDirective.isFetchDirective(name)) {
191+
// Calculate inherited values from fallback chain
193192
FetchDirective current = FetchDirective.fromKey(name);
194-
195-
// Check if this directive was initialized
196-
boolean wasInitialized = initializedFDs.contains(current);
197-
198-
// If NOT initialized, traverse fallback chain
199-
if (!wasInitialized) {
200-
FetchDirective fallback = current.getFallback();
201-
while (fallback != null && !initializedFDs.contains(fallback)) {
202-
fallback = fallback.getFallback();
203-
}
204-
if (fallback == null) {
205-
// This could happen if nothing was initialized, in that case, fallback is default-src
206-
fallback = FetchDirective.DEFAULT_SRC;
207-
}
208-
// If we found an initialized fallback, inherit its values
209-
if (directives.containsKey(fallback.toKey())) {
210-
effectiveValues.addAll(directives.get(fallback.toKey()));
211-
}
212-
}
213-
214-
effectiveValues.addAll(entry.getValue());
215-
result.add(new Directive(name, !wasInitialized, List.copyOf(effectiveValues)));
193+
result.add(new Directive(name, !initializedFDs.contains(current), List.copyOf(getEffectiveValues(current))));
216194
} else {
217195
// Non-fetch directives don't inherit
218-
effectiveValues.addAll(entry.getValue());
219-
result.add(new Directive(name, null, List.copyOf(effectiveValues)));
196+
result.add(new Directive(name, null, List.copyOf(entry.getValue())));
220197
}
221198
}
222199
return List.copyOf(result);
223200
}
224201

202+
private Set<String> getEffectiveValues(FetchDirective fetchDirective) {
203+
if (fetchDirective == null) {
204+
return Set.of();
205+
}
206+
207+
final Set<String> currentDirectiveValues = directives.getOrDefault(fetchDirective.toKey(), Set.of());
208+
209+
if (initializedFDs.contains(fetchDirective)) {
210+
return new HashSet<>(currentDirectiveValues);
211+
}
212+
213+
Set<String> effectiveValues = new HashSet<>();
214+
effectiveValues.addAll(getEffectiveValues(fetchDirective.getFallback()));
215+
effectiveValues.addAll(currentDirectiveValues);
216+
return effectiveValues;
217+
}
218+
225219
/**
226220
* Build the final CSP string. Any directives with no values left will have the 'none' value set.
227221
*

core/src/test/java/jenkins/security/csp/CspBuilderTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,21 @@ void testNullValueAddition() {
429429
assertThat(builder.build(), is("img-src example.org;"));
430430
}
431431

432+
@Test
433+
void testLongChain() {
434+
CspBuilder builder = new CspBuilder();
435+
builder.initialize(FetchDirective.DEFAULT_SRC, Directive.SELF);
436+
builder.add(Directive.SCRIPT_SRC, "example.org");
437+
builder.add(Directive.SCRIPT_SRC_ELEM, "example.com");
438+
assertThat(builder.build(), is("default-src 'self'; script-src 'self' example.org; script-src-elem 'self' example.com example.org;"));
439+
440+
builder.initialize(FetchDirective.SCRIPT_SRC);
441+
assertThat(builder.build(), is("default-src 'self'; script-src example.org; script-src-elem example.com example.org;"));
442+
443+
builder.initialize(FetchDirective.SCRIPT_SRC_ELEM);
444+
assertThat(builder.build(), is("default-src 'self'; script-src example.org; script-src-elem example.com;"));
445+
}
446+
432447
private static Matcher<LogRecord> logMessageContainsString(String needle) {
433448
return new LogMessageContainsString(containsString(needle));
434449
}

0 commit comments

Comments
 (0)