# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
This site has an agentic ask interface you may use to query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.safetycli.com/safety-docs/index.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

---

# Introduction to Safety

**Safety secures developer workstations for the age of AI-driven development — giving security teams instant visibility, threat protection, and governance over every package, extension, AI tool, and MCP server across your organization.**

## What is Safety Endpoint Security?

AI Workstation Security is a security platform that silently deploys across your teams' workstations to discover, secure, and protect everything developers, analysts and AI agents install and use in their development environments.

In the age of AI-assisted ("vibe") coding, workstations are filled with packages, extensions, and tools suggested and installed by AI agents — often without security review. Safety gives you complete observability and control, without requiring any changes to your developers' workflows.

### What Safety Endpoint Security Discovers

* **Python Packages** — all PyPI-based packages across virtual environments, global installations, and project dependencies.
* **JavaScript Packages** — all NPM-based packages, including globally installed tools and project dependencies.
* **Package Managers** — pip, uv, poetry, npm, yarn, pnpm, conda, and more
* **IDEs** — VS Code, Cursor, and other IDEs
* **\[Coming Soon] Codebases**
* **\[Coming Soon] IDE Extensions** — VS Code, Cursor, and other IDE extensions analyzed for potential vulnerabilities.
* **\[Coming Soon] AI Assistants** — Inventory of AI coding tools such as Cursor, Claude Code, Codex, and others.
* **\[Coming Soon] AI Models** — Deployed AI models (e.g., from Hugging Face) scanned for security risks.
* **\[Coming Soon] MCP Servers** — Monitoring and inventory of Model Context Protocol server configurations.

### What Safety Endpoint Security Protects and Governs

* **Package installations** — Package managers including pip, uv, poetry, npm are wrapped and protected. Every package install is intercepted, analyzed in realtime, and checked against organizational or project policies, all before installation. This pro-active security and governance protects workstations from malicious packages such as XZ Utils Backdoor or Sha-Hulud.
* **\[Coming Soon] AI Assistant and AI Agent package dependencies** — AI Assistants and AI Agents are given direct access to Safety’s Proprietary Intelligence data on package risk, known vulnerabilities, and your organization’s policies. This means AI-driven installs are held to the same security and compliance standards as human-initiated ones, closing the governance gap introduced by agentic coding workflows.
* **\[Coming Soon] IDE and AI Assistants**
* **\[Coming Soon] IDE Extension installations and use**
* **\[Coming Soon] MCP tool use**

## How Safety Endpoint Security works

Safety AI Workstation Client deploys silently and agentlessly across macOS, Windows, Linux, WSL, Docker, and virtual desktops. It unobtrusively indexes all software on each workstation and wraps package installers (pip, npm, VS Code Extensions, etc.) to block malicious or unapproved software — all with zero friction and no tool changes for developers.

It can also install Security & Risk Intelligence MCP tool into every AI Assistant and AI Agent on the workstation, ensuring that every AI-generated package recommendation, dependency resolution, and installation action is informed by real-time threat intelligence and your organization's security policies — before code ever reaches the workstation.

***

## Core Capabilities

### Workstation Scanning & Inventory

Get a complete, real-time inventory of every package, extension, AI model, MCP server, and AI tool installed across all developer workstations in your organization.

### Malicious Package Protection ([Safety Firewall](/safety-docs/firewall/introduction-to-safety-firewall))

Safety Firewall wraps package managers and acts as a security filter between public repositories and your developers. Every package installation request is analyzed in real-time, blocking malicious, vulnerable, and non-compliant packages before they're installed.

{% content-ref url="/pages/aVpFrfEiANweogsdlKQ4" %}
[Introduction to Safety Firewall](/safety-docs/firewall/introduction-to-safety-firewall)
{% endcontent-ref %}

### Vulnerability Scanning ([Safety CLI](/safety-docs/safety-cli/introduction-to-safety-cli-vulnerability-scanning))

Safety scans your dependencies for known vulnerabilities, leveraging the industry's most comprehensive vulnerability database — covering 3x more vulnerabilities than competitors like Dependabot, pip-audit, Snyk, and OSV. Safety CLI can be deployed in minutes and provides clear, actionable remediation recommendations.

{% content-ref url="/pages/YZcZJBYpxqz7uc9nkf7Z" %}
[Introduction to Safety CLI Vulnerability Scanning](/safety-docs/safety-cli/introduction-to-safety-cli-vulnerability-scanning)
{% endcontent-ref %}

### Governance & Compliance

Identify risks including known vulnerabilities, copyleft licensing issues, and malicious software. Govern what can and cannot be installed across your organization's developer workstations and build pipelines.

***

## Why Safety?

| **Challenge**                                    | **How Safety Helps**                                                    |
| ------------------------------------------------ | ----------------------------------------------------------------------- |
| AI agents install unvetted packages              | Real-time blocking of malicious and vulnerable packages at install time |
| No visibility into what's on developer machines  | Complete workstation inventory across all ecosystems                    |
| Typosquatting & dependency confusion attacks     | Pre-screening every package before installation                         |
| Compromised trusted packages                     | Continuous monitoring with the industry's deepest vulnerability data    |
| IDE extensions and MCP servers as attack vectors | Discovery and risk analysis of developer tooling beyond just packages   |

***

## Supported Platforms

| Platform         | Support |
| ---------------- | ------- |
| macOS            | ✅       |
| Windows          | ✅       |
| Linux            | ✅       |
| WSL              | ✅       |
| Docker           | ✅       |
| Virtual Desktops | ✅       |

***

## Get Started

{% hint style="success" %}

### Get Started with a 7-Day Free Trial

