Skip to content

Security: slaviboy/3D-Design

Security

SECURITY.md

Planar Security and Privacy

Planar runs entirely in the browser. It has no backend and no accounts, and it collects no data. Its security work is about treating user-supplied files as untrusted and making sure the app can never reach the network.

Privacy guarantees

  • No telemetry, analytics, tracking, remote logging, crash reporting, cookies or identifiers.
  • Local storage only. All data stays in the browser's local storage (IndexedDB) or in files the user explicitly chooses.
  • Offline-verifiable. Every end-to-end test fails if the app requests any origin other than its own (see tests/e2e/fixtures.ts).

Network isolation

Production builds carry a Content-Security-Policy (vite.config.ts):

Directive Value Why
default-src 'self' Nothing loads from other origins
script-src 'self' 'wasm-unsafe-eval' No inline scripts and no eval. WebAssembly compilation is allowed for the bundled boolean kernel
style-src 'self' No inline <style> elements or style attributes
connect-src 'self' data: blob: fetch, XHR and WebSocket requests to other origins are blocked
img-src 'self' data: blob: Images from user files are decoded into blob or data URLs
worker-src 'self' blob: Only bundled workers and the offline service worker
manifest-src 'self' The web app manifest used for installation
object-src 'none' No plugins
base-uri 'self' Prevents base-tag hijacking
form-action 'none' The app never submits forms

Test hook. Builds made with vite build --mode e2e expose window.__planar so end-to-end tests can inspect the model. The normal npm run build output never includes it.

Limits of a meta-tag policy:

  • A policy set through a <meta> tag can't set frame-ancestors.
  • Deployments behind a web server should send the same policy as an HTTP header, and add frame-ancestors 'none'.

Untrusted input

Project files, imported 3D models and images are all treated as hostile.

  • No code execution. No eval, new Function, or string setTimeout (ESLint enforces this). No HTML strings are inserted as markup, and user-provided text (names, labels, dimensions) is always rendered as text.
  • Validation before use. Every parsed file is validated structurally before it touches the model:
    • finite numbers, and no NaN or Infinity;
    • index ranges and id references;
    • enum values;
    • array lengths.
  • Resource limits. File size, vertex and face counts, texture dimensions (clamped to the maximum texture size), and ZIP entry count and uncompressed size (to stop zip bombs).
  • Path safety. ZIP entries with .., absolute paths, or drive letters are rejected. Importers resolve external references (MTL files, textures, glTF buffers) only against files the user supplied. Remote URLs inside files are never fetched, and the user sees a warning.
  • Parsing in workers. Imports are parsed in a worker, so a malformed file can't freeze the interface, and parsing can be cancelled.
  • No silent corruption. A corrupt or unsupported file produces an explicit error and leaves the open model untouched.

Dependencies

  • Vetting. Runtime dependencies are kept to a minimum. Each one is checked for its license, maintenance and offline behavior (see ARCHITECTURE.md).
  • Lockfile. package-lock.json is committed, and installs use npm ci in automation.
  • Updates. npm audit is reviewed at every phase gate.

Reporting

Security issues should be reported privately to the project maintainers, not in public issue trackers.

Bundle audit (Phase 11, 2026-09-13)

The production build (dist/) was scanned for URLs and network APIs, as a check beyond the E2E network guard. Every hit is accounted for:

Finding Where Why it is safe
fetch and XMLHttpRequest in the Emscripten loader boolean.worker-*.js (manifold-3d) Loads the worker's own .wasm file from the same origin (credentials: 'same-origin'); the file is precached for offline use
fetch in three.js FileLoader and ImageBitmapLoader io.worker-*.js Reachable only through glTF loading. src/io/import/gltf.ts rewrites every buffer into one embedded binary chunk and drops images before the loader runs, so it has nothing to fetch. Other formats resolve references only among the picked files (fileSet.ts) and warn about external ones. tests/io/importFormats.test.ts asserts fetch is never called
http://www.w3.org/1999/xhtml io.worker-*.js (three.js) An XML namespace name passed to createElementNS, not a request
https://bit.ly/wb-precache workbox-*.js Text inside a Workbox console warning, never requested

No WebSocket, EventSource or sendBeacon call exists in the bundle. Even if one were added, the Content-Security-Policy above blocks connections to other origins.

To repeat the audit after a build:

grep -ohE "https?://[^\"' )]+" dist/*.js dist/assets/*.js | sort | uniq -c
grep -ohE "(fetch\(|new XMLHttpRequest|new WebSocket|sendBeacon|new EventSource)" dist/assets/*.js | sort | uniq -c

There aren't any published security advisories