Compare commits
No commits in common. "48919236d06695e38cfabb74da71a0700ffe0347" and "affb9f5a0d2ed4c308051d86cbed94c9af24d486" have entirely different histories.
48919236d0
...
affb9f5a0d
@ -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
|
|
||||||
|
|||||||
34
index.cjs
34
index.cjs
@ -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',
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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": {
|
||||||
|
|||||||
@ -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'/)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user