Skip to content

Repository files navigation

CanonicalPath / CanonicalFS

Deterministic path identity across runtimes. Root-bound filesystem access where real I/O begins.

English | Language coverage | Targets | Verification | Security

Go: Go 1.24+, Path+FS TypeScript: TS 6 and Node 22, Path+Client JavaScript: ESM/browser, Path only Unity: Unity 2022.3+, Path+Client C#/.NET: .NET 8, Path only PowerShell: 5.1 and 7, Path+Client Bash: Bash and curl, Transport CMD/BAT: Windows CMD, Transport Python: Python 3.10+, Path only Rust: Rust 2021, Path only C: C11, Path only C++: C++20, Path only Swift: Swift 6.2, Path only Kotlin: Kotlin 1.9 and JVM 17, Path only Dart/Flutter: Dart 3, Path only Haxe: Haxe interp, Path only GDScript/Godot: Godot, Path only

Path+FS = authoritative root-bound filesystem boundary; Path+Client = lexical identity plus daemon/client helpers; Transport = daemon wrapper only; Path only = lexical identity only.

Every runtime has a path library. CanonicalPath is for the moment where a path leaves one runtime and enters another.

A Unity editor tool may pass a path to a TypeScript MCP gateway, a Go daemon, a PowerShell script, a browser UI, WSL, Windows, and back again. Each layer can interpret .., slashes, drive-relative paths, UNC paths, symlinks, device names, and missing files differently. That is how path bugs become security bugs, broken artifacts, and hard-to-debug agent behavior.

The key idea:

  • CanonicalPath says what path identity the tool means.
  • CanonicalFS decides whether real filesystem I/O may touch that path under a root.
  • Non-Go runtimes share lexical identity and delegate security-sensitive I/O to the Go daemon.

What this repository provides

Layer Purpose Filesystem access Security claim
CanonicalPath Deterministic lexical identity, comparison, serialization, aliases, and shared test vectors. No Stable identity contract, not an I/O boundary.
CanonicalFS Root-bound filesystem access for real reads/writes. Yes Go implementation is the authoritative filesystem security boundary.
Go daemon Capability-style HTTP/RPC boundary for non-Go clients. Yes Other languages delegate security-sensitive I/O to this daemon.
Client/runtime ports Lexical helpers, wrappers, transports, Unity bridge adapters. Usually no Lexical/client-only unless explicitly documented otherwise.

The split is intentional: path identity is not file access.

Why not just use path.normalize, realpath, or safe-join code?

Use standard libraries for ordinary local path manipulation. Use CanonicalPath when a path must cross process, language, operating-system, editor, or agent boundaries without changing meaning.

CanonicalPath is designed for:

  • agent tools and MCP servers that must validate scoped paths before touching a project;
  • build systems and generators that reference files before they exist;
  • Unity/editor pipelines that need scoped paths such as Assets/..., Packages/..., artifacts, caches, and temp sessions;
  • Windows/WSL/macOS/Linux workflows where host paths and project-relative paths are not the same thing;
  • archive extraction and file tools where untrusted filenames must not escape a root;
  • shared test vectors so Go, TypeScript, JavaScript, Python, Dart, C#, Swift, Kotlin, C, Rust, C++, Haxe, GDScript, PowerShell, Unity, and wrappers stay aligned.

Current language coverage

Group Current surface Filesystem security boundary
Go canonicalpath, authoritative canonicalfs, daemon Yes: Go CanonicalFS / daemon
TypeScript canonicalpath, best-effort canonicalfs helpers, RPC/HTTP clients Delegates to Go daemon for adversarial I/O
JavaScript standalone/browser lexical canonicalpath No filesystem access
PowerShell 5.1 / 7 lexical module, typed HTTP client helpers, daemon transport Delegates to Go daemon
Bash / Windows CMD-BAT daemon transport wrappers Delegates to Go daemon
Unity C# managed lexical helpers, PathGuard/scoped validation, daemon transport, Burst-oriented surface Delegates to Go daemon
Python, Dart/Flutter, C#/.NET, Swift, Kotlin, C, Rust, C++, Haxe, GDScript/Godot vector-checked lexical canonicalpath surfaces Lexical/client-only until native root-bound FS or daemon transport is reviewed

See docs/language-coverage.md and docs/language-targets.md for the detailed matrix.

