{"id":50893976,"url":"https://github.com/pmarreck/rm_safe","last_synced_at":"2026-06-15T23:01:22.491Z","repository":{"id":333875162,"uuid":"1113251459","full_name":"pmarreck/rm_safe","owner":"pmarreck","description":"Prevent yourself from getting burned by `rm -rf`/`sudo rm -rf` style mistakes by diverting those files to the Trash instead.","archived":false,"fork":false,"pushed_at":"2026-06-06T19:47:41.000Z","size":90,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"yolo","last_synced_at":"2026-06-06T21:17:50.214Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pmarreck.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-12-09T18:14:03.000Z","updated_at":"2026-06-06T19:47:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pmarreck/rm_safe","commit_stats":null,"previous_names":["pmarreck/rm-safe","pmarreck/rm_safe"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pmarreck/rm_safe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmarreck%2Frm_safe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmarreck%2Frm_safe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmarreck%2Frm_safe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmarreck%2Frm_safe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pmarreck","download_url":"https://codeload.github.com/pmarreck/rm_safe/tar.gz/refs/heads/yolo","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmarreck%2Frm_safe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34383468,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-15T23:01:21.404Z","updated_at":"2026-06-15T23:01:22.478Z","avatar_url":"https://github.com/pmarreck.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"rm-safe\n======\n\n[![CI](https://github.com/pmarreck/rm_safe/actions/workflows/ci.yml/badge.svg?branch=yolo)](https://github.com/pmarreck/rm_safe/actions/workflows/ci.yml)\n[![Garnix](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fgarnix.io%2Fapi%2Fbadges%2Fpmarreck%2Frm_safe%3Fbranch%3Dyolo)](https://garnix.io/repo/pmarreck/rm_safe)\n\nSafe `rm` that moves files to Trash instead of permanently deleting them. Includes a small `rm` shim and guidance for macOS, common Linux sudoers setups, and NixOS.\n\nFeatures\n- Moves files/directories to OS trash (XDG trash on Linux, Finder Trash on macOS).\n- Refuses to touch protected locations (`/`, `/etc`, `/nix/store`, etc.).\n- Works with sudo via a wrapper, not shell aliases.\n- Restores manual trash entries with `--undo` or `--undo-picker` (gum or fzf).\n- Built-in test suite: `bin/rm-safe --test`.\n\n## Quick install (no dependencies)\n\nrm-safe runs on a stock macOS or Linux box with nothing to install:\n\n```sh\ngit clone https://github.com/pmarreck/rm-safe.git rm-safe\n# put the shim + universal bash implementation on your PATH\nln -s \"$PWD/rm-safe/bin/rm\"           ~/.local/bin/rm\nln -s \"$PWD/rm-safe/bin/rm-safe.bash\" ~/.local/bin/rm-safe.bash\n```\n\nThat's the whole install. `rm` now moves files to the trash instead of deleting\nthem. It works on the bash that ships with macOS (3.2) and on Linux.\n\n### Optional upgrades\n\n- **Speed:** install **luajit** (with LuaFileSystem) and the shim automatically\n  uses the faster `bin/rm-safe`. Without luajit you'll see a one-time note\n  (silence it with `RM_SAFE_QUIET=1`).\n- **Nix:** `nix develop` for a managed dev/test environment, or\n  `nix build .#rm-safe` / `.#rm-safe-bash` for packaged builds.\n\n### Choosing an implementation\n\nSet `RM_SAFE_BIN=/path/to/rm-safe.bash` (or the luajit `bin/rm-safe`) to force a\nspecific implementation for both `rm` and the tests.\n\nFiles\n- `bin/rm-safe.bash` — universal bash implementation; works on macOS bash 3.2 and Linux with no extra dependencies.\n- `bin/rm-safe` — faster LuaJIT implementation; used automatically when luajit+lfs is available.\n- `bin/rm` — wrapper shim: dispatches `RM_SAFE_BIN` override → luajit → bash fallback → system rm; warns (suppressible via `RM_SAFE_QUIET=1`) when falling back.\n- `bin/test/rm-safe_test` — undo/restore tests.\n- `bin/test/rm_override_test` — shim tests.\n- `bin/test/run-all` — runs the full test suite across luajit / bash-4+ / bash-3.2 lanes.\n\nPlatform notes\nWhy sudo setup differs by OS:\n- macOS `sudo` preserves the user `PATH` by default, so a user-level shim earlier in `PATH` (e.g., `~/bin/rm`) will still be found under sudo.\n- Most Linux distros set `secure_path` in sudoers, which **replaces** the user `PATH` with a fixed, root-owned path for safety. That means user-level shims are ignored unless you place a root-owned shim in the secure path.\n\nmacOS (defaults keep user PATH under sudo)\n- Symlink or copy `bin/rm` into `~/bin` (or any user directory already ahead of `/bin` on PATH).\n- `sudo rm …` will hit the shim because PATH is preserved by default.\n\nLinux with sudo `secure_path`\n- Create a root-owned override dir, e.g. `/usr/bin/overrides` (0755, root:root).\n- Install the shim there as `rm` and ensure `rm-safe` is reachable (same dir is simplest).\n- In sudoers (via `visudo`), prepend the override dir:\n  ```\n  Defaults secure_path=\"/usr/bin/overrides:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\"\n  ```\n- Avoid adding broad dirs like `/usr/local/bin` just to get the shim; overrides keeps blast-radius small.\n\nNixOS (declarative)\n- Build/install `rm-safe` and the shim into a root-owned path on PATH, e.g. `/run/wrappers/bin`.\n- Example snippet:\n  ```\n  # configuration.nix\n  {\n    security.sudo.extraConfig = ''\n      Defaults secure_path=/run/wrappers/bin:/usr/bin:/bin\n    '';\n    environment.systemPackages = [ rm-safe-package ];\n    environment.pathsToLink = [ \"/run/wrappers/bin\" ];\n  }\n  ```\n- Ensure `rm` wrapper in that path execs `rm-safe`; shim already uses `command -p rm` to reach the real rm when needed.\n\nClaude Code / AI agent integration\n- Tools like `nix develop` prepend `/nix/store/.../bin` to `PATH`, shadowing user-level `rm` shims with GNU `rm`. AI coding agents (Claude Code, Codex, etc.) run shell commands through these environments and can permanently delete files without realizing the safe wrapper is bypassed.\n- To prevent this in Claude Code, add a global `PreToolUse` hook that blocks bare `rm` in Bash calls:\n\n  **1. Create `~/.claude/hooks/block-rm/block-rm.sh`:**\n  ```bash\n  #!/bin/bash\n  set -u\n  COMMAND=$(jq -r '.tool_input.command')\n  if echo \"$COMMAND\" | grep -qE '\\brm\\b' \u0026\u0026 ! echo \"$COMMAND\" | grep -qE 'rm-safe'; then\n    echo \"BLOCKED: Use rm-safe instead of rm.\" \u003e\u00262\n    exit 2\n  fi\n  exit 0\n  ```\n  ```\n  chmod +x ~/.claude/hooks/block-rm/block-rm.sh\n  ```\n\n  **2. Add to `~/.claude/settings.json` under `hooks.PreToolUse`:**\n  ```json\n  {\n    \"matcher\": \"Bash\",\n    \"hooks\": [\n      {\n        \"type\": \"command\",\n        \"command\": \"~/.claude/hooks/block-rm/block-rm.sh\",\n        \"timeout\": 2\n      }\n    ]\n  }\n  ```\n\n  Any Bash tool call containing `rm` (but not `rm-safe`) will be rejected before execution. The agent sees the block message and retries with `rm-safe`.\n\nCautions\n- Log-rotate/cleanup scripts that expect permanent deletion will move files to trash when the shim is first in PATH; they should call `/bin/rm` explicitly if deletion is intended.\n- The shim warns to stderr if `rm-safe` is missing and falls back to system `rm`.\n\nTesting\n- Run all lanes (luajit / bash-4+ / bash-3.2): `bin/test/run-all`\n- Shim: `bin/test/rm_override_test`\n- Main tool: `bin/rm-safe --test`\n- Undo/restore: `bin/test/rm-safe_test`\n\nLicense\n- MIT (see LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmarreck%2Frm_safe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpmarreck%2Frm_safe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmarreck%2Frm_safe/lists"}