make some part of api security checklist happy: output

Continue to meet some requirement in the api security checklist: output with loopback/express.

Some recommendations:

  • Send X-Content-Type-Options: nosniff header.
  • Send X-Frame-Options: deny header.
  • Send Content-Security-Policy: default-src ‘none’ header.
  • Remove fingerprinting headers – X-Powered-By, Server, X-AspNet-Version etc.

In nodejs territory, with loopback/express area, we can control the header with helmet.

What’s helmetjs doing? It controls https header which help us meet some requirements above.

Lets do it with this loopback middleware setting.

{
  "initial": {
  ...
    "helmet-csp": {
      "params": {
        "directives": {
          "defaultSrc": [
            "\"none\""
          ]
        }
      }
    },
    "helmet#xssFilter": {},
    "helmet#frameguard": {
      "params": {
        "action": "deny"
      }
    },
    "helmet#hsts": {
      "params": {
        "maxAge": 0,
        "includeSubdomains": true
      }
    },
    "helmet#hidePoweredBy": {},
    "helmet#ieNoOpen": {},
    "helmet#noSniff": {},
    "helmet#noCache": {
      "enabled": false
    },
  ...
  }

For Content-Security-Policy setting, helmet dont enable it by default, so I will need to install another package helmet-csp, which explains why the middleware setting has item helmet-csp instead of helmet#... like the others.

Cool, lets double check it. You can use curl to verify, but I love everything to be automatic. Lets do it with mocha and suppertest.

const request = require('supertest')
const app = require('../../server/server')
const faker = require('faker')
const expect = require('chai').expect

function req (verb, url) {
  return request(app)[verb](url)
    .set('Content-Type', 'application/json')
    .set('Accept', 'application/json')
    .expect('Content-Type', /json/)
}

describe('HTTP header', () => {
  describe('should send login request, and returned headers', () => {
    let headers
    before(done => {
      req('post', '/api/users/login')
        .send({ email: faker.internet.email(), password: faker.internet.password() })
        .expect(401, function (err, res) {
          expect(err).to.be.null
          headers = res.header
          done()
        })
    })
    it('Content-Security-Policy: default-src none header', done => {
      expect(headers['content-security-policy']).to.be.eq('default-src "none"')
      done()
    })
    it('X-Content-Type-Options: nosniff', done => {
      expect(headers['x-content-type-options']).to.be.eq('nosniff')
      done()
    })
    it('X-Frame-Options: deny', done => {
      expect(headers['x-frame-options']).to.be.eq('DENY')
      done()
    })
    it('remove header X-Powered-By, Server', done => {
      expect(headers['x-powered-by']).to.be.null
      expect(headers['server']).to.be.null
      done()
    })
  })
})

Actually the middleware.json setting doing more than the API-Security-Checklist list on http header output. Read more at Loopback block on security practice