---
title: "Risk Engine - Workshop Docs"
description: "Risk Engine - Enterprise control plane for Santa. Manage rules, approvals, telemetry, and policies across your macOS fleet."
doc_version: "1"
last_updated: "2026-05-22"
canonical: "https://northpole.security/docs/workshop/risk-engine"
---
# Risk Engine

The Risk Engine empowers security teams to create policies that automatically identify when applications exceed your organization’s risk tolerance. You can configure these policies to flag applications for various reasons, such as known malware detection or organization restrictions on virtualization software.

The system uses a flexible plugin architechture, allowing multiple plugins to participate in the process of deciding whether a given application is above or below the line of risk.

When the Risk Engine is enabled, every time Santa uploads an event to Workshop the binary / application inside the events are passed to it. The Risk Engine then generates an authorization request for each of its plugins in parallel, setting a deadline. If all plugins return an `ALLOW` decision within the deadline then the event is considered safe. If any plugins returns `DENY` or `DENY_MALWARE` then the event is considered dangerous. If any plugins return errors or respond after the deadline they are treated as if the plugin returned a `DENY` decision.

## Configuration

### UI

The Risk Engine can be configured in the Settings page.

### API Methods

The Risk Engine can be configured using the [UpdateRiskEngineSettings](https://buf.build/northpolesec/workshop-api/docs/main:workshop.v1#workshop.v1.WorkshopService.UpdateRiskEngineSettings) method.

## Internal vs. Remote Plugins

Risk Engine plugins come in two flavors - Internal, which are included as part of Workshop and Remote, which are extensions that can be written by customers or North Pole Security.

## Secrets URL

Some options in Risk Engine plugins can be configured to use AWS and GCP secret stores.

### AWS

1.  Give the Workshop service account read access to the secret The Workshop service account is displayed at the top of the Settings page
    
2.  Pass the ARN to the secret prefixed with `aws://` e.g. `aws://arn:aws:secretsmanager:us-east-1:940000000003:secret:Secret-YYLN9X`
    

### GCP

1.  Give the Workshop service account read access to the secret The Workshop service account is displayed at the top of the Settings page
    
2.  Specify the path to the secret as `gcp://projects/<projectID>/secrets/<secretID>/versions/latest`
    

## Included Plugins

Workshop’s internal plugins include:

### VirusTotal

The VirusTotal plugin will check the SHA-256 of the binary against VirusTotal using the file report API.

The VirusTotal plugin will cache results per user defined parameters. This ensures that results are timely and saves expensive API calls.

#### Options

-   *API Key* - Your VirusTotal Key; this is either the raw string or a secrets URL
-   *Cache Time* - How long in seconds the cache entries should be kept alive for in seconds
-   *Cache Entries* - How many entries to cache
-   *Excluded Engines* - A list of engines to exclude results from

### Reversing Labs

The Reversing Labs plugin will check the SHA-256 hash of a binary against ReversingLabs Spectra file reputation API. If the API deems it malicious it returns a `DENY_MALWARE` response.

The ReversingLabs plugin will cache results per user defined parameters. This ensures that results are timely and saves expensive API calls.

#### Options

-   *Username* - your reversing labs username
-   *Password* - your reversing labs username or a secret URL
-   *Cache Entries* - How many entries to cache
-   *Cache Time* - How long in seconds the cache entries should be kept alive for
-   *Cache Entries* - How many entries should be cached (up to 50,000)

### Blockable Rules

The Blockable Rules plugin allows you to write rules using the [Common Expression Language](https://cel.dev/) to match properties of a *blockable*. A blockable is a collection of attributes from the binary that Santa or Workshop policy can be matched on. All matchable attributes will be populated into the `blockable` object.

If the CEL expression returns true then this plugin will return a `DENY` decision, otherwise it will return an `ALLOW`. You may also use [CEL macros](https://github.com/google/cel-go?tab=readme-ov-file#macros) for working with nested structures such as entitlements.

This is an extremely powerful feature that allows you to flag entire classes of software. Furthermore it allows you to tailor an extremely granular policy.

For example to have the risk engine return a `DENY` decision for any virtualization software you can use the following rule:

```
has(blockable.entitlements) &&
blockable.entitlements.exists(e, e.key == "com.apple.security.hypervisor") ||
blockable.entitlements.exists(e, e.key == "com.apple.security.virtualization")
```

You can use all of the attributes of a [blockable](https://buf.build/northpolesec/workshop-api/docs/main:workshop.v1#workshop.v1.BinaryBlockable) in rules.

This includes:

-   `sha256` - The SHA-256 of the binary
-   `cdhash` - The CDHash of the binary
-   `signing_id` - The signing ID, prefixed with either the team ID or platform
-   `team_id` - The 10-digit alphanumeric Team ID of the binary that uniquely identifies the publisher
-   `signed_by` - The certificate chain
-   `entitlements` - The array of entitlements provided

## Remote Risk Engine Plugins

In addition to the included plugins the Risk Engine can be extended via

### Writing Your Own Remote Risk Engine Plugins

To write your own remote risk engine plugin you need to simply create a server that takes an HTTP POST with JSON consisting of the `PluginAuthzRequest` and that returns an `RemoteRiskEnginePluginServiceAuthorizeRequest` serialized to JSON.

The interaction is essentially as follows:

```
sequenceDiagram
Workshop ->> Plugin: Makes an RemoteRiskEnginePluginServiceAuthorizeRequest for a binary
Plugin -->> Workshop: Returns a RemoteRiskEnginePluginServiceAuthorizeResponse containing a policy decision
```

#### Handling Requests

The first step is to make a web service that can receive and unmarshal a `PluginAuthzRequest`.

```
// A RemoteRiskEnginePluginServiceAuthorizeRequest is a request made by Workshop to
// a plugin to authorize a  binary / blockable.
message RemoteRiskEnginePluginServiceAuthorizeRequest {
  string tx_id = 1;               // The transaction ID of the request.
  BinaryBlockable blockable = 2;  // The binary to authorize with all blockable attributes.
  google.protobuf.Timestamp timestamp = 3; // The timestamp of the request.
  google.protobuf.Timestamp deadline = 4; // The deadline for the plugin to return a decision before it is automatically considered a denial.
}
```

After unmarshaling the `RemoteRiskEnginePluginServiceAuthorizeRequest` you can find all of the details about the binary in the `blockable` field. This contains a subset of the attributes Santa has recorded at the time of execution, including signing information.

Each request has a transaction ID (`tx_id`) field and all responses are expected to have the same value in their transaction ID field.

Each `RemoteRiskEnginePluginServiceAuthorizeRequest` also contains a `deadline` that the plugin must respond with a `PluginAuthzResponse` before to be considered. Failure to respond within the deadline will be treated as a if the plugin had responded with a deny decision.

Once the data from the request has been processed a `RemoteRiskEnginePluginServiceAuthorizeResponse` must be send back to Workshop with a decision and and explanation for the decision.

The structure of the `RemoteRiskEnginePluginServiceAuthorizeResponse` is as follows:

```
// This message is used by a remote risk engine plugin to represent the decision
// for a blockable.  All errors and timeouts are treated as denials.
message RemoteRiskEnginePluginServiceAuthorizeResponse {
  string tx_id = 1; // The transaction ID of the request this response is for.
  Decision decision = 2; // The decision for the blockable.
  Explanation explanation = 3; // An explanation for the decision.
  string error = 4; // An error message containing any errors the plugin encountered.
  string plugin_uuid = 5; // The UUID of the plugin that made the decision.
  google.protobuf.Timestamp good_until = 6; // The time the decision is considered valid until for caching.
}
```

Decisions can be one of the following:

Decision

Meaning

UNKNOWN

This is a programming error and should not be used

DENY

The plugin has determined the binary should be blocked by policy.

DENY\_MALWARE

The plugin has determined the binary is malware and should be blocked

ALLOW

The plugin believes this binary is safe.

TIMEOUT

The plugin or something it depends on has timed out

ERROR

The plugin has encountered an error

All decisions except for allow are considered a denial.

See [the Decision proto for more details](https://buf.build/northpolesec/workshop-api/docs/main:workshop.v1#workshop.v1.Decision)

Additionally plugin authors are expected to provide an explanation for the decision and optionally a URL for getting more information. Workshop presents this information to to users and also helps with debugging.

See [the Explanation proto for more details](https://buf.build/northpolesec/workshop-api/docs/main:workshop.v1#workshop.v1.Explanation)

## Exceptions

Risk Engine plugin decisions can be overridden using Exceptions. These are created through the UI or API and grant users in a targeted tag an exception to specific Risk Engine plugin decisions. All exceptions include an expiration date after which they no longer apply.

For example, if the Risk Engine’s Blockable Rules plugin has a rule banning VPN software e.g.

```
has(blockable.entitlements) &&
blockable.entitlements.exists(e, e.key == "com.apple.developer.networking.networkextension" && e.value.contains("packet-tunnel-provider-systemextension"))
```

But you wanted to let members of the tag `vpn-access` approve their own VPN software then you could accomplish this by granting the exception to tag for the specific Blockable Rule.

### Expiry

Exceptions all have an expiration date built into them after which point they will not longer be considered.

This can be updated via both the UI and API.

### Configuration

#### UI

Exceptions can be configured via the UI under the Exceptions tab on the Risk Engine Settings portion of the main Settings page.

#### API

Exceptions can also be mangaged via the [CreateException](https://buf.build/northpolesec/workshop-api/docs/main:workshop.v1#workshop.v1.WorkshopService.CreateException),[UpdateException](https://buf.build/northpolesec/workshop-api/docs/main:workshop.v1#workshop.v1.WorkshopService.UpdateException), [ListExceptions](https://buf.build/northpolesec/workshop-api/docs/main:workshop.v1#workshop.v1.WorkshopService.ListExceptions), and [DeleteException](https://buf.build/northpolesec/workshop-api/docs/main:workshop.v1#workshop.v1.WorkshopService.DeleteException) methods in API

## Sitemap

- [Home](https://northpole.security/index.md)
- [Workshop](https://northpole.security/workshop.md)
- [Santa](https://northpole.security/santa.md)
- [Features](https://northpole.security/features.md)
- [Cookbook](https://northpole.security/cookbook.md)
- [Docs](https://northpole.security/docs.md)
- [Blog](https://northpole.security/blog.md)
- [Glossary](https://northpole.security/glossary.md)
- [About](https://northpole.security/about.md)
- [Contact](https://northpole.security/contact.md)
