Fix issues

This commit is contained in:
ProgrammerIn-wonderland
2026-06-03 21:11:27 -04:00
parent 07781cd681
commit f69c92e9a8
5 changed files with 34 additions and 2 deletions
@@ -1299,6 +1299,20 @@ describe('AuthService (integration)', () => {
expect(a).toBe(b);
expect(a).toMatch(/^app-/);
});
it.each([
'javascript:alert(document.domain)',
'data:text/html,<script>alert(1)</script>',
'file:///etc/passwd',
'vbscript:msgbox(1)',
])('throws 400 for non-http(s) scheme %s', async (origin) => {
// These parse fine via `new URL()` but must never become a
// bootstrap app `index_url` — that would be a stored XSS /
// code-execution vector when launched as `iframe.src`.
await expect(
authService.appUidFromOrigin(origin),
).rejects.toMatchObject({ statusCode: 400 });
});
});
describe('getUserAppToken', () => {
+10
View File
@@ -1633,6 +1633,16 @@ export class AuthService extends PuterService {
#originFromUrl(url: string): string | null {
try {
const parsed = new URL(url);
// A real web origin is always http(s). `new URL()` happily parses
// `javascript:`, `data:`, `file:`, `vbscript:`, etc.; if one of
// those slips through it ends up persisted as an app `index_url`
// (see AppStore.createFromOrigin) and later loaded as `iframe.src`
// — an XSS/code-execution primitive. Reject anything that isn't
// http(s) so the bootstrap path matches AppDriver's validateUrl
// allow-list.
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return null;
}
return `${parsed.protocol}//${parsed.hostname}${parsed.port ? `:${parsed.port}` : ''}`;
} catch {
return null;
+8
View File
@@ -20,6 +20,7 @@
import { v4 as uuidv4 } from 'uuid';
import { PuterStore } from '../types';
import { HttpError } from '../../core/http/HttpError.js';
import { validateUrl } from '../../util/validation.js';
/**
* Persistence + cache for the `apps` table.
@@ -375,6 +376,13 @@ export class AppStore extends PuterStore {
* `#isOriginBootstrapApp` checks.
*/
async createFromOrigin(uid, origin) {
// Bootstrap apps persist `origin` straight into `index_url`, which is
// later loaded as `iframe.src`. Enforce the same http(s) scheme
// allow-list as the AppDriver create/update path so a caller-supplied
// `javascript:`/`data:`/`file:` origin can never become a stored,
// launchable code-execution vector.
validateUrl(origin, { key: 'origin' });
const fields = {
name: uid,
title: uid,
+1 -1
View File
@@ -356,7 +356,7 @@ async function UIWindowLogin (options) {
},
onAppend: function (this_window) {
if ( options.authError ) {
$(this_window).find('.login-error-msg').html(options.authError).fadeIn();
$(this_window).find('.login-error-msg').html(html_encode(options.authError)).fadeIn();
}
if ( ! window.disable_login_autofocus )
{
+1 -1
View File
@@ -138,7 +138,7 @@ function UIWindowSignup (options) {
center: true,
onAppend: function (el_window) {
if ( options.authError ) {
$(el_window).find('.signup-error-msg').html(options.authError).fadeIn();
$(el_window).find('.signup-error-msg').html(html_encode(options.authError)).fadeIn();
}
if ( ! window.disable_signup_autofocus )
{