cloudflare / cloudflare/sandbox-sdk

Backup silently drops entire top-level directories: `... a/b` exclude expansion over-excludes on mksquashfs 4.5 (ubuntu:22.04 base)

Open
#859 0 comments 0 reactions 1 assignee Claimed by @scuffi View on GitHub
bug
Dominant language
TypeScript
Stars
1.1k
Forks
114
Avg merge
22h 42m
Merged PRs (30d)
14

Description

## Summary

`createBackup()` with a multi-component exclude pattern (e.g. `excludes: ['.cache/foo/*/node_modules']`) silently drops the **entire first path component** (`.cache/`) from the archive — not just the matched subpath. The backup reports success and the archive looks valid, so the data loss only surfaces on a later `restoreBackup()`, by which point the original container filesystem may be gone.

We hit this in production-like usage: our workspace's `.entrydesk/` directory (application state) vanished from every backup because we passed `excludes: ['node_modules', '.entrydesk/sites/*/node_modules']`. Every sleep→wake cycle then restored a workspace missing that tree.

## Root cause

Two ingredients, both in this repo:

1. `packages/sandbox-container/src/services/backup-service.ts` expands every user exclude `P` into `[P, `... ${P}`]` before writing the `-ef` file for `mksquashfs -wildcards`:

```ts
const userExcludePatterns = normalizedExcludes.flatMap((pattern) => [
pattern,
`... ${pattern}`
]);
```

The gitignore-derived patterns take the same expansion (`relativePaths.flatMap((path) => [path, `... ${path}`])`), and gitignore paths are very often multi-component (`dist/foo`, `packages/x/build`), so `gitignore: true` is affected even more broadly.

2. The container image (`Dockerfile`, `FROM ubuntu:22.04`) ships **squashfs-tools 4.5**, which has a matching bug: a non-anchored `... a/b` pattern excludes the whole first component `a` at the archive root, instead of only `a/b`. squashfs-tools **4.6.1** (ubuntu 24.04) and 4.7 behave correctly.

## Reproduction (inside the sandbox container image, mksquashfs 4.5)

```bash
mkdir -p /ws/alpha/beta /ws/keep
touch /ws/alpha/file.txt /ws/keep/file.txt

printf '%s\n' '... alpha/beta/gamma' > /tmp/ex.txt # note: gamma does not even exist
mksquashfs /ws /tmp/out.sqsh -no-progress -wildcards -ef /tmp/ex.txt
unsquashfs -l /tmp/out.sqsh
# squashfs-root
# squashfs-root/keep
# squashfs-root/keep/file.txt
# -> alpha/ is gone entirely
```

Same commands on ubuntu:24.04 (squashfs-tools 4.6.1) keep `alpha/` and only exclude the named subpath. Verified on `@cloudflare/sandbox` 0.12.3; the expansion is unchanged on `main` as of today.

## Impact

- Any `BackupOptions.excludes` entry containing `/` silently truncates the archive at its first path component.
- `gitignore: true` can drop arbitrary top-level directories whenever an ignored path is nested.
- The failure is invisible at backup time (`createBackup` succeeds, plausible archive size) and destructive at restore time.

## Suggested fixes

Either (ideally both):

1. **Don't emit the `... P` variant for patterns containing `/`.** Anchored multi-component patterns already match from the source root; the recursive variant is only meaningful for single-component names. This fixes the data loss even on mksquashfs 4.5.
2. **Bump the base image / squashfs-tools to ≥ 4.6** so non-anchored multi-component patterns behave per the documented semantics.

A backup-time integrity check (e.g. verifying a known sentinel file made it into the archive) would also turn this class of bug from silent data loss into a loud failure, but that's a separate hardening suggestion.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.