# How portable-files Enables Workspace Portability Across Machines in OpenWork

> Discover how OpenWork's portable-files module ensures workspace portability across machines. Learn about secure file path validation, atomic export/import, and safe agent transfer.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-09

---

**The `portable-files` module in OpenWork enables workspace portability by whitelisting specific `.opencode` subdirectories, validating file paths to block secrets and traversal attacks, and providing atomic export and import functions that serialize user-defined agents, plugins, and tools for safe transfer between machines.**

OpenWork stores reusable workspace components—agents, plugins, and tools—inside a hidden `.opencode` directory. According to the different-ai/openwork source code, the `portable-files` module (located in [`apps/server/src/portable-files.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/portable-files.ts)) implements a strict validation and serialization layer that makes these resources truly portable while preventing the accidental inclusion of sensitive environment files or non-portable dependencies.

## Whitelisted Resource Architecture

The portability system relies on an explicit whitelist defined by the `ALLOWED_PORTABLE_PREFIXES` constant. Only files residing under `.opencode/agents/`, `.opencode/plugins/`, or `.opencode/tools/` are considered candidates for export. This architectural decision ensures that machine-specific configurations, dependency folders, and secret files remain local to the original workspace.

When `listPortableFiles(workspaceRoot)` executes, it traverses the `.opencode` directory and returns an ordered array of objects containing the relative `path` and string `content` for each whitelisted file. This explicit enumeration prevents implicit file inclusion and guarantees that the receiving machine reconstructs an identical resource structure. Complementary logic in [`apps/server/src/portable-opencode.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/portable-opencode.ts) handles sanitization of the top-level OpenCode configuration that accompanies these resources.

## Path Validation and Security Controls

Before any file enters the portable pipeline, `normalizePortablePath` processes the input to eliminate Windows-style separators, collapse duplicate slashes, and reject empty or malicious paths. The function specifically blocks directory traversal attempts (such as [`../outside.md`](https://github.com/different-ai/openwork/blob/main/../outside.md)) and filters reserved segments defined in `RESERVED_PORTABLE_SEGMENTS`, notably excluding `node_modules` directories.

Additional security layers include the `isEnvFilePath` check, which prevents `.env` files from being serialized, and strict validation within `planPortableFiles(workspaceRoot, value)`. This planning function converts raw portable data into `PlannedPortableFile` objects with absolute destination paths, throwing an `ApiError` (defined in [`apps/server/src/errors.js`](https://github.com/different-ai/openwork/blob/main/apps/server/src/errors.js)) immediately if any entry violates the whitelist or path safety rules.

## The Export and Import Workflow

Exporting a workspace's portable resources requires calling `listPortableFiles`, which produces a serializable array of file objects suitable for version control or network transmission. On the destination machine, `writePortableFiles(workspaceRoot, value, {replace})` handles reconstruction.

The write operation first validates the input through `planPortableFiles`, then optionally removes existing portable files when the `replace` flag is set to `true`. It utilizes `ensureDir` (from [`apps/server/src/utils.js`](https://github.com/different-ai/openwork/blob/main/apps/server/src/utils.js)) to create necessary directory structures before writing content, returning a list of successfully written files to confirm the operation. This idempotent approach ensures that repeated imports produce consistent results without duplicating artifacts.

## Implementation Examples

The following patterns demonstrate common portability tasks using the OpenWork API.

Export portable files from a workspace:

```typescript
import { listPortableFiles } from "./portable-files.js";

const workspaceRoot = "/path/to/my/workspace";
const portable = await listPortableFiles(workspaceRoot);
console.log(portable);
/* Example output:
[
  { path: ".opencode/agents/openwork.md", content: "# agent\n" },

  { path: ".opencode/plugins/router.json", content: '{"enabled":true}\n' },
  { path: ".opencode/tools/database.ts", content: "export default {};\n" },
]
*/

```

Import portable files into a new workspace:

```typescript
import { writePortableFiles } from "./portable-files.js";

const newWorkspace = "/path/to/new/workspace";
const portableData = [
  { path: ".opencode/agents/openwork.md", content: "# agent\n" },

  { path: ".opencode/plugins/router.json", content: '{"enabled":true}\n' },
  { path: ".opencode/tools/database.ts", content: "export default {};\n" },
];

// Write and replace any existing portable files
await writePortableFiles(newWorkspace, portableData, { replace: true });

```

Validate a portable file list before writing:

```typescript
import { planPortableFiles } from "./portable-files.js";

try {
  const planned = planPortableFiles(workspaceRoot, [
    { path: ".opencode/agents/bad/../evil.md", content: "oops" },
  ]);
  // Won't reach here – an error is thrown for invalid paths
} catch (err) {
  console.error(err.message); // "invalid_portable_file_path"
}

```

## Summary

- The `portable-files` module strictly limits portability to three whitelisted directories within `.opencode`: `agents/`, `plugins/`, and `tools/`.
- **Path normalization** via `normalizePortablePath` prevents directory traversal attacks and normalizes cross-platform path separators.
- **Security filters** block `.env` files and `node_modules` directories from serialization, protecting secrets and reducing payload size.
- **Export operations** use `listPortableFiles` to generate serializable `{path, content}` arrays, while **import operations** use `writePortableFiles` with an optional `replace` flag for idempotent reconstruction.
- The `planPortableFiles` function provides pre-validation, throwing `ApiError` exceptions before any filesystem mutations occur.

## Frequently Asked Questions

### What files does OpenWork consider portable?

OpenWork considers only files within `.opencode/agents/`, `.opencode/plugins/`, and `.opencode/tools/` as portable resources. The `ALLOWED_PORTABLE_PREFIXES` constant in [`apps/server/src/portable-files.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/portable-files.ts) enforces this whitelist, ensuring that only user-defined configurations and scripts—not dependencies or environment files—transfer between machines.

### How does portable-files prevent security vulnerabilities?

The module implements multiple validation layers: `normalizePortablePath` rejects traversal sequences like `../` and normalizes separators, `isEnvFilePath` blocks environment files containing secrets, and `RESERVED_PORTABLE_SEGMENTS` excludes `node_modules`. Additionally, `planPortableFiles` validates all paths before `writePortableFiles` touches the filesystem, throwing `ApiError` for any violation.

### Can I overwrite existing portable files when importing?

Yes. The `writePortableFiles` function accepts an optional `{replace: true}` parameter. When enabled, the function first removes existing portable files in the target workspace before writing the new set, ensuring a clean state. Without this flag, the function may fail or leave stale files depending on the specific implementation details in [`apps/server/src/portable-files.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/portable-files.ts).

### What is the difference between planPortableFiles and writePortableFiles?

`planPortableFiles` performs validation and path resolution without modifying the filesystem, returning a structured `PlannedPortableFile` array or throwing `ApiError` if validation fails. `writePortableFiles` calls the planning function internally, then executes the actual directory creation (via `ensureDir`) and file writes. Use `planPortableFiles` for pre-flight checks or custom processing, and `writePortableFiles` for standard import operations.