Skip to content

Commit

Permalink
feat: add support for Reflected XSS
Browse files Browse the repository at this point in the history
  • Loading branch information
thetutlage committed Oct 26, 2019
1 parent 4e8a997 commit 0c7e5ab
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 6 deletions.
5 changes: 3 additions & 2 deletions adonis-typings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ declare module '@ioc:Adonis/Addons/Shield' {
// X-XSS-Protection
export type XSSOptions = {
enabled: boolean,
enableOnOldIE: boolean,
reportUri: string,
enableOnOldIE?: boolean,
reportUri?: string,
mode?: 'block' | null,
}

// X-Download-Options
Expand Down
4 changes: 2 additions & 2 deletions src/hsts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import ms from 'ms'
import { HstsOptions } from '@ioc:Adonis/Addons/Shield'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { noop } from './noop'

const DEFAULT_MAX_AGE = 180 * 24 * 60 * 60

Expand All @@ -37,8 +38,7 @@ function normalizeMaxAge (maxAge?: string | number): number {
*/
export function hsts (options: HstsOptions) {
if (!options.enabled) {
return function hstsMiddlewareFn (_ctx: HttpContextContract) {
}
return noop
}

const maxAge = normalizeMaxAge(options.maxAge)
Expand Down
4 changes: 2 additions & 2 deletions src/noSniff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@

import { ContentTypeSniffingOptions } from '@ioc:Adonis/Addons/Shield'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { noop } from './noop'

/**
* Adds `X-Content-Type-Options` header based upon given
* user options
*/
export function noSniff (options: ContentTypeSniffingOptions) {
if (!options.enabled) {
return function noSniffMiddlewareFn (_ctx: HttpContextContract) {
}
return noop
}

return function noSniffMiddlewareFn ({ response }: HttpContextContract) {
Expand Down
12 changes: 12 additions & 0 deletions src/noop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* @adonisjs/shield
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export function noop (_ctx: HttpContextContract) {
}
70 changes: 70 additions & 0 deletions src/xssProtection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* @adonisjs/shield
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/// <reference path="../adonis-typings/index.ts" />

import { XSSOptions } from '@ioc:Adonis/Addons/Shield'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { noop } from './noop'

/**
* A boolean to know if user agent is of IE8
*/
function isOldIE (userAgent?: string) {
if (!userAgent) {
return false
}

const match = /msie\s*(\d{1,2})/i.exec(userAgent)
return match ? parseFloat(match[1]) < 9 : false
}

/**
* Adds `X-Content-Type-Options` header based upon given
* user options
*/
export function xssProtection (options: XSSOptions) {
if (!options.enabled) {
return noop
}

let isBlock = true
if (options.mode === null) {
isBlock = false
}

let value = '1'
if (isBlock) {
value += '; mode=block'
}

if (options.reportUri) {
value += `; report=${options.reportUri}`
}

/**
* Returned when `X-XSS-Protection` is enabled on all browser
*/
if (options.enableOnOldIE) {
return function xssProtectionMiddlewareFn ({ response }: HttpContextContract) {
response.header('X-XSS-Protection', value)
}
}

/**
* Returned when disabled for IE < 9 needs
*/
return function xssProtectionMiddlewareFn ({ request, response }: HttpContextContract) {
if (isOldIE(request.header('user-agent'))) {
response.header('X-XSS-Protection', '0')
} else {
response.header('X-XSS-Protection', value)
}
}
}
46 changes: 46 additions & 0 deletions test/xss-protections.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* @adonisjs/shield
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import test from 'japa'
import { HttpContext } from '@adonisjs/http-server/build/standalone'
import { xssProtection } from '../src/xssProtection'

test.group('Xss Protection', () => {
test('return noop function when enabled is false', (assert) => {
const middlewareFn = xssProtection({ enabled: false })
const ctx = HttpContext.create('/', {}, {}, {}, {})
middlewareFn(ctx)

assert.isUndefined(ctx.response.getHeader('X-XSS-Protection'))
})

test('set X-XSS-Protection header', (assert) => {
const middlewareFn = xssProtection({ enabled: true })
const ctx = HttpContext.create('/', {}, {}, {}, {})
middlewareFn(ctx)

assert.equal(ctx.response.getHeader('X-XSS-Protection'), '1; mode=block')
})

test('disable block mode', (assert) => {
const middlewareFn = xssProtection({ enabled: true, mode: null })
const ctx = HttpContext.create('/', {}, {}, {}, {})
middlewareFn(ctx)

assert.equal(ctx.response.getHeader('X-XSS-Protection'), '1')
})

test('set report uri', (assert) => {
const middlewareFn = xssProtection({ enabled: true, reportUri: '/' })
const ctx = HttpContext.create('/', {}, {}, {}, {})
middlewareFn(ctx)

assert.equal(ctx.response.getHeader('X-XSS-Protection'), '1; mode=block; report=/')
})
})

0 comments on commit 0c7e5ab

Please sign in to comment.