{"id":48408513,"url":"https://github.com/probably-not/safe-nif","last_synced_at":"2026-04-06T04:33:49.450Z","repository":{"id":334873849,"uuid":"1141894707","full_name":"probably-not/safe-nif","owner":"probably-not","description":"Wrap your untrusted NIFs so that they can never crash your node.","archived":false,"fork":false,"pushed_at":"2026-02-11T23:26:33.000Z","size":108,"stargazers_count":37,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-07T00:29:19.959Z","etag":null,"topics":["elixir","nif"],"latest_commit_sha":null,"homepage":"https://hex.pm/packages/safe_nif","language":"Elixir","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/probably-not.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","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":"2026-01-25T16:07:11.000Z","updated_at":"2026-02-25T16:54:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/probably-not/safe-nif","commit_stats":null,"previous_names":["probably-not/safe-nif"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/probably-not/safe-nif","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/probably-not%2Fsafe-nif","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/probably-not%2Fsafe-nif/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/probably-not%2Fsafe-nif/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/probably-not%2Fsafe-nif/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/probably-not","download_url":"https://codeload.github.com/probably-not/safe-nif/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/probably-not%2Fsafe-nif/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31460103,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T21:22:52.476Z","status":"online","status_checked_at":"2026-04-06T02:00:07.287Z","response_time":112,"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":["elixir","nif"],"created_at":"2026-04-06T04:33:48.773Z","updated_at":"2026-04-06T04:33:49.445Z","avatar_url":"https://github.com/probably-not.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SafeNIF\n\n![Elixir CI](https://github.com/probably-not/safe-nif/actions/workflows/pipeline.yaml/badge.svg)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Hex version badge](https://img.shields.io/hexpm/v/safe_nif.svg)](https://hex.pm/packages/safe_nif)\n\n\u003c!-- README START --\u003e\n\n\u003c!-- HEX PACKAGE DESCRIPTION START --\u003e\n\nWrap your untrusted NIFs so that they can never crash your node.\n\n\u003c!-- HEX PACKAGE DESCRIPTION END --\u003e\n\n## Motivation\n\nNIFs are great - sometimes... when they're written in a safe way, have been in use for a very long time, and are trusted by the community, then they have likely been through the process of finding most bugs that are in their underlying source. However, sometimes new libraries come out, and have not been as battle tested as you'd like. Some may have bugs, and when a NIF has a bug, it can crash your entire BEAM node! Code running inside of a NIF does not provide the same safety guarantees that the BEAM gives.\n\nBut... what if it could?\n\nI recently ran into this issue, using a library based on a NIF, and the NIF's underlying source was having sporadic crashes. I don't own the library, nor do I own the underlying C source, so while I can submit PRs to them to get it fixed, I still need some way to guarantee safety in the meantime. And thus, SafeNIF was born!\n\nSafeNIF allows you to wrap your NIFs to run on an isolated peer node raised on the same machine. If the NIF crashes, only this peer node dies.\nThe guarantees of the BEAM continue, and you get fault tolerance and crash isolation, even for NIFs, all in native Elixir (with a touch of Erlang's standard library).\n\n## Benchmarks\n\nBenchmarks can be found in the `bench` directory.\n\nAs of v0.2.0, SafeNIF has implemented a lazy pool of reusable nodes which scale down when idle.\nOn cold starts, a startup cost is incurred to initialize the peer node, which can take anywhere from 100ms to over a second,\ndepending on how much code needs to be loaded onto the peer node. It should be noted that pooling also incurs costs around memory and CPU since it spins up a node on the same machine.\n\nThe benchmarks show that a CLI based Port is slower than SafeNIF. However, different types of workloads and Ports may yield different results.\nFor example, Ports that communicate over `:stdio` and use a protocol so they are constantly alive and responding may perform better than how a CLI based port may perform.\n\nPorts have both upsides and downsides just like NIFs, so your mileage may vary as you work with them.\nSafeNIF's main concern is allowing any consumers to simply wrap any NIF by calling `SafeNIF.wrap/1` and immediately having the safety and isolation that the BEAM natively provides.\n\n**The following information was generated by Claude and Reviewed by @probably-not. If issues in this README are found, feel free to open up a PR to fix them!**\n\n## Usage\n\n### Basic Usage\n\nSafeNIF provides a single function: `SafeNIF.wrap/2`. Pass it an MFA (module, function, arguments) tuple and it runs on an isolated peer node:\n\n```elixir\n# Successful execution returns {:ok, result}\n{:ok, 6} = SafeNIF.wrap({Kernel, :+, [2, 4]})\n\n# Complex return values work fine\n{:ok, %{name: \"test\"}} = SafeNIF.wrap({Map, :put, [%{}, :name, \"test\"]})\n```\n\n### Wrapping Potentially Dangerous NIFs\n\nThe primary use case is wrapping NIFs that might crash:\n\n```elixir\ndefmodule MyApp.ImageProcessor do\n  def safe_process(image_binary) do\n    # UntrustedNIF.process/1 might crash the BEAM\n    case SafeNIF.wrap({UntrustedNIF, :process, [image_binary]}) do\n      {:ok, processed} -\u003e \n        {:ok, processed}\n      {:error, :noconnection} -\u003e \n        # The NIF crashed the peer node\n        {:error, :nif_crashed}\n      {:error, :timeout} -\u003e \n        {:error, :processing_timeout}\n      {:error, reason} -\u003e \n        {:error, reason}\n    end\n  end\nend\n```\n\n### Timeouts\n\nThe default timeout is 5 seconds. Specify a custom timeout as the second argument using `to_timeout/1`:\n\n```elixir\n# 30 second timeout for long-running operations\nSafeNIF.wrap({HeavyComputation, :run, [data]}, to_timeout(second: 30))\n\n# 2 minute timeout for very long operations\nSafeNIF.wrap({BatchJob, :process, [items]}, to_timeout(minute: 2))\n\n# 500ms timeout for quick operations\nSafeNIF.wrap({QuickCheck, :validate, [input]}, to_timeout(millisecond: 500))\n```\n\nWhen a timeout occurs, the peer node is killed and `{:error, :timeout}` is returned.\n\n### Anonymous Functions\n\nAnonymous functions are supported but with an important caveat: the module that defines the function must be loadable on the peer node.\n\n```elixir\n# Works\nSafeNIF.wrap(fn -\u003e 1 + 1 end)\n\n# Works (application modules are loaded on the peer)\nSafeNIF.wrap(fn -\u003e MyApp.Worker.do_work() end)\n\n# May fail if defined inside a code path that is not part of the application.\ndefmodule MyTest do\n  def run_test do\n    SafeNIF.wrap(fn -\u003e :test_result end)\n  end\nend\n```\n\nFor maximum reliability, prefer MFA tuples over anonymous functions.\n\n### Error Handling\n\nSafeNIF returns tagged tuples to distinguish between successful results and failures:\n\n```elixir\ncase SafeNIF.wrap({SomeModule, :some_function, [arg]}) do\n  {:ok, result} -\u003e\n    # Function executed successfully, result is the return value\n    handle_success(result)\n    \n  {:error, :timeout} -\u003e\n    # Function exceeded the timeout\n    handle_timeout()\n    \n  {:error, :noconnection} -\u003e\n    # Peer node crashed (NIF crash, :erlang.halt, etc.)\n    handle_crash()\n    \n  {:error, :not_alive} -\u003e\n    # Current node isn't running in distributed mode\n    handle_not_distributed()\n    \n  {:error, reason} -\u003e\n    # Function raised/exited with reason\n    handle_error(reason)\nend\n```\n\nNote that if your wrapped function returns an error tuple, it's wrapped in `{:ok, ...}`:\n\n```elixir\n# Function returns {:error, :not_found}\n{:ok, {:error, :not_found}} = SafeNIF.wrap({MyModule, :find, [123]})\n```\n\nThis follows the same convention as `Task.async_stream/5`.\n\n## Requirements\n\n### Distributed Mode\n\nSafeNIF requires your node to be running in distributed mode. If you call `SafeNIF.wrap/2` on a non-distributed node, you'll get `{:error, :not_alive}`.\n\nFor development, start IEx with a node name:\n\n```bash\niex --sname myapp -S mix\n```\n\nFor production releases, ensure your node is started with distribution enabled.\n\n### Running Tests\n\nTests require distribution. Add this to your `test/test_helper.exs`:\n\n```elixir\n{:ok, _} = Node.start(:\"test@127.0.0.1\", :shortnames)\nExUnit.start()\n```\n\nOr run tests with:\n\n```bash\nmix test --sname test\n```\n\n## How It Works\n\nWhen you call `SafeNIF.wrap/2`:\n\n1. A new BEAM node is started as a hidden peer using OTP's `:peer` module\n2. All code paths and application configuration are copied to the peer\n3. Applications are started on the peer\n4. Your function executes on the peer node\n5. The result is sent back via Erlang distribution\n6. The peer node shuts down\n\n### Hidden Nodes\n\nPeer nodes are started with the `-hidden` flag. This means they:\n\n- Don't appear in `Node.list/0`\n- Don't trigger `:net_kernel.monitor_nodes/1` callbacks\n- Won't be discovered by clustering libraries (libcluster, Horde, etc.)\n\nThis prevents SafeNIF's ephemeral peers from interfering with your cluster topology.\n\n## Performance Considerations\n\nSince v0.2.0, SafeNIF now creates a lazy pool of ready peer nodes for use.\n\nThis does not mean, however, that SafeNIF is without overhead.\nThere is still overhead in sending messages between the nodes, and wrapping the function in a way that can communicate with the caller.\n\nSafeNIF is designed for \"performant-enough\" isolation, ensuring that functions, specifically NIFs which are untrusted, can run without affecting the current node, and not high performance.\nUse it for:\n\n- Untrusted or potentially crashy NIFs\n- Operations where safety trumps speed\n\nDon't use it for:\n\n- Trusted code that won't crash the node\n\n## Installation\n\n[SafeNIF is available on Hex](https://hex.pm/packages/safe_nif).\n\nTo install, add it to you dependencies in your project's `mix.exs`.\n\n```elixir\ndef deps do\n  [\n    {:safe_nif, \"\u003e= 0.0.1\"}\n  ]\nend\n```\n\nDocumentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)\nand published on [HexDocs](https://hexdocs.pm). Once published, the docs can\nbe found at \u003chttps://hexdocs.pm/safe_nif\u003e.\n\n\u003c!-- README END --\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprobably-not%2Fsafe-nif","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprobably-not%2Fsafe-nif","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprobably-not%2Fsafe-nif/lists"}