Skip to content

Commit 4169f2a

Browse files
Add default-src 'none' to our report-only CSP (#243545)
## Summary Adds `default-src 'none'` to our report-only content security policy. In support of this, the report-only policy was updated to account for all expected `*-src` directives, so that we do not receive false positive reports. Relates: #56996 **Important:** This does not change any of the enforced CSP directives. This only adjusts the "Report" directive, which does not perform any enforcement. --------- Co-authored-by: kibanamachine <[email protected]>
1 parent 3874f40 commit 4169f2a

File tree

4 files changed

+86
-9
lines changed

4 files changed

+86
-9
lines changed

src/core/packages/http/server-internal/src/csp/csp_config.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('CspConfig', () => {
3737
"disableEmbedding": false,
3838
"disableUnsafeEval": true,
3939
"header": "script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'",
40-
"reportOnlyHeader": "form-action 'report-sample' 'self'",
40+
"reportOnlyHeader": "form-action 'report-sample' 'self'; default-src 'report-sample' 'none'; font-src 'report-sample' 'self'; img-src 'report-sample' 'self' data:; connect-src 'report-sample' 'self' telemetry.elastic.co telemetry-staging.elastic.co; script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'",
4141
"strict": true,
4242
"warnLegacyBrowsers": true,
4343
}

src/core/packages/http/server-internal/src/csp/csp_directives.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,19 @@ describe('CspDirectives', () => {
5151
);
5252
});
5353

54+
it('augments report-only directives when testing default-src none', () => {
55+
const config = cspConfig.schema.validate({
56+
img_src: ['img-src-value'],
57+
});
58+
const directives = CspDirectives.fromConfig(config);
59+
expect(directives.getCspHeadersByDisposition()).toMatchInlineSnapshot(`
60+
Object {
61+
"enforceHeader": "script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'; img-src 'self' img-src-value",
62+
"reportOnlyHeader": "form-action 'report-sample' 'self'; default-src 'report-sample' 'none'; font-src 'report-sample' 'self'; img-src 'report-sample' 'self' data: img-src-value; connect-src 'report-sample' 'self' telemetry.elastic.co telemetry-staging.elastic.co; script-src 'report-sample' 'self'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'",
63+
}
64+
`);
65+
});
66+
5467
it('automatically adds single quotes for keywords', () => {
5568
const directives = new CspDirectives();
5669
directives.addDirectiveValue('script-src', 'none');

src/core/packages/http/server-internal/src/csp/csp_directives.ts

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,33 @@ export type CspDirectiveName =
2323
| 'report-uri'
2424
| 'report-to'
2525
| 'form-action'
26-
| 'object-src';
26+
| 'object-src'
27+
| 'child-src'
28+
| 'manifest-src'
29+
| 'media-src'
30+
| 'object-src'
31+
| 'prefetch-src'
32+
| 'script-src-elem'
33+
| 'script-src-attr'
34+
| 'style-src-elem'
35+
| 'style-src-attr';
2736

2837
/**
2938
* The default report only directives rules
3039
*/
3140
export const defaultReportOnlyRules: Partial<Record<CspDirectiveName, string[]>> = {
3241
'form-action': [`'report-sample'`, `'self'`],
42+
'default-src': [`'report-sample'`, `'none'`],
43+
'font-src': [`'report-sample'`, `'self'`],
44+
'img-src': [`'report-sample'`, `'self'`, 'data:'],
45+
'connect-src': [
46+
`'report-sample'`,
47+
`'self'`,
48+
// TODO: Ideally, Core would not know about these endpoints, as they are governed by the Telemetry plugin.
49+
// This can be improved once https://github.com/elastic/kibana/issues/181812 is implemented.
50+
'telemetry.elastic.co',
51+
'telemetry-staging.elastic.co',
52+
],
3353
};
3454

3555
/**
@@ -55,6 +75,29 @@ export const additionalRules: Partial<Record<CspDirectiveName, string[]>> = {
5575
'frame-src': [`'self'`],
5676
};
5777

78+
/**
79+
* Child directives that should inherit from `default-src` if not explicitly set.
80+
* Reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/default-src
81+
*/
82+
export const defaultSrcChildDirectives: CspDirectiveName[] = [
83+
'child-src',
84+
'connect-src',
85+
'font-src',
86+
'frame-src',
87+
'img-src',
88+
'manifest-src',
89+
'media-src',
90+
'object-src',
91+
'prefetch-src',
92+
'script-src',
93+
'script-src-elem',
94+
'script-src-attr',
95+
'style-src',
96+
'style-src-elem',
97+
'style-src-attr',
98+
'worker-src',
99+
];
100+
58101
interface CspConfigDirectives {
59102
enforceDirectives: Map<CspDirectiveName, string[]>;
60103
reportOnlyDirectives: Map<CspDirectiveName, string[]>;
@@ -80,6 +123,14 @@ export class CspDirectives {
80123
directive.delete(`'none'`);
81124
}
82125
directive.add(normalizedDirectiveValue);
126+
127+
// If we are testing default-src 'none', then we need to add all expected child directives to the report-only policy
128+
// to prevent reports from being generated for those child directives.
129+
const enforcingDefaultSrcChildDirective =
130+
enforce && defaultSrcChildDirectives.includes(directiveName);
131+
if (this.isTestingDefaultSrc() && enforcingDefaultSrcChildDirective) {
132+
this.addDirectiveValue(directiveName, directiveValue, false);
133+
}
83134
}
84135

85136
clearDirectiveValues(directiveName: CspDirectiveName) {
@@ -106,6 +157,17 @@ export class CspDirectives {
106157
.join('; ');
107158
}
108159

160+
/**
161+
* Determines if we are currently testing the default-src 'none' configuration.
162+
* @returns True if we are testing default-src 'none', false otherwise.
163+
*/
164+
private isTestingDefaultSrc(): boolean {
165+
return this.reportOnlyDirectives.has('default-src') &&
166+
this.reportOnlyDirectives.get('default-src')?.has(`'none'`)
167+
? true
168+
: false;
169+
}
170+
109171
static fromConfig(
110172
firstConfig: CspConfigType,
111173
...otherConfigs: Array<Partial<CspConfigType>>
@@ -116,17 +178,19 @@ export class CspDirectives {
116178
);
117179
const cspDirectives = new CspDirectives();
118180

119-
// combining `default` directive configurations
120-
Object.entries(defaultRules).forEach(([key, values]) => {
181+
// combining `default` report only directive configurations
182+
// it's important to add these before the enforced directives below so that we can handle report-only updates
183+
// in response to enforced directives (e.g., default-src 'none' testing)
184+
Object.entries(defaultReportOnlyRules).forEach(([key, values]) => {
121185
values?.forEach((value) => {
122-
cspDirectives.addDirectiveValue(key as CspDirectiveName, value);
186+
cspDirectives.addDirectiveValue(key as CspDirectiveName, value, false);
123187
});
124188
});
125189

126-
// combining `default` report only directive configurations
127-
Object.entries(defaultReportOnlyRules).forEach(([key, values]) => {
190+
// combining `default` directive configurations
191+
Object.entries(defaultRules).forEach(([key, values]) => {
128192
values?.forEach((value) => {
129-
cspDirectives.addDirectiveValue(key as CspDirectiveName, value, false);
193+
cspDirectives.addDirectiveValue(key as CspDirectiveName, value);
130194
});
131195
});
132196

src/core/server/integration_tests/http/lifecycle.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1666,7 +1666,7 @@ describe('runs with default preResponse handlers', () => {
16661666
`script-src 'report-sample' 'self' 'unsafe-eval'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'`
16671667
);
16681668
expect(response.header['content-security-policy-report-only']).toBe(
1669-
`form-action 'report-sample' 'self'`
1669+
`form-action 'report-sample' 'self'; default-src 'report-sample' 'none'; font-src 'report-sample' 'self'; img-src 'report-sample' 'self' data:; connect-src 'report-sample' 'self' telemetry.elastic.co telemetry-staging.elastic.co; script-src 'report-sample' 'self' 'unsafe-eval'; worker-src 'report-sample' 'self' blob:; style-src 'report-sample' 'self' 'unsafe-inline'; object-src 'report-sample' 'none'`
16701670
);
16711671
});
16721672
});

0 commit comments

Comments
 (0)