mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-04-05 08:16:16 +02:00
security: fix CORS, CSRF, CSP, HSTS, sessions, and path traversal
- CORS: restrict origin from wildcard '*' to app URL from env (prevents cross-origin requests from arbitrary sites) - Sessions: enable @adonisjs/session with cookie store and httpOnly/secure cookie flags; uncomment session middleware in kernel.ts - CSRF: enable shield CSRF protection (requires sessions); uses XSRF-TOKEN cookie mechanism compatible with Inertia.js/Axios; exempts /api/health and SSE transmit endpoints - CSP: enable Content Security Policy with restrictive directives (no object-src, no frame-src, self-only script/style/connect/font) - HSTS: enable HTTP Strict Transport Security in production only - Path traversal: tighten filenameParamValidator to block /, \, .., and shell special characters; reduce max length from 4096 to 255 - env: add URL to .env.example; uncomment SESSION_DRIVER validation in env.ts https://claude.ai/code/session_01WfRC4tDeYprykhMrg4PxX6
This commit is contained in:
parent
f004c002a7
commit
3ddbe731a5
|
|
@ -1,5 +1,6 @@
|
||||||
PORT=8080
|
PORT=8080
|
||||||
HOST=localhost
|
HOST=localhost
|
||||||
|
URL=http://localhost:8080
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
APP_KEY=some_random_key
|
APP_KEY=some_random_key
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,14 @@ export const remoteDownloadValidatorOptional = vine.compile(
|
||||||
export const filenameParamValidator = vine.compile(
|
export const filenameParamValidator = vine.compile(
|
||||||
vine.object({
|
vine.object({
|
||||||
params: vine.object({
|
params: vine.object({
|
||||||
filename: vine.string().trim().minLength(1).maxLength(4096),
|
filename: vine
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.minLength(1)
|
||||||
|
.maxLength(255)
|
||||||
|
.regex(/^[^/\\]*$/) // Disallow path separators to prevent traversal
|
||||||
|
.regex(/^(?!\.\.)/) // Disallow leading double-dots
|
||||||
|
.regex(/^[^<>:"|?*\x00-\x1f]+$/), // Disallow shell/filesystem special chars
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import env from '#start/env'
|
||||||
import { defineConfig } from '@adonisjs/cors'
|
import { defineConfig } from '@adonisjs/cors'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -8,7 +9,7 @@ import { defineConfig } from '@adonisjs/cors'
|
||||||
*/
|
*/
|
||||||
const corsConfig = defineConfig({
|
const corsConfig = defineConfig({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
origin: ['*'],
|
origin: [env.get('URL')],
|
||||||
methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
|
methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
|
||||||
headers: true,
|
headers: true,
|
||||||
exposeHeaders: [],
|
exposeHeaders: [],
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,48 @@
|
||||||
// import env from '#start/env'
|
import env from '#start/env'
|
||||||
// import app from '@adonisjs/core/services/app'
|
import app from '@adonisjs/core/services/app'
|
||||||
// import { defineConfig, stores } from '@adonisjs/session'
|
import { defineConfig, stores } from '@adonisjs/session'
|
||||||
|
|
||||||
// const sessionConfig = defineConfig({
|
const sessionConfig = defineConfig({
|
||||||
// enabled: false,
|
enabled: true,
|
||||||
// cookieName: 'adonis-session',
|
cookieName: 'nomad-session',
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * When set to true, the session id cookie will be deleted
|
* When set to true, the session id cookie will be deleted
|
||||||
// * once the user closes the browser.
|
* once the user closes the browser.
|
||||||
// */
|
*/
|
||||||
// clearWithBrowser: false,
|
clearWithBrowser: false,
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * Define how long to keep the session data alive without
|
* Define how long to keep the session data alive without
|
||||||
// * any activity.
|
* any activity.
|
||||||
// */
|
*/
|
||||||
// age: '2h',
|
age: '2h',
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * Configuration for session cookie and the
|
* Configuration for session cookie and the
|
||||||
// * cookie store
|
* cookie store
|
||||||
// */
|
*/
|
||||||
// cookie: {
|
cookie: {
|
||||||
// path: '/',
|
path: '/',
|
||||||
// httpOnly: true,
|
httpOnly: true,
|
||||||
// secure: app.inProduction,
|
secure: app.inProduction,
|
||||||
// sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
// },
|
},
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * The store to use. Make sure to validate the environment
|
* The store to use. Make sure to validate the environment
|
||||||
// * variable in order to infer the store name without any
|
* variable in order to infer the store name without any
|
||||||
// * errors.
|
* errors.
|
||||||
// */
|
*/
|
||||||
// store: env.get('SESSION_DRIVER'),
|
store: env.get('SESSION_DRIVER'),
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * List of configured stores. Refer documentation to see
|
* List of configured stores. Refer documentation to see
|
||||||
// * list of available stores and their config.
|
* list of available stores and their config.
|
||||||
// */
|
*/
|
||||||
// stores: {
|
stores: {
|
||||||
// cookie: stores.cookie(),
|
cookie: stores.cookie(),
|
||||||
// },
|
},
|
||||||
// })
|
})
|
||||||
|
|
||||||
// export default sessionConfig
|
export default sessionConfig
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import app from '@adonisjs/core/services/app'
|
||||||
import { defineConfig } from '@adonisjs/shield'
|
import { defineConfig } from '@adonisjs/shield'
|
||||||
|
|
||||||
const shieldConfig = defineConfig({
|
const shieldConfig = defineConfig({
|
||||||
|
|
@ -6,8 +7,19 @@ const shieldConfig = defineConfig({
|
||||||
* to learn more
|
* to learn more
|
||||||
*/
|
*/
|
||||||
csp: {
|
csp: {
|
||||||
enabled: false,
|
enabled: true,
|
||||||
directives: {},
|
directives: {
|
||||||
|
defaultSrc: ["'self'"],
|
||||||
|
scriptSrc: ["'self'", "'unsafe-inline'"], // unsafe-inline required for Inertia.js page props
|
||||||
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||||
|
imgSrc: ["'self'", 'data:', 'blob:'],
|
||||||
|
connectSrc: ["'self'"],
|
||||||
|
fontSrc: ["'self'"],
|
||||||
|
objectSrc: ["'none'"],
|
||||||
|
frameSrc: ["'none'"],
|
||||||
|
baseUri: ["'self'"],
|
||||||
|
formAction: ["'self'"],
|
||||||
|
},
|
||||||
reportOnly: false,
|
reportOnly: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -16,8 +28,9 @@ const shieldConfig = defineConfig({
|
||||||
* to learn more
|
* to learn more
|
||||||
*/
|
*/
|
||||||
csrf: {
|
csrf: {
|
||||||
enabled: false, // TODO: Enable CSRF protection
|
enabled: true,
|
||||||
exceptRoutes: [],
|
// Exempt health check and SSE/transmit endpoints from CSRF
|
||||||
|
exceptRoutes: ['/api/health', '/__transmit/events', '/__transmit/unsubscribe'],
|
||||||
enableXsrfCookie: true,
|
enableXsrfCookie: true,
|
||||||
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
|
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
|
||||||
},
|
},
|
||||||
|
|
@ -35,7 +48,7 @@ const shieldConfig = defineConfig({
|
||||||
* Force browser to always use HTTPS
|
* Force browser to always use HTTPS
|
||||||
*/
|
*/
|
||||||
hsts: {
|
hsts: {
|
||||||
enabled: false, // TODO: Enable HSTS in production
|
enabled: app.inProduction,
|
||||||
maxAge: '180 days',
|
maxAge: '180 days',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export default await Env.create(new URL('../', import.meta.url), {
|
||||||
| Variables for configuring session package
|
| Variables for configuring session package
|
||||||
|----------------------------------------------------------
|
|----------------------------------------------------------
|
||||||
*/
|
*/
|
||||||
//SESSION_DRIVER: Env.schema.enum(['cookie', 'memory'] as const),
|
SESSION_DRIVER: Env.schema.enum(['cookie', 'memory'] as const),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|----------------------------------------------------------
|
|----------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ server.use([
|
||||||
*/
|
*/
|
||||||
router.use([
|
router.use([
|
||||||
() => import('@adonisjs/core/bodyparser_middleware'),
|
() => import('@adonisjs/core/bodyparser_middleware'),
|
||||||
// () => import('@adonisjs/session/session_middleware'),
|
() => import('@adonisjs/session/session_middleware'),
|
||||||
() => import('@adonisjs/shield/shield_middleware'),
|
() => import('@adonisjs/shield/shield_middleware'),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user