[Click here to create an account and access a 7-day Free Trial.](https://platform.safetycli.com/register/)
{% endhint %}

{% hint style="info" %}

### Book a Demo

[See Safety in action with a guided demo.](https://getsafety.com/)
{% endhint %}

### Guides

{% content-ref url="/pages/aVpFrfEiANweogsdlKQ4" %}
[Introduction to Safety Firewall](/safety-docs/firewall/introduction-to-safety-firewall)
{% endcontent-ref %}

{% content-ref url="/pages/7fkmHwKSabJy1yUHvtCR" %}
[Installation and Configuration](/safety-docs/firewall/installation-and-configuration)
{% endcontent-ref %}

{% content-ref url="/pages/nolLqsyRLZHjUEFJevcE" %}
[Quick Start Guide](/safety-docs/safety-cli/introduction-to-safety-cli-vulnerability-scanning/quick-start-guide)
{% endcontent-ref %}

{% content-ref url="/pages/I8hRfKfCtmeMjXUDGmwd" %}
[Viewing Scan Results](/safety-docs/safety-cli/scanning-for-vulnerable-and-malicious-packages/viewing-scan-results)
{% endcontent-ref %}

{% content-ref url="/pages/enjZt75CrEWub15wwSOa" %}
[Available Commands and Inputs](/safety-docs/safety-cli/scanning-for-vulnerable-and-malicious-packages/available-commands-and-inputs)
{% endcontent-ref %}

***

{% hint style="info" %}

### Upgrade to Safety 3.7.0

[Safety 3.7.0](/safety-docs/safety-cli/introduction-to-safety-cli-vulnerability-scanning) is now available. The minimum version required to run Safety Firewall is 3.5.0. To upgrade, use `pip install -U safety` or `uv tool install safety==3.7.0`

For details on upgrading from Safety CL 2.x to Safety CLI 3.x, [refer to our migration guide](/safety-docs/safety-cli/introduction-to-safety-cli-vulnerability-scanning/migrating-from-safety-cli-2.x-to-safety-cli-3.x).
{% endhint %}


# PyUp.io is now Safety Cybersecurity

PyUp.io rebranded to Safety in July 2023.

Over the past five years, thousands of teams have secured their open-source Python projects thanks to PyUp’s industry-leading vulnerability database and Safety CLI scanner.

As Safety Cybersecurity, we will be expanding these capabilities by focusing on reducing vulnerability noise, securing your systems end-to-end, and applying the same industry-leading data analysis and features to the Javascript, Java, .NET, Go, Rust, C and Ruby ecosystems.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-8541a07e42fe347e452646ba909b352c7f6d9928%2FGitBook.png?alt=media" alt=""><figcaption><p>New Safety Cybersecurity Logo</p></figcaption></figure>

**What does this mean for existing customers?**

1\. Invoices, contracts, and other formal documentation, including [Terms of Service](https://campaigns-events.pdx-1.onpdr.com/track/link/g034rj38y9/9g6goys8e3obuyj1klfueek0f/n4g034y9q6), will now be issued from Safety CLI Cybersecurity, Inc.

2\. There are no changes to ownership structure.

3\. Our public website will transition from [https://pyup.io](https://campaigns-events.pdx-1.onpdr.com/track/link/g034rj38y9/9g6goys8e3obuyj1klfueek0f/ry10ejndo7) to [https://safetycli.com](https://campaigns-events.pdx-1.onpdr.com/track/link/g034rj38y9/9g6goys8e3obuyj1klfueek0f/v5p0obrdrw).

4\. All existing features will continue to work as they do today.

[Read about our rebrand from PyUp to Safety Cybersecurity, improvements to the Safety CLI vulnerability scanner, and more here.](https://safetycli.com/research/welcome-to-safety)


# Introduction to Safety Firewall

## What is Safety Firewall?

Safety Firewall is a new approach to software supply chain security that *prevents* vulnerable and malicious packages from entering your systems before they can cause harm. Safety Firewall intercepts package installation requests, analyzes them against Safety's comprehensive vulnerability and malicious package database, and either approves, warns about, or blocks installations based on your organization's security policies.

{% hint style="info" %}
**What Safety Firewall governs.** Safety Firewall governs a package when that package resolves through the Safety index, which proxies the public PyPI and npm registries. A package that resolves through a different index, whether one added alongside Safety's or one configured in place of it, installs without Safety's checks.

See [How Each Package Manager Is Configured](/safety-docs/firewall/installation-and-configuration#how-each-package-manager-is-configured) for what Safety sets on your system and how to confirm it is in effect.
{% endhint %}

### Key Benefits

* **Prevention-First Security**: Stop threats before they enter your system instead of detecting them after installation
* **Developer-Friendly Protection**: Seamlessly integrates with existing workflows and package managers (pip, poetry, and more)
* **Comprehensive Coverage**: Protects against known vulnerabilities, malicious packages, typosquatting, and other supply chain risks
* **Real-Time Protection**: Analyzes every package installation request as it happens
* **Organizational Visibility**: Provides insights into what packages are being installed, where, and by whom across your organization'

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-1f8ffc8c1fd9acc738f0cc19ae5dee5eac21c006%2FFirewall%20Dashboard.png?alt=media" alt=""><figcaption></figcaption></figure>

### How Safety Firewall Works

1. Safety Firewall aliases your package managers (pip, poetry, etc.) and points them at the Safety index
2. When a package installation is requested, Safety Firewall intercepts the request.
3. The package is analyzed in real-time against Safety's comprehensive vulnerability and malicious package database.
4. Based on your policies, the package is either approved, flagged with a warning, or blocked.
5. All installation activity is logged to Safety Platform for visibility and audit purposes.

{% hint style="success" %}
Safety Firewall protects not only code projects but also development environments and systems that fall outside of version-controlled codebases. This means even ad-hoc package installations by developers or data scientists are protected.
{% endhint %}

### Who Benefits from Safety Firewall?

* **Security Teams**: Gain confidence that supply chain threats are being prevented, not just detected
* **DevOps Teams**: Ensure consistent security policies across all environments without disrupting workflows
* **Developers**: Stay protected without changing how you work or adding security overhead
* **Compliance Teams**: Generate comprehensive audit logs of all package installations across the organization


# Installation and Configuration

## System Requirements

Before installing Safety Firewall, ensure your system meets the following requirements:

* **Operating Systems**:
  * macOS 10.14 or later
  * Linux (Ubuntu, Debian, CentOS, RHEL)
  * Windows 10 or later
* **Python**: Version 3.8 or later

{% hint style="warning" %}
Safety Firewall works with pip, uv, poetry, and npm. We'll automatically detect and configure the package managers on your system.
{% endhint %}

## Before You Begin

### Create a Safety Account

If you don't already have a Safety account, [sign up here](https://platform.safetycli.com/register).

### Get Your Organization Ready

To use Safety Firewall, your organization must have the Firewall feature enabled. If you're unsure whether your organization has access, contact your Safety administrator or [reach out to our support team](mailto:support@safetycli.com).

{% hint style="info" %}
If your Safety account was created before March 2025, the Firewall feature is disabled by default to ensure no breaking changes occur. To enable Firewall, please reach out to our [support team](mailto:support@safetycli.com).
{% endhint %}

## 1. Installation

### 1.1 Install Safety CLI

Open your terminal and run the following command:

{% tabs %}
{% tab title="pip" %}

```bash
pip install safety
```

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-79138e45a55f0dcb617e56d78d0f28455235d91f%2Fpip%20install%20safety.png?alt=media" alt="" width="563"><figcaption></figcaption></figure>

{% hint style="info" %}
If you already have Safety installed, please use `pip install -U safety` . The minimum version required to run Safety Firewall is v3.5.0.
{% endhint %}
{% endtab %}

{% tab title="UV" %}

```bash
uv tool install safety
```

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-79138e45a55f0dcb617e56d78d0f28455235d91f%2Fpip%20install%20safety.png?alt=media" alt="" width="563"><figcaption></figcaption></figure>
{% endtab %}
{% endtabs %}

### 1.2 Authenticate with Safety

Run the authentication command:

```bash
safety auth login
```

This will open a browser window where you can log in to your Safety account. Once authenticated, your terminal will show a success message.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-6db09f66580e957a5910d08f4c2f3b420acb6d2b%2Fsafety%20auth%20login.png?alt=media" alt="" width="563"><figcaption></figcaption></figure>

### 1.3 Verify Authentication Status

You can check your authentication status at any time with:

```bash
safety auth status
```

This should display your email address and confirm that you're authenticated.

{% hint style="warning" %}
Make sure you're authenticated before proceeding to the next step. If you're not authenticated or don't have the Firewall feature enabled, the `safety init` command will not be available.
{% endhint %}

## 2. Initialization of Safety Firewall

After installing the Safety CLI and authenticating your account, you can initialize Safety Firewall with a single command:

```bash
safety init
```

This command starts the interactive setup process for Safety Firewall.

#### What Happens During Initialization

When you run `safety init`, the following actions take place:

1. Safety checks if you're authenticated and asks if you want to setup Safety Firewall.
2. Safety identifies the package managers on your system (pip, poetry, etc.)
3. Safety configures secure aliases for each package manager
4. Safety detects if there is a codebase in your current directory
5. Safety offers to set up this codebase for ongoing protection

{% hint style="warning" %}
**IMPORTANT**: After initialization, you'll need to refresh your shell environment for the aliases to take effect. This is typically done by running `source ~/.safety/.safety_profile` or the equivalent for your shell.
{% endhint %}

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-95cf07c9e047394403bbc74d8335a6a8cd0fd641%2Fsafety%20init.png?alt=media" alt="" width="563"><figcaption></figcaption></figure>

## 3. Verifying Firewall Installation

After initialization, you can verify that Safety Firewall is correctly installed by checking your package manager aliases:

{% tabs %}
{% tab title="Mac/Linux" %}
After initialization, you can verify that Safety Firewall is correctly installed by checking your package manager aliases:

```bash
which pip
```

You should see output similar to:

```
pip: aliased to safety pip
```

If you don't see this output, your shell environment may need to be reloaded.
{% endtab %}

{% tab title="CMD.exe" %}
After initialization, you can verify that Safety Firewall is correctly installed by checking your package manager aliases:

```bash
where pip
```

You should see output similar to:

```
pip: aliased to safety pip
```

If you don't see this output, your shell environment may need to be reloaded.
{% endtab %}

{% tab title="PowerShell" %}
After initialization, you can verify that Safety Firewall is correctly installed by checking your package manager aliases:

```bash
gcm pip
```

You should see output similar to:

```
pip: aliased to safety pip
```

If you don't see this output, your shell environment may need to be reloaded.
{% endtab %}
{% endtabs %}

## Understanding Safety Firewall Configuration

### How Safety Firewall Intercepts Commands

Safety Firewall intercepts your package manager commands so it can check a package before it installs. The mechanism differs by operating system.

On macOS and Linux, Safety writes shell aliases into `~/.safety/.safety_profile` and sources that file from your shell startup files. Running `pip install requests` matches the `pip` alias, which routes the command through Safety Firewall.

On Windows, Safety writes `.bat` shims into `%USERPROFILE%\AppData\Local\safety\`, adds that directory to your `PATH`, registers a Command Prompt macro, and adds functions to your PowerShell profile.

Once a command is intercepted, Safety Firewall:

1. Analyzes the requested package(s) for vulnerabilities and malicious code
2. Applies your organization's security policies
3. Either warns, blocks, or allows the installation
4. Records the installation event in the Safety Platform

### Where Command Interception Does Not Reach

Interception applies to the command you type, so three cases fall outside it.

1. An alias matches only the first word of a command. `python -m pip install requests` runs pip without matching the `pip` alias.
2. A script or tool that starts pip as a subprocess does not inherit the aliases from your interactive shell.
3. A container image build has no shell startup files, so no aliases exist. Write `safety pip install` explicitly in a `RUN` line to keep those installs covered.

Safety Firewall also points your package managers at the Safety index, which is a separate mechanism from interception. That configuration lives in a file, so it applies to any command the package manager runs, whether or not it was intercepted. A command-line flag, an environment variable or a project-level file can still override it. [What Can Override Your Configuration](#what-can-override-your-configuration) covers that surface per package manager.

### How Each Package Manager Is Configured

The Safety index proxies the public PyPI and npm registries. `safety init` writes it into each package manager's index configuration. For pip and npm it replaces the configured index. For uv it is added alongside the existing default index, so PyPI stays configured and uv still resolves from it for any name the Safety index does not carry.

| Package manager | User-wide                                                                                                                                                                        | Per project                                       |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| pip             | `index-url` in your user `pip.conf`                                                                                                                                              | `-i <index-url>` line added to requirements files |
| uv              | An additional index entry, written with `default = false`, in your user `uv.toml` at `$XDG_CONFIG_HOME/uv/uv.toml`, `~/.config/uv/uv.toml`, or `%APPDATA%\uv\uv.toml` on Windows | An index entry in `pyproject.toml`                |
| npm             | `registry` in your global `.npmrc`                                                                                                                                               | `registry` in the project `.npmrc`                |
| poetry          | Not configured                                                                                                                                                                   | A `safety` source added to `pyproject.toml`       |

Poetry is the exception. Safety configures Poetry per project, so a Poetry project that Safety has not configured resolves from PyPI directly. Running a Poetry command through `safety poetry` adds the source to that project before the command runs.

### How uv Resolves Indexes

uv flattens every configured index into one ordered list. Under its default `first-index` strategy it installs from the first index in that list that carries the package name. "First" means highest priority, and priority runs in this order.

1. `--index`, or the `UV_INDEX` environment variable where the flag is absent. These are one tier rather than two, because `--index` replaces the environment value instead of ranking above it. The deprecated `--extra-index-url` and `UV_EXTRA_INDEX_URL` map here.
2. Index entries in configuration files. A project's entries come before your user `uv.toml` entries, because uv concatenates the two arrays instead of replacing one with the other.
3. The default index, always last. `safety init` leaves it alone, so it stays as PyPI unless something sets `default = true`, `--default-index` or `UV_DEFAULT_INDEX`.

`safety init` writes the Safety index into your user `uv.toml` with `default = false`.

#### uv.lock Pins the Index Per Package

`uv.lock` records `source = { registry = ... }` for every package resolved from an index, and `uv sync --frozen` and `uv sync --locked` install from the recorded registry without re-resolving. A lock file created before `safety init` therefore keeps resolving from whatever registry it recorded, the same caveat that applies to `package-lock.json` and `npm ci`. Re-run `uv lock` after `safety init` to move the recorded registries to the Safety index.

#### Which uv Commands Safety Firewall Audits

The user `uv.toml` that `safety init` writes applies to every uv command that reads it. Interception, which is what audits a package before it installs, covers `uv add`, `uv remove`, `uv sync`, `uv pip install` and `uv pip uninstall`. `uvx` is a separate executable with no alias, so it is not intercepted.

### What Can Override Your Configuration

Package managers read configuration from more than one location, and the setting closest to the command wins. The overrides below are listed per package manager, because both the flags and the multiple-index behavior differ enough that a rule for one manager is wrong for another.

#### pip

Three things override the index `safety init` writes.

1. A command-line flag. `pip install --index-url <url> requests` replaces the configured index for that command.
2. The `PIP_INDEX_URL` environment variable, which overrides your user configuration file.
3. A `pip.conf` closer to the command. One inside an active virtual environment overrides your user `pip.conf`.

pip treats every configured index as equal. It queries all of them and installs the highest version it finds, with no preference between them. Adding a second index with `--extra-index-url` therefore does not leave the Safety index in charge.

> Using the `--extra-index-url` option to search for packages which are not in the main repository (for example, private packages) is unsafe. This is a class of security issue known as dependency confusion.
>
> From the [pip documentation](https://pip.pypa.io/en/stable/cli/pip_install/)

Replacing the index with `--index-url` does not carry this risk, because pip then queries one index only.

#### uv

uv reads no `pip.conf` and no `PIP_*` variable, so a team that standardized on `PIP_INDEX_URL` for an internal mirror will find uv unaffected by it in both directions. [How uv Resolves Indexes](#how-uv-resolves-indexes) covers the priority order these overrides slot into. The list below groups them by kind, because only the first two entries are priority tiers.

1. `--index`, or `UV_INDEX` where the flag is absent, which outranks every configured index including the one `safety init` writes.
2. A project `uv.toml`, or a `pyproject.toml` where no project `uv.toml` exists, whose index entries come before your user `uv.toml` entries.
3. `--default-index` or `UV_DEFAULT_INDEX`, which replaces the lowest-priority default index and leaves the Safety entry in place above it.
4. `--index-url` and `--extra-index-url`, both deprecated in uv. `--index-url` maps to the default index and `--extra-index-url` maps to `--index`.
5. `--index-strategy` or `UV_INDEX_STRATEGY`, which changes how uv chooses between the configured indexes.
6. `--config-file` or `UV_CONFIG_FILE`, and `--no-config` or `UV_NO_CONFIG`. Both discard every configuration file uv would otherwise read, a project `uv.toml` or `pyproject.toml` included, not only your user `uv.toml`.

#### poetry

poetry searches all primary sources for every package. Adding your own primary source alongside Safety's means both are searched.

#### npm

npm resolves each package from one registry, chosen by the package's scope, and has no equivalent of `--extra-index-url`. Lock files are the case to watch. A `package-lock.json` created while a different custom registry was configured records that registry, and `npm ci` keeps installing from it after you configure the Safety registry.

### Checking Your Configuration

To see which index pip uses and which file set it, run:

```bash
pip config debug
```

The output lists every configuration file pip checked, in precedence order, with the values each one sets. `pip config list` shows the effective values without showing their source.

For npm, run:

```bash
npm config get registry
npm config list
```

`npm config list` labels each value with the file or environment variable it came from.

uv has no equivalent of `pip config debug`, and reading `uv.toml` alone misses environment variables, command-line flags, project-level entries and `UV_NO_CONFIG`. Resolve a package with logging instead. In a project, run:

```bash
uv lock -v --refresh
```

Outside a project, run this from an active virtual environment. `command` bypasses the Safety alias, which otherwise injects its own index into the environment you are trying to inspect:

```bash
command uv pip install --dry-run --refresh -v <package>
```

Read the `Sending fresh GET request for: <index>/<package>/` lines, which appear highest priority first. `--refresh` matters, because a warm cache logs `Found fresh response for:` instead and the index never appears in the output. After a lock, `uv.lock` records the registry each package resolved from.

For Poetry, check the sources in `pyproject.toml` directly.

Installs run through Safety Firewall set the index for that command only, so these commands show your configuration rather than the index a Safety-intercepted install used.

### Configuration Files

Safety Firewall creates several configuration files on your system:

* **`~/.safety/`**: The main directory for Safety Firewall configuration
* **`~/.safety/config.toml`**: Global configuration file
* **`.safety-project.ini`**: Project-specific configuration (created in each code base directory)

{% hint style="info" %}
Most users won't need to manually edit these files. Configuration changes are typically made through the Safety CLI or Safety Platform
{% endhint %}

## Configuring Your First Codebase

During initialization, Safety may detect a requirements file or Python project in your current directory and offer to configure it as a code base. If you accept, Safety will:

1. Create a `.safety-project.ini` file in the directory
2. Perform an initial scan of the project's dependencies
3. Upload the scan results to the Safety Platform
4. Configure the directory for ongoing monitoring

### Manual Codebase Configuration

If you want to set up a codebase after initialization, navigate to the project directory and run:

```bash
safety codebase init
```

Follow the prompts to name the code base and set up initial scanning.

{% hint style="info" %}
A "codebase" in Safety refers to a project that is tracked and monitored by the Safety Platform. When a codebase is configured, any package installations or removals within that directory will automatically trigger scans and update the project's security status in the Safety Platform.
{% endhint %}

### Configuration Options

#### Supported Package Managers

Safety Firewall currently supports the following package managers:

* **pip**: Supported user-wide and per project
* **uv**: Supported user-wide and per project. Safety writes your user `uv.toml`, not uv's system file at `/etc/uv/uv.toml` or `%PROGRAMDATA%\uv\uv.toml`
* **npm**: Supported user-wide and per project
* **poetry**: Supported per project

Safety configures Poetry by adding a source to a project's `pyproject.toml`, so a Poetry project that Safety has not configured resolves from PyPI directly. See [How Each Package Manager Is Configured](#how-each-package-manager-is-configured).

Additional package managers will be added in future updates.


# Installing Safety CLI via Binary

### Overview

Safety CLI can be installed using three different methods: pip, uv, or pre-compiled binaries. This guide covers the binary installation method.

#### Benefits of Binary Installation

Installing Safety via binaries offers several advantages:

* **No Python Required**: Safety runs as a standalone executable, eliminating Python installation and version management
* **Zero Dependency Conflicts**: Completely isolated from your Python environment and projects
* **Faster Startup**: Pre-compiled binaries start faster than Python-based installations
* **Simplified CI/CD**: Easier integration into build pipelines without Python environment setup
* **Consistent Performance**: Guaranteed consistent behavior across different environments
* **Smaller Footprint**: No need to install and manage Python dependencies

### Prerequisites

Before installing Safety via binary, ensure you have:

* **Linux/macOS**: `curl` installed (typically pre-installed)
* **Windows**: PowerShell 5.0 or later (included in Windows 10+)
* Internet connection to download the binary from GitHub releases

### Uninstalling Existing Safety Versions

If you already have Safety installed through pip or uv, you must uninstall it first to avoid conflicts between the global binary and Python package installations.

#### Check Current Installation

First, verify if Safety is currently installed and identify the installation method:

```bash
safety --version
which safety  # Linux/macOS
where safety  # Windows
```

#### Uninstall Python-based Installations

**If installed via pip:**

```bash
pip uninstall safety
```

**If installed via uv tool:**

```bash
uv tool uninstall safety
```

**If installed in a virtual environment:**

If Safety was installed in a virtual environment, you don't need to uninstall it unless you want the binary to be your primary Safety installation. The binary will be installed globally and take precedence when called from outside virtual environments.

#### Verify Uninstallation

After uninstalling, verify that Safety is no longer available:

```bash
safety --version
```

This command should return "command not found" or similar. If it still shows a version, check for additional installations:

```bash
# Linux/macOS: Check all Safety installations
which -a safety

# Windows: Check all Safety installations  
where /R C:\ safety.exe
```

## Installing Safety CLI Binary

The binary installation uses a shell script (Linux/macOS) or PowerShell script (Windows) that automatically:

1. Detects your operating system and architecture
2. Downloads the appropriate pre-compiled binary from GitHub releases
3. Installs Safety to your system PATH
4. Makes the `safety` command available globally

{% tabs %}
{% tab title="Windows" %}
**Windows**

Run the following command in PowerShell (as Administrator recommended):

```powershell
powershell -ExecutionPolicy ByPass -c '$env:SAFETY_LATEST_TAG=1; irm https://getsafety.com/cli/install.ps1 | iex'
```

**What this does:**

* `-ExecutionPolicy ByPass`: Temporarily bypasses PowerShell execution restrictions for this installation
* `$env:SAFETY_LATEST_TAG=1`: Sets the environment variable to download the latest version, including prereleases
* `irm`: PowerShell alias for `Invoke-RestMethod` (downloads the script)
* `iex`: PowerShell alias for `Invoke-Expression` (executes the script)

**Installation Location**

After installation, the Safety binary will be available globally:

* **Windows**: Typically installed to `%``USERPROFILE%\.safety\bin\safety.exe`
  {% endtab %}

{% tab title="Linux and macOS" %}
**Linux and macOS**

Run the following command in your terminal:

```bash
curl -LsSf https://getsafety.com/cli/install.sh | SAFETY_LATEST_TAG=1 sh
```

**What this does:**

* `curl -LsSf`: Downloads the installation script
  * `-L`: Follows redirects
  * `-s`: Silent mode (no progress bar)
  * `-S`: Shows errors if they occur
  * `-f`: Fails silently on server errors
* `SAFETY_LATEST_TAG=1`: Flag to download the latest available version (including pre-releases)
* `sh`: Executes the installation script

**Installation Location**

After installation, the Safety binary will be available globally:

* **Linux/macOS**: Typically installed to `~/.local/bin/safety` or `/usr/local/bin/safety`
  {% endtab %}
  {% endtabs %}

### Verifying Installation

After installation completes, verify that Safety is installed correctly:

```bash
safety --version
```

This should display the installed Safety version. You can also check the installation location:

```bash
which safety  # Linux/macOS
where safety  # Windows
```

### Important Notes and Troubleshooting

#### Download Speed and Rate Limiting

The installation script downloads binaries directly from GitHub releases. If you experience slow downloads or timeouts:

* **GitHub Rate Limiting**: Unauthenticated GitHub API requests are limited. Downloads may take up to 3 minutes depending on GitHub's current rate limits and your location
* **Network Issues**: Ensure you have a stable internet connection
* **Future Improvement**: CloudFront-based distribution is planned to improve download speeds and reliability

#### Version Control

The `SAFETY_LATEST_TAG=1` flag ensures you receive the latest available version:

* Includes both stable releases and pre-releases
* If you need a specific version, this flag can be adjusted once additional version options are available

#### Permission Issues

**Linux/macOS:** If you encounter permission errors during installation:

```bash
# Try with elevated permissions
curl -LsSf https://getsafety.com/cli/install.sh | sudo SAFETY_LATEST_TAG=1 sh
```

**Windows:** Run PowerShell as Administrator if you encounter access denied errors.

#### PATH Configuration

If the `safety` command is not found after installation:

**Linux/macOS:** Add the installation directory to your PATH by adding this line to your `~/.bashrc`, `~/.zshrc`, or equivalent:

```bash
export PATH="$HOME/.local/bin:$PATH"
```

Then reload your shell configuration:

```bash
source ~/.bashrc  # or ~/.zshrc
```

**Windows:** The installer should automatically add Safety to your PATH. If not, manually add the installation directory to your system PATH through System Properties > Environment Variables.

### Next Steps

After successfully installing Safety CLI, proceed to authenticate with your Safety account:

For detailed authentication instructions, see the [Installation and Authentication](/safety-docs/safety-cli/installation-and-authentication) guide.

### Switching Between Installation Methods

If you want to switch from binary installation back to pip or uv:

1. Remove the binary from your system PATH (location varies by OS)
2. Install using your preferred method: `pip install safety` or `uv pip install safety`
3. Verify the installation: `safety --version` and check the installation path

***

**Note**: Binary installation is currently in preview. Once fully production-ready, this will become a recommended installation method alongside pip and uv options.


# Uninstalling Firewall

## Firewall Uninstallation

If you need to uninstall Safety Firewall, you can do so with the following command:

```bash
safety firewall uninstall
```

This will remove all aliases and return your package managers to their original configuration.

{% hint style="warning" %}
If you're unable to run the uninstall command (for example, if you're not authenticated), you may need to manually remove the aliases from your shell configuration files and the `~/.safety` directory.
{% endhint %}


# Using Safety Firewall in Docker

This guide explains how to integrate Safety Firewall into your Docker builds to protect package installations during image creation.

### Overview

Safety Firewall wraps your package manager (e.g. pip and uv) to automatically scan packages for vulnerabilities and block malicious packages before they're installed. In Docker, you install Safety first, then use it to protect your package installations.

### Prerequisites

* A Safety API key (available from your [Safety dashboard](/safety-docs/firewall/installation-and-configuration/using-safety-firewall-in-docker))
* Docker with BuildKit support (recommended for secure secret handling)

### Basic Setup

The simplest approach is to install Safety and then use it to wrap your pip install command:

```dockerfile
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .

RUN pip install safety
RUN safety --key "YOUR_API_KEY" pip install --no-cache-dir -r requirements.txt
```

{% hint style="warning" %}
This example includes the API key directly in the Dockerfile, which is not recommended for production use. The key will be visible in your image history and any systems that have access to your Dockerfile. Use this approach only for initial testing.
{% endhint %}

### Recommended: Using Docker BuildKit Secrets

Docker BuildKit secrets allow you to pass sensitive values to your build without embedding them in the image or Dockerfile.

#### Option 1: Environment Variable

Pass your API key as an environment variable at build time.

**Dockerfile:**

```dockerfile
# syntax=docker/dockerfile:1.4
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .

RUN pip install safety
RUN --mount=type=secret,id=safety_api_key \
    SAFETY_API_KEY="$(cat /run/secrets/safety_api_key)" && \
    safety --key "$SAFETY_API_KEY" pip install --no-cache-dir -r requirements.txt
```

**Build command:**

```bash
export SAFETY_API_KEY="your-api-key-here"

DOCKER_BUILDKIT=1 docker build \
    --secret id=safety_api_key,env=SAFETY_API_KEY \
    -t my-app .
```

#### Option 2: Using a .env File

If you prefer to store your API key in a file (useful for local development or when using .env files), you can source it directly.

**Dockerfile:**

```dockerfile
# syntax=docker/dockerfile:1.4
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .

RUN pip install safety
RUN --mount=type=secret,id=safety_api_key \
    . /run/secrets/safety_api_key && \
    safety --key "$SAFETY_API_KEY" pip install --no-cache-dir -r requirements.txt
```

**Create a secrets file** (e.g., `safety-api-key.env`):

```bash
SAFETY_API_KEY="your-api-key-here"
```

{% hint style="info" %}
Make sure to add this file to your `.gitignore` to avoid accidentally committing your API key.
{% endhint %}

**Build command:**

```bash
DOCKER_BUILDKIT=1 docker build \
    --secret id=safety_api_key,src=./safety-api-key.env \
    -t my-app .
```

### CI/CD Integration

In CI/CD pipelines, store your Safety API key as a secret in your CI platform (GitHub Actions, GitLab CI, etc.) and pass it to the Docker build using the environment variable approach shown above.

For example, in GitHub Actions:

```yaml
- name: Build Docker image
  env:
    SAFETY_API_KEY: ${{ secrets.SAFETY_API_KEY }}
  run: |
    DOCKER_BUILDKIT=1 docker build \
        --secret id=safety_api_key,env=SAFETY_API_KEY \
        -t my-app .
```

### Troubleshooting

| Issue                       | Solution                                                               |
| --------------------------- | ---------------------------------------------------------------------- |
| `safety: command not found` | Ensure `pip install safety` runs before the Safety Firewall command    |
| Authentication errors       | Verify your API key is valid and properly passed to the build          |
| BuildKit not enabled        | Set `DOCKER_BUILDKIT=1` or configure Docker to use BuildKit by default |


# Using Firewall

This guide covers the everyday experience of working with Safety Firewall, including how package installations work, viewing scan results, and understanding warning messages.

## Using Aliased Package Managers

After installing Safety Firewall, your package managers (like pip) are aliased to their Safety equivalents. This means every time you use a package manager, Safety Firewall automatically intercepts and analyzes the request.

Interception covers the command you type. Safety Firewall also points your package managers at the Safety index, which applies to commands that interception does not reach. See [How Safety Firewall Intercepts Commands](/safety-docs/firewall/installation-and-configuration#how-safety-firewall-intercepts-commands) for both mechanisms and what can override them.

### Verifying Alias Configuration

After initialization, you can verify that Safety Firewall is correctly installed by checking your package manager aliases:

{% tabs %}
{% tab title="Mac/Linux" %}
After initialization, you can verify that Safety Firewall is correctly installed by checking your package manager aliases:

```bash
which pip
```

You should see output similar to:

```
pip: aliased to safety pip
```

If you don't see this output, you may need to reload your shell configuration:

```bash
source ~/.profile  # or ~/.bashrc, ~/.zshrc, etc.
```

{% endtab %}

{% tab title="CMD.exe" %}
After initialization, you can verify that Safety Firewall is correctly installed by checking your package manager aliases:

```bash
where pip
```

You should see output similar to:

```
pip: aliased to safety pip
```

If you don't see this output, your shell environment may need to be reloaded.
{% endtab %}

{% tab title="PowerShell" %}
After initialization, you can verify that Safety Firewall is correctly installed by checking your package manager aliases:

```bash
gcm pip
```

You should see output similar to:

```
pip: aliased to safety pip
```

If you don't see this output, your shell environment may need to be reloaded.
{% endtab %}
{% endtabs %}

## Installing Packages

### Basic Package Installation

Install packages as you normally would:

```bash
pip install requests
```

Safety Firewall will:

1. Intercept the request
2. Analyze the package and its dependencies
3. Apply your organization's policies
4. Either warn, block, or allow the installation
5. Record the installation event in Safety Platform

### Installing from Requirements Files

When installing from requirements files:

```bash
pip install -r requirements.txt
```

Safety Firewall will analyze all packages specified in the file before installation.

{% hint style="info" %}
When working within a registered codebase, Safety Firewall automatically updates your requirements files to use Safety's secure package index.
{% endhint %}

## Understanding Firewall Messages

Every install through Safety Firewall which produced findings ends with a short status message. For example, a blocked install looks like:

```
× Safety Firewall blocked this install
  Package   : lodestone@0.0.58 (pypi)
  Reason    : this version is known-malicious
  Policy    : default
  Report    : contact your administrator or email support@safetycli.com
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

Every message you can see, and how to resolve each one, is documented in:

{% content-ref url="/pages/TMeYlFP8C8b0Qc1mP4x1" %}
[Understanding Firewall Messages](/safety-docs/firewall/using-firewall/understanding-firewall-messages)
{% endcontent-ref %}

{% hint style="warning" %}
**IMPORTANT**: Blocked installations are recorded in Safety Platform for audit purposes. If a legitimate package is blocked, contact your organization's Safety administrator.
{% endhint %}

## Performance Considerations

### Installation Speed

Package installations through Safety Firewall may be slightly slower than direct installations. This is because Safety Firewall downloads the package before delivering it to your system.

{% hint style="info" %}
The slight increase in installation time is offset by the security benefits of preventing vulnerable or malicious packages from entering your system.
{% endhint %}

## Working with Codebases

### Automatic Scans

When working in a registered codebase, Safety Firewall automatically scans your dependencies whenever you:

* Install packages with `pip install`
* Remove packages with `pip uninstall`
* Update your requirements with `pip install -r requirements.txt`

These scans happen in the background and results are uploaded to Safety Platform.

### Manual Scans

You can still perform manual scans at any time:

```bash
safety scan
```

This is useful when you want to check the current security status of your project.

## Viewing Results in Safety Platform

### Installation Activity

All package installations across your organization appear in the "Firewall" section of Safety Platform. Here you can:

* See who installed what packages and when
* Filter by user, package, or date
* View detailed information about each installation event

### Codebase Security Status

The "Codebases" section of Safety Platform shows:

* Current vulnerability counts for each codebase
* Recent scan activity
* Package installation history


# Working with Codebases

This guide explains how Safety Firewall interacts with your codebases and how to manage your projects efficiently.

## What is a Codebase in Safety?

A "codebase" in Safety refers to a project directory that is registered with Safety for vulnerability scanning and protection. When a directory is configured as a codebase:

1. Its dependencies are automatically scanned for vulnerabilities
2. Installation activities within that directory are tracked and monitored
3. Scan results and package installations are reported to Safety Platform
4. Organization policies are applied to package installations in that directory

## Setting Up a Codebase

### During Initialization

When you run `safety init`, Safety automatically detects Python projects in your current directory and offers to configure them as codebases.

### Manual Configuration

To manually set up a directory as a codebase, navigate to the project directory and run:

```bash
safety codebase init
```

Follow the prompts to name the codebase and configure it.

### What Happens When You Set Up a Codebase

When you configure a codebase, Safety:

1. Creates a `.safety-project.ini` file in the directory
2. Performs an initial scan of the project's dependencies
3. Uploads the scan results to the Safety Platform
4. Configures the directory for ongoing monitoring

{% hint style="info" %}
The name you choose for your codebase will be displayed in the Safety Platform, so choose something descriptive that helps you identify the project.
{% endhint %}

## Automatic Scanning

One of the key benefits of Safety Firewall is automatic dependency scanning:

### When Packages Are Installed or Removed

When you run commands like `pip install -r requirements.txt` or `pip uninstall package-name` within a configured codebase:

1. Safety Firewall intercepts the command
2. After the installation/removal completes, a scan is automatically triggered
3. Scan results are uploaded to Safety Platform
4. The codebase's security status is updated

This means you don't need to run `safety scan` manually after changing dependencies.

{% hint style="info" %}
Safety automatically updates your requirements files or pyproject.toml to use the Safety package index. This ensures that package installations are routed through Safety Firewall.
{% endhint %}

## Manual Scanning

You can still perform manual scans at any time:

```bash
safety scan
```

This will scan the current directory's dependencies and update the results in the Safety Platform.

## Viewing Codebase Information

#### In Safety Platform

Your codebases appear in the "Codebases" section of the Safety Platform, where you can:

* View vulnerability counts and security status
* See detailed scan results for each codebase
* Track package installation history
* Manage codebase-specific policies

## Working with Unregistered Projects

If you install packages in a directory that isn't configured as a codebase:

1. Safety Firewall still protects you by analyzing packages before installation
2. Warnings or blocks are applied based on your organization's policies
3. Installation events are still recorded in the Safety Platform
4. However, scan results are not automatically uploaded to Platform

{% hint style="info" %}
**IMPORTANT**: Even if a directory isn't registered as a codebase, Safety Firewall still provides protection against vulnerable and malicious packages. The main difference is that scan results aren't automatically tracked in the Safety Platform.
{% endhint %}

## Managing Codebase Policies

Each codebase can have specific policies that override organization-level policies:

1. Navigate to your codebase in [Safety Platform](https://platform.safetycli.com)
2. Click "Codebase Settings"
3. Go to the "Policies" tab
4. Configure codebase-specific policies

If you have a local `.safety-project.yml` then your local `.safety-policy.yml` will no longer take effect. Instead, Safety will use the policy defined for the project in the Safety Platform.


# Firewall Monitoring and Management

This guide covers how to monitor Safety Firewall activity and manage your organization's protection status using Safety Platform.

## Accessing Firewall Monitoring

To access Firewall monitoring features:

1. Log in to Safety Platform at [platform.safetycli.com](https://platform.safetycli.com)
2. Navigate to the "Firewall" section in the main navigation

### Live Firewall Updates

The Live Firewall Updates stream displays real-time events from across your organization, including:

* Package installations
* Installation warnings
* Blocked installations
* Authentication events

This stream is particularly useful for:

* Quick debugging of Firewall activity
* Monitoring recent activity across your organization
* Verifying that events are being properly recorded

{% hint style="info" %}
The live Firewall Updates stream is an appropriate way to confirm that Safety Firewall is properly configured and reporting events from your team members' environments.
{% endhint %}

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-1f8ffc8c1fd9acc738f0cc19ae5dee5eac21c006%2FFirewall%20Dashboard.png?alt=media" alt=""><figcaption></figcaption></figure>

## Organization Protection Status

The Protection Status section provides an overview of your organization's Safety Firewall implementation, including:

### Users Overview

* Total number of protected users
* List of users with Safety Firewall installed
* Last activity time for each user

### Package Managers

For each user, you can see which package managers are protected:

* pip
* uv
* npm
* poetry
* Other package managers (future releases)

This helps you ensure complete coverage across your organization.

### Installed Packages Overview

The Installed Packages section shows all packages installed across your organization:

* Package name and version
* Installation date and time
* User who performed the installation
* Whether any warnings or blocks were triggered

You can search and filter this list to find specific packages or activity.

## Managing Team Members

### Adding Users to Firewall

To add new users to Safety Firewall:

1. Navigate to the "Team" section in Safety Platform
2. Click "Invite Team Member"
3. Enter the user's email address
4. Assign appropriate roles and permissions
5. Send the invitation

New users will receive an email invitation with instructions to set up Safety Firewall.

### Monitoring Installation Status

After inviting team members, you can monitor their Firewall installation status:

1. Navigate to the "Firewall" section
2. Check the "Users" tab to see which team members have installed Firewall
3. Follow up with team members who haven't completed installation

{% hint style="info" %}
Team members must complete both the installation of Safety CLI and the Firewall initialization process to appear as protected in Safety Platform.
{% endhint %}


# Firewall Policy Management

This guide explains how to configure and manage policies in Safety Firewall to control package installation behavior across your organization.

### Understanding Firewall Policies

Policies define how Safety Firewall responds when users attempt to install packages. You can configure policies to:

* Warn users about vulnerabilities
* Block installation of vulnerable or malicious packages
* Apply different rules based on vulnerability severity
* Set organization-wide or codebase-specific policies

### Policy Hierarchy

Safety Firewall policies follow a hierarchical structure:

1. **Organization Policies**: Apply to all users and codebases
2. **Codebase Policies**: Specific to individual codebases, override organization policies
3. **Default Policies**: Applied when no specific policies are defined

{% hint style="info" %}
More specific policies always override more general policies. For example, a codebase policy takes precedence over an organization policy.
{% endhint %}

### Default Policies

Safety Firewall includes sensible default policies out of the box:

* **Installation**: Warns on all vulnerability severities
* **Scanning**: Reports all vulnerabilities regardless of severity
* **Malicious Packages**: Blocks known malicious packages

### Accessing Policy Management

To manage policies:

1. Log in to Safety Platform at [platform.safetycli.com](https://platform.safetycli.com)
2. Navigate to "Organization" → "Policies" for organization-wide policies
3. Navigate to a specific codebase and select the "Policies" tab for codebase-specific policies

{% hint style="warning" %}
**IMPORTANT:** the visual Policy Builder wizard in Safety Platform does not yet support Firewall policies. Until this is supported, you must select the **Advanced Configuration** option on the policy configuration page.
{% endhint %}

### Policy Structure and Syntax

Safety Firewall policies use a JSON structure with specific rules for allowing and denying packages or vulnerabilities.

#### Basic Policy Structure

{% code overflow="wrap" %}

```json
{
  "installation": {
    "default-action": "allow",
    "audit-logging": {
      "enabled": true
    },
    "allow": {
      // Allow rules
    },
    "deny": {
      // Deny rules
    }
  }
}
```

{% endcode %}

{% hint style="info" %}
The `default-action` determines what happens when no specific rule matches. We recommend keeping this as `"allow"` and defining specific denial rules for vulnerabilities and packages.
{% endhint %}

### Configuring Allow and Deny Rules

#### Allow Rules

Allow rules specify packages or vulnerabilities that should be explicitly permitted:

```json
"allow": {
  "packages": [
    {
      "ecosystem": "pip",
      "specifications": [
        "boto3==2.14",
        "django>=2.0",
        "flask>=1.0",
        "jinja2~=2.0"
      ]
    }
  ],
  "vulnerabilities": {
    "59901": {
      "reason": "We are not impacted by this vulnerability",
      "expires": "2024-03-15"
    },
    "62044": {
      "reason": "No upstream python images provide updated pip yet",
      "expires": "2024-06-01"
    }
  }
}
```

{% hint style="info" %}
Allow rules for vulnerabilities are helpful for temporarily ignoring specific vulnerabilities that don't affect your application or can't be remediated immediately. Always include an expiration date to ensure these exceptions are revisited.
{% endhint %}

{% hint style="warning" %}
Before allowing a package by name, confirm that name is published on the public registry. An allow rule for a name nobody has published stays in place if someone later registers it, and would then allow that package. Scoping the rule to a version, as in `boto3==2.14`, keeps it narrow.

If an install is failing because the package is not on the public registry, see [Package Not Available on the Public Index](/safety-docs/firewall/using-firewall/troubleshooting#package-not-available-on-the-public-index).
{% endhint %}

#### Deny Rules

Deny rules specify packages or vulnerabilities that should trigger warnings or blocks:

```json
"deny": {
  "packages": {
    "warning-on-any-of": {
      "age-below": "3 months",
      "packages": [
        {
          "ecosystem": "pip",
          "specifications": [
            "safety"
          ]
        }
      ]
    },
    "block-on-any-of": {
      "age-below": "1 month",
      "packages": [
        {
          "ecosystem": "pip",
          "specifications": [
            "safety"
          ]
        }
      ]
    }
  },
  "vulnerabilities": {
    "warning-on-any-of": {
      "cvss-severity": [
        "critical",
        "high"
      ]
    },
    "block-on-any-of": {
      "cvss-severity": [
        "critical"
      ]
    }
  }
}
```

### Complete Policy Example

Here's a complete policy example that:

* Allows specific package versions
* Exempts specific vulnerabilities with explanations
* Warns on packages less than 3 months old and on critical/high vulnerabilities
* Blocks packages less than 1 month old and packages with critical vulnerabilities

```json
{
  "installation": {
    "default-action": "allow",
    "audit-logging": {
      "enabled": true
    },
    "allow": {
      "packages": [
        {
          "ecosystem": "pip",
          "specifications": [
            "boto3==2.14",
            "django>=2.0",
            "flask>=1.0",
            "jinja2~=2.0"
          ]
        }
      ],
      "vulnerabilities": {
        "59901": {
          "reason": "We are not impacted by this vulnerability",
          "expires": "2024-03-15"
        },
        "62044": {
          "reason": "No upstream python images provide updated pip yet",
          "expires": "2024-06-01"
        }
      }
    },
    "deny": {
      "packages": {
        "warning-on-any-of": {
          "age-below": "3 months",
          "packages": [
            {
              "ecosystem": "pip",
              "specifications": [
                "safety"
              ]
            }
          ]
        },
        "block-on-any-of": {
          "age-below": "1 month",
          "packages": [
            {
              "ecosystem": "pip",
              "specifications": [
                "safety"
              ]
            }
          ]
        }
      },
      "vulnerabilities": {
        "warning-on-any-of": {
          "cvss-severity": [
            "critical",
            "high"
          ]
        },
        "block-on-any-of": {
          "cvss-severity": [
            "critical"
          ]
        }
      }
    }
  }
}
```

{% hint style="info" %}
When defining package specifications, you can use the same version specifiers as in requirements.txt files: exact versions (`==`), minimum versions (`>=`), compatible releases (`~=`), etc.
{% endhint %}

### Common Policy Patterns

#### Basic Security Policy

Warn on high severity vulnerabilities, block critical ones:

```json
{
  "installation": {
    "default-action": "allow",
    "audit-logging": {
      "enabled": true
    },
    "deny": {
      "vulnerabilities": {
        "warning-on-any-of": {
          "cvss-severity": [
            "high"
          ]
        },
        "block-on-any-of": {
          "cvss-severity": [
            "critical"
          ]
        }
      }
    }
  }
}
```

#### New Package Caution

Warn about packages that are less than 3 months old:

```json
{
  "installation": {
    "default-action": "allow",
    "audit-logging": {
      "enabled": true
    },
    "deny": {
      "packages": {
        "warning-on-any-of": {
          "age-below": "3 months"
        }
      }
    }
  }
}
```

### Best Practices for Policy Management

* **Start Permissive**: Begin with warning-only policies to minimize disruption
* **Gradually Increase Restrictions**: Tighten policies as your team becomes familiar with Safety Firewall
* **Communicate Changes**: Inform your team before implementing blocking policies
* **Add Documentation**: Use the `"reason"` field to document why exceptions are being made
* **Set Expirations**: Always include an `"expires"` date for vulnerability exceptions
* **Monitor Impact**: Watch the Firewall logs to see how policies affect your team's workflow

{% hint style="info" %}
Blocking policies can disrupt developer workflows. Before implementing blocking policies, start with warnings and communicate the planned changes to your team.
{% endhint %}


# Understanding Firewall Messages

What Safety Firewall prints when you install packages: successful checks, blocked installs, failed installs, and warnings, and how to resolve each one.

Every install that runs through Safety Firewall ends with a short message telling you what was checked and what happened. This page lists each message you can see, what it means, and what to do about it.

| You see                                                           | It means                                           | Details                                                                           |
| ----------------------------------------------------------------- | -------------------------------------------------- | --------------------------------------------------------------------------------- |
| `✓ Safety Firewall checked N packages`                            | The install succeeded                              | [Successful Installs](#successful-installs)                                       |
| `× Safety Firewall blocked this install`                          | Your firewall policy blocked it                    | [Blocked Installs](#blocked-installs)                                             |
| `× pip install failed`                                            | The install failed for a reason other than policy  | [Installs That Failed (Not Blocked)](#installs-that-failed-not-blocked)           |
| `⚠ Safety Firewall: X was already present, so it was not blocked` | A blocked version bypassed the firewall            | [Package was already present](#package-was-already-present-so-it-was-not-blocked) |
| `⚠ Safety could not generate a report`                            | Safety could not show a summary of what it checked | [Report unavailable](#report-unavailable)                                         |

## Anatomy of a Firewall Message

Each message starts with a status symbol:

* `✓` The install succeeded and every package was checked.
* `×` The install did not complete. It was either blocked by the firewall policy or failed for another reason.
* `⚠` A warning. The install completed with something you should fix, Safety could not show a summary, or a package failed to install for a reason other than policy.
* `ⓘ` An informational note, such as a requested version that does not exist upstream.

Below the header, a message typically contains these fields:

| Field       | Meaning                                                         | When it appears                                                                                                                                           |
| ----------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Package`   | The package and version the message is about, and its registry  | Every message that names a package. The [report unavailable](#report-unavailable) warning does not name one                                               |
| `Reason`    | Why the package was blocked, warned about, or failed to install | Every message except a clean success                                                                                                                      |
| `Policy`    | Which firewall policy made the decision                         | Every block, and the [already present](#package-was-already-present-so-it-was-not-blocked) warning. Absent when no policy decided the outcome             |
| `Resolve`   | The suggested fix                                               | Only when the firewall can name a fix                                                                                                                     |
| `Report`    | Who to contact if you believe the result is wrong               | Every message except a clean success, an [upstream registry failure](#upstream-registry-failure), and a [report unavailable](#report-unavailable) warning |
| `Report ID` | The unique id of this check. Include it when contacting support | Every report, unless Safety could not reach the [report service](#report-unavailable) before the install                                                  |

Messages print on standard output, with one exception: the [report unavailable](#report-unavailable) warning prints on standard error.

## Successful Installs

### Clean install

The normal case. Everything you asked for was checked and installed:

```
✓ Safety Firewall checked 12 packages
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

### Installed, but with known vulnerabilities

When installed packages have known vulnerabilities that your policy still allows, the install succeeds and the firewall lists what it found:

```
✓ Safety Firewall checked 12 packages (2 with vulnerabilities)

  ╭───────────────┬──────────╮
  │ Package       │ Severity │
  ├───────────────┼──────────┤
  │ flask@2.0.1   │ high     │
  │ urllib3@2.0.7 │ medium   │
  ╰───────────────┴──────────╯

  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

### Nothing to check

If a command installs nothing (for example, every requirement is already satisfied), Safety prints nothing at all. pip's own `Requirement already satisfied` output is all you see.

The firewall still checks the packages that were already installed, so silence means it found nothing to report. If one of them breaks a policy, you get the [already present](#package-was-already-present-so-it-was-not-blocked) warning instead.

## Blocked Installs

When the firewall policy refuses a package, the requested version is withheld from the package index. pip fails to install it, and the firewall explains why.

### Why packages get blocked

A block message names one of these reasons:

* **Known-malicious**: the version is confirmed malware.
* **Cooldown**: the version was released too recently. The policy holds back versions younger than a set window, for example 30 days. A window can also be set in weeks or months, and the reason names it as given, as in `your firewall policy's 2-week cooldown`.
* **Known vulnerabilities**: the version has vulnerabilities the policy does not allow.
* **Deny list or allow list**: the package is on your firewall policy's deny list, or is not on its allow list.
* **Blocked by firewall policy**: the version is blocked, but the firewall cannot name the specific cause.

The `Reason` line says what is wrong with the package. The `Policy` line below it says which policy decided that, so the name is printed once, in its own field:

* Normally it is the policy's name, as in `Policy : engineering-default`. It may be an organization-wide policy or one scoped to a single project.
* `Policy : default` means the firewall received no policy name for this decision. Most examples on this page show it.

A reason that names something your policy was configured to do calls it "your firewall policy", as in `below your firewall policy's 30-day cooldown` or `on your firewall policy's deny list`. Cooldown windows, deny lists and allow lists are all empty until someone sets them, so reaching one of those reasons means it was set. The two reasons where no configured rule made the call, [blocked by policy](#blocked-installs) and a version the firewall could not evaluate, say "the firewall policy" instead. Either way, the `Policy` line says which policy.

See [Firewall Policy Management](/safety-docs/firewall/using-firewall/firewall-policy-management) to configure these rules.

### Blocked with a specific reason

When you pin a version and the firewall knows why it is blocked, the message names the exact reason.

A known-malicious version:

```
× Safety Firewall blocked this install
  Package   : lodestone@0.0.58 (pypi)
  Reason    : this version is known-malicious
  Policy    : default
  Report    : contact your administrator or email support@safetycli.com
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

A version still inside a cooldown window, blocked by a policy the firewall can name:

```
× Safety Firewall blocked this install
  Package   : boto3@1.36.2 (pypi)
  Reason    : released 2 days ago, below your firewall policy's 30-day cooldown
  Policy    : engineering-default
  Resolve   : install boto3==1.35.99
  Report    : contact your administrator or email support@safetycli.com
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

The `Resolve` line appears when the report names a version that passed every policy. A block with no such version, a known-malicious one for example, shows `Reason` and `Report` only.

### Blocked without a specific reason

Sometimes the firewall knows your pinned version is blocked but cannot name the exact cause. The `Package` line then shows the name only, and the `Resolve` line points to a version you can install:

```
× Safety Firewall blocked this install
  Package   : requests (pypi)
  Reason    : the version you requested is blocked
  Policy    : default
  Resolve   : install requests==2.32.5
  Report    : contact your administrator or email support@safetycli.com
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

### No installable version at all

If you request a version range and no version in it is allowed:

```
× Safety Firewall blocked this install
  Package   : requests (pypi)
  Reason    : no version in your requested range (>=2.28,<2.32) is allowed
  Policy    : default
  Resolve   : install requests==2.32.5
  Report    : contact your administrator or email support@safetycli.com
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

If every version of the package is withheld, no version is installable at all:

```
× Safety Firewall blocked this install
  Package   : pycrypto (pypi)
  Reason    : all 12 versions are withheld
  Policy    : default
  Resolve   : no version of pycrypto is installable
  Report    : contact your administrator or email support@safetycli.com
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

A package with exactly one upstream version reads `the only version is withheld` instead. When versions were withheld by more than one policy, the `Policy` line lists them all, as in `Policy : engineering-default, org-baseline`.

### Resolving a block

Some block messages include a `Resolve` line with a suggested fix. When present, it takes one of two forms:

* `install X==Y`: the newest version the firewall audited and allowed.
* `no version of X is installable`: there is nothing safe to install. Contact your administrator if you need this package.

If you believe a block is wrong, the `Report` line says who to contact. Include the `Report ID`.

## Installs That Failed (Not Blocked)

A `× pip install failed` line followed by a Safety note means the install failed for a reason other than your policy.

### Upstream registry failure

```
× pip install failed
⚠ Safety Firewall failed to install the package
  Package   : requests (pypi)
  Reason    : fetching from the repository failed
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

The upstream registry could not serve the package. This is usually transient; retry the install.

### Version not found

```
× pip install failed
ⓘ Safety Firewall did not block this install
  Package   : requests@9.9.9 (pypi)
  Reason    : the requested version was not found in the upstream registry
  Resolve   : install requests==2.32.5
  Report    : contact your administrator or email support@safetycli.com
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

The version you pinned does not exist in the upstream registry. Check the pin for typos or a yanked release. The `Resolve` line names a version that does exist, and only appears when the firewall received version data for the package.

## Warnings

### Package was already present, so it was not blocked

The firewall can only block packages it serves. If a bad version is already installed, or is served from a local cache without reaching the firewall, the firewall warns you instead:

```
⚠ Safety Firewall: lodestone was already present, so it was not blocked
  Package   : lodestone@0.0.58 (pypi)
  Reason    : this version is known-malicious
  Policy    : default
  Report    : contact your administrator or email support@safetycli.com
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

Remove a known-malicious package before trying another version. The command exits 0, matching pip's behavior for packages that are already present.

### Report unavailable

If the Safety report service cannot be reached, the firewall cannot show you a summary. It prints this warning on standard error instead.

The install ran and was policy-checked, but there is no summary to show:

```
⚠ Safety could not generate a report
  Reason    : installed and policy-checked (Safety report service could not be
              reached)
  Report ID : 9a1e4d6b-0000-4000-8000-0000000000ff
```

If the service was unreachable before the install started, Safety never got a check id, so the `Report ID` line is missing:

```
⚠ Safety could not generate a report
  Reason    : installed and policy-checked (Safety report service could not be
              reached)
```

## Getting Help

If you believe a block is wrong, or you need a policy changed, contact your Safety administrator or email <support@safetycli.com> with:

1. The full message.
2. The `Report ID`. It lets support look up exactly what was checked and why it was decided.

For installation and configuration problems, see [Troubleshooting](/safety-docs/firewall/using-firewall/troubleshooting).


# Troubleshooting

This guide helps you identify and resolve common issues with Safety Firewall. If you encounter problems during installation, configuration, or daily usage, the solutions here should help you get back

## Verifying Installation

### Problem: Command Not Found

If you see `safety: command not found` or similar errors:

1. Verify that Safety CLI is installed:

   ```bash
   pip list | grep safety-cli
   ```
2. Ensure the installation directory is in your PATH:

   ```bash
   echo $PATH
   ```
3. Reinstall Safety CLI if needed:

   ```bash
   pip install --upgrade safety-cli
   ```

### Problem: "No such command" for Firewall Commands

If you see `Error: No such command 'init'` or `Error: No such command 'firewall'`:

1. Verify that you're authenticated:

   ```bash
   safety auth status
   ```
2. If not authenticated, log in:

   ```bash
   safety auth login
   ```
3. Verify that your account has the Firewall feature enabled in Safety Platform

{% hint style="warning" %}
If you're logged in but still can't access Firewall commands, your organization may not have the Firewall feature enabled. Contact your Safety administrator or [reach out to support](mailto:support@safetycli.com).
{% endhint %}

## Alias Configuration Issues

#### Problem: Package Manager Not Aliased

If `which pip` doesn't show `pip is aliased to safety pip`:

1. Reload your shell configuration:

   ```bash
   source ~/.safety/.safety_profile # or ~/.bashrc, ~/.zshrc, etc.
   ```
2. If that doesn't work, check the alias in your profile file:

   ```bash
   grep -r "alias pip=" ~/.profile ~/.bashrc ~/.zshrc 2>/dev/null
   ```
3. If the alias is missing, run the installation again:

   ```bash
   safety init
   ```

{% hint style="info" %}
On Windows, we recommend restarting your terminal after installation to ensure the alias takes effect.
{% endhint %}

## Alias Not Working After System Restart

If aliases stop working after restarting your system:

1. Check which shell you're using:

   ```bash
   echo $SHELL
   ```
2. Ensure Safety added aliases to the correct profile file for your shell
3. Add the source command to your shell's startup file if needed

{% hint style="info" %}
On macOS or Linux, you can add `source ~/.safety/shell/profile.sh` to your shell's startup file to ensure aliases are always loaded.
{% endhint %}

## Authentication Issues

### Unable to Authenticate

If you're having trouble authenticating:

1. Ensure you have a valid Safety account
2. Check your internet connection
3. Try authenticating with the verbose flag:

   ```bash
   safety auth login --verbose
   ```
4. If the browser doesn't open automatically, manually copy and paste the URL from the terminal

### Authentication Unexpectedly Fails

If you suddenly lose authentication:

1. Check your authentication status:

   ```bash
   safety auth status
   ```
2. Re-authenticate if needed:

   ```bash
   safety auth login
   ```
3. Check if your organization's API key has been rotated (for organization admins)

## Firewall Uninstallation Issues

### Unable to Uninstall Firewall

If `safety firewall uninstall` fails with "No such command":

1. First, ensure you're authenticated:

   ```bash
   safety auth login
   ```
2. If still unable to uninstall, manually remove the aliases:
   * Check your shell profile files (\~/.bashrc, \~/.zshrc, etc.) for Safety aliases
   * Remove the Safety-related lines
   * Delete the \~/.safety directory:

     ```bash
     rm -rf ~/.safety
     ```

{% hint style="info" %}
Manually uninstalling should be a last resort. This process will remove all Safety configuration from your system.
{% endhint %}

## Package Installation Issues

### Slow Package Installation

If package installations through Safety Firewall are slower than expected:

1. This is normal behaviour in the current version of Safety Firewall
2. Future versions will improve performance by streaming packages while analyzing them

{% hint style="info" %}
The slight increase in installation time is a trade-off for the security benefits of preventing vulnerable or malicious packages from entering your system.
{% endhint %}

### Unexpected Blocking of Packages

If legitimate packages are being blocked:

1. Check your organization's policies in Safety Platform
2. Look for overly restrictive rules that might be causing false positives
3. Consider temporarily modifying the policy to use warnings instead of blocks
4. If the package is incorrectly flagged, report it to [Safety support](mailto:support@safetycli.com)

### Package Not Available on the Public Index

A vendor may publish a package only to its own index and never to public PyPI or npm. The Safety index proxies the public registries, so installing a package that was never published publicly fails to resolve.

An allow rule does not fix this. Allow rules govern whether Safety permits a package it can see, and this package is absent from the index rather than blocked. An allow rule for a name nobody has published also stays in place if someone later registers that name on the public registry.

Adding the vendor's index alongside the Safety index is the wrong fix, and it fails differently in pip and in uv. Recipes for both follow. npm has no equivalent of `--extra-index-url` and resolves each package from one registry chosen by its scope, so it is not covered here.

Each one bypasses Safety's shell alias with `command`. In a configured shell `pip` routes through Safety, and the wrapper strips any `--index-url` or `-i` whose value does not point at the Safety index before pip sees it, then sets `PIP_INDEX_URL` back to the Safety index. Typing the recipe without the bypass resolves against the Safety index again, which is the failure this section works around. `\pip` or the binary's absolute path work the same way as `command pip`.

**pip.** Do not use `--extra-index-url`. pip queries all configured indexes and installs the highest version it finds, so a name published on both the vendor index and the public registry can resolve to either one. Install from the vendor's index instead, which replaces the index for that command:

```bash
command pip install --index-url <vendor-index-url> <package>
```

`--index-url` also routes the install's dependencies through the vendor index, so dependencies that index does not carry need to be installed separately. It replaces the primary index only, so an `extra-index-url` from any `pip.conf` or from `PIP_EXTRA_INDEX_URL` still applies. Remove those first.

**uv.** The pip recipe does not carry over. `--index-url` is deprecated in uv and maps to the default index, and neither it nor `--default-index` displaces the Safety index entry, because that entry is not the default index. Under uv, adding the vendor index with `--index` or a project `uv.toml` entry gives it higher priority than the Safety index for every name it carries, so it can shadow packages the Safety index serves. To resolve a single install from the vendor index, ignore your uv configuration for that command:

```bash
command uv pip install --no-config --default-index <vendor-index-url> <package>
```

`--no-config` skips configuration files only. Environment variables that add a package source, such as `UV_INDEX`, `UV_EXTRA_INDEX_URL` and `UV_FIND_LINKS`, still apply. Unset them first.

For a project, pin the vendor package to the vendor index instead of adding a competing index. `explicit = true` restricts an index to the packages that name it in `tool.uv.sources`, so it is consulted for those names only and cannot shadow anything else:

```toml
[[tool.uv.index]]
name = "vendor"
url = "<vendor-index-url>"
explicit = true

[tool.uv.sources]
vendor-package = { index = "vendor" }
```

An install that bypasses the alias is not checked against your policies at install time.

To have vendor or private registries governed by Safety Firewall, contact [Safety support](mailto:support@safetycli.com).

## Codebase Issues

### Codebase Not Appearing in Platform

If your codebase doesn't appear in Safety Platform after configuration:

1. Verify the codebase is properly initialized:

   ```bash
   ls -la | grep .safety-project.ini
   ```
2. Ensure you've run a scan at least once:

   ```bash
   safety scan
   ```
3. Check that you're authenticated with the correct organization:

   ```bash
   safety auth status
   ```

### Automatic Scans Not Working

If automatic scans aren't running after package installations:

1. Verify that you're in a properly configured codebase directory
2. Check the `.safety-project.ini` file for any configuration issues
3. Verify package manager alias is working correctly (use `which pip`)
4. Run a manual scan to check if scanning works at all:

   ```bash
   safety scan --verbose
   ```

## Platform Connection Issues

### CLI Can't Connect to Platform

If the CLI can't connect to Safety Platform:

1. Check your internet connection
2. Verify your authentication:

   ```bash
   safety auth status
   ```
3. Check for proxy or firewall issues in your network
4. Try with the verbose flag to see more details:

   ```bash
   safety scan --verbose
   ```

## Getting Additional Help

If you're still experiencing issues:

1. Run commands with the `--verbose` flag to get more detailed output
2. Check the Safety logs (located in `~/.safety/logs/`)
3. Contact Safety support at <support@safetycli.com> with:
   * Command output (including any error messages)
   * Log contents
   * Your operating system and version
   * Your Safety CLI version (`safety --version`)


# Introduction to Safety CLI Vulnerability Scanning

Vulnerability Scanning for Secure Python Development

Safety CLI is a Python dependency vulnerability scanner designed to enhance software supply chain security by detecting packages with known vulnerabilities and malicious packages in local development environments, CI/CD, and production systems.

Safety CLI can be [deployed in minutes](/safety-docs/safety-cli/introduction-to-safety-cli-vulnerability-scanning/quick-start-guide) and provides [clear, actionable recommendations](/safety-docs/safety-cli/scanning-for-vulnerable-and-malicious-packages/viewing-scan-results) for [remediation](/safety-docs/vulnerability-remediation/applying-fixes) of detected vulnerabilities.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-523a4b860dc416d78c0cb55e3643b5dd2699e64b%2Fsafety_scan_S_white.gif?alt=media" alt="" width="518"><figcaption></figcaption></figure>

Leveraging the industry's most comprehensive database of vulnerabilities and malicious packages, Safety CLI Scanner allows teams to detect vulnerabilities at every stage of the software development lifecycle.

## Key Features

* Versatile, comprehensive dependency security scanning for Python packages.
* Leverages Safety DB, the most comprehensive vulnerability data available for Python.
* Clear output with detailed recommendations for vulnerability remediation.
* Automatically updates requirements files to secure versions of dependencies where available, guided by your project's policy settings.
* Scanning of individual requirements files and project directories or system-wide scans on developer machines, CI/CD pipelines, and Production systems to detect vulnerable or malicious dependencies.
* JSON, SBOM, HTML and text output.
* Easy integration with CI/CD pipelines, including GitHub Actions.
* **Enterprise Ready:** Safety CLI can be deployed to large teams with complex project setups with ease, on-premise or as a SaaS product.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-f4b6f14cc6a06fced68e0358f67746b490122485%2Fimage.png?alt=media" alt=""><figcaption><p>Safety CLI, Version 3.0.0</p></figcaption></figure>

Integrating into your existing workflow is easy, and it is possible to scan the full software development lifecycle, from developer machines to CI/CD pipelines and Production systems.<br>

Safety CLI is backed by our industry-leading vulnerability data and recommends fixes for vulnerabilities as they are detected.

> ### Versatile, comprehensive dependency security scanning

‍Safety can be deployed in minutes, seamlessly integrates with existing workflows, and allows developers to make informed security-based decisions without disrupting productivity.

> We transitioned from the free Snyk scanning to Safety because of the recommendation of one of our lead developers. **And we have loved it.**
>
> **Sean Howard -** CEO, Flightpath

## **Supported Ecosystems**

Safety CLI vulnerability scanning supports Python. Safety Firewall additionally governs npm installations.


# Quick Start Guide

Running your first scan using Safety CLI takes less than a minute and can be performed via our [Command Line Interface](#command-line-interface) or through the [GitHub Action](#github-action) . Below we detail [1. Installation](#id-1.-installation), [2. Authentication](#id-2.-log-in-or-register), and [3. Running your first scan](#id-3.-running-your-first-scan).

To learn more about upgrading from Safety 2.x to Safety CLI please check out our [Migration guide](/safety-docs/safety-cli/introduction-to-safety-cli-vulnerability-scanning/migrating-from-safety-cli-2.x-to-safety-cli-3.x).

## Command Line Interface

## 1. Installation

Begin by installing Safety on your development machine.

1. Open your Terminal
2. Run the following command to install:

```
pip install safety
```

{% hint style="info" %}
If you already have Safety installed, please use `pip install -U safety`
{% endhint %}

## 2. Log In or Register

1\. Once installed, try to run your first scan using the following command:

```
safety scan
```

2\. If you are already logged in, Safety will perform the scan. If you are not already authenticated, Safety CLI will prompt you to [create an account](https://platform.safetycli.com/register/) or [log in](https://platform.safetycli.com/login/) using existing credentials.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-f1aa237f0c4f436864aa3acb11dca98e45f3149f%2FAuth%20Step%201.gif?alt=media" alt=""><figcaption></figcaption></figure>

In both cases, a browser window will open with clear instructions on how to log in or create a new account. Once logged in, Safety CLI will show that you are authenticated and can proceed with the next step.

{% hint style="warning" %}
You will be unable to perform vulnerability scans unless you are authenticated. [Create an account and access your free trial here. ](https://platform.safetycli.com/register/)If you require assistance, please email <support@safetycli.com>.
{% endhint %}

{% hint style="info" %}
To check your authentication status, you can run **`safety auth`** at any time.
{% endhint %}

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-b651c3539271f58c6cead2a9d9369800e9260f3a%2Fimage.png?alt=media" alt="" width="563"><figcaption><p>Safety CLI after Successful Authentication</p></figcaption></figure>

## 3. Running Your First Scan

1. Using the Terminal, navigate to a project, e.g. `cd my/project/`. (This root folder would normally contain files such as `composer.lock`, `requirements.txt`, `READMEs`, `Pipfile.lock`, `pyproject.toml`, `.gitignores` etc.)
2. Run the **`safety scan`** command.
3. Safety will now perform a scan of the current project directory, detecting all Python installations and requirements files. The output of the scan will be presented in the Terminal window.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-523a4b860dc416d78c0cb55e3643b5dd2699e64b%2Fsafety_scan_S_white.gif?alt=media" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
**Performing scans across entire development machines and in CI/CD**

Detailed documentation on how to integrate Safety with other tools, perform system-wide scans, and more are available via the links to the left.
{% endhint %}

#### Jupyter Notebook Quickstart

For users who prefer a more interactive environment, we also provide a Jupyter Notebook Quickstart guide. This notebook offers step-by-step instructions for running Safety CLI within a Jupyter environment, making it easier to explore the functionality and perform your first scan in a familiar interface.

You can access the quickstart notebook here: [Jupyter Notebook Quickstart](https://github.com/pyupio/safety/blob/main/docs/Safety-CLI-Quickstart.ipynb).

## Basic Commands

The following are the most commonly used commands. [A full glossary of available commands can be found here](/safety-docs/safety-cli/scanning-for-vulnerable-and-malicious-packages/available-commands-and-inputs).

* **`safety --help`** accesses Help and displays all available commands, utility commands, and options.
* **`safety auth`** starts the authentication flow if not logged in and displays authentication status if logged in.
* **`safety scan`** performs a vulnerability scan in the current directory.
* **`safety system-scan`** performs a vulnerability scan across the entire development machine.
* `safety scan --apply-fixes` performs a scan and automatically updates vulnerable dependencies to the next secure version.

{% hint style="info" %}
**Enterprise Customers:**

* Your organization may require installation to be performed via approved software bundles.
* If your organization leverages SAML-based authentication, you will be prompted to enter your corporate login credentials at the authentication stage.

If you are unsure whether your organization uses either of these options, please contact your administrator or email <support@safetycli.com>.
{% endhint %}

## GitHub Action

The quickest way to test Safety CLI in CI/CD is by using our [GitHub Action](https://github.com/pyupio/safety-actions), new in Safety CLI. Full [documentation on the GitHub Action](https://docs.safetycli.com/safety-docs/installation/github-actions) is available here:

[GitHub Actions](/safety-docs/installation/securing-git-repositories/github/github-actions)

If you require assistance, please email <support@safetycli.com>.


# Migrating from Safety CLI 2.x to Safety CLI 3.x

Safety CLI 3 is a significant update from Safety CLI 2.x versions, including enhancements to core features, new capabilities, and [breaking changes](/safety-docs/miscellaneous/release-notes/breaking-changes-in-safety-3).

Here's a step-by-step guide covering everything you need to know when upgrading.

## Installing Safety 3.x

1. Start by opening your Terminal and uninstall the current version of Safety CLI installed in your environment:

```
pip uninstall safety
```

2. Install the latest version of Safety using the following command:

```
pip install safety
```

Safety CLI 3.x should now be installed on your machine. To check which version is currently installed:

```
safety --version
```

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-92c8e10c231825eb1fb846b8eb56e73fe1a6bef4%2FCLI%20Version.png?alt=media" alt="" width="563"><figcaption></figcaption></figure>

{% hint style="success" %}
If this returns a version other than 3.x, there could be more than one installation of Safety on your machine. To check where Safety is being run from, run the **`which safety`** command.
{% endhint %}

## Switching to the new \`scan\` command:

The most significant change is Safety CLI 3's `scan` command, which replaces the old `safety check` command as the main security scanning command. `safety scan` is more powerful, configurable, and easier to use compared to `safety check`, and differs in the following key ways:

* it searches the target directory being scanned recursively, finding and reporting all Python dependency manifest files and virtual environments automatically, without any need for specifying where these files are
* it natively supports requirements.txt, poetry.lock, and Pipfile.lock files as well as Python virtual environment directories
* it is configured with a differently structured .safety-policy.yml file, which is incompatible with the .safety-policy.yml file used with the older \`safety check\` command, and visa versa. This new configuration file is more flexible and powerful
* it can be configured for how it searches the project directory, which vulnerabilities it reports on, and separately defines rules for which vulnerabilities will return a failing (non-zero) exit code, for use within CI/CD pipelines and build scripts

We highly recommend switching to `safety scan` as soon as possible.

## Configuration files: Convert to the new policy file format

{% hint style="info" %}
If you are not currently using a `.safety-policy.yml` file with safety check, you can skip this section!
{% endhint %}

Safety CLI 3's `scan` and `system-scan` commands are configured using a new and updated configuration file. See [Safety 3's policy file documentation page](/safety-docs/administration/safety-policy-files) for full details on the new structure and features of this policy file.

If you have been using a `.safety-policy.yml` file with `safety check`, you'll need to convert this to safety scan's new policy file format to use it to maintain the same policies with `safety scan`. To do this:

* Navigate to the root of your existing project
* Move your existing policy file to a different location within your project, so that you can reference it later. (If you want to continue using safety check while also the new safety scan command, see instructions below \[link])
* Using Safety CLI 3 (run `safety --version` to confirm your Safety CLI version), run `safety generate policy_file` to generate a new policy file with the updated format.
* Open both policy files, and translate the relevant parts from your old policy file into the new one. The key configurations that move are:
  * Any specific vulnerabilities you have ignored in `security:ignore-vulnerabilities` move to `report:auto-ignore-in-report:vulnerabilities` which contains the same list of ignores.
  * `security:ignore-cvss-severity-below` and `security:ignore-cvss-unknown-severity` move to a combined `report:auto-ignore-in-report:cvss-severity` which is now a YAML list with options: `critical`, `high`, `medium`, `low`, and `unknown`.
  * The boolean `security:continue-on-vulnerability-error:True` property is now removed. To suppress exit codes in the new policy file, use the `fail-scan-with-exit-code:dependency-vulnerabilities:enabled:False` property.
  * The alert section of the old policy file is no longer supported and must be removed. This functionality is being replaced by Safety Platform.
* With these changes your policies are now translated to the new policy format, ready to be used with `safety scan`
* When using the `validate` command, Safety CLI 3 will validate a 3.0 policy file by default.

## Updating your scan target(s)

If you were using Safety check with the -r flag to specify requirements.txt files to scan, you no longer need these with `safety scan` - they should be found automatically.

If `safety scan` is finding dependency files or virtual environments you do not want to include in your reports, use the new `scanning-settings:exclude` property in your `.safety-policy.yml` file to exclude specific files, file types or folders from the scan. [See more details on excluding here.](/safety-docs/administration/safety-policy-files)

## New JSON output format

Safety scan has a new [JSON output](/safety-docs/output/json-output) format that differs substantially from the JSON output of safety check.

## Upgrading from safety 2.x to safety 3.x while using safety check's JSON output

Safety CLI 3 still supports the safety check command, which is almost identical to safety check from Safety 2.x. If you are upgrading from Safety CLI 2 to Safety CLI 3, you can do so without needing to make any changes. The only breaking changes in this case may be the JSON output structure. Safety 3.0's JSON output from `safety check` uses the same structure as Safety CLI 2.4.0b. If you are upgrading from safety<=2.3.5 to safety>=3.x the JSON output from `safety check` will differ.

## Using both safety check and safety scan in the same project

You can use Safety CLI 3 to run both `safety check` and `safety scan` commands, each with their own policy files. To achieve this, keep your old `safety check` policy file in your project directory under a new name, for example `.safety-check-policy.yml`, and run the check command using `safety check --policy-file .safety-check-policy.yml` to specify the policy file's path. This will allow you to shift to using `safety scan` and the new policy file format at the default `.safety-policy.yml` location.


# Installation and Authentication

## Installation

To scan and secure Python projects, you first need Safety CLI version 3 to be installed on your machine.

{% hint style="info" %}
To check whether Safety CLI 3.x is already installed, open your **terminal** and type:

```
safety --version
```

{% endhint %}

If the safety command is not found, **or your safety version is less than 3.0,** you need to install Safety version 3 using the following command before continuing:

```
pip install safety
```

{% hint style="info" %}
If you already have Safety installed, please use `pip install -U safety`
{% endhint %}

## Login Methods

When the installation of Safety version 3 has been confirmed, launch your terminal and authenticate using one of the methods outlined below.

### **Free & Team Customers**

Customers on our Free and Team plans will log in using an email and password defined when you [create your account](https://platform.safetycli.com/register/).

In your **terminal,** type:

```
safety auth login
```

Your default browser will open and you will be asked to authenticate using your Safety username and password. Login via Google is currently supported, and support for additional SSO providers will be added soon.

Once logged in, return to your terminal to verify Safety is authenticated with your account details by typing:

```
safety auth
```

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-6b8a5f7ece1ca354d52b821cb39e579b5f891614%2Fimage.png?alt=media" alt="" width="563"><figcaption><p>1. "safety auth" launches the default browser.</p></figcaption></figure>

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-6410c9b0ce01927e93d2172cc3a31911c693b875%2Fimage.png?alt=media" alt="" width="563"><figcaption><p>2. Browser-based Authentication</p></figcaption></figure>

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-8952157a4eb7c9ec28bda3d1748451924f96835b%2Fimage.png?alt=media" alt="" width="563"><figcaption><p>"safety auth" triggering browser authentication and successful login</p></figcaption></figure>

### **Enterprise Customers**

Enterprise customers can leverage the method described above or SAML-based authentication, allowing users to be authenticated using organization-specific identities. The latter preserves approved authentication flows and prevents access to anyone not registered in your internal identity platform.

As above, when installation has been confirmed, in your **terminal,** type:

```
safety auth login
```

This will open your browser to authenticate the Safety CLI tool using your work email address and password. If your organization uses SAML authentication, you will be redirected to your corporate login page.

Once logged in, return to your terminal to verify Safety is authenticated with your account details by typing:

```
safety auth
```

If you are unclear as to which method your organization uses, please contact your administrator or email us at <support@safetycli.com>.


# Scanning for Vulnerable and Malicious Packages

Scan and secure projects against dependency vulnerabilities.

## Scanning a Python project

Once Safety CLI is installed and you have authenticated, let's **scan a Python project.**

In your terminal, navigate to the root folder of a Python project, e.g. **`cd /my/project/`**. (This root folder would normally contain files such as **`composer.lock`**, **`requirements.txt`**, **`READMEs`**, **`Pipfile.lock`**, **`pyproject.toml`**, **`.gitignores`** etc.)

Once you have navigated your terminal to your Python project's root directory, run:

```
safety scan
```

{% hint style="info" %}
If this is the first time Safety has scanned this project, you may be prompted to set the project's name for tracking within Safety Platform.
{% endhint %}

Running `safety scan` will:

* Scan your Python project's entire directory for Python package files and Python virtual environments, indexing all the packages found.
* Conduct a security analysis of these packages against known security vulnerabilities and malicious package lists.
* Identify known vulnerabilities in these packages, including their location and version
* Provide fix recommendations.

{% hint style="info" %}
Safety CLI is a powerful and flexible command-line tool. It can be used in a variety of use cases, environments and stages of the development lifecycle. It can output scan reports into different formats like JSON, and it can be integrated into any CI/CD pipeline or testing system. To learn more, refer to [Safety CLI Documentation](/safety-docs/safety-cli/introduction-to-safety-cli-vulnerability-scanning).
{% endhint %}

Once complete, your terminal will show a summary of the vulnerable packages that were found and recommended actions.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-0041903d119ff51e3d9165af8a215b0c4b3f0b8e%2Fimage.png?alt=media" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
If the `safety scan` command is not found, **or your safety version is less than 3.0,** you need to [install Safety version 3](/safety-docs/safety-cli/installation-and-authentication) before continuing below.
{% endhint %}

{% hint style="info" %}
**Targeting/Including Specific Requirements Files**

In Safety CLI 2, it was possible to target specific requirements files. The new Safety Scan command is designed to allow you to scan all files in a project directory (or sub-directory) simultaneously rather than running separate scans targeted on each file.

The [Policy File](/safety-docs/administration/safety-policy-files) enables you to control the depth of those scans to detect nested requirements files, e.g. six folders deep within the current directory.

If you wish to specify a target directory for the Safety Scan, you can do so using the **`--target`** option, e.g. ` safety scan`` `` `**`--target /path/to/project`**. Safety Scan does not allow you to target single files, but the include-files section of the Policy File does allow you to include specific files in your scan if these are not detected in a normal scan.

Example:

**`include-files:`**

**`- path: inside_target_dir/requirements-docs.txt`**

**`file-type: requirements.txt`**

**`- path: inside_target_dir/requirements-dev.txt`**

**`file-type: requirements.txt`**

When running a new Safety Scan, the new CLI output will separate findings and recommendations by requirements file, e.g. requirements.txt will have its own set of recommendations, requirements-dev.txt will have its own, etc. This means that instead of running separate scans for each file, you can now run one simple scan and see all findings and recommendations in one output.
{% endhint %}


# Viewing Scan Results

How to view and understand scan results in the Safety CLI

## CLI Screen Output

When a **`safety scan`** is run, output will be displayed in the Terminal window. This output is split into the following sections:

1. **Scan Details:**
   * Version of Safety installed
   * Project repository being scanned
   * Account details of the user performing the scan
   * Confirmation that Python has been detected and the number of requirements files detected in the current location.
2. **Dependency Vulnerabilities Detected**
   * Safety provides details on all dependencies detected during the scan, the number of vulnerabilities present in each, and detailed data about those vulnerabilities, including the Vulnerability ID and relevant CVE IDs.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-12760288faa94742bdbef34d8315197468f06730%2Fimage.png?alt=media" alt=""><figcaption><p>Safety CLI output showing vulnerabiities detected in a requirements file.</p></figcaption></figure>

3. **Recommendations**
   * For each vulnerability that has been detected, Safety will recommend that each be updated to a version in which the vulnerabilities have been fixed.
   * A URL is provided, which can be copied and pasted into your browser to review additional information on each dependency, the vulnerabilities detected, and versions with the fix applied.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-3152a8c4fc5e97d15c7cb3e591417f79c28a4be8%2Fimage.png?alt=media" alt=""><figcaption><p>Recommendations provided for each vulnerability detected in the previous step.</p></figcaption></figure>

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-06021d546a0dc923dedf94214c74b3da34f1b777%2Fimage.png?alt=media" alt=""><figcaption><p>Example of detailed changelogs for a package detected in the original scan using the URL provided.</p></figcaption></figure>

## Safety Platform

In addition to viewing output in the Terminal, all scan results are pushed to Safety Platform. Full details on how to view, interpret, and act upon Safety Platform information will be published as part of the Safety Platform documentation.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-2476aefdec51b0553b470f7a278cd467b8914ce3%2Fimage.png?alt=media" alt=""><figcaption></figcaption></figure>


# Available Commands and Inputs

The available commands and options within Safety CLI Scanner are detailed below. These can also be found within Safety CLI by typing `safety --help` or `safety [command] --help`.

## Authentication

<table><thead><tr><th width="188.33333333333331">safety auth --help</th><th width="239">Command/Option</th><th>Description</th></tr></thead><tbody><tr><td>Commands</td><td><code>login</code></td><td>Authenticate with Safety CLI to perform scans. Your default browser will automatically open to <a href="https://platform.safetycli.com/">https://platform.safetycli.com</a> unless already authenticated.<br><strong>Example: <code>safety auth login</code></strong></td></tr><tr><td></td><td><code>logout</code></td><td>Log out from the current Safety CLI session.<br><strong>Example: <code>safety auth logout</code></strong></td></tr><tr><td></td><td><code>status</code></td><td>Show the current authentication status.<br><strong>Example: <code>safety auth status</code></strong></td></tr><tr><td>Options</td><td><code>--install-completion</code></td><td>Install shell-specific completion scripts.<br><strong>Example: <code>safety auth --install-completion fish</code></strong></td></tr><tr><td></td><td><code>--show-completion</code></td><td>Show shell completion scripts for manual setup.<br><strong>Example: <code>safety auth --show-completion fish</code></strong></td></tr><tr><td></td><td><code>--help</code></td><td>Show detailed help information for commands and options.<br><strong>Example: <code>safety auth --help</code></strong></td></tr></tbody></table>

## Commands

<table><thead><tr><th width="189.33333333333331">safety --help</th><th width="251">Command/Option</th><th>Description</th></tr></thead><tbody><tr><td>Commands</td><td><code>auth</code></td><td>Authenticate Safety CLI to perform scans. Your default browser will automatically open to <a href="https://platform.safetycli.com/">https://platform.safetycli.com</a>.<br><strong>Example: <code>safety auth login</code></strong></td></tr><tr><td></td><td><code>scan</code></td><td>Run vulnerability scans on a Python project directory.<br><strong>Example: <code>safety scan</code></strong></td></tr><tr><td></td><td><code>system-scan</code></td><td>Run a comprehensive scan for packages and vulnerabilities across your entire machine/environment.<br><strong>Example: <code>safety system-scan</code></strong></td></tr><tr><td>Other Commands</td><td><code>alert</code></td><td>[Deprecated] Create GitHub Pull Requests from scan results. Being replaced by newer features.<br><strong>Example: <code>safety alert</code></strong></td></tr><tr><td></td><td><code>check</code></td><td>[Deprecated] Find vulnerabilities in target files/environments. Now replaced by safety scan.<br><strong>Example: <code>safety check /path/to/requirements.txt</code></strong></td></tr><tr><td></td><td><code>check-updates</code></td><td>Check for updates to the Safety CLI.<br><strong>Example: <code>safety check-updates</code></strong></td></tr><tr><td></td><td><code>configure</code></td><td>Set up global configurations for Safety CLI, including proxy settings and organization details.<br><strong>Example: <code>safety configure --proxy-host 192.168.0.1</code></strong></td></tr><tr><td></td><td><code>generate</code></td><td>Generate a standard Safety CLI policy file to establish baseline policies for scans.<br><strong>Example: <code>safety generate policy_file</code></strong></td></tr><tr><td></td><td><code>validate</code></td><td>Check if your local Safety CLI policy file is valid.<br><strong>Example: <code>safety validate /path/to/policy.yml</code></strong></td></tr><tr><td>Options</td><td><code>--stage</code></td><td>Assign a development lifecycle stage to your scan (default: development).<br><strong>Example: <code>safety --stage production scan</code></strong></td></tr><tr><td></td><td><code>--key</code></td><td>Use an API key for scans in CI/CD or Production (default: none).<br>For Development scans, unset the API key and authenticate using safety auth.<br><strong>Example: <code>safety --key 'your_api_key' scan</code></strong></td></tr><tr><td></td><td><code>--proxy-host</code></td><td>Specify a proxy host for network communications.<br><strong>Example: <code>safety configure --proxy-host 'proxy.example.com'</code></strong></td></tr><tr><td></td><td><code>--proxy-port</code></td><td>Set the proxy port (default: 80).<br>Note: proxy details can be set globally in a config file. See safety configure --help.<br><strong>Example: <code>safety configure --proxy-port 8080</code></strong></td></tr><tr><td></td><td><code>--proxy-protocol</code></td><td>Choose the proxy protocol (default: https).<br>Note: proxy details can be set globally in a config file. See safety configure --help.<br><strong>Example: <code>safety configure --proxy-protocol https</code></strong></td></tr><tr><td></td><td><code>--disable-optional-telemetry-data</code></td><td>Opt-out of sending optional telemetry for privacy. Anonymized telemetry data will remain.<br><strong>Example: <code>safety --disable-optional-telemetry-data scan</code></strong></td></tr><tr><td></td><td><code>--debug</code></td><td>Enable debug mode for detailed output in addition to standard output.<br><strong>Example: <code>safety --debug scan</code></strong></td></tr><tr><td></td><td><code>--version</code></td><td>Display the installed version of Safety CLI.<br><strong>Example: <code>safety --version</code></strong></td></tr><tr><td></td><td><code>--help</code></td><td>Show detailed help information for commands and options.<br><strong>Example: <code>safety --help</code></strong></td></tr></tbody></table>

## Safety Scan Options

<table><thead><tr><th width="192.33333333333331">safety scan --help</th><th width="252">Command/Option</th><th>Description</th></tr></thead><tbody><tr><td>Options</td><td><code>--target</code></td><td>Define a specific project path for scanning.<br><strong>Example: <code>safety scan --target /path/to/project</code></strong></td></tr><tr><td></td><td><code>--output</code></td><td>Set the format for scan results (default: screen) using Screen, JSON, HTML, or SPDX.<br><strong>Example: <code>safety scan --output json</code></strong></td></tr><tr><td></td><td><strong>--detailed-output</strong></td><td>Enable a verbose scan report for detailed insights.<br><strong>Example: <code>safety scan --detailed-output</code></strong></td></tr><tr><td></td><td><code>--save-as</code></td><td>Save scan results in different formats, including text, json, html, and spdx.<br><strong>Example: <code>safety scan --save-as json results.json</code></strong></td></tr><tr><td></td><td><code>--policy-file</code></td><td>Use a local policy file for scanning.<br><strong>Example: <code>safety scan --policy-file /path/to/policy.yml</code></strong></td></tr><tr><td></td><td><code>--install-completion</code></td><td>Install command-line completion for easier use.<br><strong>Example: <code>safety --install-completion bash</code></strong></td></tr><tr><td></td><td><code>--show-completion</code></td><td>Display shell completion scripts for customization.<br><strong>Example: <code>safety --show-completion zsh</code></strong></td></tr><tr><td></td><td><code>--apply-fixes</code></td><td>Automatically apply updates to requirements files for identified vulnerabilities in accordance with the parameters set in the config file.<br><strong>Example: <code>safety scan --apply-fixes</code></strong></td></tr></tbody></table>

## Environment Variables

| Env Var                  | Default Value | Description                                                                                                                                                                               |
| ------------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| SAFETY\_REQUEST\_TIMEOUT | 30 seconds    | Allows setting a custom request timeout for Safety CLI when pulling vulnerability and license JSON data from servers. If set, this value takes priority over the default hardcoded value. |
|                          |               |                                                                                                                                                                                           |
|                          |               |                                                                                                                                                                                           |


# Scanning in CI/CD

## Using Safety as a GitHub Action

Safety can be integrated into your existing GitHub CI pipeline as an Action. Just add the following as a step in your workflow YAML file after setting your `SAFETY_API_KEY` secret on GitHub under Settings -> Secrets -> Actions:

```
      - uses: pyupio/safety-action@v1
        with:
          api-key: ${{ secrets.SAFETY_API_KEY }}
```

(Don't have an API Key? You can sign up for one with <https://safetycli.com/resources/plans>.)

This will run Safety scan and will fail your CI pipeline if any vulnerable packages are found.

If you have something more complicated such as a monorepo; or once you're finished testing, read the [Documentation](https://docs.safetycli.com/) for more details on configuring Safety as an action.

Link to GitHub Action: <https://github.com/marketplace/actions/pyupio-safety-action>

For more information, visit the [GitHub Action](/safety-docs/installation/securing-git-repositories/github/github-actions) documentation below:

{% content-ref url="/pages/rSKxHYyhcoPjujqSKeuI" %}
[GitHub Actions](/safety-docs/installation/securing-git-repositories/github/github-actions)
{% endcontent-ref %}


# License Scanning

Safety provides a clear overview of the licenses used across all your dependencies.

To display the licenses in use, run the **`safety license`** command instead of the usual **`safety scan`** command used to perform vulnerability scans.

You can run the **`safety license --help`** to see a full list of available options.

Running **`safety license`** will scan the current Python environment for all installed dependencies and report on their licenses.

Running **`safety license -r requirements.txt`** will report on the packages in the named requirements file.

## Output Options <a href="#output-options" id="output-options"></a>

The default output option is to the screen.

If you wish to ingest or analyze the resulting license report data you can generate a JSON file from the report by adding the **`--output json`** argument, as in the example below:

**`safety license -r requirements.txt --output json`**

Another output option is **`--output bare`** which will print the unique set of licenses that were present in the packages that were analyzed, as in the example below:

**`safety license -r requirements.txt --output bare`**


# Exit Codes

Safety natively supports improved exit codes. It will return a zero (success) exit code for scans that find no vulnerabilities and non-zero exit codes for scans that find vulnerabilities or have other issues.

If you want to suppress non-zero exit codes for scans that find vulnerabilities, you can set this in your Safety policy file. To read more, please refer to the Safety Policy File documentation.


# Scanning in Production

To run Safety in the production environment, you need to set the stage to production. This ensures that the security scans are aligned with the strict requirements of production environments.

#### Option 1: Run Safety in Production

To run the Safety CLI in production, use the `--stage` flag set to production like this: `safety --stage production scan`

Ensure the `$SAFETY_API_KEY` is set as an environment variable before running the command.

#### Option 2: Run as a Cron Job

If you want to automate this process, you can set up a cron job to periodically run Safety CLI within your Python repository. Here's an example cron job configuration:

```bash
# Run Safety every day at midnight
0 0 * * * cd /path/to/repo && \
safety --stage production scan >> \
/var/log/safety.log 2>&1
```

This will scan your production environment every day at midnight, appending the output to `/var/log/safety.log`.


# System-Wide Developer Machine Scanning

## Shift Left: Protection at Every Stage of Development

Performing scans in single project repositories or in CI/CD and Production is insufficient to provide true end-to-end security. Safety CLI 3 allows developers to detect *all* Python installations and vulnerabilities in their environment by performing system-wide scans of development machines.

Once Safety CLI is installed and you have authenticated, we follow a similar process to the normal directory-specific **`safety scan`**.

In your terminal, run the following command:

```
safety system-scan 
```

Running **`safety system-scan`** will:

* Scan your entire development machine for Python package files, requirements files and Python virtual environments, indexing all the packages found.
* Conduct a security analysis of these packages against known security vulnerabilities and malicious package lists.
* Identify known vulnerabilities in these packages, including their location and version.
* Provide fix recommendations.

Once complete, your terminal will show a summary of the vulnerable packages that were found and recommended actions.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-0041903d119ff51e3d9165af8a215b0c4b3f0b8e%2Fimage.png?alt=media" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
If the safety `system-scan` command is not found, **or your safety version is less than 3.0,** you need to [install Safety version 3](/safety-docs/safety-cli/installation-and-authentication) before continuing below.
{% endhint %}


# Safety Telemetry

By default, Safety receives non-sensitive, anonymized telemetry data when scans are performed. These data are captured solely for the purpose of delivering, maintaining and improving the Safety product.

The data captured include the following:

| Name                                                           | Description                                                                                | Essential / Optional |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------- |
| **Safety Version** (safety\_version)                           | The version of Safety that was used, e.g. v3.0.1.                                          | Essential            |
| <p><strong>Safety Source</strong></p><p>(safety\_source)</p>   | The method by which Safety was used, e.g. via the CLI or via code.                         | Essential            |
| <p><strong>Safety Command</strong></p><p>(safety\_command)</p> | Limited to the command used when running the scan (e.g. `safety scan`, `safety check`)     | Optional             |
| <p><strong>Safety Options</strong></p><p>(safety\_options)</p> | Limited to the options used and how many times each is used, e.g. `-r`, `--output`, et al. | Optional             |
| <p><strong>Python Version</strong></p><p>(python\_version)</p> | The version of Python installed.                                                           | Optional             |
| <p><strong>OS Type</strong></p><p>(os\_type)</p>               | The OS type used.                                                                          | Optional             |
| <p><strong>OS Release</strong></p><p>(os\_release)</p>         | The version of the OS used.                                                                | Optional             |
| **OS Description** (os\_description)                           | Description of the OS used.                                                                | Optional             |

## Disabling Non-Essential Telemetry Data

It is possible to disable the non-essential telemetry data by using the following option:

`--disable-optional-telemetry` e.g. `safety scan --disable-optional-telemetry`

When this option is employed, Safety will still collect the anonymized Safety Version and Safety Source. These data are required for us to be able to give our customers security protection, including security protection if Safety itself needs an update. We may also use the safety\_version to alert customers of any vulnerability or risk and recommend the user upgrade.

<br>


# Enterprise Deployment

Deploy Safety Endpoint across your organization using your enterprise management platform

Safety Endpoint can be deployed and managed at scale through your enterprise management platform. The deployment uses a lightweight script that automatically installs, updates, and maintains Safety Endpoint on enrolled devices.

## How It Works

The deployment script downloads the official Safety Endpoint setup script and executes it with your organization's enrollment key. On Windows, the script verifies the Authenticode signature from Safety CLI Cybersecurity Inc before execution. The setup script is idempotent — it installs Safety Endpoint if missing, updates it if outdated, or exits cleanly if already up to date.

The same pattern works on any platform that can run scripts on endpoints with elevated privileges on a recurring schedule — including MDMs, RMM tools, and EDR script-orchestration features like SentinelOne RemoteOps.

## Supported Platforms

<table data-view="cards"><thead><tr><th data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><a href="/safety-docs/deployment/deployment/microsoft-intune">Microsoft Intune</a></td></tr><tr><td><a href="/safety-docs/deployment/deployment/manageengine-endpoint-central">ManageEngine</a></td></tr><tr><td><a href="/safety-docs/deployment/deployment/microsoft-configuration-manager">MCM (SCCM/MECM)</a></td></tr><tr><td><a href="/safety-docs/deployment/deployment/jamf-pro">Jamf Pro</a></td></tr><tr><td><a href="/safety-docs/deployment/deployment/sentinelone">SentinelOne</a></td></tr></tbody></table>


# Microsoft Intune

Deploy and manage Safety Endpoint on Windows devices using Microsoft Intune Remediations

This guide covers deploying Safety Endpoint to Windows devices using the **Remediations** feature in Microsoft Intune. The deployment uses a single detection script that installs, updates, and verifies Safety Endpoint on a recurring schedule.

## Overview

Safety Endpoint is deployed as an Intune Remediation with a **detection script only** — no remediation script is needed. The detection script downloads the official setup script, verifies its Authenticode signature, and executes it. Since the setup script is idempotent, it handles installation, updates, and health checks internally.

Intune reports device status based on the script's exit code:

| Exit Code | Intune Status | Meaning                                     |
| --------- | ------------- | ------------------------------------------- |
| 0         | Compliant     | Safety Endpoint is installed and up to date |
| 1         | Non-Compliant | Setup failed — requires manual review       |

## Prerequisites

{% hint style="info" %}
Before starting, make sure you have:

* **Microsoft Intune** admin access with permissions to create Remediations
* **Enrollment key** from the [Safety Platform](https://getsafety.com/app/device-enrollment) — click **Manage Enrollment Key** → **Create Enrollment Key**
* Target devices running **Windows 10 or later** with PowerShell 5.0+
  {% endhint %}

## Deploy Safety Endpoint

{% stepper %}
{% step %}

### Get Your Enrollment Key

Log in to the [Safety Platform](https://getsafety.com/app/device-enrollment), click **Manage Enrollment Key** → **Create Enrollment Key**, and copy the generated key. This key links devices to your Safety organization.
{% endstep %}

{% step %}

### Prepare the Detection Script

Copy the script below and replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your organization's enrollment key:

{% code title="run.ps1" %}

```powershell
# Safety Endpoint - MDM Run Script (Windows)
# Template: values below are populated by the Safety Platform per organization.
# Paste this into your MDM script field (Intune, NinjaOne, etc.)
# Run as: SYSTEM | Schedule: recurring (e.g. every 1 hour)
#
# Downloads the Safety Endpoint setup script, verifies its Authenticode
# signature is from Safety CLI Cybersecurity Inc, and executes it.

# ── Organization Configuration ───────────────────────────────────────
$EnrollmentKey = "REPLACE_WITH_YOUR_ENROLLMENT_KEY"
# Comma-separated list of firewall tools to exclude (e.g. "pip,npm")
$ExcludeFirewallTools = ""
# ─────────────────────────────────────────────────────────────────────

$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$setupUrl = "https://getsafety.com/cli/setup.ps1"
$tmpFile  = Join-Path $env:TEMP "safety-mdm-setup-$(Get-Random).ps1"

# ── HttpClient with proxy support (same as install.ps1 / setup.ps1) ─
Add-Type -AssemblyName System.Net.Http -ErrorAction Stop

$handler = [System.Net.Http.HttpClientHandler]::new()

$proxyUrl = @($env:HTTPS_PROXY, $env:ALL_PROXY, $env:HTTP_PROXY) |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1

if ($proxyUrl) {
    $proxyUri = [Uri]$proxyUrl
    $proxy = New-Object Net.WebProxy($proxyUri)
    if ($proxyUri.UserInfo) {
        $parts = $proxyUri.UserInfo -split ":", 2
        $proxy.Credentials = New-Object Net.NetworkCredential(
            [Uri]::UnescapeDataString($parts[0]),
            $(if ($parts.Length -gt 1) { [Uri]::UnescapeDataString($parts[1]) } else { "" })
        )
    } else {
        $proxy.UseDefaultCredentials = $true
    }
    $handler.Proxy = $proxy
} else {
    $handler.DefaultProxyCredentials = [Net.CredentialCache]::DefaultCredentials
}

$client = [System.Net.Http.HttpClient]::new($handler)
$null = $client.DefaultRequestHeaders.UserAgent.ParseAdd("Safety-Endpoint/1 (run)")

# ── Download, verify, execute ────────────────────────────────────────
try {
    # 1. Download (preserves exact bytes for Authenticode)
    Write-Host "Downloading $setupUrl ..."
    $response = $client.GetAsync($setupUrl).GetAwaiter().GetResult()
    $null = $response.EnsureSuccessStatusCode()
    $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
    [System.IO.File]::WriteAllBytes($tmpFile, $bytes)

    # 2. Verify Authenticode signature
    $sig = Get-AuthenticodeSignature -FilePath $tmpFile
    if ($sig.Status -ne "Valid") {
        throw "Signature status: $($sig.Status) - $($sig.StatusMessage)"
    }
    if ($sig.SignerCertificate.Subject -notmatch "O=Safety CLI Cybersecurity Inc") {
        throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
    }
    Write-Host "Signature verified: $($sig.SignerCertificate.Subject)"

    # 3. Execute
    $setupArgs = @{ EnrollmentKey = $EnrollmentKey }
    if ($ExcludeFirewallTools) { $setupArgs.ExcludeFirewallTools = $ExcludeFirewallTools }
    & $tmpFile @setupArgs
}
catch {
    Write-Error "Safety MDM setup failed: $_"
    exit 1
}
finally {
    Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
    if ($client) { $client.Dispose() }
}
```

{% endcode %}

{% hint style="warning" %}
Make sure you replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your actual enrollment key before uploading to Intune.
{% endhint %}
{% endstep %}

{% step %}

### Create the Remediation in Intune

1. In the [Microsoft Intune admin center](https://intune.microsoft.com), go to **Devices** → **Remediations**
2. Click **Create script package**
3. On the **Basics** tab:
   * **Name**: `Safety Endpoint - Setup`
   * **Description**: Installs and maintains Safety Endpoint via signed setup script
4. On the **Settings** tab:
   * **Detection script file**: Upload your configured `run.ps1`
   * **Remediation script file**: Leave empty
   * **Run this script using the logged-on credentials**: **No**
   * **Enforce script signature check**: **No** (the script verifies the setup script's Authenticode signature internally)
   * **Run script in 64-bit PowerShell**: **Yes**
     {% endstep %}

{% step %}

### Configure the Schedule

On the **Assignments** tab:

1. Click **Add groups** and select the device groups to target
2. Under **Schedule**, set the recurrence:
   * **Recommended**: Once every **1 hour**
   * This ensures new devices get Safety Endpoint quickly and existing devices stay updated

{% hint style="info" %}
The setup script is idempotent. Running it frequently has minimal overhead — it exits quickly when Safety Endpoint is already up to date.
{% endhint %}
{% endstep %}

{% step %}

### Review and Create

Review the configuration and click **Create** to deploy the remediation.

{% hint style="success" %}
**Quick test:** You can verify the setup works immediately without waiting for the schedule. Navigate to **Devices** → select a target device → click the **three-dot menu (⋯)** → **Run remediation** (Preview). This triggers the detection script on demand so you can confirm it completes successfully.
{% endhint %}
{% endstep %}
{% endstepper %}

## Monitor Compliance

After deployment, monitor your fleet status in Intune:

1. Go to **Devices** → **Remediations**
2. Select **Safety Endpoint - Setup**
3. Review the **Device status** tab

| Status            | What It Means                            | Action                                     |
| ----------------- | ---------------------------------------- | ------------------------------------------ |
| **Compliant**     | Safety Endpoint is installed and current | None                                       |
| **Non-Compliant** | The script failed on this device         | Review the script output in device details |
| **Pending**       | Script has not yet run                   | Wait for the next scheduled run            |

To view the script output for a specific device, click on the device name and check the **Detection output** column for error details.

## Uninstall Safety Endpoint

If you need to remove Safety Endpoint from devices, deploy the uninstall wrapper script as a standalone Intune script. Like the deployment script, it downloads the official uninstall script from Safety, verifies its Authenticode signature, and executes it.

{% hint style="danger" %}
The uninstall script removes Safety Endpoint artifacts from the machine, including configuration, firewall wrappers, package manager settings, and data for all user profiles. Configuration Safety wrote into a project file stays with the project, including the index line in a requirements file, the Safety index entry in a `pyproject.toml`, and the registry in a project `.npmrc`. Remove those with the project's own tooling, for example `poetry source remove safety`.
{% endhint %}

{% stepper %}
{% step %}

### Prepare the Uninstall Script

Copy the script below — no configuration is needed:

{% code title="uninstall-run.ps1" %}

```powershell
# Safety Endpoint - MDM Uninstall Script (Windows)
# Template: paste this into your MDM script field (Intune, NinjaOne, etc.)
# Run as: SYSTEM | Schedule: one-time or on-demand
#
# Downloads the Safety Endpoint uninstall script, verifies its Authenticode
# signature is from Safety CLI Cybersecurity Inc, and executes it.

$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$uninstallUrl = "https://getsafety.com/cli/uninstall.ps1"
$tmpFile      = Join-Path $env:TEMP "safety-mdm-uninstall-$(Get-Random).ps1"

# ── HttpClient with proxy support (same as install.ps1 / setup.ps1) ─
Add-Type -AssemblyName System.Net.Http -ErrorAction Stop

$handler = [System.Net.Http.HttpClientHandler]::new()

$proxyUrl = @($env:HTTPS_PROXY, $env:ALL_PROXY, $env:HTTP_PROXY) |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1

if ($proxyUrl) {
    $proxyUri = [Uri]$proxyUrl
    $proxy = New-Object Net.WebProxy($proxyUri)
    if ($proxyUri.UserInfo) {
        $parts = $proxyUri.UserInfo -split ":", 2
        $proxy.Credentials = New-Object Net.NetworkCredential(
            [Uri]::UnescapeDataString($parts[0]),
            $(if ($parts.Length -gt 1) { [Uri]::UnescapeDataString($parts[1]) } else { "" })
        )
    } else {
        $proxy.UseDefaultCredentials = $true
    }
    $handler.Proxy = $proxy
} else {
    $handler.DefaultProxyCredentials = [Net.CredentialCache]::DefaultCredentials
}

$client = [System.Net.Http.HttpClient]::new($handler)
$null = $client.DefaultRequestHeaders.UserAgent.ParseAdd("Safety-Endpoint/1 (run)")

# ── Download, verify, execute ────────────────────────────────────────
try {
    # 1. Download (preserves exact bytes for Authenticode)
    Write-Host "Downloading $uninstallUrl ..."
    $response = $client.GetAsync($uninstallUrl).GetAwaiter().GetResult()
    $null = $response.EnsureSuccessStatusCode()
    $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
    [System.IO.File]::WriteAllBytes($tmpFile, $bytes)

    # 2. Verify Authenticode signature
    $sig = Get-AuthenticodeSignature -FilePath $tmpFile
    if ($sig.Status -ne "Valid") {
        throw "Signature status: $($sig.Status) - $($sig.StatusMessage)"
    }
    if ($sig.SignerCertificate.Subject -notmatch "O=Safety CLI Cybersecurity Inc") {
        throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
    }
    Write-Host "Signature verified: $($sig.SignerCertificate.Subject)"

    # 3. Execute
    & $tmpFile
}
catch {
    Write-Error "Safety MDM uninstall failed: $_"
    exit 1
}
finally {
    Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
    if ($client) { $client.Dispose() }
}
```

{% endcode %}
{% endstep %}

{% step %}

### Add the Script in Intune

1. In the Intune admin center, go to **Devices** → **Scripts and remediations** → **Platform scripts**
2. Click **Add** → **Windows 10 and later**
3. On the **Basics** tab:
   * **Name**: `Safety Endpoint - Uninstall`
   * **Description**: Removes all Safety Endpoint artifacts from the device
     {% endstep %}

{% step %}

### Configure Script Settings

On the **Script settings** tab:

* **Script file**: Upload `uninstall-run.ps1`
* **Run this script using the logged-on credentials**: **No**
* **Enforce script signature check**: **No** (the script verifies the uninstall script's Authenticode signature internally)
* **Run script in 64-bit PowerShell**: **Yes**
  {% endstep %}

{% step %}

### Assign and Run

1. On the **Assignments** tab, select the device groups to target
2. Click **Review + add** to deploy

The script runs once per device. It requires SYSTEM or Administrator privileges and will clean up:

* Safety Endpoint binaries and system PATH entries
* Per-user configuration, firewall wrappers, and shell profiles
* Package manager configurations (pip, uv, npm)
* Scheduled tasks created by Safety Endpoint
  {% endstep %}
  {% endstepper %}

## Troubleshooting

<details>

<summary>Script fails with proxy or network errors</summary>

The detection script automatically detects proxy settings from the `HTTPS_PROXY`, `ALL_PROXY`, or `HTTP_PROXY` environment variables. If your network requires a proxy that is not configured via these variables, set the appropriate environment variable at the system level:

1. Go to **System Properties** → **Environment Variables**
2. Add a system variable `HTTPS_PROXY` with your proxy URL (e.g., `http://proxy.company.com:8080`)

If using an authenticated proxy, include credentials in the URL: `http://user:password@proxy.company.com:8080`

</details>

<details>

<summary>Signature verification fails</summary>

The detection script verifies that the downloaded setup script is signed by **Safety CLI Cybersecurity Inc** using Authenticode. If signature verification fails:

* Ensure the device has internet access to `getsafety.com`
* Check that TLS 1.2 is enabled on the device
* Verify no network appliance is intercepting or modifying HTTPS traffic (SSL inspection can break Authenticode signatures)

</details>

<details>

<summary>Script shows Non-Compliant but Safety Endpoint is installed</summary>

A Non-Compliant status means the detection script exited with a non-zero code. This can happen if:

* The setup script detected a problem during its health check
* A newer version failed to install
* A temporary network issue occurred during the run

Check the detection output in the device details for the specific error. The next scheduled run will retry automatically.

</details>

<details>

<summary>PowerShell execution policy blocks the script</summary>

When deploying through Intune Remediations, scripts run under the SYSTEM account with execution policy bypass. If you still encounter execution policy issues:

* Ensure **Run script in 64-bit PowerShell** is set to **Yes**
* Verify no Group Policy overrides the execution policy for the SYSTEM account

</details>


# MCM (SCCM/MECM)

Deploy and manage Safety Endpoint on Windows devices using Microsoft Configuration Manager Packages & Programs

This guide covers deploying Safety Endpoint to Windows devices using **Packages & Programs** in Microsoft Configuration Manager (MCM), formerly known as System Center Configuration Manager (SCCM) and Microsoft Endpoint Configuration Manager (MECM). The deployment uses the same signed script used across all supported platforms — it installs, updates, and verifies Safety Endpoint on a recurring schedule.

## Overview

Safety Endpoint is deployed as a Configuration Manager **Package** with a **Program** that runs the detection script on a recurring schedule. The script downloads the official setup script, verifies its Authenticode signature, and executes it. Since the setup script is idempotent, it handles installation, updates, and health checks internally.

The script reports status through its exit code:

| Exit Code | Meaning                                     |
| --------- | ------------------------------------------- |
| 0         | Safety Endpoint is installed and up to date |
| 1         | Setup failed — requires manual review       |

## Prerequisites

{% hint style="info" %}
Before starting, make sure you have:

* **Configuration Manager** admin console access with permissions to create Packages and deploy to collections
* **At least one distribution point** configured and operational
* **Enrollment key** from the [Safety Platform](https://getsafety.com/app/device-enrollment) — click **Manage Enrollment Key** → **Create Enrollment Key**
* Target devices running **Windows 10 or later (64-bit)** with PowerShell 5.0+
* The **Configuration Manager client** installed and healthy on target devices
  {% endhint %}

## Deploy Safety Endpoint

{% stepper %}
{% step %}

### Get Your Enrollment Key

Log in to the [Safety Platform](https://getsafety.com/app/device-enrollment), click **Manage Enrollment Key** → **Create Enrollment Key**, and copy the generated key. This key links devices to your Safety organization.
{% endstep %}

{% step %}

### Prepare the Script

Copy the script below and replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your organization's enrollment key. Save it as `run.ps1` in a network-accessible folder (e.g., `\\your-server\sources\SafetyEndpoint\run.ps1`):

{% code title="run.ps1" %}

```powershell
# Safety Endpoint - MDM Run Script (Windows)
# Template: values below are populated by the Safety Platform per organization.
# Paste this into your MDM script field (Intune, NinjaOne, etc.)
# Run as: SYSTEM | Schedule: recurring (e.g. every 1 hour)
#
# Downloads the Safety Endpoint setup script, verifies its Authenticode
# signature is from Safety CLI Cybersecurity Inc, and executes it.

# ── Organization Configuration ───────────────────────────────────────
$EnrollmentKey = "REPLACE_WITH_YOUR_ENROLLMENT_KEY"
# Comma-separated list of firewall tools to exclude (e.g. "pip,npm")
$ExcludeFirewallTools = ""
# ─────────────────────────────────────────────────────────────────────

$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$setupUrl = "https://getsafety.com/cli/setup.ps1"
$tmpFile  = Join-Path $env:TEMP "safety-mdm-setup-$(Get-Random).ps1"

# ── HttpClient with proxy support (same as install.ps1 / setup.ps1) ─
Add-Type -AssemblyName System.Net.Http -ErrorAction Stop

$handler = [System.Net.Http.HttpClientHandler]::new()

$proxyUrl = @($env:HTTPS_PROXY, $env:ALL_PROXY, $env:HTTP_PROXY) |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1

if ($proxyUrl) {
    $proxyUri = [Uri]$proxyUrl
    $proxy = New-Object Net.WebProxy($proxyUri)
    if ($proxyUri.UserInfo) {
        $parts = $proxyUri.UserInfo -split ":", 2
        $proxy.Credentials = New-Object Net.NetworkCredential(
            [Uri]::UnescapeDataString($parts[0]),
            $(if ($parts.Length -gt 1) { [Uri]::UnescapeDataString($parts[1]) } else { "" })
        )
    } else {
        $proxy.UseDefaultCredentials = $true
    }
    $handler.Proxy = $proxy
} else {
    $handler.DefaultProxyCredentials = [Net.CredentialCache]::DefaultCredentials
}

$client = [System.Net.Http.HttpClient]::new($handler)
$null = $client.DefaultRequestHeaders.UserAgent.ParseAdd("Safety-Endpoint/1 (run)")

# ── Download, verify, execute ────────────────────────────────────────
try {
    # 1. Download (preserves exact bytes for Authenticode)
    Write-Host "Downloading $setupUrl ..."
    $response = $client.GetAsync($setupUrl).GetAwaiter().GetResult()
    $null = $response.EnsureSuccessStatusCode()
    $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
    [System.IO.File]::WriteAllBytes($tmpFile, $bytes)

    # 2. Verify Authenticode signature
    $sig = Get-AuthenticodeSignature -FilePath $tmpFile
    if ($sig.Status -ne "Valid") {
        throw "Signature status: $($sig.Status) - $($sig.StatusMessage)"
    }
    if ($sig.SignerCertificate.Subject -notmatch "O=Safety CLI Cybersecurity Inc") {
        throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
    }
    Write-Host "Signature verified: $($sig.SignerCertificate.Subject)"

    # 3. Execute
    $setupArgs = @{ EnrollmentKey = $EnrollmentKey }
    if ($ExcludeFirewallTools) { $setupArgs.ExcludeFirewallTools = $ExcludeFirewallTools }
    & $tmpFile @setupArgs
}
catch {
    Write-Error "Safety MDM setup failed: $_"
    exit 1
}
finally {
    Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
    if ($client) { $client.Dispose() }
}
```

{% endcode %}

{% hint style="warning" %}
Make sure you replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your actual enrollment key before saving the file.
{% endhint %}
{% endstep %}

{% step %}

### Create a Package

1. Open the **Configuration Manager console**
2. Go to **Software Library** → **Application Management** → **Packages**
3. Right-click **Packages** and select **Create Package**
4. On the **Package** page:
   * **Name**: `Safety Endpoint - Setup`
   * **Description**: Installs and maintains Safety Endpoint via signed setup script
   * Check **This package contains source files**
   * **Source folder**: Browse to the network folder containing `run.ps1` (e.g., `\\your-server\sources\SafetyEndpoint`)
5. Click **Next** — do not create a program yet (we configure it in the next step)
6. Complete the wizard
   {% endstep %}

{% step %}

### Create a Program

1. Right-click the **Safety Endpoint - Setup** package and select **Create Program**
2. On the **Standard Program** page:
   * **Name**: `Run Safety Setup`
   * **Command line**: `powershell.exe -ExecutionPolicy Bypass -NoProfile -File run.ps1`
   * **Run**: **Hidden**
   * **Program can run**: **Whether or not a user is logged on**
   * **Run mode**: **Run with administrative rights**
3. On the **Requirements** page:
   * **Platform requirements**: Select your target **64-bit** Windows versions only (Windows 10 x64 and later) — Safety Endpoint does not support 32-bit systems
   * **Maximum allowed run time (minutes)**: `60`
4. Complete the wizard
   {% endstep %}

{% step %}

### Distribute Content

Before deploying, the package must be copied to your distribution points:

1. Right-click the **Safety Endpoint - Setup** package
2. Select **Distribute Content**
3. Add your **distribution points** or **distribution point groups**
4. Complete the wizard and wait for content distribution to finish

{% hint style="info" %}
You can monitor distribution status in **Monitoring** → **Distribution Status** → **Content Status** → **Safety Endpoint - Setup**.
{% endhint %}
{% endstep %}

{% step %}

### Deploy to a Device Collection

1. Right-click the **Safety Endpoint - Setup** package and select **Deploy**
2. On the **General** page:
   * **Collection**: Select the device collection to target
3. On the **Content** page:
   * Verify your distribution points are listed
4. On the **Deployment Settings** page:
   * **Purpose**: **Required** (runs automatically without user interaction)
5. On the **Scheduling** page:
   * Under **Assignment schedule**, click **New\...**
   * In the dialog, select **Custom interval** and set **Recur every**: **1 hour**
   * Click **OK** to add the schedule entry — this ensures new devices get Safety Endpoint quickly and existing devices stay updated
   * Set **Rerun behavior**: **Always rerun program**
6. On the **User Experience** page:
   * **Software installation**: Allow
   * **Allow clients to use distribution points from the default site boundary group**: Yes
7. Complete the wizard

{% hint style="info" %}
The setup script is idempotent. Running it frequently has minimal overhead — it exits quickly when Safety Endpoint is already up to date.
{% endhint %}
{% endstep %}
{% endstepper %}

## Monitor Deployments

After deployment, monitor your fleet status in the Configuration Manager console:

1. Go to **Monitoring** → **Deployments**
2. Find the **Safety Endpoint - Setup** deployment
3. Review the deployment status summary

| Status          | What It Means                                 | Action                                      |
| --------------- | --------------------------------------------- | ------------------------------------------- |
| **Success**     | Safety Endpoint is installed and current      | None                                        |
| **Error**       | The script failed on this device              | Review the status message for error details |
| **In Progress** | Script is currently running or waiting to run | Wait for completion                         |
| **Unknown**     | Client has not reported status yet            | Verify client health and connectivity       |

To view details for a specific device, click the deployment, then select the **Asset Details** tab. Right-click a device and choose **More Details** → **Status Messages** to see the script output.

## Uninstall Safety Endpoint

If you need to remove Safety Endpoint from devices, create a separate package with the uninstall wrapper script. Like the deployment script, it downloads the official uninstall script from Safety, verifies its Authenticode signature, and executes it.

{% hint style="danger" %}
The uninstall script removes Safety Endpoint artifacts from the machine, including configuration, firewall wrappers, package manager settings, and data for all user profiles. Configuration Safety wrote into a project file stays with the project, including the index line in a requirements file, the Safety index entry in a `pyproject.toml`, and the registry in a project `.npmrc`. Remove those with the project's own tooling, for example `poetry source remove safety`.
{% endhint %}

{% stepper %}
{% step %}

### Prepare the Uninstall Script

Copy the script below and save it as `uninstall-run.ps1` in a network-accessible folder (e.g., `\\your-server\sources\SafetyEndpoint-Uninstall\uninstall-run.ps1`) — no configuration is needed:

{% code title="uninstall-run.ps1" %}

```powershell
# Safety Endpoint - MDM Uninstall Script (Windows)
# Template: paste this into your MDM script field (Intune, NinjaOne, etc.)
# Run as: SYSTEM | Schedule: one-time or on-demand
#
# Downloads the Safety Endpoint uninstall script, verifies its Authenticode
# signature is from Safety CLI Cybersecurity Inc, and executes it.

$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$uninstallUrl = "https://getsafety.com/cli/uninstall.ps1"
$tmpFile      = Join-Path $env:TEMP "safety-mdm-uninstall-$(Get-Random).ps1"

# ── HttpClient with proxy support (same as install.ps1 / setup.ps1) ─
Add-Type -AssemblyName System.Net.Http -ErrorAction Stop

$handler = [System.Net.Http.HttpClientHandler]::new()

$proxyUrl = @($env:HTTPS_PROXY, $env:ALL_PROXY, $env:HTTP_PROXY) |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1

if ($proxyUrl) {
    $proxyUri = [Uri]$proxyUrl
    $proxy = New-Object Net.WebProxy($proxyUri)
    if ($proxyUri.UserInfo) {
        $parts = $proxyUri.UserInfo -split ":", 2
        $proxy.Credentials = New-Object Net.NetworkCredential(
            [Uri]::UnescapeDataString($parts[0]),
            $(if ($parts.Length -gt 1) { [Uri]::UnescapeDataString($parts[1]) } else { "" })
        )
    } else {
        $proxy.UseDefaultCredentials = $true
    }
    $handler.Proxy = $proxy
} else {
    $handler.DefaultProxyCredentials = [Net.CredentialCache]::DefaultCredentials
}

$client = [System.Net.Http.HttpClient]::new($handler)
$null = $client.DefaultRequestHeaders.UserAgent.ParseAdd("Safety-Endpoint/1 (run)")

# ── Download, verify, execute ────────────────────────────────────────
try {
    # 1. Download (preserves exact bytes for Authenticode)
    Write-Host "Downloading $uninstallUrl ..."
    $response = $client.GetAsync($uninstallUrl).GetAwaiter().GetResult()
    $null = $response.EnsureSuccessStatusCode()
    $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
    [System.IO.File]::WriteAllBytes($tmpFile, $bytes)

    # 2. Verify Authenticode signature
    $sig = Get-AuthenticodeSignature -FilePath $tmpFile
    if ($sig.Status -ne "Valid") {
        throw "Signature status: $($sig.Status) - $($sig.StatusMessage)"
    }
    if ($sig.SignerCertificate.Subject -notmatch "O=Safety CLI Cybersecurity Inc") {
        throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
    }
    Write-Host "Signature verified: $($sig.SignerCertificate.Subject)"

    # 3. Execute
    & $tmpFile
}
catch {
    Write-Error "Safety MDM uninstall failed: $_"
    exit 1
}
finally {
    Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
    if ($client) { $client.Dispose() }
}
```

{% endcode %}
{% endstep %}

{% step %}

### Create the Uninstall Package

1. In the Configuration Manager console, go to **Software Library** → **Application Management** → **Packages**
2. Right-click **Packages** and select **Create Package**
3. On the **Package** page:
   * **Name**: `Safety Endpoint - Uninstall`
   * **Description**: Removes all Safety Endpoint artifacts from the device
   * Check **This package contains source files**
   * **Source folder**: Browse to the folder containing `uninstall-run.ps1`
     {% endstep %}

{% step %}

### Create the Uninstall Program

1. Right-click the **Safety Endpoint - Uninstall** package and select **Create Program**
2. On the **Standard Program** page:
   * **Name**: `Run Safety Uninstall`
   * **Command line**: `powershell.exe -ExecutionPolicy Bypass -NoProfile -File uninstall-run.ps1`
   * **Run**: **Hidden**
   * **Program can run**: **Whether or not a user is logged on**
   * **Run mode**: **Run with administrative rights**
     {% endstep %}

{% step %}

### Distribute and Deploy

1. Right-click the package → **Distribute Content** → add your distribution points
2. Right-click the package → **Deploy** → select the target device collection
3. On the **Deployment Settings** page:
   * **Purpose**: **Required**
4. On the **Scheduling** page:
   * Set to run **As soon as possible** (one-time deployment)
   * **Rerun behavior**: **Never rerun deployed program**

The script runs once per device. It requires SYSTEM privileges and will clean up:

* Safety Endpoint binaries and system PATH entries
* Per-user configuration, firewall wrappers, and shell profiles
* Package manager configurations (pip, uv, npm)
* Scheduled tasks created by Safety Endpoint
  {% endstep %}
  {% endstepper %}

## Troubleshooting

<details>

<summary>Script fails with proxy or network errors</summary>

The script automatically detects proxy settings from the `HTTPS_PROXY`, `ALL_PROXY`, or `HTTP_PROXY` environment variables. If your network requires a proxy that is not configured via these variables, set the appropriate environment variable at the system level:

1. Go to **System Properties** → **Environment Variables**
2. Add a system variable `HTTPS_PROXY` with your proxy URL (e.g., `http://proxy.company.com:8080`)

If using an authenticated proxy, include credentials in the URL: `http://user:password@proxy.company.com:8080`

</details>

<details>

<summary>Signature verification fails</summary>

The script verifies that the downloaded setup script is signed by **Safety CLI Cybersecurity Inc** using Authenticode. If signature verification fails:

* Ensure the device has internet access to `getsafety.com`
* Check that TLS 1.2 is enabled on the device
* Verify no network appliance is intercepting or modifying HTTPS traffic (SSL inspection can break Authenticode signatures)

</details>

<details>

<summary>Content distribution fails or clients cannot access the package</summary>

If devices report that the package content is not available:

* In the console, go to **Monitoring** → **Distribution Status** → **Content Status** and verify the package shows as successfully distributed
* Ensure the target devices are within a **boundary group** that includes the distribution point
* Check that the source folder path (`\\your-server\sources\...`) is still accessible and contains `run.ps1`
* If using a pull distribution point, allow time for content to replicate

</details>

<details>

<summary>Deployment shows "Error" but Safety Endpoint is installed</summary>

An Error status means the script exited with a non-zero code. This can happen if:

* The setup script detected a problem during its health check
* A newer version failed to install
* A temporary network issue occurred during the run

Review the status messages for the specific device in **Monitoring** → **Deployments**. The next scheduled run will retry automatically.

</details>

<details>

<summary>PowerShell execution policy blocks the script</summary>

The program command line includes `-ExecutionPolicy Bypass` which should override local policy. If you still encounter execution policy issues:

* Verify no Group Policy enforces a restrictive execution policy that overrides the `-ExecutionPolicy` parameter
* Ensure the Configuration Manager client agent is configured to allow script execution
* Check **Client Settings** → **Computer Agent** → **PowerShell execution policy** is set to **Bypass**

</details>

<details>

<summary>Client agent is not running or unhealthy</summary>

Deployments require a healthy Configuration Manager client on each device. If a device is not receiving the deployment:

* Verify the **CCMExec** service is running on the device
* Check the client health status in **Assets and Compliance** → **Devices** → select the device → **Client Activity** tab
* Run a **Client Notification** → **Evaluate Machine Policy** to force a policy refresh
* Review `C:\Windows\CCM\Logs\execmgr.log` on the device for program execution details

</details>


# Jamf Pro

Deploy and manage Safety Endpoint on macOS devices using Jamf Pro Policies

This guide covers deploying Safety Endpoint to macOS devices using **Scripts** and **Policies** in Jamf Pro. The deployment uses a lightweight shell script that installs, updates, and verifies Safety Endpoint on a recurring schedule.

{% hint style="warning" %}
**Jamf Pro required.** Jamf Now does not support scheduled script execution. This guide requires Jamf Pro.
{% endhint %}

## Overview

Safety Endpoint is deployed using two Jamf Pro components:

1. **Script** — uploaded once under Settings, contains the deployment logic
2. **Policy** — references the script, defines when and where it runs

The script downloads the official setup script from Safety over HTTPS and executes it. Since the setup script is idempotent, it handles installation, updates, and health checks internally. A built-in throttle ensures the script runs at most once per hour, even if the policy triggers more frequently.

The script reports status through its exit code:

| Exit Code | Meaning                                                                                      |
| --------- | -------------------------------------------------------------------------------------------- |
| 0         | Safety Endpoint is installed and up to date (or skipped — last run was less than 1 hour ago) |
| 1         | Setup failed — requires manual review                                                        |

## Prerequisites

{% hint style="info" %}
Before starting, make sure you have:

* **Jamf Pro** admin access with permissions to create Scripts and Policies
* **Enrollment key** from the [Safety Platform](https://getsafety.com/app/device-enrollment) — click **Manage Enrollment Key** → **Create Enrollment Key**
* Target devices running **macOS 12 or later** enrolled in Jamf Pro
  {% endhint %}

## Deploy Safety Endpoint

{% stepper %}
{% step %}

### Get Your Enrollment Key

Log in to the [Safety Platform](https://getsafety.com/app/device-enrollment), click **Manage Enrollment Key** → **Create Enrollment Key**, and copy the generated key. This key links devices to your Safety organization.
{% endstep %}

{% step %}

### Add the Script in Jamf Pro

1. In Jamf Pro, go to **Settings** → **Computer management** → **Scripts**
2. Click **New Script**
3. On the **General** tab:
   * **Display Name**: `Safety Endpoint - Run`
   * **Category**: `Security` (or your preferred category)
   * **Information**: Installs and maintains Safety Endpoint via the official setup script
4. On the **Script** tab, paste the script below with your enrollment key:

{% code title="run.sh" %}

```bash
#!/bin/bash
# Safety Endpoint - Run Script (macOS/Linux)
# Template: values below are populated by the Safety Platform per organization.
# Paste this into your script field (Jamf Pro, Kandji, etc.)
# Run as: root | Trigger: recurring check-in
#
# Downloads the Safety Endpoint setup script and executes it.
# Includes a 1-hour throttle so frequent check-ins don't cause redundant runs.

# ── Organization Configuration ───────────────────────────────────────
ENROLLMENT_KEY="REPLACE_WITH_YOUR_ENROLLMENT_KEY"
# Comma-separated list of firewall tools to exclude (e.g. "pip,npm")
EXCLUDE_FIREWALL_TOOLS=""
# ─────────────────────────────────────────────────────────────────────

set -euo pipefail

SETUP_URL="https://getsafety.com/cli/setup.sh"
USER_AGENT="Safety-Endpoint/1 (run)"
STAMP_FILE="/var/tmp/.safety-endpoint-last-run"
THROTTLE_SECONDS=3600
MAX_STAMP_AGE=86400  # 24h — treat older/invalid stamps as stale

# ── Throttle: skip if last successful run was less than 1 hour ago ───
should_run=true
if [ -f "$STAMP_FILE" ]; then
    last=$(cat "$STAMP_FILE" 2>/dev/null) || last=""
    now=$(date +%s)

    if echo "$last" | grep -qE '^[0-9]+$'; then
        age=$((now - last))
        if [ "$age" -lt 0 ] || [ "$age" -gt "$MAX_STAMP_AGE" ]; then
            echo "[Safety Endpoint Setup] Stamp has invalid age (${age}s). Removing stale stamp."
            rm -f "$STAMP_FILE"
        elif [ "$age" -lt "$THROTTLE_SECONDS" ]; then
            echo "[Safety Endpoint Setup] Last run was ${age}s ago (< ${THROTTLE_SECONDS}s). Skipping."
            should_run=false
        fi
    else
        echo "[Safety Endpoint Setup] Stamp file corrupt. Removing."
        rm -f "$STAMP_FILE"
    fi
fi

if [ "$should_run" = "false" ]; then
    exit 0
fi

# ── Download and execute ─────────────────────────────────────────────
echo "[Safety Endpoint Setup] Downloading $SETUP_URL ..."

# Build optional arguments
SETUP_ARGS="--enrollment-key $ENROLLMENT_KEY"
[ -n "$EXCLUDE_FIREWALL_TOOLS" ] && SETUP_ARGS="$SETUP_ARGS --exclude-firewall-tools $EXCLUDE_FIREWALL_TOOLS"

if command -v curl >/dev/null 2>&1; then
    # shellcheck disable=SC2086
    curl -fsSL -A "$USER_AGENT" "$SETUP_URL" | bash -s -- $SETUP_ARGS
elif command -v wget >/dev/null 2>&1; then
    # shellcheck disable=SC2086
    wget -qO- --user-agent="$USER_AGENT" "$SETUP_URL" | bash -s -- $SETUP_ARGS
else
    echo "[Safety Endpoint Setup] ERROR: curl or wget required" >&2
    exit 1
fi

# ── Mark successful run (atomic write) ───────────────────────────────
tmp_stamp=$(mktemp "${STAMP_FILE}.XXXXXX")
date +%s > "$tmp_stamp"
mv -f "$tmp_stamp" "$STAMP_FILE"
```

{% endcode %}

{% hint style="warning" %}
Make sure you replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your actual enrollment key before saving the script.
{% endhint %}

5. Click **Save**
   {% endstep %}

{% step %}

### Create the Setup Policy

1. In Jamf Pro, go to **Computers** → **Policies**
2. Click **New Policy**
3. On the **Options** tab, configure the **General** section:
   * **Display Name**: `Safety Endpoint - Setup`
   * **Enabled**: checked
   * **Category**: `Security`
   * **Trigger**: check both **Recurring Check-in** and **Enrollment Complete**
   * **Execution Frequency**: **Ongoing**

{% hint style="info" %}
Using **Ongoing** with **Recurring Check-in** means the policy triggers at every check-in, but the script's built-in 1-hour throttle ensures it only performs work once per hour. The **Enrollment Complete** trigger ensures new Macs get Safety Endpoint immediately after enrollment.
{% endhint %}
{% endstep %}

{% step %}

### Attach the Script to the Policy

1. Still on the **Options** tab, click **Scripts** in the left sidebar
2. Click **Configure**
3. Select the **Safety Endpoint - Run** script you created earlier
4. Leave script parameters empty (the enrollment key is embedded in the script)
   {% endstep %}

{% step %}

### Set the Scope

1. Click the **Scope** tab
2. Under **Targets**, set **Target Computers** to **All Computers** or select specific computer groups
3. Use the **Limitations** and **Exclusions** tabs if you need to narrow the scope further
   {% endstep %}

{% step %}

### Save and Verify

Click **Save** to deploy the policy.

{% hint style="success" %}
**Quick test:** To verify the setup works on a specific Mac without waiting for the next check-in, run the following command on the device as root:

```bash
sudo jamf policy
```

This forces an immediate check-in and triggers the policy. Check the policy logs in Jamf Pro to confirm it completed successfully.
{% endhint %}
{% endstep %}
{% endstepper %}

## Monitor Compliance

After deployment, monitor your fleet status in Jamf Pro:

1. Go to **Computers** → **Policies**
2. Select **Safety Endpoint - Setup**
3. Click **Logs** to view execution history

| Status        | What It Means                          | Action                                                        |
| ------------- | -------------------------------------- | ------------------------------------------------------------- |
| **Completed** | Script ran successfully                | None — Safety Endpoint is installed or was already up to date |
| **Failed**    | The script exited with a non-zero code | Click the log entry to view script output for error details   |
| **Pending**   | Device has not checked in yet          | Wait for the next scheduled check-in                          |

To view logs for a specific device, go to **Computers** → **Search Inventory** → select the device → **History** → **Policy Logs**.

## Uninstall Safety Endpoint

If you need to remove Safety Endpoint from devices, create a separate policy with the uninstall wrapper script. Like the deployment script, it downloads the official uninstall script from Safety over HTTPS and executes it.

{% hint style="danger" %}
The uninstall script removes Safety Endpoint artifacts from the machine, including configuration, firewall wrappers, package manager settings, and data for all user profiles. Configuration Safety wrote into a project file stays with the project, including the index line in a requirements file, the Safety index entry in a `pyproject.toml`, and the registry in a project `.npmrc`. Remove those with the project's own tooling, for example `poetry source remove safety`.
{% endhint %}

{% stepper %}
{% step %}

### Add the Uninstall Script

1. In Jamf Pro, go to **Settings** → **Computer management** → **Scripts**
2. Click **New Script**
3. On the **General** tab:
   * **Display Name**: `Safety Endpoint - Uninstall`
   * **Category**: `Security`
4. On the **Script** tab, paste the following:

{% code title="uninstall-run.sh" %}

```bash
#!/bin/bash
# Safety Endpoint - Uninstall Script (macOS)
# Paste this into your script field (Jamf Pro, Kandji, etc.)
# Run as: root | Schedule: one-time or on-demand
#
# Downloads the Safety Endpoint uninstall script and executes it.

set -euo pipefail

UNINSTALL_URL="https://getsafety.com/cli/uninstall.sh"
USER_AGENT="Safety-Endpoint/1 (run)"

echo "[Safety Endpoint Setup] Downloading $UNINSTALL_URL ..."
if command -v curl >/dev/null 2>&1; then
    curl -fsSL -A "$USER_AGENT" "$UNINSTALL_URL" | bash -s
elif command -v wget >/dev/null 2>&1; then
    wget -qO- --user-agent="$USER_AGENT" "$UNINSTALL_URL" | bash -s
else
    echo "[Safety Endpoint Setup] ERROR: curl or wget required" >&2
    exit 1
fi

# Clean up the throttle stamp if it exists
rm -f /var/tmp/.safety-endpoint-last-run
```

{% endcode %}

5. Click **Save**
   {% endstep %}

{% step %}

### Create the Uninstall Policy

1. Go to **Computers** → **Policies** → **New Policy**
2. On the **Options** tab, configure the **General** section:
   * **Display Name**: `Safety Endpoint - Uninstall`
   * **Enabled**: checked
   * **Category**: `Security`
   * **Trigger**: **Custom** — enter the event name: `safetyUninstall`
   * **Execution Frequency**: **Once per computer**
3. On the **Options** tab, click **Scripts** → **Configure** → select **Safety Endpoint - Uninstall**
4. On the **Scope** tab, select the target computers or groups
5. Click **Save**

To trigger the uninstall on a device, run:

```bash
sudo jamf policy -event safetyUninstall
```

{% hint style="info" %}
**Self Service alternative:** You can also make the uninstall policy available in Self Service. On the **Self Service** tab, check **Make the policy available in Self Service**, set the display name, and configure the button labels. Users with appropriate permissions can then trigger the uninstall themselves.
{% endhint %}

The script runs once per device with root privileges and will clean up:

* Safety Endpoint binaries and system PATH entries
* Per-user configuration, firewall wrappers, and shell profiles
* Package manager configurations (pip, uv, npm)
* Scheduled tasks and LaunchDaemons created by Safety Endpoint
  {% endstep %}
  {% endstepper %}

## Troubleshooting

<details>

<summary>Script fails with network errors</summary>

The script uses `curl` (pre-installed on macOS) to download the setup script over HTTPS. If the download fails:

* Ensure the device has internet access to `getsafety.com`
* If your network uses a proxy, set the `HTTPS_PROXY` environment variable at the system level — `curl` on macOS respects standard proxy environment variables automatically
* Verify no network appliance is blocking or intercepting HTTPS traffic to `getsafety.com`

</details>

<details>

<summary>Policy does not run on devices</summary>

If the policy is not executing on target devices:

* Verify the device is enrolled in Jamf Pro and checking in regularly (**Computers** → select device → **General** → **Last Check-in**)
* Confirm the device is within the policy's **Scope**
* Check that the **Recurring Check-in** trigger is enabled and the startup script is configured in **Settings** → **Computer management** → **Check-In**
* Force an immediate check-in on the device with `sudo jamf policy`

</details>

<details>

<summary>Script shows "skipping" every time</summary>

The message `Last run was Xs ago (< 3600s). Skipping.` means the throttle is working as expected. The script only performs work once per hour. If you need to force a re-run (e.g., for testing), delete the timestamp file on the device:

```bash
sudo rm -f /var/tmp/.safety-endpoint-last-run
sudo jamf policy
```

</details>

<details>

<summary>Policy shows "Failed" but Safety Endpoint is installed</summary>

A failed status means the script exited with a non-zero code. This can happen if:

* The setup script detected a problem during its health check
* A newer version failed to install
* A temporary network issue occurred during the run

Check the policy logs for the specific device to see the script output. The next check-in will retry automatically (subject to the 1-hour throttle).

</details>

<details>

<summary>Policy does not retry after a failure</summary>

Jamf Pro has an internal **1-hour cooldown** after a policy failure — if the script fails, Jamf will not retry it on that device for approximately 1 hour, regardless of check-in frequency. This is a server-side setting and is not configurable via the GUI.

* On **Jamf Pro (on-premises)**, the cooldown can be shortened via a database setting
* On **Jamf Cloud**, this is not configurable — but it aligns with our script's 1-hour throttle, so the behavior is consistent

During testing, if you need to force a retry after a failure, edit the policy in the Jamf Pro console and click **Save** (even without changes) — this resets the cooldown for all devices.

</details>


# ManageEngine

Deploy and manage Safety Endpoint on Windows devices using ManageEngine Endpoint Central Custom Script Configurations

This guide covers deploying Safety Endpoint to Windows devices using **Custom Script Configurations** in ManageEngine Endpoint Central (formerly Desktop Central). The deployment uses the same signed script used across all supported platforms — it installs, updates, and verifies Safety Endpoint on a recurring schedule.

{% hint style="warning" %}
**Endpoint Central required.** ManageEngine Mobile Device Manager Plus (MDM Plus) does not support custom script execution. This guide requires **Endpoint Central**, which is available as both a cloud and on-premises deployment.
{% endhint %}

## Overview

Safety Endpoint is deployed as an Endpoint Central **Computer Configuration** using the Custom Script feature. The script downloads the official setup script, verifies its Authenticode signature, and executes it. Since the setup script is idempotent, it handles installation, updates, and health checks internally.

The configuration is set to run on every **Refresh Cycle** (approximately every 90 minutes), ensuring devices stay up to date without manual intervention.

The script reports status through its exit code:

| Exit Code | Meaning                                     |
| --------- | ------------------------------------------- |
| 0         | Safety Endpoint is installed and up to date |
| 1         | Setup failed — requires manual review       |

## Prerequisites

{% hint style="info" %}
Before starting, make sure you have:

* **Endpoint Central** admin access (cloud or on-premises)
* **Endpoint Central agent** installed and healthy on target devices
* **Enrollment key** from the [Safety Platform](https://getsafety.com/app/device-enrollment) — click **Manage Enrollment Key** → **Create Enrollment Key**
* Target devices running **Windows 10 or later** with PowerShell 5.0+
  {% endhint %}

## Deploy Safety Endpoint

{% stepper %}
{% step %}

### Get Your Enrollment Key

Log in to the [Safety Platform](https://getsafety.com/app/device-enrollment), click **Manage Enrollment Key** → **Create Enrollment Key**, and copy the generated key. This key links devices to your Safety organization.
{% endstep %}

{% step %}

### Prepare the Script

Copy the script below and replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your organization's enrollment key:

{% code title="run.ps1" %}

```powershell
# Safety Endpoint - MDM Run Script (Windows)
# Template: values below are populated by the Safety Platform per organization.
# Paste this into your MDM script field (Intune, NinjaOne, etc.)
# Run as: SYSTEM | Schedule: recurring (e.g. every 1 hour)
#
# Downloads the Safety Endpoint setup script, verifies its Authenticode
# signature is from Safety CLI Cybersecurity Inc, and executes it.

# ── Organization Configuration ───────────────────────────────────────
$EnrollmentKey = "REPLACE_WITH_YOUR_ENROLLMENT_KEY"
# Comma-separated list of firewall tools to exclude (e.g. "pip,npm")
$ExcludeFirewallTools = ""
# ─────────────────────────────────────────────────────────────────────

$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$setupUrl = "https://getsafety.com/cli/setup.ps1"
$tmpFile  = Join-Path $env:TEMP "safety-mdm-setup-$(Get-Random).ps1"

# ── HttpClient with proxy support (same as install.ps1 / setup.ps1) ─
Add-Type -AssemblyName System.Net.Http -ErrorAction Stop

$handler = [System.Net.Http.HttpClientHandler]::new()

$proxyUrl = @($env:HTTPS_PROXY, $env:ALL_PROXY, $env:HTTP_PROXY) |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1

if ($proxyUrl) {
    $proxyUri = [Uri]$proxyUrl
    $proxy = New-Object Net.WebProxy($proxyUri)
    if ($proxyUri.UserInfo) {
        $parts = $proxyUri.UserInfo -split ":", 2
        $proxy.Credentials = New-Object Net.NetworkCredential(
            [Uri]::UnescapeDataString($parts[0]),
            $(if ($parts.Length -gt 1) { [Uri]::UnescapeDataString($parts[1]) } else { "" })
        )
    } else {
        $proxy.UseDefaultCredentials = $true
    }
    $handler.Proxy = $proxy
} else {
    $handler.DefaultProxyCredentials = [Net.CredentialCache]::DefaultCredentials
}

$client = [System.Net.Http.HttpClient]::new($handler)
$null = $client.DefaultRequestHeaders.UserAgent.ParseAdd("Safety-Endpoint/1 (run)")

# ── Download, verify, execute ────────────────────────────────────────
try {
    # 1. Download (preserves exact bytes for Authenticode)
    Write-Host "Downloading $setupUrl ..."
    $response = $client.GetAsync($setupUrl).GetAwaiter().GetResult()
    $null = $response.EnsureSuccessStatusCode()
    $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
    [System.IO.File]::WriteAllBytes($tmpFile, $bytes)

    # 2. Verify Authenticode signature
    $sig = Get-AuthenticodeSignature -FilePath $tmpFile
    if ($sig.Status -ne "Valid") {
        throw "Signature status: $($sig.Status) - $($sig.StatusMessage)"
    }
    if ($sig.SignerCertificate.Subject -notmatch "O=Safety CLI Cybersecurity Inc") {
        throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
    }
    Write-Host "Signature verified: $($sig.SignerCertificate.Subject)"

    # 3. Execute
    $setupArgs = @{ EnrollmentKey = $EnrollmentKey }
    if ($ExcludeFirewallTools) { $setupArgs.ExcludeFirewallTools = $ExcludeFirewallTools }
    & $tmpFile @setupArgs
}
catch {
    Write-Error "Safety MDM setup failed: $_"
    exit 1
}
finally {
    Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
    if ($client) { $client.Dispose() }
}
```

{% endcode %}

{% hint style="warning" %}
Make sure you replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your actual enrollment key before uploading.
{% endhint %}
{% endstep %}

{% step %}

### Create the Custom Script Configuration

1. In the Endpoint Central console, go to **Configurations** → **Add Configurations** → **Windows Configuration** → **Custom Script** → **Computer Configuration**
2. Enter the configuration details:
   * **Name**: `Safety Endpoint - Setup`
   * **Description**: Installs and maintains Safety Endpoint via signed setup script
3. Under **Script Execution**:
   * Select **Command Line**
   * Paste the full script content, or select **Repository** if you have already added the script to the Script Repository
4. Set **Exit Code** to `0` (success indicator)
5. Set **Deploy Schedule**:
   * **Frequency**: **Every Refresh Cycle**
6. Set **Execution Context**:
   * **Run as**: **System user**
     {% endstep %}

{% step %}

### Define Targets and Deploy

1. Under **Define the targets**, select the computers or custom groups to deploy to
2. Click **Deploy** to apply the configuration

{% hint style="info" %}
The Refresh Cycle runs approximately every **90 minutes**. The setup script is idempotent — it exits quickly when Safety Endpoint is already up to date, so frequent execution has minimal overhead.
{% endhint %}
{% endstep %}
{% endstepper %}

## Monitor Compliance

After deployment, monitor your fleet status in Endpoint Central:

1. Go to **Configurations** → **Deployed Configurations**
2. Find the **Safety Endpoint - Setup** configuration
3. Click to view the deployment status summary

| Status           | What It Means                             | Action                                       |
| ---------------- | ----------------------------------------- | -------------------------------------------- |
| **Success**      | Safety Endpoint is installed and current  | None                                         |
| **Failed**       | The script exited with a non-zero code    | Click the device entry to view error details |
| **Yet to Apply** | Agent has not picked up the configuration | Wait for the next refresh cycle              |
| **In Progress**  | Script is currently running               | Wait for completion                          |

To retry a failed deployment on a specific device, select the device and click **Retry**.

## Uninstall Safety Endpoint

If you need to remove Safety Endpoint from devices, create a separate Custom Script Configuration with the uninstall wrapper script. Like the deployment script, it downloads the official uninstall script from Safety, verifies its Authenticode signature, and executes it.

{% hint style="danger" %}
The uninstall script removes Safety Endpoint artifacts from the machine, including configuration, firewall wrappers, package manager settings, and data for all user profiles. Configuration Safety wrote into a project file stays with the project, including the index line in a requirements file, the Safety index entry in a `pyproject.toml`, and the registry in a project `.npmrc`. Remove those with the project's own tooling, for example `poetry source remove safety`.
{% endhint %}

{% stepper %}
{% step %}

### Create the Uninstall Configuration

1. Go to **Configurations** → **Add Configurations** → **Windows Configuration** → **Custom Script** → **Computer Configuration**
2. Enter the configuration details:
   * **Name**: `Safety Endpoint - Uninstall`
   * **Description**: Removes all Safety Endpoint artifacts from the device
3. Under **Script Execution**, select **Command Line** and paste the following:

{% code title="uninstall-run.ps1" %}

```powershell
# Safety Endpoint - MDM Uninstall Script (Windows)
# Template: paste this into your MDM script field (Intune, NinjaOne, etc.)
# Run as: SYSTEM | Schedule: one-time or on-demand
#
# Downloads the Safety Endpoint uninstall script, verifies its Authenticode
# signature is from Safety CLI Cybersecurity Inc, and executes it.

$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$uninstallUrl = "https://getsafety.com/cli/uninstall.ps1"
$tmpFile      = Join-Path $env:TEMP "safety-mdm-uninstall-$(Get-Random).ps1"

# ── HttpClient with proxy support (same as install.ps1 / setup.ps1) ─
Add-Type -AssemblyName System.Net.Http -ErrorAction Stop

$handler = [System.Net.Http.HttpClientHandler]::new()

$proxyUrl = @($env:HTTPS_PROXY, $env:ALL_PROXY, $env:HTTP_PROXY) |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1

if ($proxyUrl) {
    $proxyUri = [Uri]$proxyUrl
    $proxy = New-Object Net.WebProxy($proxyUri)
    if ($proxyUri.UserInfo) {
        $parts = $proxyUri.UserInfo -split ":", 2
        $proxy.Credentials = New-Object Net.NetworkCredential(
            [Uri]::UnescapeDataString($parts[0]),
            $(if ($parts.Length -gt 1) { [Uri]::UnescapeDataString($parts[1]) } else { "" })
        )
    } else {
        $proxy.UseDefaultCredentials = $true
    }
    $handler.Proxy = $proxy
} else {
    $handler.DefaultProxyCredentials = [Net.CredentialCache]::DefaultCredentials
}

$client = [System.Net.Http.HttpClient]::new($handler)
$null = $client.DefaultRequestHeaders.UserAgent.ParseAdd("Safety-Endpoint/1 (run)")

# ── Download, verify, execute ────────────────────────────────────────
try {
    # 1. Download (preserves exact bytes for Authenticode)
    Write-Host "Downloading $uninstallUrl ..."
    $response = $client.GetAsync($uninstallUrl).GetAwaiter().GetResult()
    $null = $response.EnsureSuccessStatusCode()
    $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
    [System.IO.File]::WriteAllBytes($tmpFile, $bytes)

    # 2. Verify Authenticode signature
    $sig = Get-AuthenticodeSignature -FilePath $tmpFile
    if ($sig.Status -ne "Valid") {
        throw "Signature status: $($sig.Status) - $($sig.StatusMessage)"
    }
    if ($sig.SignerCertificate.Subject -notmatch "O=Safety CLI Cybersecurity Inc") {
        throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
    }
    Write-Host "Signature verified: $($sig.SignerCertificate.Subject)"

    # 3. Execute
    & $tmpFile
}
catch {
    Write-Error "Safety MDM uninstall failed: $_"
    exit 1
}
finally {
    Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
    if ($client) { $client.Dispose() }
}
```

{% endcode %}

4. Set **Exit Code** to `0`
5. Set **Deploy Schedule**:
   * **Frequency**: **Once**
6. Set **Run as**: **System user**
   {% endstep %}

{% step %}

### Deploy the Uninstall

1. Under **Define the targets**, select the computers to uninstall from
2. Click **Deploy**

The script runs once per device. It requires SYSTEM privileges and will clean up:

* Safety Endpoint binaries and system PATH entries
* Per-user configuration, firewall wrappers, and shell profiles
* Package manager configurations (pip, uv, npm)
* Scheduled tasks created by Safety Endpoint
  {% endstep %}
  {% endstepper %}

## Troubleshooting

<details>

<summary>Script fails with proxy or network errors</summary>

The script automatically detects proxy settings from the `HTTPS_PROXY`, `ALL_PROXY`, or `HTTP_PROXY` environment variables. If your network requires a proxy that is not configured via these variables, set the appropriate environment variable at the system level:

1. Go to **System Properties** → **Environment Variables**
2. Add a system variable `HTTPS_PROXY` with your proxy URL (e.g., `http://proxy.company.com:8080`)

If using an authenticated proxy, include credentials in the URL: `http://user:password@proxy.company.com:8080`

</details>

<details>

<summary>Signature verification fails</summary>

The script verifies that the downloaded setup script is signed by **Safety CLI Cybersecurity Inc** using Authenticode. If signature verification fails:

* Ensure the device has internet access to `getsafety.com`
* Check that TLS 1.2 is enabled on the device
* Verify no network appliance is intercepting or modifying HTTPS traffic (SSL inspection can break Authenticode signatures)

</details>

<details>

<summary>Configuration shows "Yet to Apply" for extended periods</summary>

If a device does not pick up the configuration:

* Verify the Endpoint Central agent is installed and running on the device — check the **ManageEngine** service in Windows Services
* Confirm the device appears in **Scope of Management** and is reachable
* Check the agent's last contact time in the device details
* Trigger a manual agent refresh from the Endpoint Central console: right-click the device → **Scan Now**

</details>

<details>

<summary>Configuration shows "Failed" but Safety Endpoint is installed</summary>

A Failed status means the script exited with a non-zero code. This can happen if:

* The setup script detected a problem during its health check
* A newer version failed to install
* A temporary network issue occurred during the run

Click the failed device entry to view the error details. The configuration will automatically retry on the next Refresh Cycle (approximately 90 minutes).

</details>

<details>

<summary>Script requires interactive input</summary>

Endpoint Central cannot run scripts that require user interaction. The Safety Endpoint setup and uninstall scripts are fully non-interactive when run with the enrollment key parameter. If you see errors about interactive input, verify you are using the exact script provided in this guide.

</details>


# SentinelOne

Deploy and manage Safety Endpoint on macOS, Windows, and Linux devices using SentinelOne RemoteOps

This guide covers deploying Safety Endpoint to macOS, Windows, and Linux devices using **RemoteOps** (Remote Script Orchestration) in the SentinelOne Singularity console. The deployment uses a lightweight script that installs, updates, and verifies Safety Endpoint through the SentinelOne agent.

{% hint style="warning" %}
**RemoteOps required.** RemoteOps is a licensed SentinelOne add-on and is not included in every Singularity package. Confirm that RemoteOps is enabled for your account and that your console role has permission to upload and run scripts before starting.
{% endhint %}

## Overview

Safety Endpoint is deployed using two RemoteOps components:

1. **Script** — uploaded once to the Script Library, contains the deployment logic
2. **Run Script task** — targets the script at your endpoints on demand; recurrence is achieved by re-triggering the task (see the Schedule Recurring Runs step below)

The script downloads the official Safety Endpoint setup script over HTTPS and executes it. On Windows, it first verifies the setup script's Authenticode signature from Safety CLI Cybersecurity Inc. The setup script is idempotent, so re-running it is always safe: the install step is skipped when Safety Endpoint is already up to date, and each run re-verifies enrollment and firewall protection and performs a system scan. On each run, the setup script:

* Installs or updates the Safety Endpoint binary (`/opt/safety-cybersecurity/bin/safety` on macOS/Linux, `C:\Program Files\Safety\bin\safety.exe` on Windows)
* Enrolls the device with your Safety organization using the enrollment key
* Configures package-manager firewall protection (pip, uv, poetry, and npm by default — customizable via the script's `EXCLUDE_FIREWALL_TOOLS` setting)
* Runs a system scan in the context of the logged-in user(s)

{% hint style="info" %}
Unlike an MDM, RemoteOps is **push-based**: it does not continuously enforce that software stays installed, and Run Script tasks are one-shot. To keep devices updated and cover machines that were offline, re-run the task periodically — with a time-triggered Singularity Hyperautomation workflow, via the SentinelOne API from your automation platform, or manually on a regular cadence (see the Schedule Recurring Runs step below). The macOS/Linux script includes a built-in 1-hour throttle, so triggering it more frequently than hourly does not cause extra work.
{% endhint %}

The script reports status through its exit code:

| Exit Code | Meaning                                                                                                       |
| --------- | ------------------------------------------------------------------------------------------------------------- |
| 0         | Safety Endpoint is installed and up to date (or, on macOS/Linux, skipped — last run was less than 1 hour ago) |
| 1         | Setup failed — requires manual review                                                                         |

## Prerequisites

{% hint style="info" %}
Before starting, make sure you have:

* **SentinelOne console** access with a role that can manage the Script Library and run RemoteOps tasks
* **RemoteOps** enabled for your account and for each target Site — in the Site's configuration, the **Remote Script Orchestration** option must be checked
* **Enrollment key** from the [Safety Platform](https://getsafety.com/app/device-enrollment) — click **Manage Enrollment Key** → **Create Enrollment Key**
* Target devices running the **SentinelOne agent** on macOS 12 or later, Windows 10 or later with PowerShell 5.0+, or a 64-bit Linux distribution (x86\_64 or ARM64)
* Outbound HTTPS access from endpoints to `getsafety.com` (installer downloads) and `*.safetycli.com` (enrollment, data reporting, and package firewall services)
  {% endhint %}

## Deploy Safety Endpoint

{% stepper %}
{% step %}

### Get Your Enrollment Key

Log in to the [Safety Platform](https://getsafety.com/app/device-enrollment), click **Manage Enrollment Key** → **Create Enrollment Key**, and copy the generated key. This key links devices to your Safety organization.
{% endstep %}

{% step %}

### Prepare the Script

Copy the script for your target platform and replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your organization's enrollment key. Save it as a file (`run.sh` or `run.ps1`) so you can upload it to the Script Library.

{% tabs %}
{% tab title="macOS / Linux" %}
{% code title="run.sh" %}

```bash
#!/bin/bash
# Safety Endpoint - Run Script (macOS/Linux)
# Template: values below are populated by the Safety Platform per organization.
# Upload to the SentinelOne Script Library as an action (non-collection) script.
# Requires: root | Re-run on a recurring cadence (suggested: 1 hour)
#
# Downloads the Safety Endpoint setup script and executes it.
# Includes a 1-hour throttle, so being triggered more often than
# hourly does not cause extra work.

# ── Organization Configuration ───────────────────────────────────────
ENROLLMENT_KEY="REPLACE_WITH_YOUR_ENROLLMENT_KEY"
# Comma-separated list of firewall tools to exclude (e.g. "pip,npm")
EXCLUDE_FIREWALL_TOOLS=""
# ─────────────────────────────────────────────────────────────────────

set -euo pipefail

SETUP_URL="https://getsafety.com/cli/setup.sh"
USER_AGENT="Safety-Endpoint/1 (run)"
STAMP_FILE="/var/tmp/.safety-endpoint-last-run"
THROTTLE_SECONDS=3600
MAX_STAMP_AGE=86400  # 24h — treat older/invalid stamps as stale

# ── Throttle: skip if last successful run was less than 1 hour ago ───
should_run=true
if [ -f "$STAMP_FILE" ]; then
    last=$(cat "$STAMP_FILE" 2>/dev/null) || last=""
    now=$(date +%s)

    if echo "$last" | grep -qE '^[0-9]+$'; then
        age=$((now - last))
        if [ "$age" -lt 0 ] || [ "$age" -gt "$MAX_STAMP_AGE" ]; then
            echo "[Safety Endpoint Setup] Stamp has invalid age (${age}s). Removing stale stamp."
            rm -f "$STAMP_FILE"
        elif [ "$age" -lt "$THROTTLE_SECONDS" ]; then
            echo "[Safety Endpoint Setup] Last run was ${age}s ago (< ${THROTTLE_SECONDS}s). Skipping."
            should_run=false
        fi
    else
        echo "[Safety Endpoint Setup] Stamp file corrupt. Removing."
        rm -f "$STAMP_FILE"
    fi
fi

if [ "$should_run" = "false" ]; then
    exit 0
fi

# ── Download and execute ─────────────────────────────────────────────
echo "[Safety Endpoint Setup] Downloading $SETUP_URL ..."

# Build optional arguments
SETUP_ARGS="--enrollment-key $ENROLLMENT_KEY"
[ -n "$EXCLUDE_FIREWALL_TOOLS" ] && SETUP_ARGS="$SETUP_ARGS --exclude-firewall-tools $EXCLUDE_FIREWALL_TOOLS"

if command -v curl >/dev/null 2>&1; then
    # shellcheck disable=SC2086
    curl -fsSL -A "$USER_AGENT" "$SETUP_URL" | bash -s -- $SETUP_ARGS
elif command -v wget >/dev/null 2>&1; then
    # shellcheck disable=SC2086
    wget -qO- --user-agent="$USER_AGENT" "$SETUP_URL" | bash -s -- $SETUP_ARGS
else
    echo "[Safety Endpoint Setup] ERROR: curl or wget required" >&2
    exit 1
fi

# ── Mark successful run (atomic write) ───────────────────────────────
tmp_stamp=$(mktemp "${STAMP_FILE}.XXXXXX")
date +%s > "$tmp_stamp"
mv -f "$tmp_stamp" "$STAMP_FILE"
```

{% endcode %}
{% endtab %}

{% tab title="Windows" %}
{% code title="run.ps1" %}

```powershell
# Safety Endpoint - Run Script (Windows)
# Template: values below are populated by the Safety Platform per organization.
# Upload to the SentinelOne Script Library as an action (non-collection) script.
# Requires: SYSTEM | Re-run on a recurring cadence (suggested: 1 hour)
#
# Downloads the Safety Endpoint setup script, verifies its Authenticode
# signature is from Safety CLI Cybersecurity Inc, and executes it.

# ── Organization Configuration ───────────────────────────────────────
$EnrollmentKey = "REPLACE_WITH_YOUR_ENROLLMENT_KEY"
# Comma-separated list of firewall tools to exclude (e.g. "pip,npm")
$ExcludeFirewallTools = ""
# ─────────────────────────────────────────────────────────────────────

$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$setupUrl = "https://getsafety.com/cli/setup.ps1"
$tmpFile  = Join-Path $env:TEMP "safety-mdm-setup-$(Get-Random).ps1"

# ── HttpClient with proxy support (same as install.ps1 / setup.ps1) ─
Add-Type -AssemblyName System.Net.Http -ErrorAction Stop

$handler = [System.Net.Http.HttpClientHandler]::new()

$proxyUrl = @($env:HTTPS_PROXY, $env:ALL_PROXY, $env:HTTP_PROXY) |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1

if ($proxyUrl) {
    $proxyUri = [Uri]$proxyUrl
    $proxy = New-Object Net.WebProxy($proxyUri)
    if ($proxyUri.UserInfo) {
        $parts = $proxyUri.UserInfo -split ":", 2
        $proxy.Credentials = New-Object Net.NetworkCredential(
            [Uri]::UnescapeDataString($parts[0]),
            $(if ($parts.Length -gt 1) { [Uri]::UnescapeDataString($parts[1]) } else { "" })
        )
    } else {
        $proxy.UseDefaultCredentials = $true
    }
    $handler.Proxy = $proxy
} else {
    $handler.DefaultProxyCredentials = [Net.CredentialCache]::DefaultCredentials
}

$client = [System.Net.Http.HttpClient]::new($handler)
$null = $client.DefaultRequestHeaders.UserAgent.ParseAdd("Safety-Endpoint/1 (run)")

# ── Download, verify, execute ────────────────────────────────────────
try {
    # 1. Download (preserves exact bytes for Authenticode)
    Write-Host "Downloading $setupUrl ..."
    $response = $client.GetAsync($setupUrl).GetAwaiter().GetResult()
    $null = $response.EnsureSuccessStatusCode()
    $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
    [System.IO.File]::WriteAllBytes($tmpFile, $bytes)

    # 2. Verify Authenticode signature
    $sig = Get-AuthenticodeSignature -FilePath $tmpFile
    if ($sig.Status -ne "Valid") {
        throw "Signature status: $($sig.Status) - $($sig.StatusMessage)"
    }
    if ($sig.SignerCertificate.Subject -notmatch "O=Safety CLI Cybersecurity Inc") {
        throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
    }
    Write-Host "Signature verified: $($sig.SignerCertificate.Subject)"

    # 3. Execute
    $setupArgs = @{ EnrollmentKey = $EnrollmentKey }
    if ($ExcludeFirewallTools) { $setupArgs.ExcludeFirewallTools = $ExcludeFirewallTools }
    & $tmpFile @setupArgs
}
catch {
    Write-Error "Safety MDM setup failed: $_"
    exit 1
}
finally {
    Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
    if ($client) { $client.Dispose() }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="warning" %}
Make sure you replace `REPLACE_WITH_YOUR_ENROLLMENT_KEY` with your actual enrollment key before uploading the script.
{% endhint %}
{% endstep %}

{% step %}

### Upload the Script to the Script Library

1. In the SentinelOne console, go to the **Automation** section and open the script library (the **Remote Ops** tab in newer consoles)
2. Click **Upload New Script** (or **+ Create New** in newer console versions)
3. Configure the script:
   * **Name**: `Safety Endpoint - Setup (macOS/Linux)` or `Safety Endpoint - Setup (Windows)`
   * **Type**: select the script type used for scripts that perform an action — do not select the data/artifact collection type (the exact labels vary by console version)
   * **OS**: select **macOS** and/or **Linux** for `run.sh`, **Windows** for `run.ps1`
   * **Script file**: upload the file you prepared in the previous step (do not zip it)
   * Set the **Timeout** to at least `1800` seconds (30 minutes) — every run performs a system scan, and the first run also downloads the binary and enrolls the device
   * Leave **Input is Required** unchecked (the enrollment key is embedded in the script)
4. Save the script

{% hint style="info" %}
Upload `run.sh` once (tagged for both macOS and Linux) and `run.ps1` separately for Windows. Because the enrollment key is embedded in the script body, anyone who can view the Script Library can read it — if your console supports script input parameters (the **Input is Required** option), you can instead keep the placeholder in the script and supply the key when creating the task. If you rotate the enrollment key later, remember to update the uploaded script(s).
{% endhint %}
{% endstep %}

{% step %}

### Run the Script on Your Endpoints

1. Go to **Sentinels** and select the target scope — start with a single test endpoint, then widen to a Site or Group (you can also use saved filters or live queries to target dynamically). If your fleet mixes operating systems, create per-OS Groups and run each script against the matching Group.
2. Select the endpoints (or use **Select All** for the whole scope)
3. Click **Actions** → **Response** → **Run Script**
4. Choose the **Safety Endpoint - Setup** script for the matching OS
5. Confirm the **timeout** is at least `1800` seconds. For the **output destination**, keep an output location if you want to retain the script's log for review (recommended at least for the pilot run); for routine recurring runs you can set it to **None**, since the task status reflects the script's exit code and there are no artifacts to collect.
6. Confirm to start the task

RemoteOps scripts run under the SentinelOne agent's elevated context (root on macOS/Linux, SYSTEM on Windows), which is what the setup script requires. The setup script detects logged-in users (the console user on macOS/Linux; active sessions on Windows) and runs enrollment and scans in their context. If no user is logged in when the script runs, the run reports success and the user-level steps are performed on a later run.
{% endstep %}

{% step %}

### Schedule Recurring Runs

RemoteOps Run Script tasks are **one-shot** — RemoteOps itself has no recurring scheduler. To keep Safety Endpoint installed and up to date across the fleet, including devices that were offline or newly enrolled, re-run the task periodically. We suggest a **1-hour cadence**; pick whichever approach fits your environment:

* **Singularity Hyperautomation (recommended if licensed)** — Hyperautomation workflows support time-based interval triggers in addition to event triggers. Create a workflow with an interval trigger set to your chosen cadence, whose action runs the Safety Endpoint setup script — through a RemoteOps script action if your action catalog provides one, or through a custom integration action that calls the RemoteOps execute API (`POST /web/api/v2.1/remote-scripts/execute`), targeting your endpoints by filter. This keeps the entire loop inside the SentinelOne console with no external infrastructure. Hyperautomation is licensed separately from RemoteOps — if workflows are not available in your console, use the next approach instead.
* **External scheduler + API** — works with any RemoteOps license: trigger the run on a schedule from your automation platform (SOAR, cron job, CI runner) using `POST /web/api/v2.1/remote-scripts/execute`, targeting the Site or Group by filter. Check progress with `GET /web/api/v2.1/remote-scripts/status`. Pre-built SentinelOne integrations exist for Torq, Tines, Cortex XSOAR, and Axonius, all of which support scheduled execution. Consult the API reference in your console (`https://<your-console>/api-doc`) for the exact request format, and note that API tokens expire and must be renewed for the schedule to keep running.
* **Manual re-runs** — re-run the task from the console on a regular cadence and whenever new devices are enrolled

{% hint style="info" %}
Don't worry about triggering too often: the setup script is idempotent, and on macOS/Linux the wrapper throttles itself to one real run per hour — if your trigger fires more frequently than that, the extra runs exit immediately without doing work. Less frequent cadences also work; they simply mean updates and new devices are picked up more slowly.
{% endhint %}
{% endstep %}

{% step %}

### Optional: React to Detections

SentinelOne STAR (Storyline Active Response) custom rules cannot run scripts directly — their native responses are limited to alert, kill process, quarantine, remediate, and network quarantine. If you want to run the Safety Endpoint script in response to a detection or console event (for example, a new agent registering), use that alert as the trigger for a Hyperautomation workflow whose action runs the script.
{% endstep %}

{% step %}

### Verify

Monitor the task in the **Automation** section under the **Tasks** tab — tasks move through **Pending**, **In Progress**, and **Completed**. Once a task completes on a device:

1. Check the task status and script output for that endpoint — a successful run ends with the setup script's summary
2. Log in to the [Safety Platform](https://getsafety.com/app/device-enrollment) and confirm the device appears as enrolled

{% hint style="success" %}
**Quick test:** Run the task against a single test endpoint first and review its full script output before targeting the whole fleet.
{% endhint %}
{% endstep %}
{% endstepper %}

## Monitor Compliance

After deployment, monitor your fleet through the RemoteOps task history:

1. In the SentinelOne console, go to the **Automation** section
2. Open the **Tasks** tab
3. Review the status of the **Safety Endpoint - Setup** task per endpoint

| Status                    | What It Means                                                                                  | Action                                                                                                                          |
| ------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| **Completed**             | Script ran successfully                                                                        | None — Safety Endpoint is installed or was already up to date                                                                   |
| **Failed / error**        | The script exited with a non-zero code (how failures are surfaced may vary by console version) | Review the script output for that endpoint                                                                                      |
| **Pending / In Progress** | The endpoint has not run the task yet (for example, it is offline)                             | If a task never completes on a device, re-run it once the device is back online — a recurring cadence covers this automatically |

Device-level enrollment status is always visible in the [Safety Platform](https://getsafety.com/app/device-enrollment), which is the source of truth for which machines are reporting. To find machines that never ran the task, compare the enrolled-device list there against your SentinelOne scope — the difference is your remaining deployment backlog.

## Uninstall Safety Endpoint

If you need to remove Safety Endpoint from devices, upload the uninstall wrapper script to the Script Library and run it as a one-time task. Like the deployment script, it downloads the official uninstall script from Safety over HTTPS (with Authenticode verification on Windows) and executes it.

{% hint style="warning" %}
**Stop the recurring runs first.** If you set up a recurring re-run (Hyperautomation workflow, scheduled API call), disable it — or remove the target devices from its scope — before uninstalling. Otherwise the next scheduled run will reinstall Safety Endpoint.
{% endhint %}

{% hint style="danger" %}
The uninstall script removes Safety Endpoint artifacts from the machine, including configuration, firewall wrappers, package manager settings, and data for all user profiles. Configuration Safety wrote into a project file stays with the project, including the index line in a requirements file, the Safety index entry in a `pyproject.toml`, and the registry in a project `.npmrc`. Remove those with the project's own tooling, for example `poetry source remove safety`.
{% endhint %}

{% tabs %}
{% tab title="macOS / Linux" %}
{% code title="uninstall-run.sh" %}

```bash
#!/bin/bash
# Safety Endpoint - Uninstall Script (macOS/Linux)
# Upload to the SentinelOne Script Library as an action (non-collection) script.
# Requires: root | Run: one-time or on-demand
#
# Downloads the Safety Endpoint uninstall script and executes it.

set -euo pipefail

UNINSTALL_URL="https://getsafety.com/cli/uninstall.sh"
USER_AGENT="Safety-Endpoint/1 (run)"

echo "[Safety Endpoint Setup] Downloading $UNINSTALL_URL ..."
if command -v curl >/dev/null 2>&1; then
    curl -fsSL -A "$USER_AGENT" "$UNINSTALL_URL" | bash -s
elif command -v wget >/dev/null 2>&1; then
    wget -qO- --user-agent="$USER_AGENT" "$UNINSTALL_URL" | bash -s
else
    echo "[Safety Endpoint Setup] ERROR: curl or wget required" >&2
    exit 1
fi

# Clean up the throttle stamp if it exists
rm -f /var/tmp/.safety-endpoint-last-run
```

{% endcode %}
{% endtab %}

{% tab title="Windows" %}
{% code title="uninstall-run.ps1" %}

```powershell
# Safety Endpoint - Uninstall Script (Windows)
# Upload to the SentinelOne Script Library as an action (non-collection) script.
# Requires: SYSTEM | Run: one-time or on-demand
#
# Downloads the Safety Endpoint uninstall script, verifies its Authenticode
# signature is from Safety CLI Cybersecurity Inc, and executes it.

$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$uninstallUrl = "https://getsafety.com/cli/uninstall.ps1"
$tmpFile      = Join-Path $env:TEMP "safety-mdm-uninstall-$(Get-Random).ps1"

# ── HttpClient with proxy support (same as install.ps1 / setup.ps1) ─
Add-Type -AssemblyName System.Net.Http -ErrorAction Stop

$handler = [System.Net.Http.HttpClientHandler]::new()

$proxyUrl = @($env:HTTPS_PROXY, $env:ALL_PROXY, $env:HTTP_PROXY) |
    Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -First 1

if ($proxyUrl) {
    $proxyUri = [Uri]$proxyUrl
    $proxy = New-Object Net.WebProxy($proxyUri)
    if ($proxyUri.UserInfo) {
        $parts = $proxyUri.UserInfo -split ":", 2
        $proxy.Credentials = New-Object Net.NetworkCredential(
            [Uri]::UnescapeDataString($parts[0]),
            $(if ($parts.Length -gt 1) { [Uri]::UnescapeDataString($parts[1]) } else { "" })
        )
    } else {
        $proxy.UseDefaultCredentials = $true
    }
    $handler.Proxy = $proxy
} else {
    $handler.DefaultProxyCredentials = [Net.CredentialCache]::DefaultCredentials
}

$client = [System.Net.Http.HttpClient]::new($handler)
$null = $client.DefaultRequestHeaders.UserAgent.ParseAdd("Safety-Endpoint/1 (run)")

# ── Download, verify, execute ────────────────────────────────────────
try {
    # 1. Download (preserves exact bytes for Authenticode)
    Write-Host "Downloading $uninstallUrl ..."
    $response = $client.GetAsync($uninstallUrl).GetAwaiter().GetResult()
    $null = $response.EnsureSuccessStatusCode()
    $bytes = $response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult()
    [System.IO.File]::WriteAllBytes($tmpFile, $bytes)

    # 2. Verify Authenticode signature
    $sig = Get-AuthenticodeSignature -FilePath $tmpFile
    if ($sig.Status -ne "Valid") {
        throw "Signature status: $($sig.Status) - $($sig.StatusMessage)"
    }
    if ($sig.SignerCertificate.Subject -notmatch "O=Safety CLI Cybersecurity Inc") {
        throw "Unexpected signer: $($sig.SignerCertificate.Subject)"
    }
    Write-Host "Signature verified: $($sig.SignerCertificate.Subject)"

    # 3. Execute
    & $tmpFile
}
catch {
    Write-Error "Safety MDM uninstall failed: $_"
    exit 1
}
finally {
    Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
    if ($client) { $client.Dispose() }
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

Run it as a **Run Script** task against the target endpoints. The script runs once per device with elevated privileges and will clean up:

* Safety Endpoint binaries and system PATH entries
* Per-user configuration, firewall wrappers, and shell profiles
* Package manager configurations (pip, uv, npm)
* Scheduled tasks created by Safety Endpoint (Windows)

## Troubleshooting

<details>

<summary>RemoteOps options are missing from the console</summary>

If you don't see the Script Library or the Run Script action:

* RemoteOps is a licensed add-on — confirm it is included in your SentinelOne subscription
* RemoteOps is enabled **per Site** — check that the **Remote Script Orchestration** option is enabled in the Site's configuration
* Check that your console role has RemoteOps permissions (the ability to view/manage the Script Library and run scripts is controlled by RBAC)
* Contact your SentinelOne administrator or account representative to enable it

</details>

<details>

<summary>Script fails with network or proxy errors</summary>

The script downloads the setup script over HTTPS from `getsafety.com`. If the download fails:

* Ensure the endpoint has outbound HTTPS access to `getsafety.com`
* If your network uses a proxy, set the `HTTPS_PROXY` environment variable at the system level — both the shell and PowerShell scripts respect standard proxy environment variables
* Verify no network appliance is blocking or intercepting HTTPS traffic to `getsafety.com`

</details>

<details>

<summary>Signature verification fails on Windows</summary>

The Windows script verifies that the downloaded setup script is signed by **Safety CLI Cybersecurity Inc** using Authenticode. If verification fails:

* Check that TLS 1.2 is enabled on the device
* Verify no network appliance is intercepting or modifying HTTPS traffic (SSL inspection breaks Authenticode signatures)

</details>

<details>

<summary>The task times out before finishing</summary>

The first run on a device downloads the Safety Endpoint binary, enrolls the device, and performs an initial system scan, which can take several minutes. If tasks report a timeout:

* Increase the task timeout when creating the Run Script task (30 minutes is a safe value)
* Runs after the first are typically faster because the binary download and enrollment are skipped, but every run still performs a system scan — keep the timeout generous rather than tightening it after the first success

</details>

<details>

<summary>Script shows "skipping" every time (macOS/Linux)</summary>

The message `Last run was Xs ago (< 3600s). Skipping.` means the throttle is working as expected — the script only performs work once per hour. To force a re-run (e.g., for testing), delete the timestamp file on the device and run the task again:

```bash
sudo rm -f /var/tmp/.safety-endpoint-last-run
```

</details>

<details>

<summary>Task shows Failed but Safety Endpoint is installed</summary>

A failed status means the script exited with a non-zero code. This can happen if:

* The setup script detected a problem during its health check
* A newer version failed to install
* A temporary network issue occurred during the run

Check the script output for the endpoint. Re-run the task to retry (on macOS/Linux, subject to the 1-hour throttle).

</details>

<details>

<summary>New devices don't get Safety Endpoint automatically</summary>

RemoteOps tasks target the endpoints that existed when the task was created — they are not a continuously enforced policy. To cover new devices:

* Re-trigger the Run Script task against the Site or Group on a recurring cadence (via a Hyperautomation workflow or the API — see the Schedule Recurring Runs step) so newly enrolled endpoints are picked up on the next cycle
* Or trigger the script for new endpoints via the SentinelOne API as part of your provisioning workflow

</details>


# Applying Fixes

When performing a **`safety scan`**, Safety provides a list of the vulnerabilities detected and, where available, recommended fixes for each.

Safety CLI can automatically update requirements files based on these recommendations by using the **`safety scan --apply-fixes`** command.

{% hint style="info" %}
**Summary**

* Where possible, updates to requirements files are applied automatically using the closest package version in which the detected vulnerability has been resolved.
* Upgrades are performed in accordance with the [Policy File](#policy-file), which limits automatic upgrades to patch, minor, or major updates. Any upgrades beyond the policy-defined threshold will result in a prompt (Y/N/Skip) that must be responded to by the user.
* When no fixes are available, a message is displayed to that effect.
* Safety CLI does not download or install packages. Instead, requirements files are updated.
  {% endhint %}

## 1. Applying Security Updates Automatically

Safety can apply recommended security updates by including the **`--apply-fixes`** command.

#### **Example**

**`safety scan --apply-fixes`**

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-6593d43096372f5cf2384af9a4082ece27b29ac7%2Fimage.png?alt=media" alt=""><figcaption></figcaption></figure>

In this example, Safety has automatically updated the package versions in the requirements.txt file. Our policy file has a threshold limiting automatic upgrades to patches and minor upgrades only. As a result, the user is asked whether or not they wish to upgrade to the new version of the last package.

### Threshold for Applying Fixes Automatically

#### Policy File

The Safety policy file referenced when performing the scan includes the automatic update threshold, beyond which the user will be prompted to confirm whether or not they wish to update packages with known vulnerabilities.

This threshold is necessary to prevent Safety from applying updates that could impact projects, e.g. by upgrading to a new major version with breaking changes.

#### Terminal

To set the maximum version change that Safety will apply without user input, append that limit to the command, e.g. **` safety scan --apply-fixes requirements.txt`` `` `**<mark style="color:blue;">**`minor`**</mark>. Possible values are: `major, minor, patch`. The default is value `patch`.

{% hint style="info" %}
In both cases, the value used is an upper limit. Using **`major`** is equivalent to automatically applying all the fixes without user input.
{% endhint %}

**Examples:**

```
safety scan --apply-fixes requirements.txt minor
```

This will update the requirements.txt file (and any other requirements files it references) with all the security remediations that are `minor` or `patch` updates. If remediation requires a `major` version update, then Safety will ask for user input if they want to make this change.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-c30462be83d06234cd086752c508588716a6b1f9%2Fimage.png?alt=media" alt=""><figcaption><p>Patch and Minor Updates Applied Automatically. Major updates require user input.</p></figcaption></figure>

`safety scan --apply-fixes requirements.txt major`

In this case, as `major` was used, all the remediations will be automatically applied in the file, and any of its recursive include files.

## Skipping Update Prompts

If you want to ensure that Safety will not wait for user input, the `--no-prompt` flag will apply all automatic fix updates that fall within the `--auto-security-updates-limit` limit, and ignore those that require user input.

`safety scan -r requirements.txt --apply-fixes -afl minor --no-prompt`

This will apply all `patch` and `minor` version security updates to `requirements.txt` and ignore any `major` version updates, with no user input prompt.


# Securing Git Repositories

## Securing your git source control management system

The best place to start with scanning your Python codebases for dependency vulnerabilities is in a central place for your team, like your build pipeline or your central source control management system. This allows quick setup and will allow you to know what dependencies are in your systems, and secure them before they get to your production systems, without having to set up each developer's environment.

## A quick intro to transitive dependencies

It's important to scan all the Python dependencies present in your systems, and not just the ones listed in your requirements files (these are called transitive, recursive, and run-time dependencies).

## Scanning *all* of your dependencies in your SCM systems

Luckily, scanning your Python environments is really easy to do using our Safety command-line tool. Its default configuration is to scan the local environment where it is running. See our guides below to integrate Safety CLI into your SCM system:


# GitHub

## GitHub Actions

This is a guide to setting up and configuring Safety to scan your GitHub repositories for dependency security vulnerabilities using Safety as a GitHub Action. This enables you to configure security and compliance scans on your repositories on new commits, new branches, pull requests, and more.Safety is available as an action in the [GitHub Marketplace](https://github.com/marketplace/actions/pyupio-safety).

#### Step 1: Get your Safety API Key <a href="#step-1-get-your-safety-api-key" id="step-1-get-your-safety-api-key"></a>

To scan any systems for security vulnerabilities, you first need a Safety API key. You can create a Safety account and get your API key [here](https://safetycli.com/contact-sales).

In your GitHub repository, navigate to **Settings** -> **Secrets** -> **Actions**, and add your Safety API key as a secret that matches the variable name you've used in the workflow YAML file (`SAFETY_API_KEY` in all the examples here). Once added, it should look similar to the screenshot below:​​

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-11c65424d98ad3f18e35295aef2ea1f957655fe2%2F7cb6184-8886cae-Screen_Shot_2022-02-02_at_5.27.37_PM.png?alt=media" alt="" width="563"><figcaption></figcaption></figure>

#### Step 2: Set up a GitHub Actions workflow on your repository (If you don't have one already) <a href="#step-2-set-up-a-github-actions-workflow-on-your-repository-if-you-dont-have-one-already" id="step-2-set-up-a-github-actions-workflow-on-your-repository-if-you-dont-have-one-already"></a>

GitHub Actions are an easy and powerful way to run CI/CD processes on your codebases hosted on GitHub. Adding Safety security scans to your repositories is as easy as adding a few lines of code to your Github Action workflow configuration file to run Safety.We've created some full pipeline examples below if you don't have one set up yet. If you need help configuring your Python workflow, you can read more on [getting startup with GitHub workflows in Python](https://docs.github.com/actions/automating-builds-and-tests/building-and-testing-nodejs-or-python?langId=py).

#### Step 3: Configure your GitHub workflow YAML file to run Safety scans <a href="#step-3-configure-your-github-workflow-yaml-file-to-run-safety-scans" id="step-3-configure-your-github-workflow-yaml-file-to-run-safety-scans"></a>

GitHub Actions are configured using YAML workflow files in a special `.github/workflows/` folder. Here is an example YAML file that runs Safety to scan your project for security vulnerabilities. This will scan in auto-detect mode, which will try and scan the most appropriate thing automatically.You can read more about Safety's scan modes and different options.YAML# This workflow will run Safety security scans on all dependencies that are installed into the environment.# For more information see: <https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions#> Saved to \`.github/workflows/safety.yml\`​name: Safety Security Scan​on:push: # Run on every push to any branchpull\_request: # Run on new pull requests​jobs:safety:runs-on: ubuntu-lateststeps:- uses: safety/safety\@2.3.4with:api-key: ${{secrets.SAFETY\_API\_KEY}}


# GitHub Actions

## Introduction to GitHub Actions

[GitHub Actions](https://github.com/features/actions) is a powerful automation tool that integrates directly with GitHub repositories to allow you to automate your workflow by setting up a series of commands (actions) that execute in response to specific GitHub events like a push or a pull request. These actions can be used for a variety of tasks, such as testing code, deploying applications and, in the case of Safety, scanning for vulnerabilities.

[**The Safety CLI Scanner GitHub Action**](https://github.com/marketplace/actions/pyupio-safety-action) enables automated scanning of your projects for vulnerabilities directly within your GitHub workflow.

Link to Safety GitHub Action: <https://github.com/marketplace/actions/pyupio-safety-action>

## Setting Up the Safety GitHub Action

### **Step 1: Create a Safety Account and Obtain an API Key**

* To utilize the Safety CLI scanner, you first need to [create a Safety account](https://platform.safetycli.com/register/).
* Once your account is set up, you can obtain your API key from your [Safety Dashboard](https://platform.safetycli.com/). This key will be used to authenticate your GitHub Action with Safety's services.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-346d09f802986e10ea7577618aa4d2c620128306%2FScreenshot%202024-07-15%20at%2012.03.09.png?alt=media" alt=""><figcaption><p>Organization and User API Keys are available in Organization->API Keys</p></figcaption></figure>

### **Step 2: Configure the GitHub Secret**

* After obtaining your Safety API key, go to your GitHub repository's settings.
* Navigate to the '[Secrets](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions)' section and add a new secret.
* Name the secret (e.g., `SAFETY_API_KEY`) and paste your Safety API key as the value.

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-4aaa70467a64d58bb7b3cadd3db039b11d705288%2FScreenshot%202024-07-15%20at%2012.07.00.png?alt=media" alt=""><figcaption><p>Add a new Secret to your Repo called SAFETY_API_KEY</p></figcaption></figure>

### **Step 3: Set Up the Workflow File**

* You may need to create a Personal Access Token (PAT) with workflow permissions in order to push a workflow file to your repo. To do so, please [refer to this guide](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens).
* In your repository, create a new file in the `.github/workflows` directory. You can name this file according to its purpose (e.g., `safety_scan.yml`).
* Add the following content to your workflow file:

```yaml
name: Example workflow for Python using Safety Action
on: push
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@main
      - name: Run Safety CLI to check for vulnerabilities
        uses: pyupio/safety-action@v1
        with:
          api-key: ${{ secrets.SAFETY_API_KEY }}

```

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-3b9f35628745af097a2b82d29bc6b5ef2fbf3fc8%2FScreenshot%202024-07-15%20at%2012.29.26.png?alt=media" alt=""><figcaption></figcaption></figure>

### **Step 4: Activate the Workflow**

* Commit and push the workflow file to your repository.
* The Safety CLI Scanner Action will run automatically on each push, scanning your Python project for any vulnerabilities.

### **Additional Configurations (Optional)**

* You can customize the behaviour of the Safety Action by using various properties.
* You can also add arguments like `--detailed-output` to get more information from the scan:

```yaml
name: Example workflow customizing the Safety Action
on: push
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@main
      - name: Run Safety CLI to check for vulnerabilities
        uses: pyupio/safety-action@v1
        with:
          api-key: ${{ secrets.SAFETY_API_KEY }}
          args: --detailed-output # To always see detailed output from this action

```

#### Available Properties

<table><thead><tr><th>Property</th><th width="122.33333333333331">Default</th><th>Description</th></tr></thead><tbody><tr><td>api-key</td><td></td><td>Your Safety API Key</td></tr><tr><td>output-format</td><td>screen</td><td>Options are: screen, json, html, spdx, none</td></tr><tr><td>args</td><td></td><td>Override the default arguments to Safety CLI 3.</td></tr></tbody></table>

For more detailed information about Safety's CLI and its functionalities, please refer to [Safety 3 Documentation](https://docs.safetycli.com/safety-docs) or contact our [Support Team](/safety-docs/support/support).


# GitLab

This is a guide to setting up and configuring Safety to scan your Gitlab repositories for dependency security vulnerabilities. This enables you to configure security and compliance scans on your repositories on new commits, new branches, pull requests, and more.

You can set up Safety to run security scans on your Python repositories in GitLab using Gitlab Pipelines.

### Step 1: Get your Safety API Key

To scan any systems for security vulnerabilities, you first need a Safety API key. [You can create a Safety account and get your API key here](https://platform.safetycli.com/register/).

### Step 2: Set up a Gitlab Pipeline on your repository (If you don't have one already)

Gitlab pipelines are an easy and powerful way to run CI/CD processes on your codebases managed on Gitlab. Adding Safety security scans to your repositories is as easy as adding a few lines of code to your Gitlab pipeline configuration file to install Safety (our command-line tool) and then run a Safety scan.

We've created some full pipeline examples below if you don't have one set up yet. If you need help configuring your pipeline, you can read more on [getting startup with Gitlab pipelines](https://docs.gitlab.com/ee/ci/pipelines/).

### Step 3: Configure your .gitlab-ci.yml YAML file to run Safety

Gitlab pipelines are configured using a `.gitlab-ci.yml` YAML file at the root of your Gitlab repository. Here is an example YAML file that installs and runs Safety to scan your Python environment for security vulnerabilities.

YAML

```yaml
#  PyUp Security Scans Template

#  This template allows you to run security scans on your Python dependencies.
#  The workflow allows running tests on the default branch.

image: python:latest

# Change pip's cache directory to be inside the project directory since we can
# only cache local items.
variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

cache:
  paths:
    - .cache/pip
    - venv/

before_script:
  - python --version  # For debugging
  - pip install virtualenv
  - virtualenv venv
  - source venv/bin/activate
  - pip install safety

run:
  script:
    # Install your requirements. Alternatively install via Poetry or Pipenv
    - pip install -r requirements.txt
    # Run Safety's environment scan
    - safety --key $SAFETY_API_KEY --stage cicd scan
```

Your pipeline YAML file will likely end up running other tests and actions and deployments. All you have to do to ensure that Safety is scanning your dependencies for security vulnerabilities is to ensure that the following code (script) is in your YAML file amongst your other tests and scripts that are running.

YAML

```yaml
...
  script:
    # Install Safety - PyUp's command-line tool
    - pip install safety
    # Install your Python dependencies as per usual. 
    # This example uses requirements.txt and pip, but you may use Poetry with its pyproject.toml, or pipenv with its Pipfile. 
    - pip install -r requirements.txt
    # Run safety to scan the local Python environment. This will scan all installed dependencies, including any transitive dependncies that get installed during your installation
    - safety --key $SAFETY_API_KEY --stage cicd scan
```

### Final Step: Add your Safety API Key as a Gitlab repository variable

Your safety script requires the Safety API key to connect to Safety and get the latest commercial vulnerability database. To link up this API key to the $SAFETY\_API\_KEY variable defined in your pipeline YAML file (example above), you need to add your Safety API key as a Gitlab CI/CD environment variable. To do this, navigate to your repository on Gitlab, go to your project’s **Settings > CI/CD** and expand the **Variables** section.

Once added, the new variable should display like the screenshot below on the Gitlab repository variable page:

![973](https://files.readme.io/94ef9af-Screen_Shot_2022-03-28_at_5.44.40_PM.png)

### You're done!

That's it! You now have a fully working Gitlab CI/CD pipeline that will run and scan your Python dependencies for security vulnerabilities on new pushes and pull requests using Safety's commercial vulnerability database.

If there is a vulnerability found, Safety will return a non-zero exit code and fail the test. You can then see the pipeline's output in Gitlab to see what Safety found and how to patch the vulnerabilities. Here is our example running on a new pull request:

![1072](https://files.readme.io/0febb5b-Screen_Shot_2022-03-28_at_5.52.21_PM.png)

### Next Steps: Configure your Pipeline file, and learn more about Safety

**Gitlab Pipelines**\
There are many more configuration options on Gitlab CI/CD Pipelines. For example, you can set up this Pipeline to only run on certain branches or run when other conditions are met. You can also configure it to run periodically using a cron so that your repository is scanned for security vulnerabilities every hour or every day, not just when new code is committed.

You can read more about Gitlab pipelines on their [documentation page](https://docs.gitlab.com/ee/ci/pipelines/).

**Safety Command-Line Interface (CLI)**\
These scans use Safety's Command-Line tool, which has many options and configurations to meet your needs. Instead of scanning your local environment after you've installed your dependencies, you can also configure it to scan specific requirements files, output different formats, or even scan for license compliance issues.

You can read more about Safety and how to use it on its [Github page](https://github.com/pyupio/safety).


# BitBucket

This is a guide to setting up and configuring Safety to scan your BitBucket repositories for dependency security vulnerabilities. This enables you to configure security and compliance scans on your repositories on new commits, new branches, pull requests, and more.

You can set up Safety to run security scans on your Python repositories in BitBucket using BitBucket pipelines.

### Step 1: Get your Safety API Key

To scan any systems for security vulnerabilities, you first need a Safety account. [You can create a Safety account and get your API key here.](https://platform.safetycli.com/register/)

### Step 2: Set up a Bitbucket Pipeline on your repository (If you don't have one already)

BitBucket pipelines are an easy and powerful way to run CI/CD processes on your codebases hosted on BitBucket. Adding Safety security scans to your repositories is as easy as adding a few lines of code to your BitBucket pipeline configuration file to install and run Safety CLI.

We've created some full pipeline examples below if you don't have one set up yet. If you need help configuring your pipeline, you can read more on [getting startup with BitBucket pipelines](https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines/) as well as [setting up BitBucket pipelines in Python projects](https://support.atlassian.com/bitbucket-cloud/docs/python-with-bitbucket-pipelines/).

### Step 3: Configure your bitbucket-pipelines.yml YAML file to run Safety

BitBucket pipelines are configured using a `bitbucket-pipelines.yml` YAML file at the root of your BitBucket repository. Here is an example YAML file that installs and runs Safety to scan your Python environment for security vulnerabilities.

YAML

```yaml
#  Safety Security Scans Template

#  This template allows you to run security scans on your Python dependencies.
#  The workflow allows running tests on the default branch.

image: python:3.12

pipelines:
  default:
    - parallel:
      - step:
          # Run Safety to scan your Python Environment (recommended and best practice)
          name: Safety Security Scan on the Python Environment
          script:
            # Install Safety CLI - Safety's command-line tool
            - pip install safety
            # Install your Python dependencies as per usual. 
            # This example uses requirements.txt and pip, but you may use Poetry with its pyproject.toml, or pipenv with its Pipfile. 
            - pip install -r requirements.txt
            # Run safety to scan the local Python environment. This will scan all installed dependencies, including any transitive dependncies that get installed during your installation
            - safety --key $SAFETY_API_KEY --stage cicd scan
```

Your pipeline YAML file will likely end up running other tests and actions and deployments. All you have to do to ensure that Safety is scanning your dependencies for security vulnerabilities is to ensure that the following code (script) is in your YAML file amongst your other tests and scripts that are running.

YAML

```yaml
...
  script:
    # Install Safety - PyUp's command-line tool
    - pip install safety
    # Install your Python dependencies as per usual. 
    # This example uses requirements.txt and pip, but you may use Poetry with its pyproject.toml, or pipenv with its Pipfile. 
    - pip install -r requirements.txt
    # Run safety to scan the local Python environment. This will scan all installed dependencies, including any transitive dependncies that get installed during your installation
    - safety --key $SAFETY_API_KEY --stage cicd scan
```

### Final Step: Add your Safety API Key as a BitBucket repository variable

Your safety script requires the Safety API key to connect to Safety and get the latest commercial vulnerability database.

To link up this API key to the $SAFETY\_API\_KEY variable defined in your pipeline YAML file (example above), you need to add your Safety API key as a BitBucket repository variable.

To do this, navigate to your repository on BitBucket, then **Repository settings** then the **Repository variables** sub-menu.

Once added, the new variable should display like the screenshot below on the BitBucket repository variable page:

![918](https://files.readme.io/6b80389-Screen_Shot_2022-02-01_at_7.27.19_PM.png)

### You're done!

That's it! You now have a fully working BitBucket pipeline that will run and scan your Python dependencies for security vulnerabilities on new pushes and pull requests using Safety's commercial vulnerability database.

If there is a vulnerability found, Safety will return a non-zero exit code and fail the test. You can then see the pipeline's output in Bitbucket to see what Safety found and how to patch the vulnerabilities. Here is our example running on a new pull request:

<figure><img src="https://files.readme.io/76d8aab-Screen_Shot_2022-02-01_at_7.46.14_PM.png" alt=""><figcaption></figcaption></figure>

### Next Steps: Configure your Pipeline file, and learn more about Safety

**BitBucket Pipelines**\
There are many more configuration options on BitBucket Pipelines. For example, you can set up this Pipeline to only run on certain branches or run when other conditions are met. You can also configure it to run periodically using a cron so that your repository is scanned for security vulnerabilities every hour or every day, not just when new code is committed.

You can read more about BitBucket's pipelines on their [documentation page](https://support.atlassian.com/bitbucket-cloud/docs/configure-bitbucket-pipelinesyml/).


# Azure DevOps

This is a guide to setting up and configuring Safety to scan your Azure DevOps repositories for dependency security vulnerabilities. This enables you to configure security and compliance scans on your repositories on new commits, new branches, pull requests, and more.

You can set up Safety to run security scans on your Python repositories in Azure DevOps using Azure Pipelines.

### Step 1: Get your Safety API Key

To scan any systems for security vulnerabilities, you first need a Safety account. [You can create a Safety account and get your API key here.](https://platform.safetycli.com/organization/apikeys)

### Step 2: Set up an Azure DevOps Pipeline on your Repository

Azure Pipelines are a simple and powerful way to build, test, and deploy your code hosted in Azure DevOps. Integrating Safety into your CI/CD pipeline enables automated security and compliance checks for every commit, pull request, or scheduled run.

If you don’t already have a pipeline configured, follow these Microsoft guides to get started:

* [Get started with Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/get-started-yaml?view=azure-devops)
* [Build Python apps with Azure Pipelines](https://learn.microsoft.com/en-us/azure/devops/pipelines/languages/python?view=azure-devops)

Once your pipeline is in place, proceed to the next step to configure Safety CLI.

***

### Step 2.5: Set up a Self-Hosted Agent (Optional)

If you prefer to run pipelines on your own infrastructure—such as a local machine or internal server—you can configure a self-hosted agent instead of using Microsoft's hosted runners.

For complete setup instructions, see:

* [Set up a self-hosted agent in Azure DevOps](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/v2-linux?view=azure-devops)

Once your agent is configured and running, reference its pool name in your pipeline YAML:

```yaml
pool:
  name: Default
```

This tells Azure DevOps to execute the pipeline on the self-hosted runner you just configured in the `Default` agent pool.

### Step 3: Configure your azure-pipelines.yml YAML file to run Safety

Azure Pipelines are configured using an `azure-pipelines.yml` file at the root of your Azure DevOps repository. Here is an example YAML file that installs and runs Safety to scan your Python environment for security vulnerabilities.

```yaml
# Safety Security Scans Template

# This template allows you to run security scans on your Python dependencies.
# The workflow is configured to run on pushes to the 'main' branch.

trigger:
  - main  # Triggers the pipeline on changes to the 'main' branch

pool:
  name: Default  # Uses the default agent pool defined in Azure DevOps

steps:
  - script: |
      # Upgrade pip to the latest version
      python3 -m pip install --upgrade pip

      # Install Safety CLI – Safety’s command-line tool for dependency scanning
      pip install safety

      # (Optional) Install your project dependencies if you want to scan the active environment.
      # This is not required, Safety can scan your static files directly (e.g., requirements.txt, pyproject.toml)
      # You may also use Poetry or Pipenv instead.
      # pip3 install -r requirements.txt

      # Run Safety to scan the local Python environment
      # This will check all installed packages (including transitive dependencies) for vulnerabilities
      safety --key $(SAFETY_API_KEY) --stage cicd scan
    env:
      SAFETY_API_KEY: $(SAFETY_API_KEY)  # Injects your Safety API key as an environment variable
      SAFETY_CLI_DISABLE_LOCAL_CONFIG: "1"  # Disables any local Safety config to avoid misconfiguration
```

### Final Step: Add your Safety API Key as an Azure DevOps pipeline variable

Your Safety script requires the Safety API key to connect to Safety and retrieve the latest commercial vulnerability database.

There are two ways to securely inject the API key into your pipeline:

**Option 1: Define the variable in a Variable Group (via Library)**

* Navigate to your project in Azure DevOps.
* Go to Pipelines > Library.
* Create a new Variable Group or select an existing one.
* Add a variable named `SAFETY_API_KEY` and paste in your key.
* Check "Keep this value secret" to secure the key.

**Option 2: Define the variable in the pipeline UI**

* Go to your pipeline in Azure DevOps.
* Click Edit, then select the "Variables" tab.
* Add a new variable named `SAFETY_API_KEY`.
* Paste in your key and mark it as secret.

Either method will make the `$(SAFETY_API_KEY)` variable available to your pipeline, allowing the YAML configuration to authenticate successfully with Safety CLI.

### You're done!

That's it! You now have a fully working Azure DevOps pipeline that will run and scan your Python dependencies for security vulnerabilities on new pushes and pull requests using Safety's commercial vulnerability database.

If there is a vulnerability found, Safety will return a non-zero exit code and fail the test. You can then see the pipeline's output in Azure DevOps to view what Safety found and how to patch the vulnerabilities.

### Next Steps: Configure your pipeline file, and learn more about Safety

There are many more configuration options on Azure Pipelines. For example, you can:

* Set up this pipeline to only run on certain branches or run when other conditions are met.
* Configure it to run periodically using a cron so that your repository is scanned for security vulnerabilities every hour or every day, not just when new code is committed.

You can read more about Azure Pipelines on their [documentation page](https://learn.microsoft.com/en-us/azure/devops/pipelines/?view=azure-devops).

And for more on Safety CLI, visit [Safety CLI Documentation](https://docs.safetycli.com).


# Git Post-Commit Hooks

## Scanning your development environments

It's a best practice run your security scans as soon as possible in the development life-cycle - this is called shift left security.

Running PyUp security scans in your development environments is as simple as adding Safety's CLI scan to your git pre-commit hook files. This is a file that is executed before a git commit is run, and a failing command in this process will halt the commit itself, and warn the developer of the issue.

## Adding Safety CLI to your git pre-commit hooks

To add Safety scans to your git pre-commit hooks, first find your git pre-commit hook file, located at `.git/hooks/pre-commit`.

If you haven't already set up a pre-commit hook it may still be named `pre-commit.sample`. In that case, rename it to `pre-commit` and that file will start running before your git commits.

Once you've got the file ready, add the following to the bottom of the file:

Shell

```shell
# Add Safety Scan
exec safety scan
```

And that's it. Now Safety will scan your development machine before any code is pushed to central source control systems.


# Docker Containers

Safety is available in a Docker container if you'd like to scan across Python versions or use Safety without having to install it, or Python, locally.

To get started, you can run the `ghcr.io/pyupio/safety` image. Any arguments provided will be transparently passed through to Safety:

```shell
docker run --rm -ti ghcr.io/pyupio/safety:latest --version
```

Scanning from a requirements file works as expected. You must, however, make sure to volume mount your project so that Safety can access it inside the container:

```shell
docker run --rm -ti -v /path/to/my/project:/target ghcr.io/pyupio/safety --key <YOUR-API-KEY> --stage cicd scan --target /target
```


# Safety Policy Files

{% hint style="warning" %}
With the introduction of Safety Platform, our web-based Safety experience, local policy files will no longer be applied (even if specified in the CLI arguments). Instead, the Project policy that is defined in Platform will be used. Learn more about [Project Policies.](/safety-docs/administration/project-policies)
{% endhint %}

Safety CLI scan settings are configured using Safety policy files. These `.safety-policy.yml` files can be defined for each of your project's or codebases.

{% hint style="info" %}
**Note**: the older **`safety check`** command uses an older policy file format. See [Safety 2's policy file format](https://docs.safetycli.com/safety-2/safety-cli-2-scanner/policy-file) to configure the policies for the `safety check` command.
{% endhint %}

### Why use a policy file?

Using a policy file is recommended as a way to standardize your security policy for each project, and allows your development team to centrally share and configure rules, settings, and exceptions for your Safety scans. Some examples are:

* Setting vulnerability severity threshold to only report vulnerabilities above a certain severity threshold
* Ignoring specific vulnerabilities your team knows do not impact your project
* suppressing exit codes in scenarios in which you don't want to stop a build or test

These policy files should be checked into your source control at the root of your Python project alongside other policy files such as `.gitignore`, `requirements.txt`, `setup.py`, etc.

### Generating a Safety CLI policy file

To generate a new policy file, run the following command:

```
safety generate policy_file
```

The resulting policy file will be placed in the current directory with the name **`.safety-policy.yml`**. Please note that the file is hidden when viewing in Windows or Finder, but is visible when connected to a folder using an IDE.

### Safety Policy File Structure

Below is an example of a Safety policy file:

```yaml
version: '3.0'

scanning-settings:
  max-depth: 6
  exclude:
    - "node_modules"
    - "lib/other/**"
    - "**/*.js"
  include-files:
    - path: inside_target_dir/requirements-docs.txt
      file-type: requirements.txt
    - path: inside_target_dir/requirements-dev.txt
      file-type: requirements.txt

report:
  dependency-vulnerabilities:
    enabled: true
    auto-ignore-in-report:
      python:
        environment-results: true
        unpinned-requirements: true
      cvss-severity: []
      vulnerabilities:
        59901:
          reason: We are not impacted by this vulnerability
          expires: '2024-03-15'
        62044:
          reason: No upstream python images provide updated pip yet
          expires: '2024-06-01'

fail-scan-with-exit-code:
  dependency-vulnerabilities:
    enabled: true
    fail-on-any-of:
      cvss-severity:
        - critical
        - high
        - medium

security-updates:
  dependency-vulnerabilities:
    auto-security-updates-limit:
      - patch
```

We will now look at each section of the policy file, the items it controls, and available options.

#### Scanning Settings

The **`scanning-settings`** section of the policy file define where Safety CLI should scan in the target directory. These settings configure the **`safety scan`** command.

* **`max-depth`**: an integer that sets the maximum folder depth Safety CLI should scan to in the target directory.
* **`exclude`**: A list of paths and files that Safety CLI should exclude from the scan. This supports Python's [pathlib glob pattern matching](https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob), which are the same as for [fnmatch](https://docs.python.org/3/library/fnmatch.html#module-fnmatch), with the addition of `**` which indicates "this directory and all subdirectories, recursively". This supports Unix shell-style wildcards only, and not general regex patterns.
* **`include-files`**: The `Safety Scan` command does not allow single files to be targeted. If, however, files that you wish to be scanned are not included in the scan (e.g. because of non-standard naming conventions used), it is possible to use the `include-files` option of the policy file to include those files in your scan. See the code block above for an example of how to include such files.

#### Report

The **`report`** section of the policy file defines rules for which types and specific vulnerabilities Safety CLI should report on. This includes:

* **`dependency-vulnerabilities`**: true/false value, set to false if you want to completely disable dependency vulnerability reporting.
* **`auto-ignore-in-report`**: Everything defined under this key will be automatically ignored by Safety CLI and not included in reports or outputs.
  * **`python:environment-results`**: true/false - sets whether Safety should report on the dependencies found in the current Python environment.
  * **`python:unpinned-requirements`**: true/false - sets whether results should be returned for unpinned packages in requirements files, i.e. packages without a specified version.
  * **`cvss-severity`**: A list of CVSS severity values to ignore, options include: critical, high, medium, low, and unknown. Any vulnerabilities found that match any of these severity values will be ignored and not included in Safety's scan report.
  * **`vulnerabilities`**: A list of specific vulnerabilities Safety should ignore, using Safety's vulnerability IDs, and including **`reason`** string and **`expires`** datetime properties for logging and audit purposes.

#### Fail Scan with Exit Code

**`fail-scan-with-exit-code`** section of the policy file defines rules for when Safety CLI should return non-zero (failing) exit codes, for running Safety CLI within build and integration pipelines such as GitHub Actions, Azure Pipelines, GitLab Pipelines, Jenkins, and CircleCI pipelines.

This gives you the flexibility to set different rules for what to report, and when to fail builds or pipelines.

* **`dependency-vulnerabilities`**: enabled/disabled
  * **`enabled`**: true/false
  * **`fail-on-any-of`**: rules defining when Safety CLI should fail critical, high, meduim, low and unknown.
    * **`cvss-severity`**: A list of CVSS severity values, options include: critical, high, medium, low, and unknown. Any vulnerabilities Safety CLI reports on that match these CVSS severity labels will result in a failing exit code.
    * **`exploitability`**: A list of exploitability labels which should result in a failing exit code. This is a stub for the upcoming vulnerability EPSS score filtering feature.

{% hint style="info" %}
Note that some vulnerabilities do not have a CVSS severity score. To fail a scan when these vulnerabilities are detected, make sure to include a `cvss-severity` match for "unknown".
{% endhint %}

#### Security Updates

* **`auto-security-updates-limit`**: - patch/minor/major to determine the upper threshold for automatic application of fixes when Safety CLI is updating packages in a requirements.txt file. Note that if "major" is used, Safety will automatically apply fixes for all vulnerabilities, even if the next secure version is a major upgrade with breaking changes.\
  Additional information is available in the [Applying Fixes documentation](/safety-docs/vulnerability-remediation/applying-fixes).

### Using a Safety CLI policy file

Safety CLI will automatically use the `.safety-policy.yml` file in the root of the target directory being scanned by either `safety check` or `safety scan`.

If you want to reference a policy file that is not in your project root or scan target directory, set the policy file's path in the scan command using the `--policy-file` when running a scan.

```
safety scan --policy-file path-to-custom-location-and-name.yml
```

You can confirm a local policy file is being found and used by Safety CLI in the top report section of the output.

### Validating a Safety CLI policy file

Safety CLI's **`validate`** command can validate the correctness of a Safety CLI policy file.

For example:

```
safety validate policy_file --path .safety-policy.yml
```

ensures Safety CLI is parsing your policy file correctly.


# Project Policies

{% hint style="info" %}
Project policies are similar to Safety Policy Files, but take precedence over policy files in cases where a codebase has been onboarded as a Safety Project.
{% endhint %}

## Creating a Project Policy

To create a policy for a project, visit <https://platform.safetycli.com> and complete these steps:

1. Select your project.
2. Click **Project Settings**.
3. Click **Policies**.
4. Select your policy and click **Edit**.
5. Build your policy using the guided policy builder, or alternatively click **Advanced Configuration** to build your policy using JSON.
6. Click **Save**.

## Migrating your Safety Policy File to a Project Policy

{% hint style="info" %}
We plan to introduce a feature that will automatically detect a local policy file during project creation and replicate this in the Project Policy. Until then, please follow these instructions.
{% endhint %}

To migrate a local policy file to a Project Policy, visit [https://platform.safetycli.com](https://platform.safetycli.com/) and complete these steps:

1. Click **Project Settings**.
2. Click **Policies**.
3. Select your policy and click **Edit**.
4. Scroll down and click **Advanced Configuration**.
5. Copy the contents of your local policy file.
6. Paste the policy file contents into the advanced configuration.
7. Click **Update Policy**.

## Modifying your Default Organization Policy

All Organizations in Safety Platform are initialized with a default Project Policy. When a new project is created, this organization policy is applied. You can modify your default organization policy by visiting [https://platform.safetycli.com](https://platform.safetycli.com/) and following these steps:<br>

1. From the top navigation items, click **Organization**.
2. From the left-hand navigation, click **Policies**.
3. Select your policy and click **Edit**.
4. Build your policy using the guided policy builder, or alternatively click **Advanced Configuration** to build your policy using JSON.
5. Click **Save**.<br>

Note that updated organization policies will not apply retrospectively to existing project policies, but instead will apply to any newly created projects.


# Output Options and Recommendations

Safety can output the result of a vulnerability scan to a variety of different output formats.

The default output is `screen` output, which prints the scan to the command line screen.

Use the `--output` argument to configure which output format Safety generates. The `--output` command line argument can be set to the following values: `screen`, `json`, `html`, `spdx`, `spdx@2.2`, `spdx@2.3`, `none`.

## Screen and text output

`--output screen` (default) will print the results to the screen

Results can be easily saved to a text file. For example:

```
safety scan --output screen > results.txt
```

{% hint style="info" %}
For more detailed output, add the **`--detailed-output`** flag
{% endhint %}

If `--detailed-output` is specified along with `--output json` then CVE details will be included in the output. In order to filter the json output to only 1 top-level key, the `--filter` option can be specified. For example:

`safety scan --detailed-output --output json --filter cve_details`

Other options that can be chosen to filter are: `meta`, `scan_results`

## Additional Output Options

Full details on each output option can be found here:

* [JSON](/safety-docs/output/json-output)
* [SBOM](/safety-docs/output/sbom-output) (SPDX, SPDX\@2.2, SPDX\@2.3)
* [HTML5](/safety-docs/output/html-output)


# JSON Output

Safety can generate JSON output, useful for parsing and analyzing the results of a scan. To do so, run the following command.

```
safety scan --output json
```

The JSON output is displayed in the terminal, as shown below. To save the JSON output to a file, use the following command, replacint `test.json` with your desired file name.

```
safety scan --output json >test.json
```

<figure><img src="https://1428014516-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0ABDwXSDJWM5juLdc6ie%2Fuploads%2Fgit-blob-18fb430c07713b209bfab803183487b67c4ea00a%2Fimage.png?alt=media" alt=""><figcaption></figcaption></figure>

### JSON structure

The resulting output is a JSON with the following sections:

`meta` contains meta information about the scan, such as timestamps, what was scanned, packages found and vulnerabilities found

`scanned_packages` is an array of packages (and versions) that were found during the scan

`affected_packages` is an array of packages that were found to have relevant vulnerabilities

`vulnerabilities` is an array of vulnerabilities that were found relating to the packages in the scan

`ignored_vulnerabilities` is an array of vulnerabilities that were found but were ignored via a command line argument or the safety policy file.

`remediations` an array of remediation (fix) recommendations for each package with relevant vulnerabilities.

`announcements` an array of announcements (messages) from the Safety team. These are not generally related to the packages of vulnerabilities found, but rather are more general announcements, such as announcing a new version of the Safety scanner.


# SBOM Output


# HTML Output

### HTML5 Output

It will return the primary information about the scan in HTML5 format.

`safety scan --output html`

To save the result into an HTML5 file in your current directory:

`safety scan --output html >output.html`


# Detecting Vulnerabilities and Sharing Results via Email

This guide outlines how to utilize the Safety CLI tool for detecting vulnerabilities within your project dependencies and automatically sending an email notification when vulnerabilities are detected.

This process involves configuring a policy file to define the behavior of the scan and crafting a command to execute the scan and send the email based on the scan results.

Configuring the Policy FileTo begin, generate a policy file that will dictate how the Safety CLI scanner operates. This policy file allows for customization of the scanning process, including setting parameters that prevent the scan from failing with an exit code when vulnerabilities are detected.

Follow these steps:

1. **Generate a new policy file by running:**

```
safety generate policy_file
```

2. Open the generated `.safety_policy.yml` file and modify it as follows to prevent the scanner from exiting with a failure code due to detected vulnerabilities:

```
version: '3.0'

scanning-settings:
  max-depth: 8
  exclude: []
  include-files: []
  system:
    targets: []

report:
  dependency-vulnerabilities:
    enabled: true
    auto-ignore-in-report:
      python:
        environment-results: true
        unpinned-requirements: true
      cvss-severity: []

fail-scan-with-exit-code:
  dependency-vulnerabilities:
    enabled: false

security-updates:
  dependency-vulnerabilities:
    auto-security-updates-limit:
      - patch

```

​Place this `.safety_policy.yml` at the root of your project directory. For more detailed information on Safety CLI's policy file and its configurations, refer to the official documentation.

#### Running the Scan and Emailing Results <a href="#running-the-scan-and-emailing-results" id="running-the-scan-and-emailing-results"></a>

{% tabs %}
{% tab title="macOS and Unix" %}
This command performs a vulnerability scan using the Safety CLI tool, checks for any detected vulnerabilities, and sends an email notification if any vulnerabilities are found. It is designed to be executed in Unix and macOS environments.

1. **Scan Execution:** The Safety CLI tool is executed with a specified API key and stage. The results are saved in both JSON and human-readable text formats.
2. **Vulnerability Detection:** The `jq` tool is used to parse the JSON output to check for any known vulnerabilities.
3. **Email Notification:** If vulnerabilities are detected, an email is sent with the contents of the text report.

**Command**

Execute the command below in your terminal:

```
safety --key "API_KEY" –stage cicd scan --save-as json report.json > text_report && jq 'any(.scan_results.projects[].files[].results.dependencies[].specifications[].vulnerabilities.known_vulnerabilities[]; .id != null)' report.json | xargs -I {} test {} = "true" && cat text_report | mail -s "Vulnerabilities found" email@domain.com || true
```

* The `--save-as json report.json > text_report` part saves Safety CLI results in a JSON format to `report.json`, while redirecting the standard output to be saved as human-readable `text_report`.
* The `jq` command checks for the presence of any vulnerabilities by examining the `id` fields within the scan results. If any vulnerabilities are found (`true`), the subsequent command is triggered.
* `xargs -I {} test {} = "true"` uses the result from `jq` to conditionally proceed with sending an email if vulnerabilities are detected.
* The `mail` command constructs an email with the subject "Vulnerabilities found" and the content of `text_report`, sending it to the specified email address.
* The `|| true` ensures that, regardless of the exit codes in the pipe, the command sequence exits with a status code of `0` to prevent interrupting any automated pipelines due to a failure status.
  {% endtab %}

{% tab title="Windows" %}
This command performs a vulnerability scan using the Safety CLI tool, checks for any detected vulnerabilities, and sends an email notification if any vulnerabilities are found. It is designed to be executed in a Windows PowerShell environment.

1. **Scan Execution:** The Safety CLI tool is executed with a specified API key and stage. The results are saved in both JSON and human-readable text formats.
2. **Vulnerability Detection:** The vulnerability detection process involves the script first checking if the report.json file exists. Upon confirming its presence, the script then parses the JSON output to identify any known vulnerabilities.
3. **Email Notification:** If vulnerabilities are detected, an email is sent with the contents of the text report. The email configuration is specified using SMTP server details.

To send an email notification about detected vulnerabilities, you need to configure an SMTP (Simple Mail Transfer Protocol) server. An SMTP server is a critical component for email communication, acting as the mail delivery agent that sends outgoing emails from your system to the recipient’s email server. Configuring the SMTP server involves specifying the server address (e.g., smtp.sendgrid.net), the port number (typically 587 for TLS or 465 for SSL), and authentication credentials, which include the SMTP username and password, usually corresponding to an API key and its value. These credentials ensure secure and authenticated communication between your system and the SMTP server.

**Command Variables**

Before executing the command, you need to set the following variables:

* **API\_KEY**: The API key for the Safety CLI scan.
* **SMTP\_SERVER**: The SMTP server address (e.g., smtp.sendgrid.net).
* **SMTP\_PORT**: The SMTP server port (e.g., 587).
* **SMTP\_USER**: The SMTP username, typically an API key identifier.
* **SMTP\_PASSWORD**: The SMTP password, typically the API key value.
* **RECIPIENT\_EMAIL**: The email address to which the report will be sent.
* **SENDER\_EMAIL**: The email address from which the report will be sent.

**Command**

```powershell
safety scan --key <API_KEY> --stage cicd --save-as json report.json > text_report.txt
if (Test-Path -Path "report.json") {
    $jsonReport = Get-Content report.json | ConvertFrom-Json
    $vulnerabilitiesFound = $false
    foreach ($project in $jsonReport.scan_results.projects) {
        foreach ($file in $project.files) {
            foreach ($dependency in $file.results.dependencies) {
                foreach ($specification in $dependency.specifications) {
                    if ($specification.vulnerabilities.known_vulnerabilities.Count -gt 0) {
                        $vulnerabilitiesFound = $true
                        break
                    }
                }
                if ($vulnerabilitiesFound) { break }
            }
            if ($vulnerabilitiesFound) { break }
        }
        if ($vulnerabilitiesFound) { break }
    }
    if ($vulnerabilitiesFound) {
        $smtpServer = "<SMTP_SERVER>"
        $smtpPort = <SMTP_PORT>
        $smtpUser = "<SMTP_USER>"
        $smtpPassword = "<SMTP_PASSWORD>"
        $emailTo = "<RECIPIENT_EMAIL>"
        $emailFrom = "<SENDER_EMAIL>"
        $subject = "Vulnerabilities found"
        $body = Get-Content text_report.txt -Raw
        $smtpClient = New-Object Net.Mail.SmtpClient($smtpServer, $smtpPort)
        $smtpClient.EnableSsl = $true
        $smtpClient.Credentials = New-Object System.Net.NetworkCredential($smtpUser, $smtpPassword)
        $mailMessage = New-Object System.Net.Mail.MailMessage
        $mailMessage.From = $emailFrom
        $mailMessage.To.Add($emailTo)
        $mailMessage.Subject = $subject
        $mailMessage.Body = $body
        $mailMessage.IsBodyHtml = $true
        try {
            $smtpClient.Send($mailMessage)
            Write-Output "Email sent successfully"
        } catch {
            Write-Output "Failed to send email: $_"
            Write-Output "SMTP Server: $smtpServer"
            Write-Output "SMTP Port: $smtpPort"
            Write-Output "SMTP User: $smtpUser"
            Write-Output "SMTP Password: $smtpPassword"
        }
    } else {
        Write-Output "No vulnerabilities found"
    }
} else {
    Write-Output "report.json was not created, scan might have failed"
}
```

{% endtab %}
{% endtabs %}

#### Alternative Approaches <a href="#alternative-approaches" id="alternative-approaches"></a>

While the above command provides a quick and integrated solution for scanning and alerting, it's possible to incorporate this logic into a script for more complex workflows or to enhance readability and maintainability. This guide aims to facilitate a seamless integration of vulnerability scanning and notification within your CI/CD pipeline, ensuring that your team is promptly informed of any security issues detected in your project dependencies.


# Support

If you require additional support beyond the documentation available here, please reach out to our support team at <support@safetycli.com>.


# Special Notes on Windows

### Setting the PYTHONENCODING Variable

In some Windows environments, Safety CLI will return an error when performing scans on files or directories that include special or accented characters, e.g. é, è, à, ç, et al.

This issue occurs because the `PYTHONIOENCODING` variable is empty or not set to `utf-8`.

If you are using the Windows CMD, you can set the variable in the following way:

```
set PYTHONIOENCODING=utf-8
safety scan --output screen > results.txt
```

This set command updates the value per terminal session; if you want to do it permanently, you must add `PYTHONIOENCODING` with the value `utf-8` to your environment variables on Windows.


# Invalid API Key Error

An invalid API key error means you are not using a valid Safety API key. If this API key was working previously, then the key may have been invalidated (for example, rotated) or your Safety account is no longer active.

#### How to get a Safety API key

If you already have a Safety account, once logged in you can find your API key on the [Teams and API Keys](https://platform.safetycli.com) page.

If you do not have a Safety account, you can obtain a Safety API key by [signing up ](https://platform.safetycli.com)for a free trial or a paid account.

If your team already has a Safety account and you need an API key assigned to you, please get in touch with your team's Safety admin to add you as a team member.

#### Using the Safety API key in Safety CLI

To access and utilize additional features of Safety, you will need to configure Safety to use an API key. There are two ways to do this:

* append your Safety commands with the command-line argument `--key 'your_api_key'`. For example `safety --key 'your_api_key' scan`
* save your API key as the environment variable `SAFETY_API_KEY`.\
  For example `export SAFETY_API_KEY=YOUR_API_KEY`

If you have any further questions, please don't hesitate to contact us via email at [support@s](mailto:support@pyup.io)[afetycli.com](mailto:support@safetycli.com).


# Headless Authentication

Learn how to authenticate sessions on machines with no browser, by leveraging a second machine with a browser.

For users who wish to perform scans on machines with no browser, e.g. EC2 instances, it is possible to authenticate the scan session by leveraging another machine with a browser installed.

1. Start by installing the latest version of Safety:

```
pip install safety==3.4.0
```

2. When installed, start by authenticating the session using the new headless option.

```
safety auth login --headless
```

3. You should see the following: “Running in headless mode. Please copy and open the following URL in a browser. Copy and paste this url into your browser.”

* Copy and paste the URL from the Terminal into a browser on another machine that does have one.
* Once authenticated on that browser a code will show on the success screen.
* Click the JSON code on the screen. This will copy the code to the clipboard.
* Paste that JSON code string into the original prompt in your Terminal.
* The Safety session should now be authenticated on the machine without a browser installed.


# Implementation Support

## Allow List Requirements

To ensure Safety runs correctly in your environments, the following domains must be added to your allow-lists.

<table><thead><tr><th width="374">Domain</th><th>Purpose</th></tr></thead><tbody><tr><td>https://platform.safetycli.com</td><td>Web-based dashboards, account administration, core platform.</td></tr><tr><td>https://auth.safetycli.com</td><td>Authentication</td></tr><tr><td>https://data.safetycli.com</td><td>Vulnerability Database, changelongs, etc.</td></tr></tbody></table>


# Global proxy and identity configuration

Safety CLI's proxy, authenticated, and other global settings can be configured at the system or user level so that these settings do not have to be repeated.

This configuration is stored in **`config.ini`** at Safety CLI's user or system path, which differs based on the operating system. Safety's **`configure`** command let's you easily set these configurations. Using Safety CLI enter **`safety configure --help`** for more details.


# Using Safety in Conda Environments

## Using Safety CLI with Anaconda Environments

### Overview

Safety CLI is the only dependency scanner powered by Safety's industry-leading vulnerability database. Safety CLI can be used to scan and secure Anaconda projects and environments, with some additional steps required to help make implementation and rollout seamless across your team.\
\
This guide will show you how to use Safety CLI effectively with your Anaconda projects.

### Table of Contents

* [General Process](#general-process)
* [Scanning Unix-based Systems (Linux/macOS)](#unix-based-systems-linux-macos)
* [Scanning Windows-based Systems](#windows-based-systems)
* [Notes](#important-note-1)

### General Process

To scan Anaconda environments with Safety CLI, the following general steps are recommended:

1. Export the Anaconda manifest (list of packages in the Anaconda project):

   ```bash
   conda activate your_environment_name
   conda list -e > requirements.txt
   ```
2. Separate pip-installed packages and conda-installed packages into two separate temporary requirements.txt files (instructions and examples for this below)
3. Scan the separated requirements files using `safety scan`
4. Delete the temporary requirements.txt files that were created

### Unix-based Systems (Linux/macOS)

For a streamlined workflow on Unix-based systems, use the following bash script:

```bash
#!/bin/bash

# Function to clean up temporary files
cleanup() {
    rm -f requirements.txt conda_requirements.txt pip_requirements.txt
}

# Trap to ensure cleanup happens even if the script is interrupted
trap cleanup EXIT

# Generate requirements file
conda list -e > requirements.txt

# Function to separate conda and pip packages
separate_requirements() {
    local input_file=$1
    local conda_output=$2
    local pip_output=$3

    > "$conda_output"
    > "$pip_output"

    while IFS= read -r line
    do
        if [[ $line =~ ^#.*$ ]] || [[ -z $line ]]; then
            echo "$line" >> "$conda_output"
        elif [[ $line == *"=pypi_0"* ]]; then
            package_info=$(echo "$line" | sed 's/=/==/;s/=pypi_0//')
            echo "$package_info" >> "$pip_output"
        else
            package_info=$(echo "$line" | awk -F= '{print $1"=="$2" #"$3}')
            echo "$package_info" >> "$conda_output"
        fi
    done < "$input_file"
}

# Separate requirements
separate_requirements "requirements.txt" "conda_requirements.txt" "pip_requirements.txt"

# Run safety with all arguments passed to this script
safety "$@"

# Cleanup is handled by the trap
```

#### Usage

1. Save the script as `conda_safety.sh`
2. Make it executable: `chmod +x conda_safety.sh`
3. Run the script as a replacement for the `safety` command, using any Safety CLI arguments. For example:

```
conda activate <your_environment_name>
./conda_safety.sh scan
```

or

```
conda activate <your_environment_name>
./conda_safety.sh scan --output json
```

#### Setting Up as a Command using an alias

To use the script as a command:

1. Add an alias in your shell configuration file (`~/.bashrc` or `~/.zshrc`):

   ```
   alias conda-safety='/path/to/your/conda_safety.sh'
   ```
2. Reload your shell configuration:

   ```
   source ~/.bashrc  # or ~/.zshrc for Zsh
   ```

Now you can use `conda-safety` as a drop-in replacement for the `safety` command. Once you've activated your conda environment, use \`conda-safety\`. For example:

```
conda activate <your_environment_name>
conda-safety auth
conda-safety scan
```

### Windows-based Systems

For Windows users, use the following PowerShell script:

```powershell
# Function to clean up temporary files
function Cleanup {
    Remove-Item -ErrorAction SilentlyContinue requirements.txt, conda_requirements.txt, pip_requirements.txt
}

# Ensure cleanup happens even if the script is interrupted
trap { Cleanup; break }

# Generate requirements file
conda list -e > requirements.txt

# Function to separate conda and pip packages
function Separate-Requirements {
    param (
        [string]$InputFile,
        [string]$CondaOutput,
        [string]$PipOutput
    )

    $null > $CondaOutput
    $null > $PipOutput

    Get-Content $InputFile | ForEach-Object {
        if ($_ -match '^#' -or $_ -eq '') {
            $_ >> $CondaOutput
        }
        elseif ($_ -match '=pypi_0') {
            $packageInfo = $_ -replace '=', '==' -replace '=pypi_0', ''
            $packageInfo >> $PipOutput
        }
        else {
            $parts = $_ -split '='
            "$($parts[0])==$($parts[1]) #$($parts[2])" >> $CondaOutput
        }
    }
}

# Separate requirements
Separate-Requirements "requirements.txt" "conda_requirements.txt" "pip_requirements.txt"

# Run safety with all arguments passed to this script
safety $args

# Cleanup
Cleanup
```

#### Usage

1. Save the script as `Conda-Safety.ps1`
2. Open PowerShell and navigate to the script's directory
3. Run the script with Safety CLI arguments:

```
conda activate <your_environment_name>
.\Conda-Safety.ps1 scan
```

or

```
conda activate <your_environment_name>
.\Conda-Safety.ps1 scan --output json
```

#### Setting Up as a Command

To use the script as a command in Windows:

1. Create a directory for the script if it doesn't exist:

   ```
   mkdir C:\Users\YourUsername\Documents\WindowsPowerShell\Scripts
   ```
2. Move the script to this directory:

   ```
   Move-Item Conda-Safety.ps1 C:\Users\YourUsername\Documents\WindowsPowerShell\Scripts
   ```
3. Add the directory to your PATH:
   * Open System Properties
   * Click on Environment Variables
   * Under System Variables, find and edit the PATH variable
   * Add `C:\Users\YourUsername\Documents\WindowsPowerShell\Scripts`
4. Create a PowerShell profile if you don't have one:

   ```
   if (!(Test-Path -Path $PROFILE)) { New-Item -ItemType File -Path $PROFILE -Force }
   ```
5. Add this line to your PowerShell profile:

   ```
   Set-Alias conda-safety C:\Users\YourUsername\Documents\WindowsPowerShell\Scripts\Conda-Safety.ps1
   ```

Now you can use `conda-safety` as a command in PowerShell as a drop-in replacement for the `safety` command. This requires `safety` and `conda` to already be installed and available in Powershell.

### Important Note

Conda-installed packages may differ from standard PyPI packages. As a result, security findings for conda-installed packages may differ from PyPi equivalent packages. Always verify findings for conda-installed packages against the specific versions in your Anaconda environment.

### Help and Implementation Assistance

Anaconda environments and setups vary. If the instructions above do not work or you encouter any issues, please don't hesitate to reach out to our support team at <support@safetycli.com>. We're here to help you ensure the security of your Python projects, regardless of your environment setup.


# Understanding Vulnerability Scoring Systems: CVSS and EPSS

This guide provides a comprehensive comparison between two major vulnerability scoring systems: the Common Vulnerability Scoring System (CVSS) and the Exploit Prediction Scoring System (EPSS).

## CVSS and EPSS

In today's rapidly evolving cybersecurity landscape, effectively prioritizing vulnerability remediation has become more critical than ever. Security teams face an overwhelming number of vulnerabilities across their systems, making it crucial to have reliable methods for assessing which ones pose the greatest risk and require immediate attention.

For many years, the Common Vulnerability Scoring System (CVSS) has served as the primary framework for evaluating vulnerability severity. However, the emergence of the Exploit Prediction Scoring System (EPSS) represents a significant shift in how we approach vulnerability prioritization. While CVSS focuses on the theoretical severity of vulnerabilities, EPSS takes a data-driven approach to predict the likelihood of actual exploitation in the wild.

This guide explores both scoring systems, their methodologies, and how they complement each other in modern vulnerability management practices. Whether you're a security practitioner, manager, or stakeholder, understanding these systems is essential for making informed decisions about vulnerability prioritization and resource allocation.

{% tabs %}
{% tab title="CVSS" %}

### Common Vulnerability Scoring System (CVSS)

CVSS is a framework for assessing the severity of computer system security vulnerabilities. It aims to provide a standardized method for rating vulnerabilities.

**Key Components of CVSS**

CVSS consists of three metric groups:

1. Base Score Metrics
   * Assess the intrinsic characteristics of a vulnerability
   * Include exploitability metrics (attack vector, complexity, privileges required, user interaction)
   * Consider impact metrics (confidentiality, integrity, availability)
2. Temporal Score Metrics
   * Reflect characteristics that change over time
   * Consider exploit code maturity, remediation level, and report confidence
3. Environmental Score Metrics
   * Allow for context-specific adjustments
   * Account for the security requirements of your implementation

**CVSS Scoring Scale**

* Scores range from 0.0 to 10.0
* Critical: 9.0-10.0
* High: 7.0-8.9
* Medium: 4.0-6.9
* Low: 0.1-3.9
* None: 0.0
  {% endtab %}

{% tab title="EPSS" %}

#### Exploit Prediction Scoring System (EPSS)

EPSS is a data-driven effort for estimating the probability that a software vulnerability will be exploited in the wild. Unlike CVSS, which measures severity, EPSS predicts the likelihood of exploitation.

**Key Features of EPSS**

1. Probability-Based Approach
   * Provides a probability score between 0 and 1
   * Based on real-world exploitation data
   * Updated daily to reflect current threat landscape
2. Machine Learning Foundation
   * Uses various data points and features to make predictions
   * Considers factors like vulnerability characteristics, social media mentions, and exploit availability
3. Dynamic Nature
   * Scores change based on new data and observations
   * Reflects real-world exploitation patterns
     {% endtab %}
     {% endtabs %}

### Comparison Table: CVSS vs EPSS

| Aspect                | CVSS                                           | EPSS                                 |
| --------------------- | ---------------------------------------------- | ------------------------------------ |
| Primary Purpose       | Measures vulnerability severity                | Predicts exploitation probability    |
| Score Range           | 0.0 to 10.0                                    | 0 to 1 (probability)                 |
| Update Frequency      | Static (unless manually updated)               | Daily                                |
| Methodology           | Expert-driven framework                        | Data-driven machine learning         |
| Complexity            | Complex (3 metric groups, multiple sub-scores) | Simple (single probability score)    |
| Context Consideration | Through environmental metrics                  | Through real-world exploitation data |
| Industry Maturity     | Well-established (20+ years)                   | Emerging standard                    |
| Primary Use Case      | Vulnerability severity assessment              | Exploitation risk prioritization     |
| Data Source           | Theoretical assessment                         | Real-world exploitation data         |
| Adaptability          | Manual updates needed                          | Automatically adapts to new threats  |


# Release Notes

## Safety 3.4.0


# Breaking Changes in Safety 3

As a major version upgrade, Safety 3.x includes several breaking changes over versions 2.x, which are summarized below. For more information on migrating from Safety CLI 2.x to Safety CLI 3.x, please refer to our [migration guide.](/safety-docs/safety-cli/introduction-to-safety-cli-vulnerability-scanning/migrating-from-safety-cli-2.x-to-safety-cli-3.x)

| Breaking Change Category                             | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Command Update                                       | `safety check` command is replaced by `safety scan`. The new command is more powerful and configurable, providing recursive search in the target directory, native support for various dependency files, and customizable scan settings.                                                                                                                                                                                                                                                                                     |
| Configuration File                                   | The `.safety-policy.yml` file structure has changed. The new format is incompatible with the old one used by `safety check`. Users need to convert their existing policy files to the new format for compatibility with `safety scan`.                                                                                                                                                                                                                                                                                       |
| Policy File Changes                                  | Specific configurations in the old policy file need to be translated to the new format. Notably, `security:ignore-vulnerabilities` moves to `report:auto-ignore-in-report:vulnerabilities`, `security:ignore-cvss-severity-below` and `security:ignore-cvss-unknown-severity` combine into `report:auto-ignore-in-report:cvss-severity`, and `security:continue-on-vulnerability-error:True` is replaced by `fail-scan-with-exit-code:dependency-vulnerabilities:enabled:False`. The `alert` section is no longer supported. |
| Scan Target Settings                                 | The `-r` flag for specifying `requirements.txt` files in `safety check` is no longer needed in `safety scan` as it finds these files automatically. The `scanning-settings:exclude` property in the new policy file can be used to exclude specific files or folders from scans.                                                                                                                                                                                                                                             |
| JSON Output Format                                   | Safety CLI 3 introduces a new JSON output format for `safety scan` that is substantially different from `safety check`’s JSON output. If upgrading from Safety CLI 2.x and using JSON output, users may face breaking changes in the JSON structure if upgrading from versions earlier than 2.4.0b.                                                                                                                                                                                                                          |
| Using Both `Safety Check` and `Safety Scan` Commands | Safety CLI 3 allows running both `safety check` and `safety scan` commands, each with their separate policy files. To continue using both, the old policy file must be renamed (e.g., `.safety-check-policy.yml`) and specified when using `safety check`.                                                                                                                                                                                                                                                                   |
| Validate Command                                     | When using the `validate` command, Safety CLI 3 will validate a 3.0 policy file by default.                                                                                                                                                                                                                                                                                                                                                                                                                                  |

{% hint style="info" %}
**Targeting Specific Requirements Files**

In Safety CLI 2, it was possible to target specific requirements files. The new Safety Scan command is designed to allow you to scan all files in a project directory (or sub-directory) simultaneously rather than running separate scans targeted on each file.

The [Policy File](/safety-docs/administration/safety-policy-files) enables you to control the depth of those scans to detect nested requirements files, e.g. six folders deep within the current directory.

If you wish to specify a target directory for the Safety Scan, you can do so using the **`--target`** option, e.g. ` safety scan`` `` `**`--target /path/to/project`**. Safety Scan does not allow you to target single files, but the include-files section of the Policy File does allow you to include specific files in your scan if these are not detected in a normal scan.

Example:

**`include-files:`**

**`- path: inside_target_dir/requirements-docs.txt`**

**`file-type: requirements.txt`**

**`- path: inside_target_dir/requirements-dev.txt`**

**`file-type: requirements.txt`**

When running a new Safety Scan, the new CLI output will separate findings and recommendations by requirements file, e.g. requirements.txt will have its own set of recommendations, requirements-dev.txt will have its own, etc. This means that instead of running separate scans for each file, you can now run one simple scan and see all findings and recommendations in one output.
{% endhint %}


