Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

4 changed files with 35 additions and 148 deletions

View File

@ -1,72 +1,28 @@
# Content Security Policy # Sets
# #
# Should yield the following header: # Should yield the follwoiung header:
# "Content-Security-Policy: default-src 'self'; # "Content-Security-Policy: default-src 'self';
# script-src 'self' example.com;object-src 'none'; # script-src 'self' example.com;object-src 'none';
# upgrade-insecure-requests" # upgrade-insecure-requests"
# Note: embedded single quotes are required # Note: embedded single quotes are required
contentSecurityPolicy: default-src: [ "'self'" ]
useDefaults: false base-uri: [ "'self'" ]
directives: font-src:
default-src: ["'self'"] # Allow content only from same origin - "'self'"
base-uri: ["'self'"] # Restrict <base> tag - "https:"
font-src: # Allow font loading from safe sources - "data:"
- "'self'" form-action: [ "'self'" ]
- "https:" frame-ancestors: [ "'self'" ]
- "data:" img-src:
form-action: ["'self'"] # Restrict form submissions - "'self'"
frame-ancestors: ["'self'"] # Prevent clickjacking - "data:"
img-src: # Allow inline and local images object-src: [ "'none'" ]
- "'self'" script-src:
- "data:" - "'self'"
object-src: ["'none'"] # Disable <object> usage - example.com
script-src: # Disallow 3rd party scripts by default script-src-attr: [ "'none'" ]
- "'self'" style-src:
- example.com - "'self'"
script-src-attr: ["'none'"] # Disallow inline script attributes - "https:"
style-src: # Inline styles okay for frameworks - "'unsafe-inline'"
- "'self'" upgrade-insecure-requests: []
- "https:"
- "'unsafe-inline'"
upgrade-insecure-requests: [] # Auto-upgrade HTTP requests
# Enforce embedding policies
crossOriginEmbedderPolicy:
policy: "require-corp" # Required for shared array buffers
crossOriginOpenerPolicy:
policy: "same-origin" # Isolate window/tab from others
crossOriginResourcePolicy:
policy: "same-origin" # Limit loading of cross-origin resources
# Use origin-based isolation for threads
originAgentCluster: true
# Limit what referrer info is sent
referrerPolicy:
policy: "no-referrer"
# Force HTTPS in browsers
strictTransportSecurity:
maxAge: 15552000 # 180 days
includeSubDomains: true
preload: true
# Don't allow content sniffing
xContentTypeOptions: true
# Disable DNS prefetching
dnsPrefetchControl:
allow: false
# Prevent page from being embedded in <iframe>
frameguard:
action: "SAMEORIGIN"
# Block Flash and Acrobat cross-domain access
permittedCrossDomainPolicies:
permittedPolicies: "none"
# Hide the Express server signature
hidePoweredBy: true

View File

