Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Selective CSP header stripping from HTTPResponse #26483

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
de633a1
feat: Selective CSP header directive stripping from HTTPResponse
pgoforth Apr 11, 2023
b66f778
feat: Selective CSP header directive permission from HTTPResponse
pgoforth May 23, 2023
517cf6c
Address Review Comments:
pgoforth May 24, 2023
52f78a7
chore: refactor driver test into system tests to get better test
AtofStryker May 24, 2023
f2b5bc1
Address Review Comments:
pgoforth May 31, 2023
85cfaff
chore: Add `frame-src` and `child-src` to conditional CSP directives
pgoforth Jun 2, 2023
2886cd9
chore: Rename `isSubsetOf` to `isArrayIncludingAny`
pgoforth Jun 2, 2023
4a19a59
chore: fix CLI linting types
AtofStryker Jun 5, 2023
ebbe44c
chore: fix server unit tests
AtofStryker Jun 5, 2023
952d092
chore: fix system tests within firefox and webkit
AtofStryker Jun 5, 2023
d2057be
chore: add form-action test
AtofStryker Jun 5, 2023
361a9eb
chore: update system test snapshots
AtofStryker Jun 5, 2023
bfd150d
chore: skip tests in webkit due to form-action flakiness
AtofStryker Jun 5, 2023
e1142ec
chore: Move 'sandbox' and 'navigate-to' into `unsupportedCSPDirectives`
pgoforth Jun 5, 2023
c240e41
chore: update system test snapshots
AtofStryker Jun 6, 2023
afa19c1
chore: fix system tests
AtofStryker Jun 6, 2023
7219304
chore: do not run csp tests within firefox or webkit due to flake iss…
AtofStryker Jun 6, 2023
d3c9a9a
chore: attempt to increase intercept delay to avoid race condition
AtofStryker Jun 7, 2023
c599a2b
Merge branch 'develop' of github.com:cypress-io/cypress into issue-10…
AtofStryker Jun 7, 2023
16310ff
Merge branch 'develop' into issue-1030/pgoforth/load-site-witout-csp-…
AtofStryker Jun 8, 2023
980b4f7
Merge branch 'develop' of github.com:cypress-io/cypress into issue-10…
AtofStryker Jun 8, 2023
41945cd
chore: update new snapshots with video defaults work
AtofStryker Jun 8, 2023
27b6a7c
chore: update changelog
AtofStryker Jun 8, 2023
c91a96a
Merge branch 'develop' into issue-1030/pgoforth/load-site-witout-csp-…
mschile Jun 13, 2023
76b20db
Merge branch 'develop' into issue-1030/pgoforth/load-site-witout-csp-…
AtofStryker Jun 13, 2023
2796e4e
Merge branch 'develop' into issue-1030/pgoforth/load-site-witout-csp-…
AtofStryker Jun 14, 2023
161a603
Merge branch 'develop' into issue-1030/pgoforth/load-site-witout-csp-…
AtofStryker Jun 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ _Released 06/20/2023 (PENDING)_
**Features:**

