Skip to content

fix(checkpoint): keep snapshots inside the checkpoint directory - #1173

Open
rascal-sl wants to merge 1 commit into
Nano-Collective:mainfrom
rascal-sl:fix-checkpoint-snapshot-path-traversal
Open

fix(checkpoint): keep snapshots inside the checkpoint directory#1173
rascal-sl wants to merge 1 commit into
Nano-Collective:mainfrom
rascal-sl:fix-checkpoint-snapshot-path-traversal

Conversation

@rascal-sl

Copy link
Copy Markdown
Contributor

Closes #1148.

The problem

A snapshot is keyed by its path relative to the workspace:

const relativePath = path.relative(this.workspaceRoot, absolutePath);

captureFiles does not require the file to be inside the workspace, so a file above it comes back as ../name. saveCheckpoint then joined that straight onto the checkpoint's files directory:

const filePath = path.join(filesDir, relativePath);
await fs.writeFile(filePath, content, 'utf-8');

which writes outside files/. restoreCheckpoint had the same join when reading snapshots back, driven by metadata.filesChanged off disk.

Worth noting file-snapshot.ts already refuses to restore or delete a path outside the workspace. The gap was only on the checkpoint copy.

The fix

getCheckpointDir already contains this exact containment rule for checkpoint names, so this reuses it as isSafeSnapshotPath, expressed on the relative path so it can run before a base directory exists. path.normalize first, so a/../../b is caught as well as a leading .., and absolute paths are rejected.

It runs at two points, which are two different trust boundaries:

  • On save, before the metadata is built, so filesChanged and the files actually written cannot disagree.
  • On restore, because metadata.json is on-disk state that may have been written by a version without the first check, or edited by hand.

An unsafe path is skipped with a warning rather than aborting the checkpoint, matching how file-snapshot.ts already handles the same situation.

Test plan

Two tests added to checkpoint-manager.spec.ts.

Confirmed the first fails without the source change:

✘ CheckpointManager does not write a snapshot outside the checkpoint directory
  snapshot escaped the files directory
  Value is not `false`: true

That true is existsSync finding the snapshot written outside files/. With the fix, npx ava source/services/checkpoint-manager.spec.ts passes 28 tests. The second test covers a normal workspace file so the guard cannot over-block.

tsc --noEmit and biome check are clean.

Three cli-integration tests fail in the full suite. They fail identically on upstream/main with this branch's changes stashed, so they are pre-existing and unrelated.


Tisankan Jeyakumar
Developed and verified

@rascal-sl

Copy link
Copy Markdown
Contributor Author

The red check is Package Audit, and it is a registry timeout rather than an advisory:

POST https://registry.npmjs.org/-/npm/v1/security/advisories/bulk error (23). Will retry in 10 seconds.
TimeoutError: The operation was aborted due to timeout
##[error]Process completed with exit code 1

This branch changes two source files and adds a changeset, no dependency or lockfile changes, so there is nothing here for pnpm audit to have found. Every other check passes, including unit tests, type checks, lint, format, Semgrep and CodeQL. A re-run should clear it; I cannot trigger one from a fork.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] checkpoint-manager.ts path traversal - writes outside workspace

1 participant