@nathapp/nestjs-csrf 4.0.2 → 4.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/guard/csrf.guard.d.ts +4 -0
- package/dist/guard/csrf.guard.js +55 -3
- package/dist/guard/csrf.guard.js.map +1 -1
- package/dist/interface/csrf-options.interface.d.ts +4 -0
- package/dist/module/csrf.module.d.ts +1 -1
- package/dist/service/resolve-options.js +6 -4
- package/dist/service/resolve-options.js.map +1 -1
- package/package.json +5 -4
- package/tsconfig.test.json +16 -0
package/README.md
CHANGED
|
@@ -26,4 +26,49 @@ The host app **must enable cookie parsing** so `req.cookies` is populated:
|
|
|
26
26
|
These are not auto-wired. If cookie parsing is not enabled, mutating requests fail
|
|
27
27
|
closed (403).
|
|
28
28
|
|
|
29
|
+
## Options
|
|
30
|
+
|
|
31
|
+
All options are optional except `secret`:
|
|
32
|
+
|
|
33
|
+
| Option | Default | Description |
|
|
34
|
+
| ------ | ------- | ----------- |
|
|
35
|
+
| `secret` | — (required) | HMAC secret used to mint/verify tokens. |
|
|
36
|
+
| `enabled` | `true` | Global kill switch for the guard. |
|
|
37
|
+
| `headerName` | `x-csrf-token` | Header the client must echo the cookie value in. |
|
|
38
|
+
| `ignoreMethods` | `['GET', 'HEAD', 'OPTIONS']` | Safe methods that skip verification (and receive the cookie). Entries are uppercased before comparison. |
|
|
39
|
+
| `skipWhenBearer` | `true` | Skip verification when an `Authorization: Bearer …` header is present. |
|
|
40
|
+
| `originCheck` | `true` | When an `Origin` or `Referer` header is present and its origin is not allowed, the request is rejected with 403 `csrf_origin_mismatch` (before the token check). Requests carrying neither header (native apps) fall through to the token check. |
|
|
41
|
+
| `allowedOrigins` | `[]` | Extra origins accepted by the origin check, in addition to the request's own origin (e.g. `['https://cdn.example.com']`). |
|
|
42
|
+
| `cookie` | see below | Cookie attributes: `name` (`csrf-token`), `path` (`/`), `httpOnly` (`false`), `secure` (`true`), `sameSite` (`lax`). |
|
|
43
|
+
|
|
44
|
+
The request's **own origin** is host-bound: the host comes from `x-forwarded-host`
|
|
45
|
+
(falling back to `host`) and the scheme from `x-forwarded-proto` (first value, if the
|
|
46
|
+
proxy sends a comma-separated chain) or `req.socket.encrypted`. When the request does
|
|
47
|
+
**not** advertise a determinate scheme — for example a TLS-terminating proxy that does
|
|
48
|
+
not set `x-forwarded-proto` — either `https://<host>` or `http://<host>` is accepted,
|
|
49
|
+
so legitimate same-origin mutations are not rejected on such deployments. An origin on
|
|
50
|
+
any *other* host is still rejected. Setting `x-forwarded-proto: https` at the proxy is
|
|
51
|
+
still recommended so the check is exact.
|
|
52
|
+
|
|
53
|
+
`cookie.secure` defaults to `true`. Over plain HTTP, browsers drop secure cookies, so
|
|
54
|
+
every protected mutation would fail with 403. To make this misconfiguration
|
|
55
|
+
diagnosable, the guard logs a **one-time warning** on the first protected request that
|
|
56
|
+
arrives over plain HTTP (no `x-forwarded-proto: https` and no `req.socket.encrypted`)
|
|
57
|
+
while `secure: true`. Serve over HTTPS or set `cookie.secure: false` for local
|
|
58
|
+
plain-HTTP development.
|
|
59
|
+
|
|
60
|
+
## Security notes and residual risk
|
|
61
|
+
|
|
62
|
+
The double-submit token is **not bound to a user session or identity** — it proves
|
|
63
|
+
same-origin presence of the cookie value, not authentication state. The mitigating
|
|
64
|
+
control is the origin check: a cross-site attacker's forged request either carries an
|
|
65
|
+
`Origin`/`Referer` that fails the allow-check, or omits them — but an attacker cannot
|
|
66
|
+
read the victim's cookies to echo a valid token in the `x-csrf-token` header, so the
|
|
67
|
+
double-submit check still holds. Scenarios where an attacker can **inject** a cookie
|
|
68
|
+
value into the victim's browser (e.g. cookie injection/fixation from a sibling
|
|
69
|
+
subdomain) remain conditional: they depend on the injected value surviving and the
|
|
70
|
+
token not being tied to anything server-side, and are not fully eliminated by the
|
|
71
|
+
double-submit pattern alone. If session binding is required, pair this guard with a
|
|
72
|
+
session-bound token check.
|
|
73
|
+
|
|
29
74
|
See `docs/superpowers/specs/2026-07-08-nestjs-csrf-design.md`.
|
|
@@ -7,7 +7,11 @@ export declare class DefaultCsrfGuard implements CanActivate {
|
|
|
7
7
|
private readonly reflector;
|
|
8
8
|
private readonly logger;
|
|
9
9
|
private readonly options;
|
|
10
|
+
private warnedInsecureCookie;
|
|
10
11
|
constructor(rawOptions: CsrfOptions, tokens: CsrfTokenService, reflector: Reflector);
|
|
11
12
|
canActivate(context: ExecutionContext): boolean;
|
|
12
13
|
private issue;
|
|
14
|
+
private isOriginAllowed;
|
|
15
|
+
private firstHeaderValue;
|
|
16
|
+
private warnIfInsecureCookie;
|
|
13
17
|
}
|
package/dist/guard/csrf.guard.js
CHANGED
|
@@ -25,10 +25,11 @@ let DefaultCsrfGuard = DefaultCsrfGuard_1 = class DefaultCsrfGuard {
|
|
|
25
25
|
this.tokens = tokens;
|
|
26
26
|
this.reflector = reflector;
|
|
27
27
|
this.logger = new common_1.Logger(DefaultCsrfGuard_1.name);
|
|
28
|
+
this.warnedInsecureCookie = false;
|
|
28
29
|
this.options = (0, resolve_options_1.resolveCsrfOptions)(rawOptions);
|
|
29
30
|
}
|
|
30
31
|
canActivate(context) {
|
|
31
|
-
var _a, _b, _c, _d, _e;
|
|
32
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
32
33
|
if (this.options.enabled === false)
|
|
33
34
|
return true;
|
|
34
35
|
if (context.getType() !== 'http')
|
|
@@ -43,6 +44,7 @@ let DefaultCsrfGuard = DefaultCsrfGuard_1 = class DefaultCsrfGuard {
|
|
|
43
44
|
}
|
|
44
45
|
return true;
|
|
45
46
|
}
|
|
47
|
+
this.warnIfInsecureCookie(req);
|
|
46
48
|
const reflectionTargets = [context.getHandler(), context.getClass()];
|
|
47
49
|
const validTargets = reflectionTargets.filter((t) => t != null);
|
|
48
50
|
if (validTargets.length > 0 &&
|
|
@@ -54,8 +56,14 @@ let DefaultCsrfGuard = DefaultCsrfGuard_1 = class DefaultCsrfGuard {
|
|
|
54
56
|
/^Bearer\s/i.test(req.headers['authorization'])) {
|
|
55
57
|
return true;
|
|
56
58
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
if (this.options.originCheck !== false) {
|
|
60
|
+
const originHeader = ((_e = (_d = req.headers) === null || _d === void 0 ? void 0 : _d['origin']) !== null && _e !== void 0 ? _e : (_f = req.headers) === null || _f === void 0 ? void 0 : _f['referer']);
|
|
61
|
+
if (typeof originHeader === 'string' && originHeader.length > 0 && !this.isOriginAllowed(originHeader, req)) {
|
|
62
|
+
throw new nestjs_common_1.ForbiddenAppException({ reason: 'csrf_origin_mismatch' });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const cookie = (_g = req.cookies) === null || _g === void 0 ? void 0 : _g[this.options.cookie.name];
|
|
66
|
+
const headerValue = (_h = req.headers) === null || _h === void 0 ? void 0 : _h[this.options.headerName];
|
|
59
67
|
const header = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
60
68
|
if (!this.tokens.verify(this.options.secret, cookie, header)) {
|
|
61
69
|
throw new nestjs_common_1.ForbiddenAppException({ reason: 'csrf_token_invalid' });
|
|
@@ -76,6 +84,50 @@ let DefaultCsrfGuard = DefaultCsrfGuard_1 = class DefaultCsrfGuard {
|
|
|
76
84
|
'Ensure cookie-parser (Express) or @fastify/cookie (Fastify) is registered.');
|
|
77
85
|
}
|
|
78
86
|
}
|
|
87
|
+
isOriginAllowed(originHeader, req) {
|
|
88
|
+
var _a, _b, _c, _d;
|
|
89
|
+
let origin;
|
|
90
|
+
try {
|
|
91
|
+
origin = new URL(originHeader, 'http://placeholder.invalid').origin;
|
|
92
|
+
}
|
|
93
|
+
catch (_e) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
const allowed = new Set(this.options.allowedOrigins);
|
|
97
|
+
const headers = (_a = req.headers) !== null && _a !== void 0 ? _a : {};
|
|
98
|
+
const forwardedProto = (_b = this.firstHeaderValue(headers['x-forwarded-proto'])) === null || _b === void 0 ? void 0 : _b.split(',')[0].trim();
|
|
99
|
+
const host = (_c = this.firstHeaderValue(headers['x-forwarded-host'])) !== null && _c !== void 0 ? _c : this.firstHeaderValue(headers['host']);
|
|
100
|
+
if (typeof host === 'string' && host.length > 0) {
|
|
101
|
+
if (forwardedProto) {
|
|
102
|
+
allowed.add(`${forwardedProto}://${host}`);
|
|
103
|
+
}
|
|
104
|
+
else if (((_d = req.socket) === null || _d === void 0 ? void 0 : _d.encrypted) === true) {
|
|
105
|
+
allowed.add(`https://${host}`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
allowed.add(`https://${host}`);
|
|
109
|
+
allowed.add(`http://${host}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return allowed.has(origin);
|
|
113
|
+
}
|
|
114
|
+
firstHeaderValue(value) {
|
|
115
|
+
return Array.isArray(value) ? value[0] : value;
|
|
116
|
+
}
|
|
117
|
+
warnIfInsecureCookie(req) {
|
|
118
|
+
var _a, _b;
|
|
119
|
+
if (this.warnedInsecureCookie || !this.options.cookie.secure)
|
|
120
|
+
return;
|
|
121
|
+
const forwardedProto = this.firstHeaderValue((_a = req.headers) === null || _a === void 0 ? void 0 : _a['x-forwarded-proto']);
|
|
122
|
+
if (forwardedProto === 'https' || ((_b = req.socket) === null || _b === void 0 ? void 0 : _b.encrypted) === true)
|
|
123
|
+
return;
|
|
124
|
+
this.warnedInsecureCookie = true;
|
|
125
|
+
this.logger.warn('CSRF cookie is issued with secure=true but this request arrived over plain HTTP ' +
|
|
126
|
+
'(no "x-forwarded-proto: https" header and no req.socket.encrypted). Browsers drop ' +
|
|
127
|
+
'secure cookies over plain HTTP, so the CSRF cookie will never be stored and every ' +
|
|
128
|
+
'protected mutation will fail with 403. Serve the app over HTTPS (set ' +
|
|
129
|
+
'x-forwarded-proto at your proxy) or set cookie.secure=false for plain-HTTP deployments.');
|
|
130
|
+
}
|
|
79
131
|
};
|
|
80
132
|
exports.DefaultCsrfGuard = DefaultCsrfGuard;
|
|
81
133
|
exports.DefaultCsrfGuard = DefaultCsrfGuard = DefaultCsrfGuard_1 = __decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csrf.guard.js","sourceRoot":"","sources":["../../lib/guard/csrf.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAMwB;AACxB,uCAAyC;AACzC,0DAA+D;AAC/D,+DAAyF;AAEzF,sEAAiE;AACjE,gEAAgE;AAGzD,IAAM,gBAAgB,wBAAtB,MAAM,gBAAgB;
|
|
1
|
+
{"version":3,"file":"csrf.guard.js","sourceRoot":"","sources":["../../lib/guard/csrf.guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,8 @@ export interface CsrfOptions {
|
|
|
12
12
|
headerName?: string;
|
|
13
13
|
ignoreMethods?: string[];
|
|
14
14
|
skipWhenBearer?: boolean;
|
|
15
|
+
allowedOrigins?: string[];
|
|
16
|
+
originCheck?: boolean;
|
|
15
17
|
cookie?: Partial<CsrfCookieOptions>;
|
|
16
18
|
}
|
|
17
19
|
export interface ResolvedCsrfOptions {
|
|
@@ -20,6 +22,8 @@ export interface ResolvedCsrfOptions {
|
|
|
20
22
|
headerName: string;
|
|
21
23
|
ignoreMethods: string[];
|
|
22
24
|
skipWhenBearer: boolean;
|
|
25
|
+
allowedOrigins: string[];
|
|
26
|
+
originCheck: boolean;
|
|
23
27
|
cookie: CsrfCookieOptions;
|
|
24
28
|
}
|
|
25
29
|
export interface CsrfModuleOptionsFactory {
|
|
@@ -6,7 +6,7 @@ export type CsrfAsyncModuleOptions = Pick<ModuleMetadata, 'imports'> & {
|
|
|
6
6
|
useFactory?: (...args: any[]) => Promise<CsrfOptions> | CsrfOptions;
|
|
7
7
|
inject?: any[];
|
|
8
8
|
};
|
|
9
|
-
declare const CsrfModule_base: import("@nathapp/nestjs-common").ConfigurableModuleCls<CsrfOptions, "register", "createOptions",
|
|
9
|
+
declare const CsrfModule_base: import("@nathapp/nestjs-common").ConfigurableModuleCls<CsrfOptions, "register", "createOptions", object>;
|
|
10
10
|
export declare class CsrfModule extends CsrfModule_base {
|
|
11
11
|
static register(options: CsrfOptions): DynamicModule;
|
|
12
12
|
static registerAsync(options: CsrfAsyncModuleOptions): DynamicModule;
|
|
@@ -10,14 +10,14 @@ const DEFAULT_COOKIE_SECURE = true;
|
|
|
10
10
|
const DEFAULT_COOKIE_SAME_SITE = 'lax';
|
|
11
11
|
function resolveCsrfOptions(options) {
|
|
12
12
|
var _a, _b, _c, _d, _e;
|
|
13
|
-
const { secret, enabled = true, headerName: headerNameOverride = DEFAULT_HEADER_NAME, ignoreMethods: ignoreMethodsOverride, skipWhenBearer = true, cookie: cookieOverrides, } = options;
|
|
13
|
+
const { secret, enabled = true, headerName: headerNameOverride = DEFAULT_HEADER_NAME, ignoreMethods: ignoreMethodsOverride, skipWhenBearer = true, allowedOrigins: allowedOriginsOverride, originCheck: originCheckOverride, cookie: cookieOverrides, } = options;
|
|
14
14
|
if (typeof secret !== 'string' || secret.length === 0) {
|
|
15
15
|
throw new Error('CsrfOptions.secret must be a non-empty string');
|
|
16
16
|
}
|
|
17
17
|
const headerName = headerNameOverride.toLowerCase();
|
|
18
|
-
const ignoreMethods = ignoreMethodsOverride
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const ignoreMethods = (ignoreMethodsOverride !== null && ignoreMethodsOverride !== void 0 ? ignoreMethodsOverride : DEFAULT_IGNORE_METHODS).map((m) => m.toUpperCase());
|
|
19
|
+
const allowedOrigins = [...(allowedOriginsOverride !== null && allowedOriginsOverride !== void 0 ? allowedOriginsOverride : [])];
|
|
20
|
+
const originCheck = originCheckOverride !== null && originCheckOverride !== void 0 ? originCheckOverride : true;
|
|
21
21
|
const cookie = {
|
|
22
22
|
name: (_a = cookieOverrides === null || cookieOverrides === void 0 ? void 0 : cookieOverrides.name) !== null && _a !== void 0 ? _a : DEFAULT_COOKIE_NAME,
|
|
23
23
|
path: (_b = cookieOverrides === null || cookieOverrides === void 0 ? void 0 : cookieOverrides.path) !== null && _b !== void 0 ? _b : DEFAULT_COOKIE_PATH,
|
|
@@ -31,6 +31,8 @@ function resolveCsrfOptions(options) {
|
|
|
31
31
|
headerName,
|
|
32
32
|
ignoreMethods,
|
|
33
33
|
skipWhenBearer,
|
|
34
|
+
allowedOrigins,
|
|
35
|
+
originCheck,
|
|
34
36
|
cookie,
|
|
35
37
|
};
|
|
36
38
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-options.js","sourceRoot":"","sources":["../../lib/service/resolve-options.ts"],"names":[],"mappings":";;AAUA,
|
|
1
|
+
{"version":3,"file":"resolve-options.js","sourceRoot":"","sources":["../../lib/service/resolve-options.ts"],"names":[],"mappings":";;AAUA,gDA6CC;AArDD,MAAM,mBAAmB,GAAG,YAAY,CAAC;AACzC,MAAM,mBAAmB,GAAG,cAAc,CAAC;AAC3C,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAC1D,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAChC,MAAM,wBAAwB,GAAG,KAAK,CAAC;AACvC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AACnC,MAAM,wBAAwB,GAAkC,KAAK,CAAC;AAEtE,SAAgB,kBAAkB,CAAC,OAAoB;;IACrD,MAAM,EACJ,MAAM,EACN,OAAO,GAAG,IAAI,EACd,UAAU,EAAE,kBAAkB,GAAG,mBAAmB,EACpD,aAAa,EAAE,qBAAqB,EACpC,cAAc,GAAG,IAAI,EACrB,cAAc,EAAE,sBAAsB,EACtC,WAAW,EAAE,mBAAmB,EAChC,MAAM,EAAE,eAAe,GACxB,GAAG,OAAO,CAAC;IAEZ,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IAED,MAAM,UAAU,GAAG,kBAAkB,CAAC,WAAW,EAAE,CAAC;IAIpD,MAAM,aAAa,GAAG,CAAC,qBAAqB,aAArB,qBAAqB,cAArB,qBAAqB,GAAI,sBAAsB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAChF,CAAC,CAAC,WAAW,EAAE,CAChB,CAAC;IAEF,MAAM,cAAc,GAAG,CAAC,GAAG,CAAC,sBAAsB,aAAtB,sBAAsB,cAAtB,sBAAsB,GAAI,EAAE,CAAC,CAAC,CAAC;IAC3D,MAAM,WAAW,GAAG,mBAAmB,aAAnB,mBAAmB,cAAnB,mBAAmB,GAAI,IAAI,CAAC;IAEhD,MAAM,MAAM,GAAsB;QAChC,IAAI,EAAE,MAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAE,IAAI,mCAAI,mBAAmB;QAClD,IAAI,EAAE,MAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAE,IAAI,mCAAI,mBAAmB;QAClD,QAAQ,EAAE,MAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAE,QAAQ,mCAAI,wBAAwB;QAC/D,MAAM,EAAE,MAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAE,MAAM,mCAAI,qBAAqB;QACxD,QAAQ,EAAE,MAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAE,QAAQ,mCAAI,wBAAwB;KAChE,CAAC;IAEF,OAAO;QACL,MAAM;QACN,OAAO;QACP,UAAU;QACV,aAAa;QACb,cAAc;QACd,cAAc;QACd,WAAW;QACX,MAAM;KACP,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nathapp/nestjs-csrf",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3",
|
|
4
4
|
"description": "Stateless double-submit-cookie CSRF protection for NestJS (Express + Fastify), zero runtime dependencies",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
"test:cov": "jest --config ./test/jest-test.json --coverage",
|
|
13
13
|
"test:watch": "jest --config ./test/jest-test.json --watch",
|
|
14
14
|
"type-check": "tsc --noEmit -p tsconfig.json",
|
|
15
|
-
"lint": "eslint -c .eslintrc.js",
|
|
16
|
-
"lint:fix": "eslint -c .eslintrc.js --fix"
|
|
15
|
+
"lint": "eslint -c .eslintrc.js \"lib/**/*.ts\" \"test/**/*.ts\"",
|
|
16
|
+
"lint:fix": "eslint -c .eslintrc.js --fix \"lib/**/*.ts\" \"test/**/*.ts\"",
|
|
17
|
+
"type-check:test": "tsc --noEmit -p tsconfig.test.json"
|
|
17
18
|
},
|
|
18
19
|
"author": "William Khoo",
|
|
19
20
|
"license": "ISC",
|
|
@@ -29,7 +30,7 @@
|
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
31
32
|
"@nathapp/eslint-config": "1.0.1",
|
|
32
|
-
"@nathapp/nestjs-common": "^4.0.
|
|
33
|
+
"@nathapp/nestjs-common": "^4.0.3",
|
|
33
34
|
"@nathapp/typescript-config": "1.0.1",
|
|
34
35
|
"@nestjs/common": "^11.0.0",
|
|
35
36
|
"@nestjs/core": "^11.0.0",
|