---
title: "Scripting"
description: "How the Outpost script library works, from library scopes, interpreters, parameters, and revisions to approval, signing, and what each platform runs."
url: "https://support.outpostplatform.com/automation/scripting/"
product: "depot"
type: "concept"
status: "draft"
ai_summary: "The Outpost script library holds PowerShell, cmd, batch, sh, bash, zsh, and Python scripts, per client or shared across clients on MSP, plus a built-in starter pack. Approving a script signs it; endpoints check the signature before unattended runs. Drafts run only on one endpoint with script:run_unapproved. Every version is kept."
availability:
  plans: ["home-lab", "essentials", "enterprise", "msp"]
  platforms: ["windows", "linux", "macos", "freebsd", "openbsd"]
  permission: "script:read"
  preview: false
source: "https://gitlab.com/outpostplatform/docs/-/edit/main/src/content/docs/automation/scripting.md"
license: "CC BY 4.0"
---

# Scripting

The script library is where your team keeps the scripts it runs on endpoints.
A script is written once, approved, and then run on any number of endpoints,
on demand or on a schedule, with every run recorded. Approval signs the script,
and the signature is what an endpoint checks before it runs anything
unattended. This page explains the parts of the library and the rules that
decide whether a script runs.

To add a script, see [Write and approve a script](/automation/write-and-approve-scripts/).
To run one, see [Run scripts on endpoints](/automation/run-scripts/).

## The library

**Scripts** > **Library** lists every script you can run. Each row shows which
library the script belongs to:

| Chip | Meaning |
| --- | --- |
| **Built-in** | Shipped with Outpost. Read-only, and offered to every client. See [Built-in script library](/automation/script-library/). |
| **This client only** | Stored in this client's own data. No other client can read it. |
| **Every client** | Stored once in your shared library and offered to every client you manage. MSP plan only. |

On the MSP plan, a new script written inside a client's workspace asks **Who
can use this script**: **This client only** or **Every client**. You can move a
script between the two later with **Move...**. A move keeps the script's id,
version numbers, and history, so schedules and past runs that name it keep
working. A move needs both `script:manage` and `script:approve`, because it
changes which clients can see and run the script.

On Home Lab, Essentials, and Enterprise there is one client, so every script
you write belongs to it, and the shared library holds only the built-in
scripts.

A client's own script can have the same name as a shared one. The chip is how
you tell them apart.

## Interpreters

| Interpreter | Runs on | Allowed by default |
| --- | --- | --- |
| PowerShell | Windows | Yes |
| cmd | Windows | Yes |
| Batch | Windows | No |
| sh | Linux, macOS, FreeBSD, OpenBSD | Yes |
| bash | Linux, macOS, FreeBSD, OpenBSD, where installed | Yes |
| zsh | Linux, macOS, FreeBSD, OpenBSD, where installed | No |
| Python | Linux, macOS, FreeBSD, OpenBSD, where installed | No |

The profile setting `script_execution.allowed_interpreters` decides which
interpreters an endpoint accepts. Its default is PowerShell, cmd, sh, and bash.
To run batch, zsh, or Python scripts, add them to the setting on a profile that
applies to the endpoints. An interpreter is allowed only where every applicable
profile allows it.

Each script also declares where it **Runs on**: **Any**, **Windows**,
**Linux**, **macOS**, or **BSD**. Outpost refuses to run a script on an
endpoint whose operating system does not match it or cannot run its
interpreter, and the console marks those endpoints with the reason.

## Parameters

A script can declare parameters. Each one has a **Key**, a **Label**, a
**Kind** (**Text**, **Number**, **Checkbox**, or **Choice**), a **Default**, and
whether it is **Required**. The console asks for the values before each run.

Parameter values are passed to the script as command-line arguments, in the
order the parameters are declared. The script body is never rewritten, because
its exact bytes are what the approval signature covers. A PowerShell script
reads them with a `param()` block, and a shell script reads them as `$1`, `$2`,
and so on.

A parameter value can contain these placeholders, which Outpost replaces before
the run:

| Placeholder | Replaced with |
| --- | --- |
| `{{endpoint.hostname}}` | The endpoint's hostname |
| `{{endpoint.agent_id}}` | The endpoint's agent id |
| `{{endpoint.logged_in_user}}` | The user signed in to the endpoint |
| `{{client.name}}` | The client's name |
| `{{site.name}}` | The endpoint's site |
| `{{field.<key>}}` | The endpoint's value for the [custom field](/automation/custom-fields/) with that key |
| `{{login.user}}` | In a schedule that runs at sign-in only: the person who signed in |

A placeholder with nothing to resolve becomes an empty string.

