# 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.

## 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

### From scratch

**Step 1:** Navigate to **Harden Runner → Policy Store** in the sidebar and click "Create policy"

![StepSecurity Policy Store page](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-16/aadafd4f-a9a3-4ff2-a9b2-e67e338f6db5/ascreenshot.jpeg?tl_px=0,0\&br_px=3839,1984\&force_format=jpeg\&q=100\&width=1120.0)

**Step 2:** Enter a policy name and add allowed endpoints manually

![StepSecurity Policy Store page](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-16/a60cbd74-30cb-48ce-b961-79acd72b5bdf/ascreenshot.jpeg?tl_px=0,87\&br_px=2752,1626\&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=188,277)

**Step 4.** Optionally add a `lockdown-mode` configuration (see [Lockdown Mode](#lockdown-mode)).

**Step 5.** Click **Add Policy**.

![StepSecurity Policy Store page](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-16/2cb1e77b-6d8c-4fe4-9a04-d49903cd6a86/ascreenshot.jpeg?tl_px=0,0\&br_px=3839,1984\&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=1041,12)

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.&#x20;

![StepSecurity Policy Store page](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-16/affc884f-9ea7-443b-87ec-d3d54328b995/ascreenshot.jpeg?tl_px=1874,0\&br_px=3840,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=773,78)

* 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

<figure><img src="https://754495266-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FQJRZY4cfEeY3I7DXTOCp%2Fuploads%2FQgs9aELUDsIpVMrCRuiS%2FScreenshot%202026-04-21%20at%2014.45.42.png?alt=media&#x26;token=1b1f9ba5-a24f-451e-af24-ae107cdc9969" alt=""><figcaption></figcaption></figure>

* Click **Import Endpoints** to populate the policy, then **Add Policy** to save.

<figure><img src="https://754495266-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FQJRZY4cfEeY3I7DXTOCp%2Fuploads%2FDWdfTyKbefuPw1BUA80t%2FScreenshot%202026-04-21%20at%2014.45.55.png?alt=media&#x26;token=bc3bdcf6-80d9-4c2c-a770-746da398e397" alt=""><figcaption></figcaption></figure>

### From the Baseline page

**Step 1:** Navigate to **Harden Runner → Baseline**

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-10-07/2bef0fdd-23d1-475e-b8d2-14b20de8000f/ascreenshot.jpeg?tl_px=0,0\&br_px=3024,1722\&force_format=jpeg\&q=100\&width=1120.0)

**Step 2:** Select a job, repository, ARC cluster, or organization, then click **Create Policy**

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-10-07/2bef0fdd-23d1-475e-b8d2-14b20de8000f/ascreenshot.jpeg?tl_px=1397,223\&br_px=3024,1132\&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=931,277)

**Step 3:** You will be redirected to the new policy page with endpoints automatically populated

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-10-07/a48582cb-b923-4614-8e06-d098f17b637e/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=660,148)

**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 **Add Policy** to save

![](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-10-07/151d7293-0c2f-4d41-9fad-7305f3b959be/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=1013,88)

## 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**](https://docs.stepsecurity.io/settings/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**](https://docs.stepsecurity.io/settings/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="https://754495266-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FQJRZY4cfEeY3I7DXTOCp%2Fuploads%2FUihRXjVehUPqvQfq0J92%2FScreenshot%202026-04-21%20at%2013.50.47.png?alt=media&#x26;token=221642cc-0173-4aac-a72e-31d724bf3c4d" 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](https://docs.stepsecurity.io/admin-console/integrations/webhook-integration), [Slack](https://docs.stepsecurity.io/admin-console/integrations/slack-oauth-integration), [S3](https://docs.stepsecurity.io/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

![StepSecurity Policy Store page](https://ajeuwbhvhr.cloudimg.io/https://colony-recorder.s3.amazonaws.com/files/2025-09-16/ec7c428c-6b62-4566-8716-3bf0a0882965/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=425,252)

* 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.stepsecurity.io/harden-runner/policy-store.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
