Skip to content

Commit 5ae5a05

Browse files
feat: add security headers config
1 parent f8ac3bd commit 5ae5a05

6 files changed

Lines changed: 28 additions & 13 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ All CLI flags can be set via environment variables instead:
101101
| `STUBIDP_REGISTRATION_INITIAL_ACCESS_TOKEN` || Bearer token required to call `POST /register` (open registration when omitted) |
102102
| `STUBIDP_TRUST_PROXY` | `false` | Trust reverse proxy headers (`X-Forwarded-*`). Enable when running behind a proxy |
103103
| `STUBIDP_HTTPS_REDIRECT` | `false` | Redirect HTTP requests to HTTPS and set CSP `upgrade-insecure-requests` |
104+
| `STUBIDP_SECURITY_HEADERS` | `false` | Enable security headers (CSP, HSTS, etc.) via helmet. Enable when deployed, not for local dev |
104105

105106
## Dynamic Client Registration
106107

bin/run.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ const app = await createApp({
111111
defaultUser,
112112
trustProxy: argv['trust-proxy'],
113113
httpsRedirect: argv['https-redirect'],
114+
securityHeaders: argv['security-headers'],
114115
rateLimit: {
115116
windowMs: argv['rate-limit-window-ms'],
116117
max: argv['rate-limit-max'],

docs/configuration.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { Aside } from '@/components/docs'
2424
| `--registration-initial-access-token` | No | Bearer token required to call `POST /register`. Omit for open registration. |
2525
| `--trust-proxy` | No | Trust reverse proxy headers (`X-Forwarded-*`). Enable when running behind a reverse proxy or load balancer. |
2626
| `--https-redirect` | No | Redirect HTTP requests to HTTPS and enable CSP `upgrade-insecure-requests`. Enable in production behind TLS termination. |
27+
| `--security-headers` | No | Enable security headers (CSP, HSTS, etc.) via helmet. Enable when deployed; omit for local development. |
2728

2829
When `--client-id` or `--client-secret` are omitted, the generated values are printed in the startup table so you can copy them into your application config.
2930

@@ -58,6 +59,7 @@ Every CLI flag can also be set via an environment variable (see below). Environm
5859
| `STUBIDP_RATE_LIMIT_DISABLED` | `false` | Set to `true` to disable rate limiting (equivalent to `--rate-limit-disabled`) |
5960
| `STUBIDP_TRUST_PROXY` | `false` | Set to `true` to trust reverse proxy headers (`X-Forwarded-*`) (equivalent to `--trust-proxy`) |
6061
| `STUBIDP_HTTPS_REDIRECT` | `false` | Set to `true` to redirect HTTP to HTTPS and enable CSP `upgrade-insecure-requests` (equivalent to `--https-redirect`) |
62+
| `STUBIDP_SECURITY_HEADERS` | `false` | Set to `true` to enable security headers (CSP, HSTS, etc.) via helmet (equivalent to `--security-headers`) |
6163

6264
## Database
6365

src/args.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ export const argv = yargs(hideBin(process.argv))
8989
description:
9090
'Redirect HTTP requests to HTTPS and set CSP upgrade-insecure-requests [env: STUBIDP_HTTPS_REDIRECT]',
9191
},
92+
'security-headers': {
93+
type: 'boolean',
94+
demandOption: false,
95+
env: 'STUBIDP_SECURITY_HEADERS',
96+
description:
97+
'Enable security headers (CSP, HSTS, etc.) via helmet. Enable when deployed [env: STUBIDP_SECURITY_HEADERS]',
98+
},
9299
})
93100
.env('STUBIDP')
94101
.parseSync()

src/server.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,29 @@ export interface AppOptions extends ProviderOptions {
1919
skipPrompt?: boolean
2020
trustProxy?: boolean
2121
httpsRedirect?: boolean
22+
securityHeaders?: boolean
2223
}
2324

2425
export async function createApp(options: AppOptions): Promise<Express> {
2526
const app: Express = express()
2627
const oidc: Provider = await createProvider(options)
2728

28-
const directives = helmet.contentSecurityPolicy.getDefaultDirectives()
29-
delete directives['form-action']
30-
delete directives['upgrade-insecure-requests']
31-
if (options.httpsRedirect) {
32-
directives['upgrade-insecure-requests'] = []
29+
if (options.securityHeaders) {
30+
const directives = helmet.contentSecurityPolicy.getDefaultDirectives()
31+
delete directives['form-action']
32+
delete directives['upgrade-insecure-requests']
33+
if (options.httpsRedirect) {
34+
directives['upgrade-insecure-requests'] = []
35+
}
36+
app.use(
37+
helmet({
38+
contentSecurityPolicy: {
39+
useDefaults: false,
40+
directives,
41+
},
42+
}),
43+
)
3344
}
34-
app.use(
35-
helmet({
36-
contentSecurityPolicy: {
37-
useDefaults: false,
38-
directives,
39-
},
40-
}),
41-
)
4245

4346
const rl = options.rateLimit ?? {}
4447
if (!rl.disabled) {

src/worker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ async function ensureApp(currentEnv: Env): Promise<Express> {
3333
issuer: currentEnv.STUBIDP_ISSUER,
3434
trustProxy: currentEnv.STUBIDP_TRUST_PROXY === 'true',
3535
httpsRedirect: currentEnv.STUBIDP_HTTPS_REDIRECT === 'true',
36+
securityHeaders: true,
3637
})
3738

3839
cachedEntry = { key, app }

0 commit comments

Comments
 (0)