:::caution
Parameter values are stored in plain text with each run's command, where anyone
who can read the endpoint's command history can see them. Do not pass
passwords, keys, or other secrets as parameters.
:::

## Versions

Every script has a version number, starting at 1. Changing the body or the
interpreter creates a new version. Changing the name, description, category,
tags, or other details does not. **History** on a script lists every version
with when it was written and why, such as **Written**, **Edited**, or **Copied
from another script**. Every version is kept, so you can view any one, copy it,
or select **Start a new draft from this version**.

Each run records the version it ran, so a run from last month names the text
that actually ran.

## Approval and signing {#approval-and-signing}

A new script, and every new version of one, is a **Draft**. Approving it, with
**Approve**, signs it with your Outpost's script-signing key. The signature
covers the interpreter and the exact body, so an approved PowerShell script
cannot be run as a shell script, and a changed body is not covered.

- Approving needs `script:approve`.
- Changing the body or the interpreter of an approved script returns it to
  draft and clears its signature. Schedules that use it stop firing until it is
  approved again.
- **Revoke** returns an approved script to draft. Endpoints that require signed
  scripts then refuse it.
- Built-in scripts arrive approved.

Before an endpoint runs a script unattended, it checks the signature against
your Outpost's public key. The signature is the one check an endpoint can still
make while it is offline and running a schedule from its own copy.

### Running a draft

A draft can run only on one endpoint at a time, by a technician with the
`script:run_unapproved` permission. This is how an author tests a script before
approving it. A draft cannot run in a batch across several endpoints, on a site
or device group, or on a schedule.

### Second-person approval {#second-person-approval}

The profile setting `script_execution.approval_required` makes endpoints run a
script only once someone other than its author has approved it. It is off by
default. Where it applies:

- A library script runs only if someone other than the author of its current
  version approved that version. An author who approves their own version
  does not count. Editing the script needs a new approval.
- `script:run_unapproved` does not get around it. A draft cannot run on these
  endpoints.
- A script typed at run time, such as on an endpoint's **Commands** tab, needs
  its own approval of its exact text and interpreter, by someone other than
  the person who asked. That approval lasts 7 days.
- Scheduled runs of a script without such an approval are skipped on these
  endpoints, and the run records the reason.
