# SEAS Desktop security audit

Date: 8 October 2026

Reviewed commit: `3530ddf` — `Add file and folder uploads to media apps`

The review predates the 9 October rename from Aster to SEAS. Product names and
file links in this report have been updated for the renamed project; the
findings still refer to the reviewed snapshot.

## Scope and method

This was a quick review of the preview server, VFS, media uploads, app loading,
Workshop import/runtime validation, and rendering of user-controlled text.
It used static inspection and a small, isolated Node.js probe of VFS path
lookup. It was not a full penetration test or dependency vulnerability scan.
No live exploitation of embedded apps or resource exhaustion was attempted.

The three findings below remain open. No fixes were made during this audit.

## 1. Highest priority: embedded games share SEAS's privileges

**Evidence:** [`mountIframeApp`](../dist/js/app-manager.js), starting at line 9,
creates frames without a `sandbox` attribute. The local `/fps/` and `/rts/`
apps share SEAS's origin. Both load Three.js from a CDN without an integrity
check:

- [`dist/fps/index.html`](../dist/fps/index.html), line 26.
- [`dist/rts/index.html`](../dist/rts/index.html), line 35.

**Potential impact:** If the CDN-delivered script were compromised, it would
run with the embedded page's privileges. In these same-origin frames, it could
access the parent desktop and execute code in its context, allowing access to
session data such as uploaded media. This is a conditional supply-chain and
isolation risk; the audit found no evidence of a compromised dependency.
Cross-origin frames already have restrictions from the browser's same-origin
policy.

**Suggested fixes:**

- Vendor the existing Three.js version used by these games, retaining its
  license, so their runtime does not depend on CDN-delivered JavaScript.
- If keeping CDN scripts, add verified Subresource Integrity hashes and the
  appropriate `crossorigin` attribute.
- Isolate embedded apps using a separate origin or a carefully configured
  iframe sandbox with only the permissions they need.
- For same-origin content, combining `allow-scripts` and `allow-same-origin`
  does not provide a reliable sandbox boundary.
- Check game loading, pointer lock, fullscreen, and input after changing the
  iframe permissions.

References: [MDN iframe sandbox guidance](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe#sandbox)
and [MDN Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource_Integrity).

## 2. Medium priority: media imports have no resource limits

**Evidence:** [`importMediaFiles`](../dist/js/system/media-import.js), starting
at line 15, accepts selected files without a file-count or size budget.
[`decodeImage`](../dist/js/apps/image-viewer-model.js), starting at line 3,
decodes images without an application-level dimension or pixel-count limit.

**Potential impact:** Very large folders can cause excessive processing and
VFS/UI work. Oversized images can require substantial decoded bitmap memory.
These inputs could freeze or exhaust the browser tab. This is primarily an
availability issue. Uploaded File/Blob references do not necessarily load
every selected file's complete contents into RAM immediately.

**Suggested fixes:**

- Add configurable limits for selected file count, individual file size, and
  total accepted bytes per import.
- Add an image-dimension or pixel-count limit, preferably checked before full
  decoding with bounded header inspection.
- Report limit failures clearly and preserve the existing atomic import
  behavior so rejected batches do not leave partial VFS writes.

No resource-exhaustion test was performed during this audit.

## 3. Low priority: VFS lookup accepts inherited object properties

**Evidence:** [`MountManager.getNode`](../dist/js/system/mount-manager.js),
line 34, reads `node.children[path[index]]` without checking that the property
belongs to the directory itself. Some directory child maps are ordinary
JavaScript objects with inherited properties.

A small probe mounted an empty `/c` directory and confirmed:

```text
getNode(['/c', '__proto__']) === Object.prototype  → true
getNode(['/c', 'constructor']) === Object          → true
getNode(['/c', 'missing'])                         → null
```

**Potential impact:** Inherited values become bogus filesystem nodes even
though no corresponding directory entries exist. They can confuse path
existence checks and CRUD operations. The probe demonstrated incorrect lookup;
it did not demonstrate prototype pollution or a host-filesystem escape.

**Suggested fixes:**

- Require `Object.hasOwn(node.children, component)` before returning a child.
- Use null-prototype directory maps consistently, including when creating or
  cloning directories.
- Apply consistent filename validation across ordinary VFS operations, as
  well as upload and package import operations.
- Add focused regressions for inherited names and ordinary missing paths.

## Positive observations

- Reviewed upload and Workshop UI paths generally use `textContent` for
  user-controlled text.
- Workshop runs a validated interpreter with an instruction budget.
- The preview server binds to `127.0.0.1`.
- Uploaded media remains in the session VFS; the upload feature adds no
  server-side file storage endpoint.

These observations apply to the reviewed paths and do not constitute a
security guarantee for the entire application.
