microsoft / microsoft/Huabu

[Bug]: Deleting an absent Space id can delete another Space by directory-title alias

Open
#90 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
146
Forks
20
Avg merge
2d 6h
Merged PRs (30d)
43

Description

Existing issues

I searched the open issues for Space deletion, directory deletion, and data loss and did not find the same problem.

What happened?

Deleting a nonexistent Space id can recursively delete a different, valid Space when the requested id matches that Space's title-derived directory name.

Example: a Space has stable id canvas-123 and title/directory AliasVictim/. DELETE /api/canvas/AliasVictim succeeds even though AliasVictim is not a Space id, then GET /api/canvas/canvas-123 returns 404 and the directory is gone.

The same fallback can resolve reserved workspace directories such as setting/, so this is a data-loss boundary rather than only an incorrect status code.

I reproduced the behavior on current PR #83 HEAD and again on its exact merge base 973c22ce, so the bug predates that PR.

Expected behavior

Deleting an absent stable Space id must return a not-found outcome and leave every existing Space and workspace-owned directory unchanged. Any orphan-cleanup path must operate only on a directory whose persisted record proves it belongs to the requested stable id.

Steps to reproduce

  1. Start Huabu with a disposable workspace.
  2. Create a Space titled AliasVictim; record the returned stable canvas-* id.
  3. Confirm the Space is readable through its stable id.
  4. Send DELETE /api/canvas/AliasVictim even though no Space has that stable id.
  5. Observe a 200 response.
  6. Fetch the real stable id and inspect the workspace: the request now returns 404 and AliasVictim/ has been recursively removed.

This occurred deterministically in both tested revisions.

Root cause

DiskSpaceRepository.beginDelete() creates a CanvasStore for the unverified requested id because absent ids are allowed to enter orphan cleanup. finish() calls CanvasStore.destroy(). Its canvasRoot(requestedId) lookup calls canvasDirName(), which falls back to the requested id itself when no stable-id index entry exists. destroy() then recursively removes that fallback path.

Relevant locations:

  • apps/server/src/modules/storage/backends/disk/space-repository.ts (beginDelete / finish)
  • apps/server/src/modules/storage/backends/disk/legacy/canvas-store.ts (destroy)
  • apps/server/src/modules/workspace/disk/canvas-dirs.ts (canvasDirName fallback)

Regression acceptance criteria

  • Deleting an absent id that matches another Space's directory/title reports not found and preserves that Space byte-for-byte.
  • A request targeting setting cannot remove workspace settings.
  • Legitimate stable-id deletion still removes structured state and composed blobs.
  • Any supported orphan cleanup resolves ownership from persisted stable identity rather than an arbitrary fallback directory name.
  • Coverage exercises a cold directory index as well as a warm one.

System information

Source checkout on Linux 6.17.0-35-generic x86_64, Node.js v24.16.0, pnpm 10.34.3. Reproduced against PR #83 HEAD 69a6a4e4 and merge base 973c22ce using isolated temporary workspaces.

Sensitive information

This report contains only synthetic Space ids, titles, and temporary-workspace observations; no credentials or private documents are included.

— posted by OpenAI Codex (via the issue-tracker skill)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with beginDelete and finish in apps/server/src/modules/storage/backends/disk/space-repository.ts, then trace destroy in legacy/canvas-store.ts and the fallback in workspace/disk/canvas-dirs.ts. Reproduce deletion with a title-derived directory, the reserved setting directory, and cold and warm indexes. Done means absent ids return not found, existing data is preserved, legitimate stable-id deletion still works, and orphan cleanup verifies persisted ownership.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.