- A backup pre or post script without such an approval fails the backup run.
  See [Pre and post scripts](/backup/policy-settings/#scripts).

A run that needs approval is refused with **This script needs a second
person's approval before it can run on these endpoints.**, naming the profile
that set it.

Asking for approval needs `script:manage`. Approving and turning down need
`script:approve`.

- On a script's page, the **Approval** card shows who approved it and when, or
  that it is waiting or was turned down. It offers **Ask for approval**,
  **Approve**, and **Turn down**. On a library row, **Actions** has **Ask for
  approval** and **Turn down**.
- When a typed script is refused for this reason, the run form offers **Ask
  for approval**. Approvers see the request under **Typed scripts waiting for
  approval** above the library, where they select **Approve** or **Turn
  down**. After it is approved, run the script again.

The person who asked cannot approve or turn down their own request. The
console says **You asked for this approval, so the decision is somebody
else's.** An author can still approve their own version, which signs it for
batches and schedules, but that approval does not count where
`script_execution.approval_required` applies. The **Approval** card says so.

Two exceptions let you approve your own work so that it counts. Both need a
fresh second factor, an authenticator app or a passkey, confirmed in the
console. Without one set up, the console says **Approving your own script
needs a second factor. Set up an authenticator app or a passkey on your
account, then try again.** Neither is available to AI clients over MCP.

If a security framework that a profile in the workspace declares requires a
second person to approve scripts, both exceptions first show a warning that
starts **A compliance framework this workspace targets** and names the
framework. Select **Approve anyway**, or submit the emergency approval with
the warning shown, to go ahead. The approval is recorded with the warning.

#### When you are the only script editor {#sole-editor}

In a workspace where you are the only account with `script:manage`, there is
nobody to ask. There, **Approve** on your own version or your own request
approves it as the sole editor, after your second factor, and you can turn
down your own request. The card then reads **Approved by its author as the only
person who can edit scripts here, with a second factor.**

- It applies to a client's own library only. It is not available on the MSP
  plan or for a script in a library every client shares.
- The approval counts only while you remain the only editor. When anyone else
  is given `script:manage` there, it stops counting, and endpoints that need a
  second person's approval stop running the script until someone else
  approves it.

#### Emergency approval {#emergency-approval}

When you must approve your own version or request and nobody else can approve
it in time, select **Emergency approval** on the **Approval** card. You need
`script:approve`. The only script editor in a workspace uses **Approve**
instead, as above.

1. In **Reason**, say why it cannot wait. The reason is required, up to 1,000
   characters.
2. Optionally, in **Ticket**, enter the ticket or incident it is for, up to 64
   characters.
3. Select **Approve in an emergency**, then confirm your second factor.

The approval counts like a second person's, even where others could have
approved. It is never quiet:

- Outpost raises a warning alert titled **Emergency script approval:**
  followed by the script's name, and emails it to every owner and every person
  with `script:approve` except you. Both carry your reason and ticket. For a
  script in the shared library, the alert and emails go to your practice's
  workspace.
- The audit log records it as `script.approve_emergency`, with the reason and
  ticket.
- The **Approval** card shows an **Emergency approval** badge with who approved
  it, the reason, and the ticket.

A later approval by a second person replaces the emergency one and clears the
badge. Editing the script clears it, like any approval.

### Endpoints that require signed scripts

The profile setting `script_execution.require_signed_scripts` makes an endpoint
refuse every unsigned script, including a draft run on one endpoint and a
script typed into an endpoint's **Commands** tab. It is off by default. The
compliance floors for SOC 2, PCI-DSS, CJIS, NIST 800-53, NIST 800-171, CMMC
Level 2, and ISO 27001 turn it on for any scope that declares one of those
frameworks. When you run a
script on one endpoint, the run form shows whether that endpoint runs signed
scripts only, and which profile or floor decided it.

An endpoint that is connected but has not yet received its scripting policy
also refuses unsigned scripts. It treats "not told yet" as "signed scripts
only".

## What else policy controls

These profile settings apply to every script an endpoint runs. See
[Script execution](/automation/policy-settings/#script-execution) in the
settings reference.

| Setting | Default | Effect |
| --- | --- | --- |
| `script_execution.enabled` | `true` | Off stops the endpoint running any script. |
| `script_execution.max_runtime_seconds` | `3600` | Longest any script may run before it is stopped. |
| `script_execution.max_concurrent_scripts` | `2` | How many scripts may run at once on one endpoint. |
| `script_execution.blocked_commands` | Empty | Command names a script may not invoke. |
| `script_execution.run_as_context` | `system` | The most privileged account a script may run as. |
| `script_execution.allowed_script_repository` | `any` | Where a script must come from: `any`, `library_only` (no scripts typed at run time), or `client_library_only` (this client's own library, not the shared one). The most restrictive profile wins. A run that breaks it is refused before it is sent, scheduled runs are skipped, and the endpoint refuses it as well. |
| `script_execution.approval_required` | `false` | Scripts need a second person's approval. See [Second-person approval](#second-person-approval). |
| `script_execution.log_script_output` | `true` | Off keeps a run's exit code and status but not its output. The run shows **Output not kept by policy.** This applies to one-off and scheduled runs. |

### Delivery and enforcement

The agent receives and enforces its script settings. Changes to profiles,
assignments, site membership, and server-role tags reach connected endpoints
right away. When `script_execution.enabled` is off, the agent refuses every
script run: one-off, scheduled, library, and AI or MCP requests.

In **Profiles** > **Effective config**, script settings show **Delivered by**
**Agent (script environment)**. Agent 0.3.0 or later reports **Applied**,
**Pending**, or **Not reported**. See
[Check delivery](/automation/effective-configuration/#script-delivery).

Interactive terminals have a separate
[Remote shell setting](/automation/policy-settings/#remote-shell). Turning off
scripts does not turn off interactive terminal access.

## Platform differences

| Capability | Windows | Linux | macOS (Preview) | FreeBSD and OpenBSD |
| --- | --- | --- | --- | --- |
| Run as **SYSTEM** (root) | Yes | Yes | Yes | Yes |
| Run as **Signed-in user** | Yes | Yes, when the agent runs as root and someone is signed in at the console | Yes | No |
| Run at sign-in | Yes, when the agent runs as a service | Yes, with systemd-logind | Yes | No |
| Scheduled runs while offline | Yes | Yes | Yes | Yes |

bash, zsh, and Python run only where they are installed. An endpoint reports
which ones it has, and the console leaves out the missing ones.

## Permissions

| Permission | Allows |
| --- | --- |
| `script:read` | Viewing the library, runs, schedules, and custom field definitions |
| `script:manage` | Creating, editing, duplicating, and deleting scripts, and defining custom fields |
| `script:approve` | Approving and revoking scripts |
| `script:run_unapproved` | Running a draft on one endpoint, except where `script_execution.approval_required` applies |
| `script:schedule` | Creating, editing, and deleting schedules |
| `command:execute` | Running a script and canceling a run |

## Related

- [Write and approve a script](/automation/write-and-approve-scripts/)
- [Run scripts on endpoints](/automation/run-scripts/)
- [Built-in script library](/automation/script-library/)
- [Endpoint custom fields](/automation/custom-fields/)
