Skip to main content
This setup is a package manager client deployment. MDM pushes the Package Firewall configuration to developer machines. Mobile device management (MDM) deployment lets an IT administrator configure many developer machines at once. You generate a self-contained script, push it through your MDM tool, and it configures each machine’s package manager clients to route installations through Package Firewall. Developers do nothing, and existing package manager client configuration is preserved. The scripts cover the following ecosystems. They are idempotent, so they are safe to re-run on every MDM check-in, and they have no runtime dependencies beyond the shell.
The scripts are available in the Endor Labs MDM scripts repository under the package-firewall/ directory.

Before you begin

Create an API key dedicated to the Package Firewall so that can authenticate to it. You can create it through one of the following methods:
  • Using the Endor Labs user interface, with the Package Firewall User role. See API keys to learn more.
  • Using endorctl, with the SYSTEM_ROLE_PACKAGE_FIREWALL role. Make sure to install and configure endorctl before you create the key.
To create the key using endorctl, run the following command and replace:
  • <namespace> with your namespace.
  • <API key name> with the name of the API key for the Package Firewall use case.
  • <YYYY-MM-DDTHH:MM:SSZ> with the API key expiration in ISO 8601 UTC format, for example 2026-12-31T23:59:59Z.

Generate the MDM scripts

Clone the generator repository and run it with your namespace and Package Firewall credentials. Select your platform for the matching commands.
You can also generate the scripts in the browser using the MDM script generator. The generator fetches the real source files from the Endor Labs MDM scripts repository (https://github.com/endorlabs/mdm-scripts). The GitHub repository is the authoritative source for the scripts.
  1. Clone the generator repository: Clone the repository and change into the bash generator directory.
  2. Generate the scripts: Pass your credentials as environment variables. This keeps them out of your shell history.
    Alternatively, store the variables in a .env file, add .env to .gitignore, and source it.
The generator writes endor-js.sh, endor-python.sh,endor-go.sh, endor-maven.sh, endor-all.sh, and endor-remove.sh to out/<namespace>/. Re-running generate.sh overwrites the same directory.
The generated scripts contain your API key and secret in plain text, because the scripts need them to write credentials on each device. MDM deployment then copies that one credential to every enrolled machine. Scope the key to the Package Firewall role only, keep the scripts and the out/ directory out of source control, restrict who can view the MDM policy, and rotate the key on a schedule by redeploying the script.

Upload the scripts to your MDM tool

Each generated script is self-contained with no runtime dependencies, so you upload it directly to your MDM tool. On macOS and Linux the scripts use the .sh extension; on Windows they use .ps1.
Run the scripts as root. The script detects the logged-in console user and writes configuration files to the correct home directory.
  1. Add the script: Go to Library > Custom Scripts > Add Script.
  2. Provide the script: Paste the script content or upload the file.
  3. Set the run context: Set Run as to Root.
  4. Set the frequency: Set Execution Frequency to Run once per device, or to every check-in for ongoing enforcement.
  5. Assign the script: Assign the script to the relevant device blueprint.
  1. Add the script: Go to Settings > Scripts > New and paste the script content.
  2. Create a policy: Go to Policies > New Policy > Scripts and add your script.
  3. Set the frequency: Set the Execution Frequency as appropriate.
  4. Scope the policy: Scope the policy to the target devices.
Upload the script file and run it as root. The script detects the logged-in console user and writes configuration files to the correct home directory.

How credentials are stored

The scripts write your credentials to a single source on each device and reference them from the package manager client configuration files. The storage mechanism depends on your platform.
The scripts write all credentials to ~/.config/endor/env.sh and set the file permissions to 600.
Each shell profile (.zshrc, .bash_profile, and .bashrc) gets a one-line block that sources this file. The configuration files reference these variables instead of embedding credentials, except pip.conf, uv.toml, and the go env file, which cannot expand variables and get a literal index URL baked in at install time.To rotate credentials, redeploy the MDM script to update env.sh on the target machines. No configuration file changes are needed.

User attribution

The generated scripts label each machine’s Package Firewall traffic with the developer who owns it. The firewall logs then attribute activity to a person and machine rather than to a shared API key. Attribution is informational. Your API key ID and secret still control access. The label does not grant or restrict any permission, and rotating the key does not change how attribution works. You can view the attributed user on each event in the Package Firewall logs and query the logs by attributed user. See View Package Firewall logs to learn more.

The default label

The label is an arbitrary value that you choose, and it is designed to be overridden. Package Firewall accepts any value that meets the constraints and stores it unchanged, so the label the scripts ship with is one convention among many, not a format the platform requires. To attribute traffic differently, for example by email address or by an asset tag from your device inventory, fork the MDM scripts repository and change the ENDOR_ATTR_LABEL assignment. It lives in package-firewall/bash/generate.sh for macOS and Linux, and in package-firewall/powershell/templates/envvars.ps1 for Windows. By default, the script builds a <console-user>@<machine> label at install time from the logged-in console user and the machine name. The machine name comes from the computer name on macOS and Windows, and from the host name elsewhere. The script then encodes the label into the Basic-auth username it writes for every package manager client it configures, which covers npm, pnpm, yarn, bun, pip, uv, Go, and Maven. It stores the encoded username as ENDOR_ATTR_USER in the credential store, which is env.sh on macOS and Linux and HKCU:\Environment on Windows.

Choose a label

The following labels are examples of conventions you can adopt, not a list the platform defines. Package Firewall stores the label as one string and never parses it, so the convention you pick is the one your reporting has to live with. Use a single convention across the fleet. A mix of formats means every query against spec.user has to account for each one. Keep the half of the label that stays stable for whatever you report on. If you report by person, hold the person half steady even when hardware changes. If you report by device, hold the device half steady even when the user changes. On a shared virtual machine the device half stops identifying anyone, so the person half has to carry the attribution alone.

How the scripts encode the label

The scripts encode the label into the Basic-auth username using the following format.
Package Firewall decodes the username, restores your API key ID to authenticate the request, and records the label as the user on the event. The inner base64 layer is what keeps the label separate from the API key ID, so the label itself can contain any character. The two environment variables hold the same value at two encoding stages, not two halves of the label. ENDOR_ATTR_LABEL holds the entire label, and ENDOR_ATTR_USER holds the Basic-auth username derived from it. Package Firewall never combines them, and the attributed user in the logs is always exactly the label. A fork that sets the label to a bare email address produces the following values.
Package Firewall treats a username that fails to decode, or one whose decoded value does not begin with userattr:, as a plain API key ID. The request authenticates exactly as it would without attribution, and the log records no attributed user.

Label constraints

Package Firewall treats the label as opaque. It stores and displays the value and never parses it, so no character in the label carries meaning to the platform. The @ in the default label is a convention of the scripts, not a separator the platform interprets.
  • Character set: The label must be valid UTF-8. Package Firewall drops a label that is not valid UTF-8. The request still authenticates and the log still records the event, but with no attributed user.
  • Delimiters: Every character is safe, including :, @, /, and spaces, and no delimiter is unsupported. The scripts base64-encode the label before it reaches the wire, so no character can break the format. A label such as team:platform/doe@laptop-14 reaches the logs exactly as written.
  • Length: Package Firewall imposes no maximum label length, and the Endor Labs service accepts labels of at least 16 KB. The practical ceiling comes from the transport on your side: HTTP proxies commonly cap a single header line at 8 KB, and pip, uv, and Go carry the encoded label inside the index URL of every request, so a long label inflates all their traffic. Encoding roughly doubles the label on the wire. The Basic-auth username is about 1.8 times the label length, and the _auth value npm derives from it is about 2.4 times. A label under 1 KB stays far below an 8 KB header line in every lane.
  • Reserved prefix: userattr: is a format marker that the scripts add and Package Firewall strips. It is not part of your label, and your label should not repeat it.

What the scripts do

Each install script writes the credential store and an Endor-managed block to the package manager client configuration files. The file locations depend on your platform.
The endor-js.sh script writes ~/.config/endor/env.sh and an Endor-managed block to the following files.The endor-go.sh script writes ~/.config/endor/env.sh and an Endor-managed block to the following file.The endor-python.sh script writes ~/.config/endor/env.sh and an Endor-managed block to the following files.The endor-maven.sh script writes ~/.config/endor/env.sh and an Endor-managed block to the following file.

Configure each package manager client

The scripts apply a few package manager client-specific behaviors that are the same on every platform.
  • JavaScript: The scripts write _auth (base64) instead of _authToken, which bun requires. Yarn classic reads authentication from .npmrc, so the .npmrc write covers it. The scripts do not write the project-level bunfig.toml.
  • Go: The scripts resolve the go env file path with go env GOENV, then write GOPROXY to it. If go is not installed, they fall back to the OS default path (~/Library/Application Support/go/env on macOS, ~/.config/go/env on Linux, or %APPDATA%\go\env on Windows). Credentials are literal because go env files cannot expand environment variables. The GOPROXY value ends in ,direct, so Go downloads a module directly from its source when the firewall does not serve it. The go env file applies to every go command regardless of shell, and it has lower precedence than the GOPROXY process variable, so project-level overrides still work.
  • Python (pip): The scripts write the pip settings in a [global] section. If the file already has a [global] section, they merge the Endor keys into it, because pip rejects duplicate [global] sections. Any conflicting keys, such as an existing index-url, are disabled with an #endor-bak# prefix and restored when the remove script runs. Credentials are literal because pip cannot expand environment variables.
  • Python (uv): uv ignores pip.conf, so the scripts write the user-level uv.toml with the firewall index URL. The URL is baked in literally because uv cannot expand environment variables in its config, so it carries the attributed username.
  • Python (Poetry): Poetry reads credentials from the POETRY_HTTP_BASIC_ENDOR_FIREWALL_* environment variables, so no separate write step is needed. Add the source to your pyproject.toml: Poetry reads the registry URL from pyproject.toml and the credentials from environment variables. Add the Package Firewall as a source in each project. The source includes the URL only, never the credentials.
  • Maven: The scripts write a user-level settings.xml (~/.m2/settings.xml, or %USERPROFILE%\.m2\settings.xml on Windows) with a <mirror> of * that points at the firewall, plus a matching <server>. Credentials are not baked into the file. The <server> references ${env.ENDOR_ATTR_USER} and ${env.ENDOR_API_SECRET}, which Maven resolves at runtime from the environment variables the credential store already sets. Gradle uses this too when it reads ~/.m2/settings.xml.

Preserve existing configuration

The scripts use a sentinel block pattern. Each script writes only a clearly delimited section to a configuration file and leaves everything else untouched, so existing settings survive every deployment. The following example shows an .npmrc file that already contains administrator settings. The script adds only the Endor-managed block between the BEGIN and END markers.
The following table describes how the scripts handle each configuration scenario.

Remove the configuration

To offboard a machine, deploy the remove script. It strips the Endor block from every managed file and removes the credentials the install scripts added.
Deploy endor-remove.sh. It removes the Endor block from each configuration file and removes the credentials from ~/.config/endor/env.sh.

Security notes

The scripts store credentials on each device. Review the following before you deploy.

Generate your MDM script in the browser

Select your platform, ecosystem, and MDM tool, then enter your namespace and API key. The script is generated entirely in your browser: your credentials are never sent to Endor Labs or GitHub, only baked into the script you download.
The generated script contains your API key and secret in plain text, because they are needed to write credentials on each device. MDM deployment then copies that one credential to every enrolled machine. Scope the key to the Package Firewall role only, keep generated scripts out of source control, restrict who can view the MDM policy, and rotate the key on a schedule by redeploying the script.