{"id":31212639,"url":"https://github.com/dundalek/firewrap","last_synced_at":"2025-09-21T07:33:16.451Z","repository":{"id":292528017,"uuid":"671058305","full_name":"dundalek/firewrap","owner":"dundalek","description":"End-user oriented security application sandboxing","archived":false,"fork":false,"pushed_at":"2025-08-31T20:01:20.000Z","size":283,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-31T22:09:28.611Z","etag":null,"topics":["bubblewrap","firejail","sandboxing","security"],"latest_commit_sha":null,"homepage":"","language":"Clojure","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/dundalek.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":null,"dco":null,"cla":null}},"created_at":"2023-07-26T12:53:34.000Z","updated_at":"2025-08-31T20:01:24.000Z","dependencies_parsed_at":"2025-05-10T15:27:27.437Z","dependency_job_id":"8f209839-4f48-4b50-9fa2-23b5f02c3166","html_url":"https://github.com/dundalek/firewrap","commit_stats":null,"previous_names":["dundalek/firewrap"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dundalek/firewrap","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dundalek%2Ffirewrap","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dundalek%2Ffirewrap/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dundalek%2Ffirewrap/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dundalek%2Ffirewrap/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dundalek","download_url":"https://codeload.github.com/dundalek/firewrap/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dundalek%2Ffirewrap/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276210103,"owners_count":25603718,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-21T02:00:07.055Z","response_time":72,"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":["bubblewrap","firejail","sandboxing","security"],"created_at":"2025-09-21T07:33:15.078Z","updated_at":"2025-09-21T07:33:16.432Z","avatar_url":"https://github.com/dundalek.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Firewrap\n\nFirewrap is a tool to run programs in an isolated runtime environment using a container-type sandbox.\n\nIt is similar to [Firejail](https://github.com/netblue30/firejail), but based on [Bubblewrap](https://github.com/containers/bubblewrap).\n\nMotivation: There will be a lot of wasted effort creating wrappers and middlewares for AI agent tool use.\nWhat we need is first-class security mechanisms in programming languages and operating systems.\nIn the end AIs will just execute commands and call APIs with given authority.\n\n\u003e ⚠️ **Disclaimer**:  \nThis is an experimental and incomplete project.  \nThere are many security mechanisms that are [not yet implemented](#implemented-mechanisms).  \nAlways verify the source before running anything.\n\n## Install\n\nDependencies:\n- [Babashka](https://github.com/babashka/babashka)\n- [Bubblewrap](https://github.com/containers/bubblewrap)\n- Posix shell (optional for appimage wrapper)\n\n```\ngit clone https://github.com/dundalek/firewrap.git\ncd firewrap\nbin/firewrap\n```\n\nTODO using [bbin](https://github.com/babashka/bbin)\n\n## Getting started\n\nTo preview sandbox parameters when wanting to run a `cmd`:\n\n```\nfirewrap -b --dry-run -- cmd\n```\n\nIt is a good practice to start with `--dry-run` which prints sandbox parameters without executing anything.\n\nBy default the sandbox has minimal privileges and most programs would not be able to run.\nTherefore we use `-b` option for `--base` which is a preset which includes a reasonable base files but excludes home directory.\n\nTo actually run the command:\n\n```\nfirewrap -b -- cmd\n```\n\n## Philosophy and Design\n\n#### Full lisp-based programming language for specifying rules\n\n- Flexibility in creating composable abstractions\n  - Similar to Infrastructure-as-Code principles but for security.\n- Production grade tooling and IDE integration\n  - Completion, Signature help, Go to definition, References, Refactoring\n\n#### The Principle of Least Privilege\n\n- Process should have the lowest level of privilege needed to accomplish its task.\n- Secure by default\n  - Default Deny as a baseline: Everything is forbidden unless explicitly allowed.\n- Sometimes hard to achieve in practice.\n  - Be pragmatic to allow starting with wider sandboxes,\n    but have mechanisms to show warnings as a reminder to nudge to tighten in future iterations.\n\n#### Tooling\n\nCreating sandbox profiles takes a lot of effort.\nA good sandboxing solution should provide tools to assist with the effort.\nWorfklow to create a profile is an iteration loop of:\n\n1. Record trace\n    - Log program behavior inside an isolated environment like a VM using strace.\n2. Map to abstractions composed out of presets\n    - Using generated rules based on a trace as-is would make it unmanageable to have a confidence that sandbox is secure and does not contain extraneous rules.\n    - For example tools like [minijail](https://www.chromium.org/chromium-os/developer-library/guides/development/sandboxing/) and [Sydbox Pandora](https://git.sr.ht/~alip/syd/tree/main/item/pandora/README.md) also use tracing to help create profiles.\n\t    However, the output can be overwhelming and some undesired access can be easily overlooked.\n    - Composing the rules using higher-level presents makes it possible to reason about security of the sandbox.\n      There is an experimental [Trace helper tool](#trace-helper-tool) that explores the approach.\n3. Audit rules (idea)\n    - To make iteration and testing easier a tool could provide static analysis and report if rules are too tight or too loose. And only test the sandbox end-to-end by running it once static issues resolved.\n    - Possible sandbox rules problems:\n      - too tight - breaks program functionality\n      - too loose - exposes unnecessary resources, violates principle of least privilege\n\n## Concepts\n\n- [Presets](src/firewrap/preset) - Are reusable pieces that define security policy.\n- [Profiles](src/firewrap/preset) - Define sandbox environment for an application and are automatically used if application name matches, usually use one or more presets.\n- Levels - It is usually hard to create minimal profile that does not break any functionality.\n  - The idea is to have common levels that add privileges, from tightest `-b0` to widest `-b9`.  \n  Then based on risk one can:\n    - Start with tightest profile, observe broken program and increase level by trial-and-error until the program works.\n    - Or start with a wider profile and working program, and tighten level just until before the program stops working.\n  - For practical purposes have an easy to use best-effort base, which does not provide much extra security, but is still useful for the most common case of isolating user data.\n\n## Usage\n\n\u003c!-- FIREWRAP_HELP_BEGIN --\u003e\n```\nRun program in sandbox\n\nUsage: firewrap [\u003coptions\u003e --] \u003ccommand\u003e [\u003cargs\u003e]\n\nOptions:\n  --profile        \u003cprofile\u003e\n  --dry-run                  Only print bubblewrap arguments but don't execute\n  --unsafe-session           Don't use --new-session option for bubblewrap (less secure)\n  --help                     Show help\n\nAd-hoc profile options:\n  -b, --base\n  -g, --gui\n  -h, --home\n  -t, --tmphome\n  -c, --cwd\n  -n, --net\n\nBase levels (-b or --base is same as -b4):\n  -b0  Base with basic bubblewrap flags, does not grant any resources\n  -b4  More granular base with system files\n  -b5  Low effort sandbox, includes system files with temporary home and empty tmp\n  -b6  Low effort sandbox with GUI support, includes X11 display binding\n  -b8  Lower effort wider sandbox, does not filter env vars and /tmp, should work better for GUI programs\n  -b9  Simplest wide sandbox with device bind mount and temporary home\n\nSandbox options:\n  --bind-ro   \u003csrc\u003e:\u003cdest\u003e  Read-only bind mount \u003csrc\u003e:\u003cdest\u003e or \u003cpath\u003e\n  --bind-rw   \u003csrc\u003e:\u003cdest\u003e  Read-write bind mount \u003csrc\u003e:\u003cdest\u003e or \u003cpath\u003e\n  --bind-dev  \u003csrc\u003e:\u003cdest\u003e  Device bind mount \u003csrc\u003e:\u003cdest\u003e or \u003cpath\u003e\n  --env-pass  \u003cenv\u003e         Pass environment variable to sandbox\n  --env-set   \u003cvar\u003e \u003cvalue\u003e Set an environment variable\n  --env-unset \u003cvar\u003e         Unset an environment variable\n```\n\u003c!-- /FIREWRAP_HELP_END --\u003e\n\nFor convenience `firewrap` can be aliased to `fw`.\n\n#### Automatic profiles\n\nBuiltin app profiles are applied automatically based on a command name (case insensitive lookup).\n\nFor example to apply profile from `nexus.profile.windsurf` namespace:\n\n```\nfirewrap Windsurf\n````\n\nis same as:\n\n```\nfirewrap --profile windsurf -- Windsurf\n```\n\nTODO make the profile registry opt-in?\n\n#### Aliasing with symlinks\n\nFor convenience it is possible to create symlinks to alias commands, for example:\n\n```\nln -s /path/to/firewrap ~/bin/windsurf\n```\n\nThen just run simple command and matching sandbox profile will be applied:\n```\nwindsurf\n```\n\nIt is possible to use profile options to add additional privileges (`-c` is treated as option to firewrap, `.` is an argument for windsurf):\n\n```\nwindsurf -c -- .\n```\n\nFor now installation/management of symlinks needs to be done manually. In the future perhaps there could be a tool similar to Firejail's `firecfg`.\n\n#### Ad-hoc profile options\n\nThere is a set of opinionated options available to run ad-hoc profile conveniently from the command line, without needing to create a proper profile first.\n\nUsually, start with the base `-b` or `--base` options, then specify additional privileges to add.\n\nExample of fairly wide set of privileges is `fw -bcnh -- appname`,  \nwhich is equivalent to `fw --base --cwd --network --home -- appname`.\n\n#### Private home\n\nThe default base uses an ephemeral tmpfs is as home directory inside a sandbox.\nUse `--home` or `-h` for isolated home for a given app.\nThis is also useful to avoid home being polluted by additional config files and removing all traces of an app is done by deleting a single folder.\n\nFollowing will use `~/sandboxes/appname` as home.\n\n```\nfirewrap -b --home appname -- appname\n```\n\nWhen no argument is passed for home, the appname is inferred. Following is the same as above:\n\n```\nfirewrap -bh -- appname\n```\n\nUse `--tmphome` or `-t` for persistent temporary home, this will create `~/sandboxes/tmp-\u003ccurrent-date-time\u003e`. This is useful for having a temporary home, but it is preserved even after app finishes.\n\n```\nfirewrap -bt -- appname\n```\n\n#### Appimages\n\nWhen a command ends with `.appimage` (case insensitive comparison) it will be executed using a special appimage handler.  \n*(Mounts that appimages rely on are not allowed in sandbox, therefore contents is first extracted and then executed.)*\n\n```\nfw -b -- SomeApp.AppImage\n```\n\n#### Auto-sandboxing using shell wrapper\n\nWhen working on a project or trying out scripts from the internet,\nwe might want to run commands in a sandbox with reduced set of only necessary privileges.\n\nOne option is to run a shell in a sandbox and execute commands from it.\nHowever, there are [limitations](#limitations) when running sandboxed shell.\n\nAnother experimental approach is a shell wrapper, that prefixes invoked commands with `firewrap` command to run them in sandbox. There is an experimental implementation [shell wrapper](./examples/firewrap_command_wrapper.fish) for fish that works in following way:\n\n- If we type a command without prefix like `ls`, it will get prefixed like `fw -bc -- ls`. The default arguments are set using FIREWRAP_ARGS env variable.\n- When a command already includes firewrap prefix, it will be passed as is. For example running in a sandbox with network `fw -bn -- ping 8.8.8.8` will execute it as is.\n\nThe challenge is security vs user experience, as different tasks need different priviledges.\nIt can be annoying to switch sandbox options, and tempting to just always run wide sandbox or disable sandbox completely.\n\nAn idea is to add shorthands for common uses in shell config. Here are some aliases/abbreviations for inspiration:\n\n```fish\nabbr -a fwe \"set -gx FIREWRAP_ARGS -bc\" # e - enable\nabbr -a fwd \"set -gx FIREWRAP_ARGS ''\" # d - disable\nabbr -a fwn \"set -gx -bcn\" # n - network\nabbr -a fwc \"set -gx FIREWRAP_ARGS '--profile cljdev'\" # c - cljdev\n```\n\nIt is also possible to use [direnv](https://direnv.net) to set custom FIREWRAP_ARGS to configure different sandboxes based on project directories.\n\n#### User config\n\nConfig file is loaded from `firewrap/init.clj` in `$XDG_CONFIG_HOME` (by default `~/.confg/firewrap/init.clj`).\nSee the example [init.clj](./examples/init.clj).\n\nMain uses are to register a custom profile function with `profile/register!` and hooking overrides using `alter-var-root`.\n\n```clj\n(ns init\n  (:require\n   [firewrap.preset.base :as base]\n   [firewrap.profile :as profile]\n   [firewrap.sandbox :as sb]))\n\n;; Registering a custom profile function\n(defn my-appname-profile [opts]\n  (base/base5))\n\n(profile/register! \"appname\" my-appname-profile)\n\n;; Overriding existing functions as a hook\n(defn my-bind-user-programs [ctx]\n  (-\u003e ctx\n      (sb/bind-ro \"/some/path/bin\")))\n\n(alter-var-root #'base/bind-user-programs\n                (constantly my-bind-user-programs))\n```\n\n#### Two-step execution\n\nThis is a pattern that separates usage of a program into two steps to protect user data: install and execute.\n- During the first install step, there is access to internet, but not to user data.\n- During the second execute step, we allow access to user data, but remove network permission so that data can't potentially leave the device.\n\nAs an example using [pdfannots](https://github.com/0xabu/pdfannots) script to extract highlights from a potentially sensitive PDF.\nCreate a working directory including only the PDF that needs to be extracted.\n\n1) Install (or fetch to cache), allow internet with `-n` flag:\n```\nfw -bn --home pdfannots -- uvx pdfannots\n```\n\n2) Execute, we add user data by including current folder with `-c`, but there is no `-n` for network:\n```\nfw -bc --home pdfannots -- uvx pdfannots my.pdf\n```\n\nNote we pass `--home` to use the same home sandbox folder, so that cached files can be shared between runs.\nAlso make sure not to run the program with network privilege again after working with data.\nInstead use a different home sandbox folder or first wipe the sandbox data.\n\n#### Limitations\n\nFish shell does not run inside sandbox. When trying to run it:\n\n`firewrap -b -- fish`\n\nit fails with:\n```\nwarning: No TTY for interactive shell (tcgetpgrp failed)\nsetpgid: Inappropriate ioctl for device\n```\n\nAs a workaround use bash for restricted shells:\n\n`firewrap -b -- bash`\n\nAnother option is to use `--unsafe-session` option:\n\n`firewrap -b --unsafe-session -- fish`\n\nThe `--unsafe-session` option makes the sandbox open to TIOCSTI ioctl attack.\nHowever, it makes it less easier to forget to run a program under a sandbox by running the whole shell in sandbox.\nSo it can end up better when the risk profile is protection against mostly misconfigured programs rather then malicious programs.\n\n## Trace helper tool\n\nCapture a trace using strace (ideally on an isolated physical hardware or in a VM):\n\n```\nstrace -f -o output.trace your-command\n```\n\nTip: Set `XDG_` dirs to smaller number of candidates for less noisy trace:\n\n```\nexport XDG_DATA_DIRS=\"$HOME/.local/share:/usr/share\" XDG_CONFIG_DIRS=\"/etc/xdg\"\n```\n\nWe leverage [b3-strace-parser](https://github.com/dannykopping/b3) to parse strace file as JSON. \nInstall it with:\n\n```\nnpm install -g b3-strace-parser\n```\n\nThen use the `firehelper generate` command to generate a profile based on the captured trace.\n\n```\ncat output.trace | b3-strace-parser | bin/firehelper generate \u003e profile/foo.clj\n```\n\nAs you can see in the example output below,\nthe tool tries to match captured file paths to existing presets like `system/libs` `system/command`.\n\nRemaining paths are then structured as a tree of `bind-ro-try` forms.\nThese can be left as is granularly binding leaf paths.\nAlternatively, they can edited or extracted into higher-level presets.\n\n```clj\n(defn\n profile\n [_]\n (-\u003e\n  (base/base)\n  (system/libs)\n  (-\u003e\n   (system/nop system/bind-ro-try \"/\")\n   (-\u003e\n    (system/nop system/bind-ro-try \"/etc\")\n    (-\u003e (system/bind-ro-try \"/etc/ld.so.preload\"))))\n  (-\u003e (system/command \"echo\"))))\n```\n\nNow a smaller example will show why the forms are structured using nested threading `-\u003e` forms.\nFor example for input paths `/foo/a` and `/foo/b` we will get:\n\n```clj\n(-\u003e\n (system/nop system/bind-ro-try \"/foo\")\n (-\u003e \n   (system/bind-ro-try \"/foo/a\")\n   (system/bind-ro-try \"/foo/b\")))\n```\n\nNotice `system/nop` in the `/foo` form which makes the form to just pass through the context and ignore the binding function.\nIt is effectively same as following granular bindings (and we can remove the `nop` form manually when decided to use the granular approach):\n\n```clj\n(-\u003e \n  (system/bind-ro-try \"/foo/a\")\n  (system/bind-ro-try \"/foo/b\"))\n```\n\nThe other option is to delete the whole tree under form and remove the `nop` form to apply the parent form.\nThis can be useful when nested paths are well scoped to the containing directory, for example when there are too many nested files or the filenames are dynamic/randomized.\n\n```clj\n(-\u003e\n (system/bind-ro-try \"/foo\"))\n```\n\nTODO consider using [sysdig](https://github.com/draios/sysdig) instead of `strace` for capturing traces since it includes builtin support to output JSON-formatted traces.\n\n## Implemented mechanisms\n\nImplemented:\n\n- [x] Basic containment using Bubblewrap\n- [x] Environment variables restriction\n\nUnimplemented:\n\n- [ ] D-Bus filtering using xdg-dbus-proxy\n- [ ] Syscall filtering using seccomp\n- [ ] XDG Portals\n- [ ] Network filtering\n- [ ] ...\n\n## Alternatives\n\n- Linux Security Modules (LSM) systems like SELinux and AppArmor\n  - Difficult for an end-user to configure.\n- Containers like Docker, Podman\n  - Complected packaging, distribution and containment.\n  - I would like to have security independent of distribution.\n- Flatpak, Snap\n  - Complected distribution and containment.\n  - Vendor provided security policies incentivize to make them too loose.\n- Firejail\n  - Larger attack surface, needs SUID.\n  - Profiles often use Default Allow instead of Default Deny principle.\n\n### Other Bubblewrap-based projects\n\n- [Bubblejail](https://github.com/igo95862/bubblejail)\n  - Python, configuration in TOML, GUI configuration utility\n- [Sandbubble](https://github.com/CauldronDevelopmentLLC/sandbubble)\n  - Python, configuration in YAML\n- [Bubblebox](https://github.com/RalfJung/bubblebox)\n  - Python, configuration as Python scripts\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdundalek%2Ffirewrap","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdundalek%2Ffirewrap","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdundalek%2Ffirewrap/lists"}