Address CodeQL findings on the OIDC auth flow
- Open redirect / client-side XSS (app-base.js): the post-login `redirect` read from the URL fragment was assigned straight to window.location. Add a same-origin guard (safeInternalPath) that rejects absolute URLs, protocol-relative "//host"/"/\\host", and scheme targets like "javascript:". Apply it in consumeTokenFragment and logInRedirect. - Server-side defense in depth: sanitize `redirect` when storing OidcState and when building the callback fragment (utils/safe_redirect.js, shared + unit-tested). - Missing rate limiting: throttle the unauthenticated auth endpoints (/login, /oidc/start, /oidc/callback) with express-rate-limit (60/IP/15m). Set `trust proxy: 1` so req.ip reflects the real client behind OpenResty. Adds test/unit/safe_redirect.test.js; unit suite 77 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
'use strict';
|
||||
|
||||
const {describe, test} = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
|
||||
const {safeInternalPath} = require('../../utils/safe_redirect');
|
||||
|
||||
/**
|
||||
* safeInternalPath guards the OIDC post-login redirect against open-redirect
|
||||
* and script-scheme (XSS) targets. Only same-origin "/path" values pass.
|
||||
*/
|
||||
describe('safeInternalPath', () => {
|
||||
|
||||
test('allows plain same-origin paths', () => {
|
||||
assert.strictEqual(safeInternalPath('/'), '/');
|
||||
assert.strictEqual(safeInternalPath('/hosts'), '/hosts');
|
||||
assert.strictEqual(safeInternalPath('/dns?x=1'), '/dns?x=1');
|
||||
assert.strictEqual(safeInternalPath('/a/b/c#frag'), '/a/b/c#frag');
|
||||
});
|
||||
|
||||
test('rejects absolute URLs', () => {
|
||||
assert.strictEqual(safeInternalPath('https://evil.com'), '/');
|
||||
assert.strictEqual(safeInternalPath('http://evil.com/x'), '/');
|
||||
});
|
||||
|
||||
test('rejects protocol-relative and backslash host tricks', () => {
|
||||
assert.strictEqual(safeInternalPath('//evil.com'), '/');
|
||||
assert.strictEqual(safeInternalPath('/\\evil.com'), '/');
|
||||
});
|
||||
|
||||
test('rejects script / data schemes', () => {
|
||||
assert.strictEqual(safeInternalPath('javascript:alert(1)'), '/');
|
||||
assert.strictEqual(safeInternalPath('data:text/html,<script>'), '/');
|
||||
});
|
||||
|
||||
test('rejects non-path and non-string input', () => {
|
||||
assert.strictEqual(safeInternalPath('hosts'), '/'); // no leading slash
|
||||
assert.strictEqual(safeInternalPath(''), '/');
|
||||
assert.strictEqual(safeInternalPath(undefined), '/');
|
||||
assert.strictEqual(safeInternalPath(null), '/');
|
||||
assert.strictEqual(safeInternalPath({}), '/');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user