- Added support for running Cypress tests with [Chrome's new `--headless=new` flag](https://developer.chrome.com/articles/new-headless/). Chrome versions 112 and above will now be run in the `headless` mode that matches the `headed` browser implementation. Addresses [#25972](https://github.com/cypress-io/cypress/issues/25972).
- Cypress can now test pages with targeted `Content-Security-Policy` and `Content-Security-Policy-Report-Only` header directives by specifying the allow list via the [`experimentalCspAllowList`](https://docs.cypress.io/guides/references/configuration#Experimental-Csp-Allow-List) configuration option. Addresses [#1030](https://github.com/cypress-io/cypress/issues/1030). Addressed in [#26483](https://github.com/cypress-io/cypress/pull/26483)
- The [`videoCompression`](https://docs.cypress.io/guides/references/configuration#Videos) configuration option now accepts both a boolean or a Constant Rate Factor (CRF) number between `1` and `51`. The `videoCompression` default value is still `32` CRF and when `videoCompression` is set to `true` the default of `32` CRF will be used. Addresses [#26658](https://github.com/cypress-io/cypress/issues/26658).

**Bugfixes:**
Expand Down
15 changes: 15 additions & 0 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2672,6 +2672,8 @@ declare namespace Cypress {
force: boolean
}

type experimentalCspAllowedDirectives = 'default-src' | 'child-src' | 'frame-src' | 'script-src' | 'script-src-elem' | 'form-action'

type scrollBehaviorOptions = false | 'center' | 'top' | 'bottom' | 'nearest'

/**
Expand Down Expand Up @@ -3051,6 +3053,19 @@ declare namespace Cypress {
* @default 'top'
*/
scrollBehavior: scrollBehaviorOptions
/**
* Indicates whether Cypress should allow CSP header directives from the application under test.
* - When this option is set to `false`, Cypress will strip the entire CSP header.
* - When this option is set to `true`, Cypress will only to strip directives that would interfere
* with or inhibit Cypress functionality.
* - When this option to an array of allowable directives (`[ 'default-src', ... ]`), the directives
* specified will remain in the response headers.
*
* Please see the documentation for more information.
* @see https://on.cypress.io/configuration#experimentalCspAllowList
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to myself that we will need to deploy the on links (see release process step 2)for this to work. I will take care of creating the manifest PR for this next week

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mschile since this isn't making it into today's release you may have to handle it for the next release

* @default false
*/
experimentalCspAllowList: boolean | experimentalCspAllowedDirectives[],
/**
* Allows listening to the `before:run`, `after:run`, `before:spec`, and `after:spec` events in the plugins file during interactive mode.
* @default false
Expand Down
1 change: 1 addition & 0 deletions packages/app/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default defineConfig({
reporterOptions: {
configFile: '../../mocha-reporter-config.json',
},
experimentalCspAllowList: false,
experimentalInteractiveRunEvents: true,
component: {
experimentalSingleTabRunMode: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@
"from": "default",
"field": "execTimeout"
},
{
"value": false,
"from": "default",
"field": "experimentalCspAllowList"
},
{
"value": false,
"from": "default",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
"from": "default",
"field": "execTimeout"
},
{
"value": false,
"from": "default",
"field": "experimentalCspAllowList"
},
{
"value": false,
"from": "default",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,11 @@
"from": "default",
"field": "execTimeout"
},
{
"value": false,
"from": "default",
"field": "experimentalCspAllowList"
},
{
"value": false,
"from": "default",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@
"from": "default",
"field": "execTimeout"
},
{
"value": false,
"from": "default",
"field": "experimentalCspAllowList"
},
{
"value": false,
"from": "default",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@
"from": "default",
"field": "execTimeout"
},
{
"value": false,
"from": "default",
"field": "experimentalCspAllowList"
},
{
"value": false,
"from": "default",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1445,6 +1445,11 @@
"from": "default",
"field": "execTimeout"
},
{
"value": false,
"from": "default",
"field": "experimentalCspAllowList"
},
{
"value": false,
"from": "default",
Expand Down
3 changes: 3 additions & 0 deletions packages/config/__snapshots__/index.spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
},
'env': {},
'execTimeout': 60000,
'experimentalCspAllowList': false,
'experimentalFetchPolyfill': false,
'experimentalInteractiveRunEvents': false,
'experimentalRunAllSpecs': false,
Expand Down Expand Up @@ -121,6 +122,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
},
'env': {},
'execTimeout': 60000,
'experimentalCspAllowList': false,
'experimentalFetchPolyfill': false,
'experimentalInteractiveRunEvents': false,
'experimentalRunAllSpecs': false,
Expand Down Expand Up @@ -204,6 +206,7 @@ exports['config/src/index .getPublicConfigKeys returns list of public config key
'e2e',
'env',
'execTimeout',
'experimentalCspAllowList',
'experimentalFetchPolyfill',
'experimentalInteractiveRunEvents',
'experimentalRunAllSpecs',
Expand Down
25 changes: 25 additions & 0 deletions packages/config/__snapshots__/validation.spec.ts.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,31 @@ exports['config/src/validation .isStringOrFalse returns error message when value
'type': 'a string or false',
}

exports['not an array error message'] = {
'key': 'fakeKey',
'value': 'fakeValue',
'type': 'an array including any of these values: [true, false]',
}

exports['not a subset of error message'] = {
'key': 'fakeKey',
'value': [
null,
],
'type': 'an array including any of these values: ["fakeValue", "fakeValue1", "fakeValue2"]',
}

exports['not all in subset error message'] = {
'key': 'fakeKey',
'value': [
'fakeValue',
'fakeValue1',
'fakeValue2',
'fakeValue3',
],
'type': 'an array including any of these values: ["fakeValue", "fakeValue1", "fakeValue2"]',
}

exports['invalid lower bound'] = {
'key': 'test',
'value': -1,
Expand Down
6 changes: 6 additions & 0 deletions packages/config/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@ const driverConfigOptions: Array<DriverConfigOption> = [
defaultValue: 60000,
validation: validate.isNumber,
overrideLevel: 'any',
}, {
name: 'experimentalCspAllowList',
defaultValue: false,
validation: validate.validateAny(validate.isBoolean, validate.isArrayIncludingAny('script-src-elem', 'script-src', 'default-src', 'form-action', 'child-src', 'frame-src')),
overrideLevel: 'never',
requireRestartOnChange: 'server',
}, {
name: 'experimentalFetchPolyfill',
defaultValue: false,
Expand Down
44 changes: 41 additions & 3 deletions packages/config/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,29 @@ const _isFullyQualifiedUrl = (value: any): ErrResult | boolean => {
return _.isString(value) && /^https?\:\/\//.test(value)
}

const isArrayOfStrings = (value: any): ErrResult | boolean => {
const isStringArray = (value: any): ErrResult | boolean => {
return _.isArray(value) && _.every(value, _.isString)
}

const isFalse = (value: any): boolean => {
return value === false
}

type ValidationResult = ErrResult | boolean | string;
type ValidationFn = (key: string, value: any) => ValidationResult

export const validateAny = (...validations: ValidationFn[]): ValidationFn => {
return (key: string, value: any): ValidationResult => {
return validations.reduce((result: ValidationResult, validation: ValidationFn) => {
if (result === true) {
return result
}

return validation(key, value)
}, false)
}
}

/**
* Validates a single browser object.
* @returns {string|true} Returns `true` if the object is matching browser object schema. Returns an error message if it does not.
Expand Down Expand Up @@ -148,6 +163,29 @@ export const isOneOf = (...values: any[]): ((key: string, value: any) => ErrResu
}
}

/**
* Checks if given array value for a key includes only members of the provided values.
* @example
```
validate = v.isArrayIncludingAny("foo", "bar", "baz")
validate("example", ["foo"]) // true
validate("example", ["bar", "baz"]) // true
validate("example", ["foo", "else"]) // error message string
validate("example", ["foo", "bar", "baz", "else"]) // error message string
```
*/
export const isArrayIncludingAny = (...values: any[]): ((key: string, value: any) => ErrResult | true) => {
const validValues = values.map((a) => str(a)).join(', ')

return (key, value) => {
if (!Array.isArray(value) || !value.every((v) => values.includes(v))) {
return errMsg(key, value, `an array including any of these values: [${validValues}]`)
}

return true
}
}

/**
* Validates whether the supplied set of cert information is valid
* @returns {string|true} Returns `true` if the information set is valid. Returns an error message if it is not.
Expand Down Expand Up @@ -332,15 +370,15 @@ export function isFullyQualifiedUrl (key: string, value: any): ErrResult | true
}

export function isStringOrArrayOfStrings (key: string, value: any): ErrResult | true {
if (_.isString(value) || isArrayOfStrings(value)) {
if (_.isString(value) || isStringArray(value)) {
return true
}

return errMsg(key, value, 'a string or an array of strings')
}

export function isNullOrArrayOfStrings (key: string, value: any): ErrResult | true {
if (_.isNull(value) || isArrayOfStrings(value)) {
if (_.isNull(value) || isStringArray(value)) {
return true
}

Expand Down
30 changes: 30 additions & 0 deletions packages/config/test/project/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,34 @@ describe('config/src/project/utils', () => {
})
})

it('experimentalCspAllowList=false', function () {
return this.defaults('experimentalCspAllowList', false)
})

it('experimentalCspAllowList=true', function () {
return this.defaults('experimentalCspAllowList', true, {
experimentalCspAllowList: true,
})
})

it('experimentalCspAllowList=[]', function () {
return this.defaults('experimentalCspAllowList', [], {
experimentalCspAllowList: [],
})
})

it('experimentalCspAllowList=default-src|script-src', function () {
return this.defaults('experimentalCspAllowList', ['default-src', 'script-src'], {
experimentalCspAllowList: ['default-src', 'script-src'],
})
})

it('experimentalCspAllowList=["default-src","script-src"]', function () {
return this.defaults('experimentalCspAllowList', ['default-src', 'script-src'], {
experimentalCspAllowList: ['default-src', 'script-src'],
})
})

it('resets numTestsKeptInMemory to 0 when runMode', function () {
return mergeDefaults({ projectRoot: '/foo/bar/', supportFile: false }, { isTextTerminal: true }, {}, this.getFilesByGlob)
.then((cfg) => {
Expand Down Expand Up @@ -1053,6 +1081,7 @@ describe('config/src/project/utils', () => {
execTimeout: { value: 60000, from: 'default' },
experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' },
experimentalSkipDomainInjection: { value: null, from: 'default' },
experimentalCspAllowList: { value: false, from: 'default' },
experimentalFetchPolyfill: { value: false, from: 'default' },
experimentalInteractiveRunEvents: { value: false, from: 'default' },
experimentalMemoryManagement: { value: false, from: 'default' },
Expand Down Expand Up @@ -1150,6 +1179,7 @@ describe('config/src/project/utils', () => {
execTimeout: { value: 60000, from: 'default' },
experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' },
experimentalSkipDomainInjection: { value: null, from: 'default' },
experimentalCspAllowList: { value: false, from: 'default' },
experimentalFetchPolyfill: { value: false, from: 'default' },
experimentalInteractiveRunEvents: { value: false, from: 'default' },
experimentalMemoryManagement: { value: false, from: 'default' },
Expand Down
Loading