diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts index ada3bf1b8f..d4fb0d2dc2 100644 --- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts +++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts @@ -287,10 +287,11 @@ describe('AwsS3Publish', () => { mockFs({ [entityRootDir]: { html: { - 'file.html': '', + 'unsafe.html': '', }, img: { 'with spaces.png': 'found it', + 'unsafe.svg': '', }, 'some folder': { 'also with spaces.js': 'found it too', @@ -328,11 +329,19 @@ describe('AwsS3Publish', () => { metadata: { namespace, name }, } = entity; - const response = await request(app).get( - `/${namespace}/${kind}/${name}/html/file.html`, + const htmlResponse = await request(app).get( + `/${namespace}/${kind}/${name}/html/unsafe.html`, ); - expect(response.text).toEqual(''); - expect(response.header).toMatchObject({ + expect(htmlResponse.text).toEqual(''); + expect(htmlResponse.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + + const svgResponse = await request(app).get( + `/${namespace}/${kind}/${name}/img/unsafe.svg`, + ); + expect(svgResponse.text).toEqual(''); + expect(svgResponse.header).toMatchObject({ 'content-type': 'text/plain; charset=utf-8', }); }); diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index 9ddf640abf..0bf720ae31 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -344,10 +344,11 @@ describe('publishing with valid credentials', () => { mockFs({ [entityRootDir]: { html: { - 'file.html': '', + 'unsafe.html': '', }, img: { 'with spaces.png': 'found it', + 'unsafe.svg': '', }, 'some folder': { 'also with spaces.js': 'found it too', @@ -385,11 +386,19 @@ describe('publishing with valid credentials', () => { metadata: { namespace, name }, } = entity; - const response = await request(app).get( - `/${namespace}/${kind}/${name}/html/file.html`, + const htmlResponse = await request(app).get( + `/${namespace}/${kind}/${name}/html/unsafe.html`, ); - expect(response.text).toEqual(''); - expect(response.header).toMatchObject({ + expect(htmlResponse.text).toEqual(''); + expect(htmlResponse.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + + const svgResponse = await request(app).get( + `/${namespace}/${kind}/${name}/img/unsafe.svg`, + ); + expect(svgResponse.text).toEqual(''); + expect(svgResponse.header).toMatchObject({ 'content-type': 'text/plain; charset=utf-8', }); }); diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts index 23110ebcf4..4db1d83c7e 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts @@ -286,10 +286,11 @@ describe('GoogleGCSPublish', () => { mockFs({ [entityRootDir]: { html: { - 'file.html': '', + 'unsafe.html': '', }, img: { 'with spaces.png': 'found it', + 'unsafe.svg': '', }, 'some folder': { 'also with spaces.js': 'found it too', @@ -318,6 +319,7 @@ describe('GoogleGCSPublish', () => { const jsResponse = await request(app).get( `/${namespace}/${kind}/${name}/some%20folder/also%20with%20spaces.js`, ); + expect(jsResponse.text).toEqual('found it too'); }); it('should pass text/plain content-type for html', async () => { @@ -326,11 +328,19 @@ describe('GoogleGCSPublish', () => { metadata: { namespace, name }, } = entity; - const response = await request(app).get( - `/${namespace}/${kind}/${name}/html/file.html`, + const htmlResponse = await request(app).get( + `/${namespace}/${kind}/${name}/html/unsafe.html`, ); - expect(response.text).toEqual(''); - expect(response.header).toMatchObject({ + expect(htmlResponse.text).toEqual(''); + expect(htmlResponse.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + + const svgResponse = await request(app).get( + `/${namespace}/${kind}/${name}/img/unsafe.svg`, + ); + expect(svgResponse.text).toEqual(''); + expect(svgResponse.header).toMatchObject({ 'content-type': 'text/plain; charset=utf-8', }); }); diff --git a/packages/techdocs-common/src/stages/publish/helpers.test.ts b/packages/techdocs-common/src/stages/publish/helpers.test.ts index ae872be016..d5af98e7e1 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.test.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.test.ts @@ -25,7 +25,7 @@ describe('getHeadersForFileExtension', () => { ['.png', 'image/png'], ['.jpg', 'image/jpeg'], ['.jpeg', 'image/jpeg'], - ['.svg', 'image/svg+xml'], + ['.svg', 'text/plain; charset=utf-8'], ['.json', 'application/json; charset=utf-8'], ['.this-in-not-an-extension', 'text/plain; charset=utf-8'], ]; diff --git a/packages/techdocs-common/src/stages/publish/helpers.ts b/packages/techdocs-common/src/stages/publish/helpers.ts index 63568699c7..3ca424055f 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.ts @@ -34,8 +34,8 @@ export const getHeadersForFileExtension = ( }; // Prevent sanitization bypass by preventing browers from directly rendering - // the contents of HTML files kept in storage. - if (headerType['Content-Type'].match(/html/)) { + // the contents of untrusted content. + if (headerType['Content-Type'].match(/html|xml/)) { headerType['Content-Type'] = 'text/plain; charset=utf-8'; } diff --git a/packages/techdocs-common/src/stages/publish/local.test.ts b/packages/techdocs-common/src/stages/publish/local.test.ts index a5dca5f057..114401f1db 100644 --- a/packages/techdocs-common/src/stages/publish/local.test.ts +++ b/packages/techdocs-common/src/stages/publish/local.test.ts @@ -84,7 +84,8 @@ describe('local publisher', () => { mockFs.restore(); mockFs({ [resolvedDir]: { - 'some-file.html': 'found it', + 'unsafe.html': '', + 'unsafe.svg': '', }, }); }); @@ -93,10 +94,16 @@ describe('local publisher', () => { mockFs.restore(); }); - it('should pass text/plain content-type for html', async () => { - const response = await request(app).get(`/some-file.html`); - expect(response.text).toEqual('found it'); - expect(response.header).toMatchObject({ + it('should pass text/plain content-type for unsafe types', async () => { + const htmlResponse = await request(app).get(`/unsafe.html`); + expect(htmlResponse.text).toEqual(''); + expect(htmlResponse.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + + const svgResponse = await request(app).get(`/unsafe.svg`); + expect(svgResponse.text).toEqual(''); + expect(svgResponse.header).toMatchObject({ 'content-type': 'text/plain; charset=utf-8', }); }); diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts index 35e5586ee4..dfac7fb0f7 100644 --- a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts +++ b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts @@ -292,9 +292,10 @@ describe('OpenStackSwiftPublish', () => { mockFs({ [entityRootDir]: { html: { - 'file.html': '', + 'unsafe.html': '', }, img: { + 'unsafe.svg': '', 'with spaces.png': 'found it', }, 'some folder': { @@ -327,17 +328,25 @@ describe('OpenStackSwiftPublish', () => { expect(jsResponse.text).toEqual('found it too'); }); - it('should pass text/plain content-type for html', async () => { + it('should pass text/plain content-type for unsafe types', async () => { const { kind, metadata: { namespace, name }, } = entity; - const response = await request(app).get( - `/${namespace}/${kind}/${name}/html/file.html`, + const htmlResponse = await request(app).get( + `/${namespace}/${kind}/${name}/html/unsafe.html`, ); - expect(response.text).toEqual(''); - expect(response.header).toMatchObject({ + expect(htmlResponse.text).toEqual(''); + expect(htmlResponse.header).toMatchObject({ + 'content-type': 'text/plain; charset=utf-8', + }); + + const svgResponse = await request(app).get( + `/${namespace}/${kind}/${name}/img/unsafe.svg`, + ); + expect(svgResponse.text).toEqual(''); + expect(svgResponse.header).toMatchObject({ 'content-type': 'text/plain; charset=utf-8', }); });