# Modly Extension Path Guard Security: How It Prevents Path Traversal Attacks

> Modly's extension path guard prevents path traversal attacks. Learn how its identifier validation and root-confinement checks secure your extensions directory from malicious writes.

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

---

**Modly's extension path guard blocks path traversal attacks through strict identifier validation and root-confinement checks, ensuring malicious extensions can never write files outside the designated extensions directory.**

Modly stores third-party extensions on the filesystem under a dedicated extensions directory. Because these extensions come from external sources, a malicious actor could attempt to escape this sandbox using path traversal sequences like `../../etc/passwd`. The extension path guard in `lightningpixel/modly` implements a defense-in-depth strategy to neutralize this threat.

## How the Extension Path Guard Works

The security mechanism operates across two distinct validation layers: identifier sanitization and path resolution containment. Each layer targets a different stage of the attack chain.

### Stage 1: Identifier Validation with `assertSafeExtensionId`

The first line of defense validates the raw extension identifier before any filesystem operation occurs. This function rejects identifiers that could be interpreted as paths.

```ts
import { assertSafeExtensionId } from './extension-path-guard'

// ✅ Valid identifier
const safeId = assertSafeExtensionId('my-cool-extension');
// Returns: 'my-cool-extension'

// ❌ All of these throw security errors
assertSafeExtensionId('../evil');      // "must not contain path separators"
assertSafeExtensionId('/absolute');    // "must not be an absolute path"
assertSafeExtensionId('');             // "must not be empty"
assertSafeExtensionId('..');           // reserved relative-path trick

```

The validator enforces these constraints (lines 5-31 of [`electron/main/extension-path-guard.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/extension-path-guard.ts)):

- **Non-string rejection** — Prevents type confusion attacks
- **Empty/whitespace blocking** — Eliminates bypass attempts
- **`.` and `..` prohibition** — Blocks relative-path traversal tricks
- **Absolute path denial** — Rejects `/…` or `C:\…` patterns
- **Path separator ban** — Forbids `/` or `\` characters
- **Pattern enforcement** — Requires `^[a-z0-9][a-z0-9._-]*$`

By refusing identifiers containing path components, the guard ensures attackers cannot inject `../` sequences directly into folder names.

### Stage 2: Root Confinement with `resolvePathWithinRoot`

Even if a later processing step accidentally concatenates malicious input, this helper guarantees the final resolved path stays within the intended root directory.

```ts
import { resolvePathWithinRoot } from './extension-path-guard'

const extensionsRoot = '/home/user/.modly/extensions';

// ✅ Safe resolution
const safePath = resolvePathWithinRoot(extensionsRoot, 'subdir/file.txt');
// → '/home/user/.modly/extensions/subdir/file.txt'

// ❌ Traversal attempt blocked
resolvePathWithinRoot(extensionsRoot, '../../evil.txt');
// Throws: "Resolved path escapes root: ../../evil.txt"

```

This function performs normalization checks (lines 34-45):

- Empty relative paths are rejected
- `..` as the entire path is blocked
- Paths starting with `../` are forbidden
- Absolute leaf paths are denied

### Combined Protection with `resolveExtensionPathWithinRoot`

A convenience wrapper chains both protections in a single call (lines 48-50):

```ts
import { resolveExtensionPathWithinRoot } from './extension-path-guard'

const extensionPath = resolveExtensionPathWithinRoot(
  extensionsRoot,
  'my-extension'
);
// Guarantees both safe ID and confined path

```

## Internal-Only Directory Isolation

The guard reserves special prefixes for Modly's internal operations, preventing malicious code from masquerading as system directories. Any folder beginning with a dot is treated as installer-internal and skipped by discovery logic.

| Prefix | Purpose |
|--------|---------|
| `.modly-backup-` | Stores previous extension versions during updates |
| `.modly-staging-` | Holds incomplete installations before atomic swap |
| `.modly-incomplete` | Marks failed or interrupted installations |

```ts
import {
  buildExtensionBackupPath,
  buildExtensionStagingPath,
} from './extension-path-guard'

// Both validate the ID and guarantee containment
const backupPath = buildExtensionBackupPath(
  extensionsRoot,
  'my-extension',
  '20241012T1530'
);
// → '/home/user/.modly/extensions/.modly-backup-my-extension-20241012T1530'

const stagingPath = buildExtensionStagingPath(
  extensionsRoot,
  'my-extension',
  'tmp123'
);
// → '/home/user/.modly/extensions/.modly-staging-my-extension-tmp123'

```

These reserved names cannot be registered as valid extension IDs (lines 52-66), ensuring temporary directories remain protected from payload pollution.

## Security Impact Assessment

The extension path guard delivers three critical protections:

- **Arbitrary file write prevention** — By sanitizing identifiers before filesystem access, the guard blocks attempts to place files outside the extensions directory
- **Archive extraction hardening** — `resolvePathWithinRoot` serves as the final defense when extracting untrusted archives, rejecting malicious filenames before any write occurs
- **Install pipeline integrity** — Internal-only prefixes guarantee that backup and staging directories cannot be compromised, with checks applied on both the Electron and Python sides of Modly

## Key Implementation Files

| File | Security Role |
|------|---------------|
| [`electron/main/extension-path-guard.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/extension-path-guard.ts) | Core implementation: ID validation, path resolution, internal directory helpers |
| [`electron/main/extension-path-guard.test.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/extension-path-guard.test.ts) | Comprehensive test suite verifying rejection of unsafe inputs |
| [`electron/main/extension-install-utils.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/extension-install-utils.ts) | Production usage during extension installation and updates |
| [`electron/main/artifact-registry-service.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/artifact-registry-service.ts) | Extension loading service relying on path guard guarantees |

## Summary

- **Dual-layer validation** — `assertSafeExtensionId` sanitizes inputs; `resolvePathWithinRoot` enforces containment
- **Traversal sequence blocking** — Forbidden characters include `/`, `\`, `.`, `..`, and absolute path indicators
- **Pattern-based enforcement** — Extension IDs must match `^[a-z0-9][a-z0-9._-]*$`
- **Root confinement guarantee** — All resolved paths are verified to stay within the extensions directory
- **Internal directory protection** — Reserved prefixes isolate installer operations from extension discovery

## Frequently Asked Questions

### What happens if an extension ID contains path traversal sequences like `../`?

The `assertSafeExtensionId` function throws an error with message "must not contain path separators" before any filesystem operation occurs. This prevents the malicious identifier from ever being used in path construction (source: lines 5-31 of [`electron/main/extension-path-guard.ts`](https://github.com/lightningpixel/modly/blob/main/electron/main/extension-path-guard.ts)).

### Can a malicious zip archive bypass the guard through extracted filenames?

No. The `resolvePathWithinRoot` function validates every resolved path against the root directory. Even if an archive contains a file named [`../../evil.txt`](https://github.com/lightningpixel/modly/blob/main/../../evil.txt), the resolution check detects the escape attempt and throws "Resolved path escapes root" before any file write (source: lines 34-45).

### How does the guard protect Modly's internal operations during extension updates?

Reserved prefixes (`.modly-backup-`, `.modly-staging-`, `.modly-incomplete`) are excluded from valid extension ID patterns. This prevents attackers from creating extensions that impersonate internal directories, ensuring backup and staging operations remain isolated from extension code (source: lines 52-66).

### Is the extension path guard active on both sides of Modly's architecture?

Yes. According to the source implementation, the guard's checks are applied on both the Electron (TypeScript) side and the Python side of Modly, providing consistent protection across the entire extension lifecycle from installation through execution.