@ -4,32 +4,14 @@ const YAML = require('yaml')
const helmet = require('helmet') const helmet = require('helmet')
module.exports = (path) => { module.exports = (path) => {
let csppolicy const csppolicy = fs.readFileSync(path, 'utf8')
const zero = {
contentSecurityPolicy: false,
crossOriginEmbedderPolicy: false,
crossOriginOpenerPolicy: false,
crossOriginResourcePolicy: false,
originAgentCluster: false,
referrerPolicy: false,
strictTransportSecurity: false,
xContentTypeOptions: false,
dnsPrefetchControl: false,
frameguard: false,
permittedCrossDomainPolicies: false,
hidePoweredBy: false,
};
try {
csppolicy = fs.readFileSync(path, 'utf8')
} catch (e) {
csppolicy = 'contentSecurityPolicy:\n useDefaults: true\n';
}
const csp = YAML.parse(csppolicy) const csp = YAML.parse(csppolicy)
// Mandatory return helmet({
csp.xXssProtection = false contentSecurityPolicy: {
csp.xDownloadOptions = false useDefaults: false,
csp.expectCt = false directives: csp,
},
return helmet({...zero,...csp}) xFrameOptions: 'SAMEORIGIN',
})
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "express-csp", "name": "express-csp",
"version": "1.1.0", "version": "1.0.0",
"description": "Rapid configurable Content Security Policy middleware", "description": "Rapid configurable Content Security Policy middleware",
"main": "./index.js", "main": "./index.js",
"exports": { "exports": {

View File

@ -6,40 +6,26 @@ const fs = require('fs')
// Import the middleware factory (don't name this `csp` to avoid shadowing!) // Import the middleware factory (don't name this `csp` to avoid shadowing!)
const createCspMiddleware = require('../index.cjs') const createCspMiddleware = require('../index.cjs')
describe('Content Security Policy middleware', () => { describe('Rapid configurable Content Security Policy middleware', () => {
const validPolicyPath = path.join(__dirname, '../csp-policy.yml') const validPolicyPath = path.join(__dirname, '../csp-policy.yml')
const malformedPolicyPath = path.join(__dirname, 'bad-policy.yml') const malformedPolicyPath = path.join(__dirname, 'bad-policy.yml')
const missingPolicyPath = path.join(__dirname, 'xxxxxx.yml')
const minimalPolicyPath = path.join(__dirname, 'minimal-policy.yml')
const customPolicyPath = path.join(__dirname, 'custom-policy.yml') const customPolicyPath = path.join(__dirname, 'custom-policy.yml')
beforeAll(() => { beforeAll(() => {
// Write a malformed YAML file (missing colon, bad list syntax) // Write a malformed YAML file (missing colon, bad list syntax)
fs.writeFileSync(malformedPolicyPath, `default-src 'self'\nthis-is: [bad yaml]`) fs.writeFileSync(malformedPolicyPath, `default-src 'self'\nthis-is: [bad yaml]`)
// Write a minimal custom policy
fs.writeFileSync(
minimalPolicyPath,
`
contentSecurityPolicy: false
`,
)
// Write a simple custom policy // Write a simple custom policy
fs.writeFileSync( fs.writeFileSync(
customPolicyPath, customPolicyPath,
` `
contentSecurityPolicy: default-src: ["'self'"]
directives: script-src: ["'self'", "https://cdn.example.com"]
default-src: ["'self'"]
script-src: ["'self'", "https://cdn.example.com"]
`, `,
) )
}) })
afterAll(() => { afterAll(() => {
//fs.unlinkSync(missingPolicyPath)
fs.unlinkSync(minimalPolicyPath)
fs.unlinkSync(malformedPolicyPath) fs.unlinkSync(malformedPolicyPath)
fs.unlinkSync(customPolicyPath) fs.unlinkSync(customPolicyPath)
}) })
@ -51,7 +37,6 @@ contentSecurityPolicy:
app.get('/', (req, res) => res.send('Hello World')) app.get('/', (req, res) => res.send('Hello World'))
const res = await request(app).get('/') const res = await request(app).get('/')
console.log(res.headers)
expect(res.headers['content-security-policy']).toBeDefined() expect(res.headers['content-security-policy']).toBeDefined()
expect(res.text).toBe('Hello World') expect(res.text).toBe('Hello World')
}) })
@ -76,40 +61,6 @@ contentSecurityPolicy:
}).toThrow(/Implicit keys|bad indentation|unexpected token/i) }).toThrow(/Implicit keys|bad indentation|unexpected token/i)
}) })
it('should show defaults for missing YAML', async () => {
const app = express()
const csp = createCspMiddleware(missingPolicyPath)
app.use(csp)
app.get('/', (req, res) => res.send('Test Custom'))
const res = await request(app).get('/')
res.headers.TEST = 'Missing'
console.log(res.headers)
const cspHeader = res.headers['content-security-policy']
expect(cspHeader).toMatch(/default-src 'self'/)
})
it('should show nothing for minimal YAML', async () => {
const app = express()
const csp = createCspMiddleware(minimalPolicyPath)
app.use(csp)
app.get('/', (req, res) => res.send('Test Custom'))
const res = await request(app).get('/')
res.headers.TEST = 'Minimal'
console.log(res.headers)
const cspHeader = res.headers['content-security-policy']
const allowed = ['x-powered-by', 'content-type', 'content-length']
Object.keys(res.headers).forEach((key) => {
if (!allowed.includes(key.toLowerCase())) {
expect(key).not.toMatch(/^(content|x)-/i)
}
})
})
it('should apply a custom policy file correctly', async () => { it('should apply a custom policy file correctly', async () => {
const app = express() const app = express()
const csp = createCspMiddleware(customPolicyPath) const csp = createCspMiddleware(customPolicyPath)
@ -117,8 +68,6 @@ contentSecurityPolicy:
app.get('/', (req, res) => res.send('Test Custom')) app.get('/', (req, res) => res.send('Test Custom'))
const res = await request(app).get('/') const res = await request(app).get('/')
res.headers.TEST = 'Custom'
console.log(res.headers)
const cspHeader = res.headers['content-security-policy'] const cspHeader = res.headers['content-security-policy']
expect(cspHeader).toMatch(/default-src 'self'/) expect(cspHeader).toMatch(/default-src 'self'/)