How Modly's Artifact Registry Manages Workspace Assets: Security, Classification, and IPC
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, 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.
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)
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) 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 |
generated-world |
text |
❌ |
.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. The assembly process follows a strict pipeline:
- Normalizes the input path using
normalizeWorkspaceAssetPath - Retrieves filesystem stats (size, modified time)
- Classifies the asset capability
- Reads embedded metadata for linked resources
- Resolves source and manifest links with validation
- 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.
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:
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
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.
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:
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
normalizeWorkspaceAssetPathandassertSafeWorkspaceRelativePathto prevent directory traversal and ensure all operations remain within allowed Workflows and Exports roots. - Automatic Classification: The
classifyAssetLibraryCandidatefunction inspects file extensions and metadata to assign capabilities likerigged-meshoranimation-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
buildEntrypipeline produces type-safeAssetLibraryEntryobjects 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), 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 to provide additional sanity checks, ensuring that any extension-mediated access conforms to the same root constraints and skipping rules as the core registry.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →