{"id":13525219,"url":"https://github.com/berstend/node-safe","last_synced_at":"2025-04-09T14:06:20.922Z","repository":{"id":45456820,"uuid":"430829616","full_name":"berstend/node-safe","owner":"berstend","description":"🤠 Make using Node.js safe again with Deno-like permissions","archived":false,"fork":false,"pushed_at":"2023-06-15T13:53:57.000Z","size":63,"stargazers_count":208,"open_issues_count":12,"forks_count":8,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-02T12:50:31.106Z","etag":null,"topics":["developer-tools","macos","nodejs","npm","sandbox","security","yarn"],"latest_commit_sha":null,"homepage":"https://node-safe.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/berstend.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-11-22T18:55:28.000Z","updated_at":"2025-02-05T04:17:44.000Z","dependencies_parsed_at":"2024-01-13T22:24:53.056Z","dependency_job_id":"2e9c8dfb-4ea5-4bea-b8a2-7bec55dba2b2","html_url":"https://github.com/berstend/node-safe","commit_stats":{"total_commits":5,"total_committers":2,"mean_commits":2.5,"dds":0.4,"last_synced_commit":"1517ca28ea17cfa5b1faced6dde0b49df8299a43"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berstend%2Fnode-safe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berstend%2Fnode-safe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berstend%2Fnode-safe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/berstend%2Fnode-safe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/berstend","download_url":"https://codeload.github.com/berstend/node-safe/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248054230,"owners_count":21039952,"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","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":["developer-tools","macos","nodejs","npm","sandbox","security","yarn"],"created_at":"2024-08-01T06:01:16.925Z","updated_at":"2025-04-09T14:06:20.876Z","avatar_url":"https://github.com/berstend.png","language":"JavaScript","readme":"# node-safe 🤠\n\n\u003c!-- ![GitHub Workflow Status](https://img.shields.io/github/workflow/status/berstend/puppeteer-extra/Test/master) --\u003e\n[![Works with node-safe](https://img.shields.io/badge/%F0%9F%A4%A0%20node--safe-enabled-brightgreen)](https://github.com/berstend/node-safe#readme)\n[![dependencies](https://img.shields.io/badge/dependencies-zero-brightgreen)](https://github.com/berstend/node-safe)\n[![npm](https://img.shields.io/npm/v/@berstend/node-safe.svg)](https://www.npmjs.com/package/@berstend/node-safe)\n![license](https://img.shields.io/npm/l/@berstend/node-safe)\n\n\u003e \u003csub\u003e\u003csup\u003e_Jump to: [Permissions](#permissions), [Usage](#usage), [Shell integration](#implicit-usage), [Configuration](#configuration), [Project status](#project-status), [Package managers](#package-managers-npm-safe-yarn-safe), [Troubleshooting](#troubleshooting), [Contributing](#contributing)_\u003c/sup\u003e\u003c/sub\u003e\n\nRun Node.js code safely with permissions, preventing rogue dependencies from compromising your system.\n\n`node-safe` is an easy to use companion for your regular `node` and uses native macOS sandboxing features to control file system access, networking and process spawning to keep your workstation safe.\n\n## Motivation\n\nAs developers using Node.js and npm we routinely run _a lot_ of untrusted code directly on our workstations.\n\nLet this sink in: **Any npm package you're using has full file system and network access and can exfiltrate your data at will. 😱** We kinda just close our eyes and hope for the best here, which seems to not work anymore:\n\n- [Popular 'coa' NPM library hijacked to steal user passwords](https://www.bleepingcomputer.com/news/security/popular-coa-npm-library-hijacked-to-steal-user-passwords/)\n- [NPM package ‘ua-parser-js’ with more than 7M weekly downloads is compromised](https://news.ycombinator.com/item?id=28962168)\n- [Hacking 20 high-profile dev accounts could compromise half of the NPM ecosystem](https://www.zdnet.com/article/hacking-20-high-profile-dev-accounts-could-compromise-half-of-the-npm-ecosystem/)\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ci\u003e...and many more\u003c/i\u003e\u003c/summary\u003e\n\n- [Embedded malware in RC (NPM package)](https://news.ycombinator.com/item?id=29122098)\n- [Popular npm Project Used by Millions Hijacked in Supply-Chain Attack](https://blog.sonatype.com/npm-project-used-by-millions-hijacked-in-supply-chain-attack)\n- [Compromised eslint JavaScript Package Caught Stealing NPM Credentials](https://news.ycombinator.com/item?id=17517338)\n- [Malicious NPM libraries install ransomware, password stealer](https://www.bleepingcomputer.com/news/security/malicious-npm-libraries-install-ransomware-password-stealer/)\n- [Compromised npm Package: event-stream](https://medium.com/intrinsic-blog/compromised-npm-package-event-stream-d47d08605502)\n- [NPM package \"pac-resolver\" with 3 million weekly downloads had a severe vulnerability](https://arstechnica.com/information-technology/2021/09/npm-package-with-3-million-weekly-downloads-had-a-severe-vulnerability/)\n- [Google News: \"npm package compromised\"](https://www.google.com/search?q=npm+package+compromised\u0026source=lnms\u0026tbm=nws)\n- [Research paper (PDF): Small World with High Risks: A Study of Security Threats in the npm Ecosystem](https://software-lab.org/publications/usenixSec2019-npm.pdf)\n  - _\"Up to 40% of all packages rely on code known to be vulnerable.\"_\n  - _\"The average npm package transitively relies on code published by 40 maintainers.\"_\n  - _\"More than 600 highly popular npm packages rely on code published by at least 100 maintainers.\"_\n- [Security issue related to the NPM registry (Nov 2021)](https://news.ycombinator.com/item?id=29245080)\n  - _\"This is probably the worst security problem ever in the JS ecosystem. Any npm package could be corrupted, and we wouldn't even know it if the original maintainers don't pay attention to new releases anymore.\"_\n  - _\"Because this is buried in the post and people don't seem to be grokking it: They correctly authenticated the attacker and checked they were authorised to upload a new version of their own package, but a malicious payload allowed the attacker to then upload a new version of a completely unrelated package that they weren't authorised for. Ouch!\"_\n  - _\"NPM keeps me up at night. We have a CRA with over 300k node_modules files and over 1700 dependencies. Just one compromised dep and suddenly someone else is driving your AWS/Heroku CLI, stealing your credentials, and etc. There was malicious dep version just a few weeks ago on an agent string parser.\"_\n\n\u003c/details\u003e\n\nDeno (a Node.js alternative/competitor) has a built-in [permission system](#how-does-this-compare-to-denos-permissions) for running code, but besides running in a [slow VM or Docker container](#alternatives) there hasn't been a simple option available to run Node.js code safely.\n\n## Quickstart\n\n\u003e _See [Usage](#usage) below for more ways to install and use `node-safe`. Note that only macOS is supported currently._\n\n```bash\nnpm install --global @berstend/node-safe\n# or use a temporary version:\nnpx @berstend/node-safe -e \"console.log('hello world')\"\n```\n\n### Example\n\nLet's take this script as an example, which unceremoniously reads your private SSH key:\n\n```js\n// example.js\nconst fs = require(\"fs\")\nconsole.log(fs.readFileSync(process.env.HOME + \"/.ssh/id_rsa\").toString())\n```\n\nRegular `node` does not restrict file system access by default 😞\n\n```bash\nnode example.js\n# =\u003e -----BEGIN RSA PRIVATE KEY----- (...)\n```\n\n`node-safe` will block file system reads and writes by default 🤠\n\n```bash\nnode-safe example.js\n# =\u003e Error: EPERM: operation not permitted, open '/Users/foo/.ssh/id_rsa'\n```\n\nSimple command line options (like `--allow-read`) are supported to configure permissions:\n\n```bash\n# allow reading any file\nnode-safe --allow-read example.js\n# =\u003e -----BEGIN RSA PRIVATE KEY----- (...)\n\n# allow reading only that specific file\nnode-safe --allow-read=\"$HOME/.ssh/id_rsa\" example.js\n# =\u003e -----BEGIN RSA PRIVATE KEY----- (...)\n```\n\n\u003cdetails\u003e\n  \u003csummary\u003eMore examples with different permissions\u003c/summary\u003e\n\n### More examples\n\nWildcards (globs) and comma-separated lists of files and folders are supported as well:\n\n```bash\nnode-safe --allow-read=\"/etc/*,./assets/*.png\" -e \"console.log(require('fs').readFileSync('/etc/hosts').toString())\"\n```\n\nThe ability to run child processes and accessing files is configured separately:\n\n```bash\nnode-safe -e 'require(\"child_process\").exec(\"touch /tmp/foo\", console.log)'\n# =\u003e Error: spawn EPERM\n\nnode-safe --allow-run=\"[bin]/touch\" -e 'require(\"child_process\").exec(\"touch /tmp/foo\", console.log)'\n# =\u003e touch: /tmp/foo: Operation not permitted\n\nnode-safe --allow-run=\"[bin]/touch\" --allow-write=\"/tmp/foo\" -e 'require(\"child_process\").exec(\"touch /tmp/foo\", console.log)'\n# =\u003e success 🎉\n```\n\nNetwork access is disabled by default:\n\n```bash\nnode-safe -e \"require('https').get({host: 'example.com'}, (res) =\u003e res.on('data', (c) =\u003e console.log(c + '')))\"\n# =\u003e Error: getaddrinfo ENOTFOUND example.com\n\nnode-safe --allow-net-outbound -e \"require('https').get({host: 'example.com'}, (res) =\u003e res.on('data', (c) =\u003e console.log(c + '')))\"\n# =\u003e \u003c!doctype html\u003e (...)\n```\n\nBe permissive when feeling lazy while still \"containing\" things to their own project directory:\n\n```bash\n# Allow network and node_module binaries but restrict read/write access to the project folder\nnode-safe --allow-read-write=\"[project]/**\" --allow-run=\"[project]/node_modules/**\" --allow-net\n```\n\n\u003c/details\u003e\n\n**Elevator pitch:**\n\n- The [permissions](#permissions) will apply to all imports, nested dependencies and even spawned/forked child processes. 🔥\n- Configure permissions with the command line, environment variables or convenient `.node-safe.json` files.\n- You can use `node-safe` instead of `node` or teach your regular `node` command to support sandboxing.\n- Package managers like `npm`, `npx` or `yarn` are supported as well.\n- `node-safe` is a light-weight wrapper that uses your existing `node` version.\n\n## How it works\n\nThe main reason why running Node.js code is so risky is the complete lack of restrictions, any npm package (or any of it's often hundreds of nested dependencies from a multitude of developers) has:\n\n- unrestricted file system access\n  - reading files can be used to steal your SSH keys, Bitcoin wallet, grab your 1Password archive or photos\n  - writing files can be used to encrypt or destroy important data or in the worst case install rootkits\n- unrestricted networking\n  - which makes it easy for bad code to phone home, fetch malware or upload your private data to a server\n- unrestricted process spawning\n  - is often used as an attack vector to bypass firewalls, execute malware payload or modify system settings\n\nThe question arises why we allow our code to run with all these permissions, when most often they're not needed.\n\n### Sandboxing to the rescue\n\nmacOS ships with a very robust [sandbox](\u003chttps://en.wikipedia.org/wiki/Sandbox_(computer_security)\u003e) implementation (in fact most of your macOS apps run sandboxed). It's a little underdocumented (to put it mildly) but while looking into it more I realized it could be packaged in a neat way to enforce a variety of restrictions while working with Node.js, through easy to use configuration options.\n\n`node-safe` is aiming at making these powerful sandbox features available on an everyday basis while working with Node.js: It acts as an API compatible \"replacement\" for `node`, with some additional command line options to configure permissions.\n\nWhen `node-safe` is called it will dynamically generate, based on the desired permissions, a custom profile to instrument the macOS sandbox and run a sandboxed instance of your regular Node.js binary using that profile.\n\nPackage managers like `npm` or `yarn` can be used sandboxed as well, to contain post installation and package.json scripts.\n\n## Usage\n\nThere's **two ways** how you can use `node-safe` and friends:\n\n- **[Explicit](#explicit-usage):** You use `node-safe`, `npm-safe`, `yarn-safe` commands directly (installed globally or locally)\n  - Quick and easy installation, it's clear what's happening but you need to re-train your muscle memory\n  - **The sandboxing will be enabled by default** and can be configured (command line, env, config file)\n- **[Implicit](#implicit-usage):** You continue using `node`, `npm`, `yarn` commands but with optional sandboxing\n  - No need to remember using the `-safe` commands but requires modifying your `$PATH` once\n  - **The sandboxing will be disabled by default** unless explicitly enabled through the command line, env or automatically when a `.node-safe.json` file is found in the project\n\nCan't decide? See [Typical usage](#typical-usage) for an opinionated take on the preferred way to use `node-safe`.\n\n### Explicit usage\n\n\u003e `node-safe` does not include `node`, it will use whichever `node` version you have installed in your system.\n\u003e To install Node.js and make switching between versions a breeze [`nvm`](https://github.com/nvm-sh/nvm/blob/master/README.md#installing-and-updating) is a good option.\n\n#### Global installation\n\nYou can install `node-safe` as global script binary and run your code with `node-safe` instead of `node`:\n\n```bash\nnpm install --global @berstend/node-safe\n# then run a script\nnode-safe myscript.js\n# or use Node's eval feature\nnode-safe -e \"console.log('hello world')\"\n# or start an interactive REPL\nnode-safe\n```\n\nIn addition to `node-safe` this will make `npm-safe`, `npx-safe` and `yarn-safe` available globally.\n\n#### Temporary usage with `npx`\n\nIf you just want to try out `node-safe` without really installing it use `npx` (which ships with `npm`):\n\n```bash\nnpx @berstend/node-safe -e \"console.log(require('fs').readFileSync('/etc/hosts').toString())\" # fails\nnpx @berstend/node-safe --allow-read -e \"console.log(require('fs').readFileSync('/etc/hosts').toString())\" # works\nnpx @berstend/node-safe --allow-read --allow-net myscript.js # run a script with file read + network permissions\nnpx @berstend/node-safe # start a REPL with no permissions (it can't even read it's own history file 😄)\nnpx @berstend/node-saf --allow-read-write=\"~/.node_repl_history\" # sandboxed REPL with history\nnpx --ignore-existing @berstend/node-safe # to always use a freshly-installed temporary version\n# To use the package manager binaries provided by node-safe:\nnpx --package @berstend/node-safe npm-safe\nnpx --package @berstend/node-safe npx-safe\nnpx --package @berstend/node-safe yarn-safe\n```\n\n#### Project specific installation\n\nYou can install `node-safe` as a local dev dependency in an existing Node.js project:\n\n```bash\nyarn add --dev @berstend/node-safe\n# or\nnpm install --dev @berstend/node-safe\n```\n\nRun the following command from the root folder of that project:\n\n```bash\n$(npm bin)/node-safe -e \"console.log('hello world')\" # will use ./node_modules/.bin/node-safe\n```\n\nIn addition to `node-safe` this will make `npm-safe`, `npx-safe` and `yarn-safe` available locally in the project.\n\n#### Package manager scripts\n\nIn a typical project custom package.json scripts are used to run things and often implicitly call `node` (like a `mocha` test runner). To run package.json scripts safely make use of `npm-safe` or `yarn-safe`:\n\n```bash\n# before (not sandboxed)\nyarn run build\nnpm run build\n\n# run the build script defined in the package.json\nyarn-safe run build\nnpm-safe run build\n\n# allow writing to the ./dist folder during build\nyarn-safe --allow-write=\"./dist/**\" run build\nnpm-safe --allow-write=\"./dist/**\" run build\n```\n\nor when `node-safe` has been installed as a dev dependency in the project:\n\n```bash\n# run the build script defined in the package.json\n$(npm bin)/yarn-safe run build # will use ./node_modules/.bin/yarn-safe\n$(npm bin)/npm-safe run build # will use ./node_modules/.bin/npm-safe\n```\n\nEverything that happens when running a package.json script (including nested commands, child processes, etc) will be sandboxed and restricted based on the configured permissions.\n\n#### Package manager commands\n\nSimply using npm or yarn to install dependencies can already compromise your machine as lifecycle scripts like `postinstall` are executed with unrestricted access. To run package manager commands sandboxed:\n\n```bash\n# Install all dependencies listed in package.json\nyarn-safe install\nnpm-safe install\n\n# Install 'got' as new dependency\nyarn-safe add got\nnpm-safe install got\n\n# prepublish hooks can execute arbitrary code as well\nnpm-safe publish\n\n# or when installed as a dev dependency\n$(npm bin)/yarn-safe install # will use ./node_modules/.bin/yarn-safe\n$(npm bin)/npm-safe install # will use ./node_modules/.bin/npm-safe\n```\n\n`npm-safe` and `yarn-safe` are optimized for your happiness and will [allow certain permissions by default](#default-sandbox-permissions).\n\nBy using a `.node-safe.json` [configuration file](#config-file) it's possible to define (and share) sandbox permissions scoped to specific package manager scripts or commands.\n\n### Implicit usage\n\n\u003e `node-safe` does not include `node`, it will use whichever `node` version you have installed in your system.\n\u003e To install Node.js and make switching between versions a breeze [`nvm`](https://github.com/nvm-sh/nvm/blob/master/README.md#installing-and-updating) is a good option.\n\n#### Using regular `node`, `npm`, `yarn` commands with sandboxing\n\nWouldn't it be great if we could teach good old node and npm permission features? Well, we can! 🎉\n\nYou can use the shell integration below to make `node-safe`, `npm-safe`, `npx-safe` and `yarn-safe` the default when their respective `node`, `npm`, `npx`, `yarn` command is called.\n\nWe do this by prepending the `$PATH` variable (which tells your system in which folders binaries can be found) with our own folder containing small script binaries as shims. When these are invoked they will find and use the regular `node` binary to run `node-safe`, which takes it from there.\n\nThings of note when using the regular `node`, `npm`, `yarn` commands with node-safe through the shell integration:\n\n- the sandboxing will be disabled by default and commands behave as normal, as to not break your existing projects or workflow. To enable sandboxing use `--enable-sandbox` or toggle sandboxing automatically by using use per project `.node-safe.json` config files.\n- it's not a replacement for your node, npm, yarn binaries, you still need to have them installed in your system\n- the shell integration doesn't include node-safe, it still needs to be installed as global npm module (the shell integration will do that automatically for you if needed though)\n- when using the shell integration all 📚 documentation and examples you read here using `node-safe`, `npm-safe`, `yarn-safe` commands will apply to your regular `node`, `npm`, `yarn` commands as well\n\n```bash\nnode --enable-sandbox --allow-read=\"./comics/**\" --allow-net # possible with the shell integration\n```\n\n#### Shell integration\n\nTo add the node-safe shell integration download [this file](./shell/env.sh) to `~/.node-safe/env.sh`:\n\n```bash\nmkdir ~/.node-safe\ncurl -o ~/.node-safe/env.sh https://raw.githubusercontent.com/berstend/node-safe/master/shell/env.sh\n```\n\nAdd this line at the end of your `~/.bashrc`, `~/.profile`, or `~/.zshrc` file(s) to have it automatically sourced upon login:\n\n```bash\n# This loads the shell integration of node-safe\nexport NODE_SAFE_DIR=\"$HOME/.node-safe\" \u0026\u0026 [ -s \"$NODE_SAFE_DIR/env.sh\" ] \u0026\u0026 source \"$NODE_SAFE_DIR/env.sh\"\n```\n\nIf the files don't exist yet just create them:\n\n```bash\ntouch ~/.zshrc \u0026\u0026 touch ~/.bashrc\n```\n\nRestart your terminal session for changes to have an effect. To verify everything works:\n\n```bash\nnode --sandbox-version\n# =\u003e 🤠 node-safe v0.1.2\n```\n\nTo disable the shell integration just remove the line we added earlier and restart your terminal.\n\n### Typical usage\n\n#### Use a config file\n\nI recommend using a `.node-safe.json` [config file](#config-file) when working on projects. You don't need to remember to use commandline arguments and your team members get to enjoy the sandboxing permissions you've created as well.\n\nAdding a config file to your project also allows you to add this cool badge to your readme 😄\n\n```md\n[![Works with node-safe](https://img.shields.io/badge/%F0%9F%A4%A0%20node--safe-enabled-brightgreen)](https://github.com/berstend/node-safe)\n```\n\n#### Use the implicit mode\n\nAs for the installation: Go for the [implicit usage](#implicit-usage) with the shell integration after you played with `node-safe` a bit. You can continue using `node` and package managers as normal but whenever a `.node-safe.json` file is found in one of your projects it will automatically keep you safe with sandboxing.\n\n#### Sandbox new projects from the start\n\nWhen creating a new project it's important to enable sandboxing from the start, so you're already safe when installing the first dependencies. When using the implicit mode you can do that by simply creating an empty `.node-safe.json` file:\n\n```bash\n# create a folder for a new project\nmkdir new-project-idea \u0026\u0026 cd new-project-idea\n# activate sandboxing with default permissions with an empty config file\ntouch .node-safe.json \u0026\u0026 yarn init --yes\n# all node, npm, yarn commands executed in this project will now run sandboxed 🎉\n```\n\nOnce you run into permission errors you can start whitelisting certain permissions in the `.node-safe.json` file as needed.\n\n## Permissions\n\nBy default [most permissions are restricted](#default-sandbox-permissions) when running `node-safe`.\n\n_The naming to configure permissions has been inspired by Deno:_\n\n- **--allow-read=\\\u003callow-read\u003e** Allow file system read access. You can specify an optional, comma-separated list of directories or files to provide an allow-list of allowed file system access. Wildcard globs are supported.\n- **--allow-write=\\\u003callow-write\u003e** Allow file system write access. You can specify an optional, comma-separated list of directories or files to provide an allow-list of allowed file system access. Wildcard globs are supported.\n- **--allow-read-write=\\\u003callow-read-write\u003e** Allow file system read \u0026 write access. You can specify an optional, comma-separated list of directories or files to provide an allow-list of allowed file system access. Wildcard globs are supported.\n- **--allow-run=\\\u003callow-run\u003e** Allow running subprocesses. You can specify an optional, comma-separated list of subprocesses to provide an allow-list of allowed subprocesses. Wildcard globs are supported. Be aware that subprocesses are subject to the same permissions as the Node.js app and file system or network access needs to be permitted for subprocesses as well.\n- **--allow-net** Allow unrestricted network access (inbound \u0026 outbound).\n- **--allow-net-inbound** Allow inbound network access. Allows binding to sockets and creating/listening local servers. Local network requests are not permitted.\n- **--allow-net-outbound** Allow outbound network access. Allows binding to sockets and outgoing requests to the local network and internet.\n- **--allow-all** Allow all permissions. This enables all security sensitive functions. Use with caution.\n\n### Other options\n\n- **--enable-sandbox** Will enable the sandboxing, only relevant if you don't use `-safe` commands but the [implicit](#implicit-usage) usage (when using the `-safe` commands sandboxing is already enabled by default)\n- **--allow-unsupported-platforms** Running `node-safe` on an unsupported platform (Windows, Linux) will show an error by default, when this option is set sandboxing will be skipped and node executed normally\n- **--disable-sandbox-hints** By default `node-safe` will print a short message to stdout to let you know it's running (only in interactive terminals, not when piping the output or similar)\n- **--debug-sandbox** Enable verbose debug logging to stdout\n- **--print-sandbox** Print the generated sandbox profile and exit\n- **--sandbox-target** Specify a different target binary to be sandboxed (by default `node`)\n- **--sandbox-help** / **--sandbox-version** Show version information and help\n\n_Learn more about [Files \u0026 Folders](#files--folders) further down._ _Having trouble finding the right permissions to allow? See [Troubleshooting](#troubleshooting)_\n\n## Configuration\n\n`node-safe` is very configurable. It reads it's configuration options from 3 places:\n\n- [Command line switches](#command-line-options)\n- [Environment variables](#environment-variables)\n- [A `.node-safe.json` config file](#config-file) (usually in the project directory next to the package.json)\n\n\u003cdetails\u003e\n  \u003csummary\u003eOptions will be merged when provided from multiple places\u003c/summary\u003e\n\n### Merging\n\nWhen it's the same permission or option:\n\n- Command line switches take precedence over environment variables which take precedence over the config file\n- If a list value is found in a config file, environment variable as well as command line their lists are merged\n- If a config file defines an option with a list but a boolean is set through the command line the boolean will win\n\nWhen it's different options provided from different places they'll all take effect.\n\n\u003c/details\u003e\n\nAll configuration options and permissions can be used with `npm-safe`, `npx-safe` and `yarn-safe` as well.\nWhen using the shell integration the regular `node`, `npm`, `npx`, `yarn` commands will support these new options too.\n\n### Command line options\n\n`node-safe` is meant to be used instead of `node`, it supports all regular `node` arguments (it will pass them on) in addition to a few new ones to control the sandbox.\n\n```bash\nUsage: node-safe [permissions] [node options] [ script.js ] [arguments]\n```\n\nMultiple permissions can be configured at once:\n\n```bash\nnode-safe --allow-read=\"./data/*.json\" --allow-write=\"[temp]/**,./logs/*.txt\" script.js\n```\n\nThe package managers follow the same pattern:\n\n```bash\nnpm-safe [permissions] \u003ccommand\u003e [arguments]\nnpx-safe [permissions] [options] \u003ccommand\u003e[@version] [command-arg]\nyarn-safe [permissions] \u003ccommand\u003e [flags]\n```\n\nWhen using the shell integration and regular commands the sandboxing must be enabled (or toggled by a config file):\n\n```bash\nnode --enable-sandbox --allow-read=\"./data/*.json\"  script.js\nnpm --enable-sandbox --allow-write=\"[temp]/**\" install electron\n```\n\n### Environment variables\n\nAll options can be defined as environment variables as well, prefixed with `NODE_SAFE_` and all uppercase:\n\n```bash\nNODE_SAFE_DEBUG_SANDBOX=true NODE_SAFE_ALLOW_READ=\"**.png\" node-safe script.js\n```\n\n### Config file\n\n`node-safe` and friends will check if a file named `.node-safe.json` exists in the current directory or it's parents.\n\n\u003e An empty `.node-safe.json` file will enable the sandboxing with default permissions when using the [implicit](#usage) mode where sandboxing is turned off by default.\n\nHere's a simple `.node-safe.json`:\n\n```json\n{\n  \"$schema\": \"https://repo.node-safe.com/schema/node-safe.schema.json\",\n  \"$readme\": \"https://github.com/berstend/node-safe#readme\",\n\n  \"node\": {\n    \"allow-read\": \"./data/**\",\n    \"allow-read-write\": \"./build,./build/**\",\n    \"allow-net-outbound\": true\n  }\n}\n```\n\n`$schema` and `$readme` are optional, but the schema enables IntelliSense while typing in editors like VSCode 😍:\n\n![config intellisense](https://i.imgur.com/kBGoIYh.png)\n\nRelative paths in the config file will resolve from the directory containing the config file, not the directory from which you invoked node/npm/yarn (most often the same but not always).\n\nThe config file supports three (all optional) top level properties with permissions:\n\n- `node` - Permissions that will apply to any node process run in that project (including package managers)\n- `scripts` - Define extra permissions that apply when running package.json scripts\n- `command` - Define extra permissions that apply when running package manager (npm, yarn) commands\n\nAnother example (comments are for explanation purposes only and not valid JSON):\n\n```jsonc\n{\n  \"$schema\": \"https://repo.node-safe.com/schema/node-safe.schema.json\",\n  \"$readme\": \"https://github.com/berstend/node-safe#readme\",\n\n  // Custom sandbox permissions when running Node.js in this project\n  \"node\": {\n    // Allow reading all reports and assets\n    \"allow-read\": \"./reports/**.csv,./assets/**\",\n    // Allow writing files to the logs folder\n    \"allow-write\": \"./logs/**\",\n    // Internet access so the app can fetch the latest conversion rates\n    \"allow-net-outbound\": true\n  },\n  // Extra permissions when running specific package.json scripts\n  \"scripts\": {\n    // Applies to any script\n    \"*\": {\n      // Required by rimraf (an npm package) to delete the results folder\n      // We use \"clean\" in multiple scripts, so we allow it for all scripts\n      \"allow-read-write\": \"./results/,./results/**\",\n      // Allow executing rimraf\n      \"allow-run\": \"./node_modules/rimraf/**\"\n    },\n    \"generate-reports,delete-reports\": {\n      // Only allow write access when generating or deleting reports\n      // Note: We could have used \"*-reports\" as well as wildcards are supported\n      \"allow-read-write\": \"./reports/,./reports\"\n    },\n    \"serve\": {\n      // Allow a local http server to run and bind to ports\n      \"allow-net-inbound\": true,\n      \"allow-run\": \"./node_modules/http-server/**\"\n    }\n  },\n  // Extra permissions when running built-in package manager commands\n  \"commands\": {\n    // One of our dependencies has a `postinstall` script that downloads something to the temp folder\n    // Note: For convenience any package manager command that installs packages will match \"install\"\n    // So no need to list \"add\", \"upgrade\", etc individually\n    \"install\": {\n      \"allow-read-write\": \"[temp]/**\",\n      // The dependency uses system curl for the download\n      \"allow-run\": \"[bin]/curl\"\n    }\n  }\n}\n```\n\nIf multiple entries match (e.g. `npm run delete-reports` will match `node`, `scripts:*` and `scripts:delete-reports`) the permissions of the matching entries will be merged.\n\n#### `node`\n\nThe underlying permissions affecting all invocations of `node-safe`, `npm-safe` or `yarn-safe`. If additional permissions for specific scripts or commands are defined and match they will be merged.\n\n#### `scripts`\n\nAdd specific permissions when package.json scripts are called (e.g. `npm run-script foo`, `yarn run foo`, etc). Comma separated lists of script names and wildcards are supported. Only the first script invoked will be considered for matching.\n\n#### `commands`\n\nAdd specific permissions when package manager commands are called (e.g. `npm whoami`, `yarn upgrade-interactive`, etc). Comma separated lists of script names and wildcards are supported. Without a space in the object property only the package manager command will be matched for portability (\"info\" will match `npm info` as well as `yarn info`). Commands that trigger package installations are additionally matched as \"install\" for convenience.\n\n## Files \u0026 folders\n\n`node-safe` tries to make it as convenient as possible to not just use a blanket `--allow-read` but be more specific.\n\nNote: If you just specify a folder it's taken literally and won't match descendents unless globbing is used:\n\n```bash\nnode-safe --allow-read=\"./assets/\" # won't allow reading ./assets/foo.png\nnode-safe --allow-read=\"./assets/*\" # will allow reading ./assets/foo.png\nnode-safe --allow-read=\"./assets/*\" # won't allow reading ./assets/content/foo.png\nnode-safe --allow-read=\"./assets/**\" # will allow reading ./assets/content/foo.png\n```\n\n### Relative paths\n\nRelative paths are supported and will internally be resolved to absolute ones.\n\n```bash\nnode-safe --allow-write=\"./logfile.txt\" script.js # allows writing to that specific file\nnode-safe --allow-write=\"./logs/*\" script.js # allows writing files to that specific folder\nnode-safe --allow-write=\"../../assets/screenshots/*.png\" script.js # allows writing .png files to the screenshots folder\n```\n\nWhen used in commandline or environment options they're resolved relative to the current working directory (from which you invoked `node-safe` or the package managers), in config files they're resolved relative to the config file.\n\n### Globbing\n\nGlobbing (aka wildcards) are supported and will internally be translated to regex.\n\n```bash\nnode-safe --allow-read=\"./*.png\" # allows reading .png files in the current directory\nnode-safe --allow-read=\"./**.png\" # allows reading .png files in the current directory + nested directories\nnode-safe --allow-read=\"**.png\" # allows reading .png files from anywhere\nnode-safe --allow-read-write=\"**/foo/*.png\" # allows reading/writing .png files from directories named foo\nnode-safe --allow-write=\"**/foo/**.png\" # allows writing .png files from foo directories and it's children\nnode-safe --allow-write=\"/Users/*/Documents/test/**\" # allows writing in `~/Documents/test/` + sub folders\nnode-safe --allow-write=\"./assets/**\" # allows deleting files in ./assets but not deleting the ./assets folder\nnode-safe --allow-write=\"./assets/**,./assets\" # allows deleting files in ./assets and the ./assets folder\nnode-safe --allow-read-write=\"[project]/**\" # full read/write file access but only in the project folder\n```\n\nUsing a single star will not match sub directories, a double star (known as \"globstar\") can be used for that.\n\n### Shorthands\n\nA couple of shorthands are supported for convenience and will internally resolve to their absolute paths.\n\n- `[cwd]` - resolves to the current working directory\n- `[temp]` - resolves to the systems temporary directory\n- `[home]` - resolves to the home directory (`~/` can be used as well)\n- `[script]` - resolves to the directory containing the target script (if any)\n- `[project]` - resolves to the closest directory (we traverse up) containing a `package.json` file\n- `[config]` - resolves to the closest directory (we traverse up) containing a `.node-safe.json` file\n- `[bin]` - resolves to a list of all directories found in the `$PATH` (not `./node_modules/.bin`)\n\nThe shorthands behave like regular folders and can be combined with globbing or used in lists:\n\n```bash\nnode-safe --allow-write=\"[temp]/**\" # allows writing anything in the temporary directory or subdirectories\nnode-safe --allow-write=\"[temp]/**.log\" # allows writing only .log files in the temporary directory\nnode-safe --allow-read=\"[project]/assets/**\" # allows reading anything from the project's assets directory\nnode-safe --allow-run=\"[bin]/curl\" # allows running curl found at `/usr/bin/curl`\nnode-safe --allow-run=\"[bin]/**\" # allows running any executable found in $PATH\n```\n\n### Lists\n\nInstead of a single file or folder a comma separated list can be provided.\n\n```bash\nnode-safe --allow-write=\"[temp]/**,./assets/**\" # allows writing anything in the temporary directory + assets folder\nnode-safe --allow-read=\"./file1.txt,./file2.txt\" # allows reading these two files\nnode-safe --allow-write=\"**/.png,/Users/foo/.bashrc\" # allows writing .png files anywhere + the .bashrc file to be read\n```\n\n## Default sandbox permissions\n\nIf we would block _everything_ the node process wouldn't even be able to start. 😄 We try to strike a balance between usability and security with sane defaults, to protect your machine while not being annoying to use.\n\n### `node-safe`\n\nThe default sandbox `node-safe` generates is locked down pretty heavily by default and whitelist (not blacklist) based.\n\n**By default blocked:**\n\n- Reading files (with few exceptions)\n- Writing files\n- Spawning child processes (except `node`)\n- Network access (inbound/outbound/binding)\n- Basically all low-level system write access (sysctls, etc)\n\n**By default allowed:**\n\n- Reading source files (`.js`/`.json`, etc.) from certain directories\n  - This is needed to allow importing scripts and node modules by default. As source files shouldn't contain sensitive data this is considered safe. This is restricted to `node_modules` folders, the directory the script is in as well as the project directory.\n\n### Package managers (`npm-safe`, `yarn-safe`)\n\nWe grant a few extra default permissions when sandboxing a package manager. If we don't they would not be usable out of the box. To still make this as safe as possible we do some light parsing of the command line arguments to understand the \"intent\" and only allow what is strictly needed to run that type of package manager command.\n\n**By default allowed:**\n\n- File read/write access to yarn/npm cache directories\n- File read/write access to `./package.json` and `./node_modules/**`\n- Outbound network access (only for known built-in commands, **not** when running package.json scripts)\n\n**Note for package authors and private package users:**\n\n`node-safe` will block access to your `~/.npmrc` by default. This is intentional: **If you're signed in it contains your npm access token that can be used to update/publish packages.** This file has been a juicy target in supply chain attacks and account takeovers before, hence the decision to block access by default.\n\nIf you need to be authenticated (while publishing or accessing private packages) you need to specifically allow access:\n\n```bash\nnpm-safe --allow-read-write=\"~/.npmrc\" login # or \"whoami\", \"publish\", etc\n```\n\nFor even more security consider combining this with using a [`NPM_TOKEN`](https://stackoverflow.com/questions/53099434/using-auth-tokens-in-npmrc/61666885#61666885) environment variable.\n\n## Q\u0026A\n\n### How does this compare to Deno's permissions?\n\nDeno implemented a [permission system](https://deno.land/manual/getting_started/permissions) from the start (as one of it's main differentiators to Node.js). Interestingly `node-safe` has a huge advantage by using native sandboxing features baked into the OS: **Restrictions will apply to all child processes as well**, whereas Deno has no control over what spawned processes can do. 😄\n\n`node-safe` borrowed it's CLI argument naming heavily from Deno though as to not reinvent the wheel.\n\nOne aspect where Deno allows more fine-grained control is networking: It supports a whitelist of domains/IPs, whereas `node-safe` can only enable/disable networking altogether (inbound \u0026 outbound separately though).\n\nWe also didn't implement an `--allow-env` equivalent to Deno, it's possible but I feel it'd be more annoying than useful.\n\n## Project status\n\nThe concept works and the sandboxing is robust, though not everything is fully fleshed out yet - consider this iteration of `node-safe` \"early access\" of sorts and feedback is very welcome.\n\nThe main thing we want to work on is probably relaxing some (safe) defaults, as to make the out of the box experience nicer when working on typical projects involving a lot of the common tooling (linting, transpiling, building code, etc).\n\nWe could also consider adding something like a `--allow-project` flag, or shipping with presets.\n\nOther things that might not be perfect yet:\n\n- Not every use-case/edge-case of running Node.js might be supported yet\n- The CLI parsing is not hardened yet and the code might choke on more exotic ways to invoke `node`\n- Package manager integration: We try to group commands and use different sandbox defaults, this grouping is probably not complete yet as package managers feature a lot of commands\n- The glob to regex part (when whitelisting files \u0026 folders) might contain bugs or not always behave like intended\n- The user-experience might be suboptimal with the default sandbox too restrictive, we'll get a better feel for where we might want to ease up some defaults over time\n- The cli options and sandbox defaults are not finalized yet and are subject to change until a more stable 1.0 release\n- Housekeeping: Automated testing, building, releasing + switch to TS\n\nFeedback, ideas, discussions and bug reports are welcome!\n\n### Limitations\n\n- **Currently macOS only**\n  - `sandbox-exec` is macOS only, though there might be ways to support other platforms as well\n\n### Other platforms\n\nIf there's sufficient interest I'll be looking into integrating a suitable sandbox implementation for **Windows** as well. [Sandboxie](https://github.com/sandboxie-plus/Sandboxie) seems like the most likely candidate here. Here's [documentation on their supported restrictions](https://sandboxie-plus.com/sandboxie/restrictionssettings/) and [file access](https://sandboxie-plus.com/sandboxie/resourceaccesssettings/). If you're a windows based dev feel free to play with Sandboxie and report your findings in an issue to start the discussion. 😄\n\n**Linux** has the advantage that Docker/LXC runs natively with little performance overhead. Still, looking into a light-weight sandboxing option so `node-safe` works on all major plattforms with the same user-facing API could make sense.\n\n- [App Armor](https://wiki.ubuntu.com/AppArmor) (MAC kernel module) has a near perfect syntax for our use-case (supports file path based globbing, sandboxes child processes) but unfortunately requires root to load/enforce profiles\n- [Firejail](https://firejail.wordpress.com/) is a SUID based sandbox and could be an option, their (glibc based) [globbing](https://github.com/netblue30/firejail/blob/e2299b2a41fea9cc76b9bd79dd80d9133470579c/src/firejail/fs_whitelist.c#L249) is [rudimentary](https://github.com/netblue30/firejail/issues/216) though and child processes are not sandboxed\n- Other options to look into: [bubblewrap](https://wiki.archlinux.org/title/Bubblewrap) (using user namespaces, though no globbing support) [sydbox](https://sr.ht/~alip/sydbox/) (seccomp-bpf based), [SELinux](https://man7.org/linux/man-pages/man8/sandbox.8.html) (though I doubt we can make it work for our purposes), [gVisor](https://gvisor.dev/docs/user_guide/quick_start/oci/) (use [`runsc do`](https://github.com/google/gvisor/issues/311) [without](https://github.com/google/gvisor/commit/356d1be140bb51f2a50d2c7fe24242cbfeedc9d6) Docker), LXC/LXD, chroots, etc\n\nIf you're interested in helping researching a suitable sandbox implementation for another platform: We're looking for a fast (no lengthy boot), non-root implementation we can control through the command line or environment. Minimum control we need is file system access (read/write separately), networking (at least outbound connections) and process forking. Ideally the filtering is file path based and supports extended (bash 4 like) globbing or regex.\n\n## Troubleshooting\n\nWhen using `node-safe` you'll eventually run into permission errors when only using the default permissions. This is perfectly fine as the idea is to exert control and only whitelist specific access when needed.\n\n### Escape hatch\n\nIf you're in a pinch and don't have time to fine-tune the perfect permissions go with this:\n\n```bash\nnode-safe --allow-read-write=\"[project]/**\" --allow-run=\"[bin]/**,[project]/node_modules/**\" --allow-net\n```\n\nThis will allow reading/writing in the project directory, executing any binaries your system or dependencies might come with and network access. Even this is still _a whole lot better_ than unrestricted access to your whole system.\n\n### Common errors\n\n#### `Error: EPERM: operation not permitted, scandir '/Users/foobar/folder'`\n\nThis error can happen due to lack of read access to a folder. Make sure to use read/write permissions as write permissions alone don't allow the process to read files.\n\nAlso make sure you whitelist a folder if needed and not only it's contents:\n\n```bash\n\"./folder/**\" # does not give access to the folder itself, only files and folders in it\n\"./folder,./folder/**\" # gives access to the folder itself as well as it's contents\n```\n\nIf the errors from the Node.js process don't help you understand what needs whitelisting have a look at [Debugging](#debugging).\n\n### Recipes\n\n#### Allowing binaries in `./node_modules/.bin` to be executed\n\nAssuming a package.json script using `rimraf` to clear a directory:\n\n```json\n  \"scripts\": {\n    \"clean\": \"rimraf dist/*\"\n  },\n```\n\nRunning this script would result in an error when running sandboxed with default permissions:\n\n```bash\nyarn-safe run clean\n\n/bin/sh: /Users/foobar/project/node_modules/.bin/rimraf: /usr/bin/env: bad interpreter: Operation not permitted\n```\n\nThe error is a bit misleading, as `node_modules/.bin/rimraf` **is a symlink**. By using `ls -lh` or the macOS sandbox logs we learn the actual location of the script being blocked from executing is `./node_modules/rimraf/bin.js`.\n\nWe need to therefore whitelist `./node_modules/rimraf/` instead of `./node_modules/.bin/rimraf`:\n\n```bash\nNODE_SAFE_ALLOW_RUN=\"./node_modules/rimraf/**\" NODE_SAFE_ALLOW_READ_WRITE=\"./dist,./dist/**\" yarn-safe run clean\n# =\u003e ✨  Done in 0.39s.\n```\n\nIn the example we additionally allow file read/write access to that folder and it's contents so `rimraf` can delete it.\n\n##### Allowing TypeScript\n\nAll binaries in `node_modules/.bin` are symlinks, when configuring permissions the real path must to be used here as well:\n\n```bash\nls -lh ./node_modules/.bin/tsc\n# =\u003e ./node_modules/.bin/tsc -\u003e ../typescript/bin/tsc\n```\n\n```bash\n# this is an example of the implict mode of using node-safe, we use `yarn run` with sandboxing enabled\nyarn --enable-sandbox --allow-run=\"./node_modules/typescript/bin/tsc\" --allow-write=\"./tsconfig.json\" run tsc --init\n# =\u003e \"Created a new tsconfig.json file\"\n```\n\nWe will improve the default TS experience in a future update by whitelisting certain things by default.\n\n#### Allowing postinstall hooks when installing packages\n\nWhen packages are installed their postinstall lifecycle scripts will be executed (as long as it's only using the node binary). This is a pretty safe default as all permissions (restricted file system access, etc) are enforced for child processes as well.\n\nSome packages (like `electron`) might run more elaborate postinstall scripts that will be blocked by the default sandbox:\n\n```bash\nyarn-safe add electron\n\nerror /Users/foobar/project/node_modules/electron: Command failed.\nExit code: 1\nCommand: node install.js\n```\n\nLooking at the macOS sandbox logs we see that electron tries to write some files to the temp folder as well as `~/Library/Caches` during installation, which we need to explicity allow:\n\n```bash\nNODE_SAFE_ALLOW_READ_WRITE=\"[temp]/**/electron**,[home]/Library/Caches/electron/**\" yarn-safe add electron\n```\n\nIt also tries to execute the `sysctl` binary for some reason but it's fine to keep that blocked. 😄\n\n#### Sandboxed Google Chrome with Puppeteer or Playwright\n\nUsing a browser automation framework is a pretty extreme case, as the sandbox applies to any child processes as well we effectively sandbox a full Google Chrome browser when launching it through puppeteer or playwright. 😄\n\nTo make this work we need to whitelist the minimum permissions Chrome requires to be able to run.\n\nPuppeteer will download the browser binaries to it's own `node_modules` folder after installation, hence we need to allow running binaries from that location:\n\n```bash\n--allow-run=\"**/node_modules/puppeteer/**,[bin]/**\"\n```\n\nWe additionally allow executing system binaries as Chrome is using some when launching.\n\nPuppeteer will by default create and use a temporary browser profile folder in temp:\n\n```bash\n--allow-read-write=\"[temp]/**\"\n```\n\nChrome cannot launch when not being able to bind to sockets: `--allow-net`\n\n**Special situation: Sandboxing a sandbox**\n\nChrome itself is using the macOS sandbox, we need to instruct Chrome not to use it as we'll already sandbox the process.\nTo make this convenient `node-safe` will expose an environment variable named `IS_SANBDOXED` that the code can check for.\n\nAll together now:\n\n```js\n// pptr-demo.js\nconst puppeteer = require(\"puppeteer\")\nconst launchOptions = {\n  headless: false,\n  defaultViewport: null,\n  args: [],\n}\n\n// Check if we're running in a sandbox already\nif (process.env.IS_SANDBOXED) {\n  launchOptions.args.push(\"--no-sandbox\")\n}\n\npuppeteer.launch(launchOptions).then(async (browser) =\u003e {\n  console.log(\"Starting..\")\n  const page = await browser.newPage()\n  await page.goto(\"https://example.com\")\n  console.log(\"✨ Launched sandboxed browser!\", await page._client.send(\"Browser.getVersion\"))\n  await page.screenshot({ path: \"screenshot.png\", fullPage: true })\n  await browser.close()\n})\n```\n\n```bash\nnode-safe --allow-write=\"[temp]/**,./screenshot.png\" --allow-run=\"**/node_modules/puppeteer/**,[bin]/**\" --allow-net pptr-demo.js\n```\n\n## Debugging\n\nTo debug a problem with node-safe itself you want to enable verbose debug logging:\n\n```bash\nNODE_SAFE_DEBUG_SANDBOX=true node-safe foobar.js\n# or\nnode-safe --debug-sandbox foobar.js\n```\n\n### macOS sandbox\n\nIf you run into permission errors but can't figure out what the problem is you want to check the macOS sandbox logs.\n\nYou can either open `Console.app`, start recording and filter for \"sandbox\" or use this nifty terminal command:\n\n```bash\nlog stream --style syslog --predicate 'process == \"sandboxd\"  \u0026\u0026 eventMessage CONTAINS[c] \"deny\"' --info | grep com.apple.sandbox.reporting\n```\n\nYou will see entries like this one that should help you understand what to whitelist:\n\n```\nSandbox: node(1071) deny(1) file-read-data /private/var/run/resolv.conf\n```\n\nIn case you want to eject the sandbox profile that `node-safe` generates and use it directly:\n\n```bash\nnode-safe --print-sandbox foobar.js \u003e profile.sb\nsandbox-exec -f profile.sb node foobar.js\n```\n\nUsing `sandbox-exec` directly can be useful when occasionally debugging permissions of external processes :\n\n```bash\n# launch Chrome Canary with all permissions but only allow writing into the temp directory\nsandbox-exec -p '(version 1) (allow default) (deny file-write*) (allow file-write* (subpath \"/private/tmp/fooprofile/\") (subpath \"/private/var/\"))' /Applications/Google\\ Chrome\\ Canary.app/Contents/MacOS/Google\\ Chrome\\ Canary --no-sandbox --user-data-dir=/private/tmp/fooprofile\n# or to deny everything\nsandbox-exec -p '(version 1) (debug all) (deny default)' curl https://example.com\n```\n\n## Alternatives\n\n- Running code in a virtual machine or Docker container is an option, but especially on macOS docker is quite slow as it runs in a Linux VM behind the scenes and file-mounting goes through many layers\n  - Please note that Docker is not considered a security solution: [Containers are not a sandbox](https://security.stackexchange.com/questions/107850/docker-as-a-sandbox-for-untrusted-code)\n- Writing \u0026 using custom macOS sandbox profiles manually (my sympathies 😅)\n- Using Deno, but forked processes are not subject to restrictions and the ecosystem compared to Node.js is still small\n\n## Contributing\n\nPlease create an issue to discuss what you have in mind before working on a PR. :-)\n\n## References\n\n### macOS sandbox reference\n\n- https://reverse.put.as/wp-content/uploads/2011/09/Apple-Sandbox-Guide-v1.0.pdf\n- https://github.com/0xbf00/simbple\n- https://jmmv.dev/2019/11/macos-sandbox-exec.html\n- https://wiki.mozilla.org/Sandbox/Mac/Debugging\n- https://www.karltarvas.com/2020/10/25/macos-app-sandboxing-via-sandbox-exec.html\n- https://chromium.googlesource.com/chromium/src/+/refs/heads/main/sandbox/mac/seatbelt_sandbox_design.md\n- https://github.com/WebKit/WebKit/blob/main/Source/WebKit/WebProcess/com.apple.WebProcess.sb.in\n- https://codechina.csdn.net/mirrors/chromium/chromium/-/blob/1419caba51dcb65d5f2bf9be15719f3f0414d601/sandbox/policy/mac/network.sb\n\n## License\n\nCopyright © 2021, [berstend̡̲̫̹̠̖͚͓̔̄̓̐̄͛̀͘](https://github.com/berstend). Released under the MIT License.\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fberstend%2Fnode-safe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fberstend%2Fnode-safe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fberstend%2Fnode-safe/lists"}