# How Modly's Artifact Registry Manages Workspace Assets: Security, Classification, and IPC

> Discover how Modly's artifact registry secures, classifies, and manages workspace assets via path validation, auto-classification, and metadata extraction for efficient renderer process integration.

- Repository: [lightningpixel/modly](https://github.com/lightningpixel/modly)
- Tags: internals
- Published: 2026-08-15

---

**Modly's artifact registry stores user-generated assets in a workspace-asset library exposed through an Electron IPC service, enforcing strict path validation, automatic asset classification by file type, and secure metadata extraction before surfacing entries to the renderer process.**

Modly is an open-source creative platform that handles complex 3D assets, animations, and scene manifests within a controlled workspace environment. The core of its asset management system resides in [`electron/main/artifact-registry-service.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/artifact-registry-service.ts), which implements a safety-first registry that normalizes paths, classifies capabilities, and exposes a curated library to the UI via type-safe IPC channels defined in [`src/shared/types/assetLibrary.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/types/assetLibrary.ts).

## Safety-First Path Normalization

The registry treats all filesystem paths as untrusted input until validated. When a request arrives, the service first normalizes the supplied *workspacePath* using `normalizeWorkspaceAssetPath` before performing any disk operations.

The function calls `assertSafeWorkspaceRelativePath` (lines 84-94) to enforce four critical constraints:

- The path must be workspace-relative (no absolute paths)
- URL-encoded escapes are forbidden
- Directory traversal sequences (`..`) are rejected
- The path must reside under allowed roots (**Workflows** or **Exports**)

```typescript
export function normalizeWorkspaceAssetPath(workspaceDir, workspacePath) {
  const safePath = assertSafeWorkspaceRelativePath(workspacePath)   // validates no traversal, encoded escapes, or absolute paths
  const root = resolve(workspaceDir)
  const absolutePath = resolve(root, ...safePath.split('/'))
  const back = relative(root, absolutePath)
  if (!back || back.startsWith('..') || isAbsolute(back))
    throw new Error('Workspace library path escapes the workspace root')
  return { workspacePath: safePath, absolutePath }
}

```

This defense-in-depth approach ensures that even if a renderer process is compromised, the main process refuses to access files outside the designated workspace directory.

## Asset Classification and Capability Detection

Once a path is validated, the registry determines the asset's high-level capability using `classifyAssetLibraryCandidate` (lines 18-43). This function inspects file extensions and special suffixes (such as [`.landmarks.v1.json`](https://github.com/lightningpixel/modly/blob/main/.landmarks.v1.json)) to assign a typed capability, preview kind, and openability flag.

| Extension / Pattern | Capability | Preview Kind | Openable |
|---------------------|------------|--------------|----------|
| `.glb` / `.gltf` (with rig metadata) | `rigged-mesh` | `3d-model` | ✅ |
| `.glb` / `.gltf` (no rig metadata) | `mesh` | `3d-model` | ✅ |
| `.obj`, `.stl` | `mesh` | `binary` | ❌ (list-only) |
| `.bvh`, `.npz` | `animation-motion` | `binary` | ❌ |
| [`.world.json`](https://github.com/lightningpixel/modly/blob/main/.world.json) | `generated-world` | `text` | ❌ |
| [`.scene.json`](https://github.com/lightningpixel/modly/blob/main/.scene.json) | `scene-manifest` | `text` | ❌ |
| Text files (`json`, `txt`, `md`) | — | `text` | ❌ |
| Anything else | — | `binary` | ❌ |

The resulting `AssetLibraryClassification` feeds into the final `AssetLibraryEntry` record, allowing the UI to render appropriate icons, preview panels, and action menus based on the asset type.

## Metadata Extraction and Link Validation

For JSON artifacts, the registry extracts embedded metadata through `readMetadata`. This includes critical linkage fields such as `sourceWorkspacePath` (a linked mesh source), `manifestWorkspacePath` (a linked manifest), `artifactId`, `versionId`, and provenance data.

All linked paths undergo secondary validation via `safeLinkedWorkspacePath` (lines 59-76), which reuses the same safety checks as the primary path normalizer to prevent traversal attacks through metadata references. This ensures that a malicious scene manifest cannot instruct the registry to open files outside the workspace.

## Building the Asset Library Entry

The `buildEntry` function (lines 25-50) orchestrates the creation of a complete `AssetLibraryEntry` object that matches the TypeScript interface declared in [`src/shared/types/assetLibrary.ts`](https://github.com/lightningpixel/modly/blob/main/src/shared/types/assetLibrary.ts). The assembly process follows a strict pipeline:

1. Normalizes the input path using `normalizeWorkspaceAssetPath`
2. Retrieves filesystem stats (size, modified time)
3. Classifies the asset capability
4. Reads embedded metadata for linked resources
5. Resolves source and manifest links with validation
6. Populates timestamps, warnings, and openability flags

This entry becomes the canonical representation of the asset for all subsequent UI operations.

## Enumerating Workspace Assets

The `listWorkspaceAssetLibrary` function provides the primary discovery mechanism for the UI. It walks two allowed roots—**Workflows** and **Exports**—while skipping hidden directories, temporary files, and internal suffixes defined in `SKIPPED_DIRS` and `INTERNAL_SUFFIXES`.

```typescript
export async function listWorkspaceAssetLibrary(request) {
  const workspacePaths = (await Promise.all(
    ALLOWED_ROOTS.map(root => collectFiles(request.workspaceDir, root))
  )).flat().sort()
  const entries = await Promise.all(
    workspacePaths.map(path => buildEntry(request.workspaceDir, path))
  )
  return { success: true, entries: entries.filter(e => e.state !== 'unsupported') }
}

```

Renderer processes invoke this through the `workspace:library:list` IPC channel:

```typescript
const result = await ipcRenderer.invoke('workspace:library:list');
if (result.success) {
  console.table(result.entries.map(e => ({
    id: e.id,
    type: e.capability,
    preview: e.previewKind,
    openable: e.openable,
  })));
}

```

## Reading and Previewing Assets

When the UI requests a specific asset, `readWorkspaceAssetLibraryEntry` validates any provided `sourceWorkspacePath`, builds the complete entry, and generates a preview payload via `previewEntry`. The preview system supports three distinct modes:

- **3-D models**: Returns `{ kind: '3d-model', viewerKind: 'glb' | 'gltf' }` for the UI to initialize a WebGL viewer
- **Text files**: Returns the first 64 KB of content with a truncation flag and total size
- **Binary files**: Returns a generic description (`message: 'Binary preview is unavailable.'`) preventing large file transfers over IPC

```typescript
const read = await ipcRenderer.invoke('workspace:library:read', {
  workspacePath: 'Workflows/my_model.glb',
});
if (read.success) {
  console.log('Metadata', read.entry);
  console.log('Preview payload', read.preview); // { kind: '3d-model', viewerKind: 'glb' }
}

```

## Opening Assets for Editing

The `openWorkspaceAssetLibraryEntry` function handles requests to open an asset in the editor. It checks the `entry.openable` flag set during classification; however, if the request includes a `sourceWorkspacePath` parameter, the operation succeeds regardless of the asset's own openability status. This allows users to open linked source meshes from derived artifacts like animations or generated worlds.

```typescript
const open = await ipcRenderer.invoke('workspace:library:open', {
  workspacePath: 'Workflows/my_model.glb',
});
if (!open.success) {
  console.error('Cannot open:', open.error.message);
}

```

## IPC Service Registration

The registry exposes its functionality to the renderer through three dedicated IPC channels registered in `registerWorkspaceAssetLibraryIpcHandlers` (lines 54-66):

| Channel | Handler |
|---------|---------|
| `workspace:library:list` | `listWorkspaceAssetLibrary` |
| `workspace:library:read` | `readWorkspaceAssetLibraryEntry` |
| `workspace:library:open` | `openWorkspaceAssetLibraryEntry` |

During application startup, the main process registers these handlers:

```typescript
import { registerWorkspaceAssetLibraryIpcHandlers } from './electron/main/artifact-registry-service';
registerWorkspaceAssetLibraryIpcHandlers({
  ipcMain,
  getWorkspaceDir: () => app.getPath('userData') + '/workspace',
});

```

This architecture ensures that all filesystem access remains privileged within the main process, while the renderer operates on sanitized, immutable `AssetLibraryEntry` objects.

## Summary

- **Path Security**: The registry uses `normalizeWorkspaceAssetPath` and `assertSafeWorkspaceRelativePath` to prevent directory traversal and ensure all operations remain within allowed **Workflows** and **Exports** roots.
- **Automatic Classification**: The `classifyAssetLibraryCandidate` function inspects file extensions and metadata to assign capabilities like `rigged-mesh` or `animation-motion`, determining UI behavior and openability.
- **Metadata Safety**: Linked assets in JSON metadata are validated through `safeLinkedWorkspacePath`, preventing malicious manifests from accessing files outside the workspace.
- **Structured Entries**: The `buildEntry` pipeline produces type-safe `AssetLibraryEntry` objects that encapsulate paths, stats, capabilities, and previews.
- **Controlled IPC Exposure**: Three IPC channels (`workspace:library:list`, `workspace:library:read`, `workspace:library:open`) provide the renderer with read-only access to asset metadata and previews without direct filesystem privileges.

## Frequently Asked Questions

### How does Modly prevent path traversal attacks in the artifact registry?

The registry implements defense-in-depth through `assertSafeWorkspaceRelativePath` (lines 84-94 in [`electron/main/artifact-registry-service.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/artifact-registry-service.ts)), which rejects absolute paths, URL-encoded characters, and `..` sequences. After normalization, `normalizeWorkspaceAssetPath` verifies that the resolved absolute path remains within the workspace root by checking the relative path back to root does not start with `..`.

### What file types does Modly classify as openable 3D assets?

According to the classification logic in `classifyAssetLibraryCandidate`, only `.glb` and `.gltf` files are marked as `openable: true`. These receive the `rigged-mesh` or `mesh` capability and a `3d-model` preview kind. Other formats like `.obj` and `.stl` are classified as `mesh` but remain list-only (`openable: false`), while animation and scene files are never directly openable.

### How does the registry handle linked assets and dependencies?

When parsing JSON artifacts, `readMetadata` extracts fields like `sourceWorkspacePath` and `manifestWorkspacePath`. These linked paths undergo validation via `safeLinkedWorkspacePath` (lines 59-76), which applies the same security checks as the primary path normalizer. This ensures that dependencies cannot reference files outside the workspace or traverse into protected directories.

### Can external extensions access the workspace asset library?

The registry is exposed exclusively through the internal IPC channels registered in `registerWorkspaceAssetLibraryIpcHandlers`. While external extensions may interact with the workspace, the service relies on the workspace directory guard implemented in [`electron/main/extension-path-guard.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/extension-path-guard.ts) to provide additional sanity checks, ensuring that any extension-mediated access conforms to the same root constraints and skipping rules as the core registry.