# The test that deleted my home folder

> Source: <https://www.shuffle-on.com/blog/the-test-that-deleted-my-home-folder>
> Published: 2026-11-05 07:16:18+00:00

[Blog · security](https://www.shuffle-on.com/blog/tag/security)

# The test that deleted my home folder

An AI assistant proved a dangerous bug by running it on the real machine. The full forensics, the cost, and what I changed.

On a Saturday morning, in about **10 seconds**, my home folder disappeared. Not a disk failure, not ransomware. A test.

An AI coding assistant was fixing a bug in a command-line tool I had built, my own local AI agent. The bug was serious: a `/delete` command that accepted a path like `../..` could escape its data folder and delete things far outside it. The assistant wrote a fix and a test for it. Then, to prove that the test really catches the bug, it put the **old, unfixed code back** and ran the test against it.

On the real machine. In my real home folder.

The test did exactly what it was written to prove: the old code followed the path out of its sandbox and deleted everything my user account could delete. This post is the full story: how it happened, how I found the exact command afterwards, what it cost, and what I changed so it cannot happen again.

## Contents · 9 sections

## TL;DR [#](#tldr)

- A test meant to prove that a **dangerous bug** exists will**trigger that bug** if you run it against the unfixed code. If the bug deletes files, the test deletes files.
- The test isolated one data folder, but **the home directory and temporary folders were the real ones** .
- The AI assistant was running with automatic approval of shell commands, on the host, not in a container.
- There was **no current backup** of the home folder.
- Recovery took about **2 days** . The fixes: destructive tests only in throwaway containers, a real nightly backup with a freshness alarm, a post-reboot check script, and narrower permissions everywhere.

## What disappeared [#](#what-disappeared)

The deletion ran for about ten seconds and removed **everything my user could delete**:

- my entire home folder: configuration, project working copies, scripts, notes, browser and desktop settings, and the assistant’s own session files;
- the shared temporary folder, including the terminal multiplexer’s sockets, so every terminal session I had open died at once;
- data folders outside home that my user happened to own or that were **world-writable** : a media server’s metadata, an OCR service’s state, a decision service’s state, cached renders.

What survived is just as instructive:

- everything owned by root or inside root-owned folders: system files, `/etc` ,`/usr` ,`/opt` ;
- data owned by container users, **unless** its folder was group- or world-writable;
- the NAS shares and other mounted disks that my user could not write to.

In other words, **file permissions were my only protection**, and they protected exactly what they were set to protect. Every overly permissive folder was a hole.

## How I found the exact command [#](#how-i-found-the-exact-command)

At first it was a mystery: no attacker, no crash, nothing in the obvious logs. The investigation took most of a day.

### 1. Pin down the window. [#](#1-pin-down-the-window)

Services that read files from my home folder logged their last success and first failure. One read a script successfully at 10:12:16, and the first “file not found” appeared at 10:12:27. The deletion ran inside those eleven seconds.

### 2. Characterise the footprint. [#](#2-characterise-the-footprint)

Exactly two kinds of places were hit, home and temporary folders, plus anything else my user could unlink. Root-owned and container-owned files survived. So the deletion ran **as my user**, not as root.

### 3. Rule things out. [#](#3-rule-things-out)

Scheduled jobs, the agent’s own background tasks, monitoring exporters, sandboxed helpers, the MCP servers: each was checked against its logs and its code. None ran anything destructive in that window.

### 4. Find who was running commands. [#](#4-find-who-was-running-commands)

Two AI assistant sessions were active. One was doing web research. The other was fixing two path-handling bugs in my agent’s command-line tool. A shell command from that session started at **10:12:07**, ran for longer than two minutes and outlived the deletion window. Immediately after it, the session reported that it was **no longer logged in**: its own credentials file had just been deleted.

### 5. Recover the deleted transcript from the raw disk. [#](#5-recover-the-deleted-transcript-from-the-raw-disk)

The session’s transcript was in the home folder, so it was gone. But deleted data stays on disk until it is overwritten. Scanning the raw disk for the unique identifier of that tool call recovered **351 records** of the lost transcript, including the exact command:

1. apply the fix to the path-handling code;
2. run the new tests;
3. **stash the fix** , putting the old vulnerable code back;
4. **run the same tests against the old code “to prove they fail”** ;
5. restore the fix (never reached).

The recovered test file even described the bug in its own words: the `/delete ..` command wiped the agent’s data folder, and `/delete ../..` wiped the home folder. The test called the delete command with paths like `..`, `../..`, `../../..` and `/`. The fixture redirected **one** data folder to a temporary location, but the old code resolved at least one path from the **real** home directory.

So the test proved the bug. Thoroughly.

## The second wave: the first reboot [#](#the-second-wave-the-first-reboot)

The damage was not finished. **3 MySQL databases** had their data folders set to be **group-writable by my user**. Their core files were deleted, but MySQL kept running on the already-open file handles, so everything looked fine.

The **first reboot**, the next day, made the deleted files disappear for real. All three databases failed to start. They were restored from the nightly SQL dumps taken a few hours earlier, the one backup that was working. The damaged folders were kept aside for comparison.

Lesson inside the lesson: **a running process can hide a deletion until it restarts.** After an incident like this, reboot deliberately while you are still in recovery mode, not weeks later by surprise.

## Recovery [#](#recovery)

Most of the machine was rebuilt from what was **not** in the home folder:

- **Infrastructure as code saved me.** The cluster’s manifests lived in a Git server inside the cluster, with its own storage, so everything deployed from them could be recreated.
- **Container data** owned by container users survived; services were reattached to it.
- **Databases** came back from the nightly dumps.
- **Configuration that only lived in my home folder** had to be rebuilt by hand, from logs, from running processes and from memory. That was most of the two days.

The things that were truly lost were the ones that existed in one place only: local notes, some scripts that were never committed, desktop settings, and the assistant’s session history.

## What I changed [#](#what-i-changed)

### Destructive tests run only in throwaway containers [#](#destructive-tests-run-only-in-throwaway-containers)

The rule is now absolute: any test that exercises deletion, path traversal, or anything that writes outside its own folder runs in a disposable container with **no access to the real home folder**, ideally with **no network** and a read-only root filesystem. If it escapes its sandbox, it destroys an empty container.

Proving that a test fails on the old code is good practice. Doing it on the host is not.

### Isolate everything the code can reach, not just one folder [#](#isolate-everything-the-code-can-reach-not-just-one-folder)

The test isolated one configured data folder. The bug used a different path. A test for a path-traversal bug must assume the code under test **will** reach for real paths, so the environment must make those paths harmless: a fake home directory, a fake temporary folder, a container.

### Guard the AI assistant, not only the code [#](#guard-the-ai-assistant-not-only-the-code)

The assistant was running with automatic approval of shell commands, on a machine full of irreplaceable state. I still use AI assistants every day, but:

- risky work happens in containers or disposable worktrees, not on the live host;
- secrets are no longer reachable from plain files the assistant can read;
- privileged operations go through a separate, audited path with a second factor, and are not done by the assistant directly.

An assistant’s mistake is still my mistake. It ran on my machine with my permissions.

### Real backups, and an alarm when they go stale [#](#real-backups-and-an-alarm-when-they-go-stale)

The home folder now has a **nightly encrypted, deduplicated backup** to separate storage, plus a **freshness check** that alerts me when any backup set is older than it should be. Before the incident, the home backup had silently stopped **11 months earlier**. A backup nobody checks is a backup that does not exist.

### A post-reboot check script [#](#a-post-reboot-check-script)

After every reboot, one script checks the kernel, every important service and container, the cluster’s pods, the security pipeline, and the age of the latest backup, and prints `OK` or `FAIL` for each. It catches the “worked until the restart” class of problems in minutes. The same script has since found other hidden failures after routine reboots.

### Least privilege on data folders [#](#least-privilege-on-data-folders)

Every folder that was group- or world-writable “for convenience” was a casualty. Data folders are now owned by the service that uses them, with no write access for anyone else, including me.

### Commit or lose it [#](#commit-or-lose-it)

Anything that matters is in Git, pushed to a server that is not my home folder: scripts, configuration, notes, even the record of what was changed and why.

## Checklist: before anything runs `rm -rf` on your behalf [#](#checklist-before-anything-runs-rm--rf-on-your-behalf)

- Destructive tests run in a **disposable container** , never on the host.
- Tests of path handling replace **home and temporary folders** , not just the app’s data folder.
- AI assistants and automation run with the **least permissions** that let them work, and risky commands need a**human approval** .
- No data folder is **writable by users** who do not own it.
- There is a **nightly backup** of every irreplaceable folder, and an**alert** when it gets old.
- After an incident, **reboot on purpose** to flush out deletions hidden behind open files.
- Everything that matters is **committed and pushed** somewhere else.

## Frequently asked questions [#](#frequently-asked-questions)

### Can deleted files be recovered after `rm -rf`? [#](#can-deleted-files-be-recovered-after-rm--rf)

Sometimes, partially. Deleted data stays on the disk until it is overwritten, so stop writing to that disk as soon as possible. Here, scanning the raw disk recovered part of the lost session transcript, enough to find the cause. It did not bring back the home folder; only backups do that.

### Why did MySQL keep working after its files were deleted? [#](#why-did-mysql-keep-working-after-its-files-were-deleted)

On Linux a deleted file remains available to processes that already have it open. The database kept using its open files until the reboot closed them, and then the data was gone.

### Is it safe to let AI coding assistants run shell commands? [#](#is-it-safe-to-let-ai-coding-assistants-run-shell-commands)

It is as safe as the environment you give them. On a disposable container or worktree, the worst case is losing the container. On your real machine with automatic approval, the worst case is what happened here. The assistant did what a careless engineer could do, at machine speed.

### What is the single most important fix? [#](#what-is-the-single-most-important-fix)

A tested, monitored backup. Every other measure reduces the chance of a disaster. A backup is what makes it survivable.

## The lesson [#](#the-lesson)

The bug was found, fixed and tested correctly. The disaster came from **where** the proof was run. Every protection that worked was boring: file permissions, infrastructure as code, a nightly database dump. Every gap was something “temporary” or “convenient”: a stale backup, a writable folder, a test on the real machine. Make your safety boring, automatic and checked, and run your experiments somewhere you can afford to lose.
