Replace deprecated csurf with csrf-csrf
csurf has been deprecated since 2022 and pins an old cookie release
with a known advisory; npm's only fix is a downgrade (issue #1634,
item 2). csrf-csrf v4 implements the same double-submit-cookie pattern
with an HMAC-signed, session-bound token keyed on the existing boot
secret (common.secret_key).
The frontend contract is unchanged: the token still arrives via the
XSRF-TOKEN cookie/header and is echoed as x-xsrf-token (all token
sources csurf accepted are still read), the signed cookie keeps the
_csrf name (now httpOnly, secure:false to match the session cookie on
plain-HTTP deployments), doubleCsrfProtection attaches req.csrfToken
so app.ts keeps working, and the error code is EBADCSRFTOKEN - already
handled in app.ts. The websocket upgrade check in authCheck.ts now
routes through the shared middleware; upgrade requests are GETs, so
its pass-through semantics are unchanged.
One fix this surfaced: app.ts called req.csrfToken() twice (cookie and
header). Under csurf every token validated against a stable secret;
under csrf-csrf each first-visit call mints a new token, desyncing the
XSRF-TOKEN cookie from the _csrf cookie it must equal. The token is
now generated once per request.
Tokens are session-bound, so a token stolen from one session no longer
validates in another - a check csurf's cookie mode did not perform.
Production npm audit drops from 6 low findings to 4, all in the
crypto-browserify/elliptic chain tracked in #1634.
Verified against the docker regtest fixture: both API suites (43
checks across LND, CLN and Eclair) plus a dedicated CSRF battery -
valid-token auth, missing token 403, garbage token 403, cross-session
replay 403, token stability across requests, the XSRF-TOKEN response
header for Quickpay, and the websocket handshake. Lint and build are
clean.
2026-07-19 17:03:53 -07:00
|
|
|
import { doubleCsrf } from 'csrf-csrf';
|
2021-12-29 18:08:41 -05:00
|
|
|
import { Logger } from './logger.js';
|
2022-01-16 15:55:50 -05:00
|
|
|
import { Common } from './common.js';
|
2021-12-29 18:08:41 -05:00
|
|
|
class CSRF {
|
|
|
|
|
constructor() {
|
|
|
|
|
this.logger = Logger;
|
2022-01-16 15:55:50 -05:00
|
|
|
this.common = Common;
|
Replace deprecated csurf with csrf-csrf
csurf has been deprecated since 2022 and pins an old cookie release
with a known advisory; npm's only fix is a downgrade (issue #1634,
item 2). csrf-csrf v4 implements the same double-submit-cookie pattern
with an HMAC-signed, session-bound token keyed on the existing boot
secret (common.secret_key).
The frontend contract is unchanged: the token still arrives via the
XSRF-TOKEN cookie/header and is echoed as x-xsrf-token (all token
sources csurf accepted are still read), the signed cookie keeps the
_csrf name (now httpOnly, secure:false to match the session cookie on
plain-HTTP deployments), doubleCsrfProtection attaches req.csrfToken
so app.ts keeps working, and the error code is EBADCSRFTOKEN - already
handled in app.ts. The websocket upgrade check in authCheck.ts now
routes through the shared middleware; upgrade requests are GETs, so
its pass-through semantics are unchanged.
One fix this surfaced: app.ts called req.csrfToken() twice (cookie and
header). Under csurf every token validated against a stable secret;
under csrf-csrf each first-visit call mints a new token, desyncing the
XSRF-TOKEN cookie from the _csrf cookie it must equal. The token is
now generated once per request.
Tokens are session-bound, so a token stolen from one session no longer
validates in another - a check csurf's cookie mode did not perform.
Production npm audit drops from 6 low findings to 4, all in the
crypto-browserify/elliptic chain tracked in #1634.
Verified against the docker regtest fixture: both API suites (43
checks across LND, CLN and Eclair) plus a dedicated CSRF battery -
valid-token auth, missing token 403, garbage token 403, cross-session
replay 403, token stability across requests, the XSRF-TOKEN response
header for Quickpay, and the websocket handshake. Lint and build are
clean.
2026-07-19 17:03:53 -07:00
|
|
|
// Signed double-submit-cookie protection (replaces the deprecated csurf).
|
|
|
|
|
// The signed token lives in the httpOnly '_csrf' cookie; the client echoes
|
|
|
|
|
// the same token (read from the XSRF-TOKEN cookie set in app.ts) in a
|
|
|
|
|
// header. The cookie is not secure-only because RTL commonly serves plain
|
|
|
|
|
// HTTP (matching the session cookie); token sources match what csurf
|
|
|
|
|
// accepted. The error code EBADCSRFTOKEN is handled in app.ts.
|
|
|
|
|
this.doubleCsrfUtilities = doubleCsrf({
|
|
|
|
|
getSecret: () => this.common.secret_key,
|
|
|
|
|
getSessionIdentifier: (req) => (req.session ? req.session.id : ''),
|
|
|
|
|
cookieName: '_csrf',
|
|
|
|
|
cookieOptions: { sameSite: 'strict', path: '/', secure: false, httpOnly: true },
|
|
|
|
|
getCsrfTokenFromRequest: (req) => (req.body && req.body._csrf) || (req.query && req.query._csrf) ||
|
|
|
|
|
req.headers['csrf-token'] || req.headers['xsrf-token'] ||
|
|
|
|
|
req.headers['x-csrf-token'] || req.headers['x-xsrf-token']
|
|
|
|
|
});
|
|
|
|
|
this.csrfProtection = this.doubleCsrfUtilities.doubleCsrfProtection;
|
2026-07-19 17:41:25 -07:00
|
|
|
// Force-mints a fresh token for the current session, discarding any token
|
|
|
|
|
// cookie bound to a previous session or boot secret (used by the
|
|
|
|
|
// EBADCSRFTOKEN error path in app.ts so a client retry succeeds).
|
|
|
|
|
this.reMintToken = (req, res) => this.doubleCsrfUtilities.generateCsrfToken(req, res, { overwrite: true });
|
2021-12-29 18:08:41 -05:00
|
|
|
}
|
|
|
|
|
mount(app) {
|
2024-06-10 12:40:37 -07:00
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'CSRF', msg: 'Setting up CSRF..' });
|
2021-12-29 18:08:41 -05:00
|
|
|
if (process.env.NODE_ENV !== 'development') {
|
|
|
|
|
app.use((req, res, next) => this.csrfProtection(req, res, next));
|
|
|
|
|
}
|
2024-06-10 12:40:37 -07:00
|
|
|
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'CSRF', msg: 'CSRF Set' });
|
2021-12-29 18:08:41 -05:00
|
|
|
return app;
|
|
|
|
|
}
|
|
|
|
|
;
|
|
|
|
|
}
|
|
|
|
|
export default new CSRF;
|