openvibers/openvibe.billing Commits
Open redirect fixed (roadmap WS-R task 5): the sign-in next let "/<TAB>/evil.com" through, which browsers read as "//evil.com" (they drop tab and newline characters and read a backslash as "/"); any control character or backslash in next now goes home. test/open-redirect.test.js feeds it the known bypasses
Changes
@@@ -61,6 +61,9 @@ const NOTICES = {};function sanitizeNext(v) {++ // Browsers drop tab and newline characters from a URL and read a backslash as "/": "/<TAB>/evil.com" would++ // leave the site. A next with any control character or backslash goes home.++ if (typeof v === 'string' && /[\u0000-\u001f\u007f\\]/.test(v)) return '/';const s = String(v || '');if (s.length > 300 || !/^\/(?![/\\])/.test(s) || s.startsWith('/auth/')) return '/';return s;nnew file mode 100644@@@ -0,0 +1,20 @@++'use strict';++// Open redirect regression (WS-R task 5): the sign-in "next" never leaves the site. Browsers drop tab and++// newline characters from a URL and read a backslash as "/", so "/<TAB>/evil.com" is "//evil.com" to them;++// until 2026-09-26 it got through here.++const assert = require('assert');++const { sanitizeNext } = require('../server/console/index');++++const config = { baseUrl: 'https://site.openvibe.test', networkUrl: 'https://openvibe.network' };++const home = (v) => {++ const out = String(sanitizeNext(v, config));++ const host = new URL(out.replace(/[\t\n\r]/g, ''), 'https://site.openvibe.test').host;++ return host === 'site.openvibe.test' || host === 'openvibe.network';++};++for (const v of ['//evil.com', '/\\evil.com', '\\\\evil.com', 'https://evil.com', 'http:evil.com', '/\t/evil.com', '/\n/evil.com', '\t//evil.com',++ ' //evil.com', '/\\/evil.com', 'https:/\\evil.com', '///evil.com', 'javascript:alert(1)', '/%09/evil.com']) {++ assert.ok(home(v), `${JSON.stringify(v)} stays on the site (got ${JSON.stringify(sanitizeNext(v, config))})`);++ assert.ok(!/^\s*javascript:/i.test(String(sanitizeNext(v, config))), JSON.stringify(v));++}++assert.strictEqual(sanitizeNext('/settings?tab=a', config), '/settings?tab=a', 'a plain relative path is kept');++console.log('open redirect: all checks passed');
@@ -61,6 +61,9 @@ const NOTICES = {
@@ -61,6 +61,9 @@ const NOTICES = {
};
};
function sanitizeNext(v) {
function sanitizeNext(v) {
+ // Browsers drop tab and newline characters from a URL and read a backslash as "/": "/<TAB>/evil.com" would
+ // leave the site. A next with any control character or backslash goes home.
+ if (typeof v === 'string' && /[\u0000-\u001f\u007f\\]/.test(v)) return '/';
const s = String(v || '');
const s = String(v || '');
if (s.length > 300 || !/^\/(?![/\\])/.test(s) || s.startsWith('/auth/')) return '/';
if (s.length > 300 || !/^\/(?![/\\])/.test(s) || s.startsWith('/auth/')) return '/';
return s;
return s;
new file mode 100644
new file mode 100644
@@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
+'use strict';
+// Open redirect regression (WS-R task 5): the sign-in "next" never leaves the site. Browsers drop tab and
+// newline characters from a URL and read a backslash as "/", so "/<TAB>/evil.com" is "//evil.com" to them;
+// until 2026-09-26 it got through here.
+const assert = require('assert');
+const { sanitizeNext } = require('../server/console/index');
+
+const config = { baseUrl: 'https://site.openvibe.test', networkUrl: 'https://openvibe.network' };
+const home = (v) => {
+ const out = String(sanitizeNext(v, config));
+ const host = new URL(out.replace(/[\t\n\r]/g, ''), 'https://site.openvibe.test').host;
+ return host === 'site.openvibe.test' || host === 'openvibe.network';
+};
+for (const v of ['//evil.com', '/\\evil.com', '\\\\evil.com', 'https://evil.com', 'http:evil.com', '/\t/evil.com', '/\n/evil.com', '\t//evil.com',
+ ' //evil.com', '/\\/evil.com', 'https:/\\evil.com', '///evil.com', 'javascript:alert(1)', '/%09/evil.com']) {
+ assert.ok(home(v), `${JSON.stringify(v)} stays on the site (got ${JSON.stringify(sanitizeNext(v, config))})`);
+ assert.ok(!/^\s*javascript:/i.test(String(sanitizeNext(v, config))), JSON.stringify(v));
+}
+assert.strictEqual(sanitizeNext('/settings?tab=a', config), '/settings?tab=a', 'a plain relative path is kept');
+console.log('open redirect: all checks passed');