mirror of
https://github.com/HeyPuter/puter.git
synced 2026-07-08 08:12:15 +00:00
Fix issues
This commit is contained in:
@@ -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', () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user