Security boundary rule

For security-sensitive filesystem operations, do not build paths with string concatenation, Join, normalize, or realpath and then open them later.

Use the root-bound filesystem layer:

  • In Go, use CanonicalFS directly.
  • In TypeScript, Unity, C#, Python, PowerShell, Bash, CMD/BAT, or other runtimes, validate and serialize with the local CanonicalPath surface, then delegate real I/O to the Go daemon unless that runtime has a separately reviewed root-bound implementation.

Problems this is meant to prevent

  • Path traversal through .., absolute paths, encoded separators, or mixed separators.
  • Windows-specific surprises: drive-relative paths like C:foo, UNC paths, reserved device names, alternate data streams, trailing dots/spaces, and NUL bytes.
  • Symlink and reparse-point escape from a trusted project root.
  • Time-of-check/time-of-use bugs from validating a path string and opening it later.
  • Zip Slip style archive writes outside the destination directory.
  • Agent/MCP tools writing into the wrong project, cache, artifact, package, or temp root.
  • Broken identity when one runtime stores a path and another runtime interprets it differently.

Package identity and release

  • Canonical repository: https://github.com/romanilyin/canonicalpath.
  • TypeScript/npm package: @romanilyin/canonicalpath.
  • JavaScript standalone npm package: @romanilyin/canonicalpath-standalone.
  • Go module: github.com/romanilyin/canonicalpath/packages/go.
  • Unity UPM package: com.romanilyin.canonicalpath.
  • Unity npmjs scoped-registry release candidate: com.romanilyin.canonicalpath@2026.6.19-1.
  • Release version: 2026.6.19-1.
  • License model:
    • Repository default: MIT.
    • @romanilyin/canonicalpath: MIT.
    • @romanilyin/canonicalpath-standalone: MIT.
    • Go canonicalpath / canonicalfs: MIT unless a package directory says otherwise.
    • Unity UPM package com.romanilyin.canonicalpath: LicenseRef-Stinger-Royalty-Free-EULA-1.0.

The earlier full source release plan is tracked in docs/release-2026.5.18-2.md; Unity registry publication history is tracked in docs/release-unity-2026.6.14-1.md. The Go module tag is packages/go/v0.2026.5-18.2; see docs/release-process.md for the release tag policy.

Status notes

  • Planned language targets and allocation-check gates are tracked in spec/language-targets.json and summarized in docs/language-targets.md.
  • Planned package directories are skeleton/not implemented placeholders unless listed as supported or as an early bridge target.
  • Go canonicalfs.Rename is intentionally unsupported on Go 1.24 because os.Root does not expose a root-bound rename method there. Do not replace it with filepath.Join(root, rel) plus os.Rename.

Quick verification

After installing dependencies, run:

pnpm install
pnpm verify

For Go race-sensitive filesystem tests, also run:

pnpm go:race

For active allocation and memory smoke gates, run:

pnpm alloc

If the pnpm shim is not available, use corepack pnpm for the same commands. Detailed verification commands live in docs/verification.md.

When not to use this

CanonicalPath is not meant to replace normal path utilities for simple, trusted, single-language applications.

You probably do not need it if:

  • paths never cross a language/process/OS boundary;
  • filenames are fully trusted and intentionally allowed to point anywhere;
  • you only need local display formatting;
  • your OS sandbox/container already handles the security boundary and you do not need cross-runtime identity.

Philosophy

This is not an attempt to invent a universal filesystem standard. It is a practical contract for codebases where path strings are already crossing too many boundaries.

xkcd Standards

Image: xkcd “Standards”. The point is taken seriously: CanonicalPath should be used as a narrow contract at tool boundaries, not as a reason to rewrite every path helper in every program.

License

This repository uses package-level licensing.

The repository default and the non-Unity CanonicalPath / CanonicalFS packages are licensed under the MIT License. See LICENSE.md and LICENSES/MIT.txt.

The Unity UPM/npm package com.romanilyin.canonicalpath under packages/unity is licensed separately under Stinger Royalty-Free EULA 1.0. See packages/unity/LICENSE.md and packages/unity/LICENSE.ru.md.

The MIT License grants rights to use the code. It does not grant trademark rights to use the CanonicalPath name, logo, or branding to imply official endorsement.

About

Cross-platform path identity and root-bound filesystem access.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages