Skip to content

icp-project: loading a project does not confine the paths its manifest names #865

Open

Description

@raymondk

What

icp project bundle checks that every path a manifest names resolves inside the project (SourceEscapesProject, EnvVarEscapesProject, ImageEscapesProject, DependencyOutsideWorkspace in operations/bundle.rs). Loading a project does not: project.rs joins each manifest path onto its base directory and reads it through the FileSystem it is given, and HostFileSystem is the plain disk with no root.

That is the right behaviour for the CLI, where the project is the user's own tree and reading ../shared/args.bin is a feature. It is the wrong behaviour for a project that came from somewhere else: a bundle handed to a deploy service by an agent, for example. There, a manifest can name ../../etc/hostname (or an absolute path) as an init_args file or an environment-variable file, and the loader reads it into the project model during consolidate_manifest, before any caller can inspect the loaded project. The contents then leave the machine at install, as the canister's arguments or settings.

Why the fix belongs in the loader

A consumer could re-implement the manifest discovery (canister globs, per-canister manifests, argument and variable files, dependency projects by path:) to validate paths before loading, but that duplicates the loader and has to track every upstream field that holds a path. The loader already resolves every path through one seam, the FileSystem trait, so confinement at that seam covers all of them at once.

Proposed change

A files::Confined filesystem: the host filesystem held to one directory. Every path is resolved against the root before the disk sees it (relative paths joined, . dropped, .. applied), and a path that comes to rest outside the root, by climbing, by being absolute elsewhere, or through a symlink inside the root that points out, is refused. Predicates answer false for such paths, scratch directories are made under the root, and resolve hands out the real path for callers that need one outside the trait (a wasm runtime mounting a directory).

Nothing changes for the CLI, which keeps using HostFileSystem. A host that loads a project from elsewhere hands the loader a Confined instead.

Context

Motivated by dfinity/imcp2#230, which deploys icp project bundle archives through icp-project from an MCP server and currently carries its own index-backed filesystem to get this guarantee.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions