Skip to main content

Policy Development

Policy Basicsโ€‹

This is a basic policy using an issue rule to block any HIGH/CRITICAL issues.

# METADATA
# title: Limit risk
# description: |
# Block issues based on risk level.
package policy.v1

import data.phylum.level
import rego.v1

# METADATA
# title: risk level cannot exceed medium
deny contains issue if {
some issue in data.issues
issue.severity > level.MEDIUM
}

The title and description from the initial metadata comments are displayed in the Phylum UI and are highly recommended.

The package policy.v1 line must be present. This is how Open Policy Agent (OPA) finds the policy's rules.

The deny rule will contain the specified issue when the if statement is true. OPA iterates through the job input data evaluating the expression against the severity level of every issue in the job.

The title field from the metadata comment above the rule will be associated with the failure in the output from Phylum.

Creating a local policy development environmentโ€‹

It is recommended to set up a local development environment for a better policy development experience. With a local development environment, you gain benefits such as faster feedback, more diagnostic abilities, version control, and automated testing.

Download the OPA CLIโ€‹

Follow the instructions at www.openpolicyagent.org to download a copy of the OPA command line tool and run opa version to ensure it is working.

Download the policy SDKโ€‹

Download the policy SDK from the Phylum API endpoint and extract it.

Download input dataโ€‹

job="YOUR JOB ID"
token=$(phylum auth token -b)
curl -H "Authorization: Bearer ${token}" "https://api.phylum.io/api/v0/data/jobs/${job}/policy/input" -fo input.json

Note: You can obtain a Job ID by using the phylum history command from the Phylum CLI.

Evaluating policies locallyโ€‹

A policy can be evaluated using opa eval --data <YOUR POLICY>.rego --data constants.json --input input.json --schema schema --format pretty data.policy.v1.

InputDescriptionProvider
<YOUR POLICY>.regopolicy you want to testUser
constants.jsonconstants that can be used in your custom policyPhylum
input.jsoninput data to evaluate, generally from a Phylum job responseUser
schemalocation of the schema filesPhylum
data.phylum.jobentry pointStatic value

If everything is working, you will receive JSON output from opa that looks like this:

{
"deny": []
}

This is what the output looks like when the job is allowed by the policy. When the policy blocks something, there will be additional data describing the failure.

Automated testingโ€‹

Open Policy Agent has documentation on policy testing. Writing an automated test for your Phylum policy looks something like this:

# example_test.rego
# This is a test for the default.rego which blocks high/critical severity issues.

package policy.v1

import data.phylum.level
import rego.v1

test_block_high if {
issue := {
"id": "abc",
"tag": "tag",
"severity": level.HIGH
}

# Evaluate policy with mock input
check := deny with data.issues as [issue]

# Assert that the set contains the expected issue
check == {issue}
}

test_allow_medium if {
issue := {
"id": "abc",
"tag": "tag",
"severity": level.MEDIUM
}

# Evaluate policy with mock input
check := deny with data.issues as [issue]

# Assert that the set is empty
check == set()
}

This test requires constants.json from the Phylum SDK. The test can be executed against the Phylum default.rego policy using opa test constants.json default.rego example_test.rego.

Evaluating policies using the Phylum APIโ€‹

Using the evaluate_policy API, it's possible to evaluate policies within Phylum. This is the same API used by Phylum tooling.

To evaluate an existing job using example.rego you can make an API call like this:

curl -H "Authorization: Bearer $(phylum auth token -b)" -H "Content-Type: text/plain" --data-binary @example.rego https://api.phylum.io/api/v0/data/jobs/YOUR_JOB_ID/policy/evaluate

If the endpoint is called with no body, the project's saved policy will be used.

If policy evaluation is successful, the result will contain both the policy output as well as a generated report in Markdown format.

Issues that have been suppressed via project preferences are visible to the policy, but the related rejections will not be included in the Markdown report.

Dependencies that are ignored via the ignored_packages parameter are filtered out before applying the policy and will not be visible in the policy input or output.

Policy Examplesโ€‹

The policy transforms your threat model into a description of why the job is being blocked. There are multiple ways to define why a job should be blocked.

The METADATA block contains OPA Annotations which correlate to the schema and can be used for type checking.

Blocking an issueโ€‹

The most common reason to block a job is because one of the dependencies has a known issue within one of Phylum's risk domains.

The following policy shows ways to block using an issue rule based on a per-domain threshold.

package policy.v1

import data.phylum.domain
import data.phylum.level
import rego.v1

# METADATA
# title: risk level cannot exceed medium
deny contains issue if {
some issue in data.issues
issue.domain in {domain.AUTHOR, domain.ENGINEERING, domain.VULNERABILITY}
issue.severity > level.MEDIUM
}

# METADATA
# title: malicious risk level cannot exceed low
deny contains issue if {
some issue in data.issues
issue.domain == domain.MALICIOUS
issue.severity > level.LOW
}

# METADATA
# title: license risk level cannot exceed high
deny contains issue if {
some issue in data.issues
issue.domain == domain.LICENSE
issue.severity > level.HIGH
}

Given the following input:

{
"issues": [{
"id": "b8ad4443-d875-427b-9eda-b4b2fb1d6212",
"domain": "malicious",
"severity": 4,
"tag": "CM0004"
}]
}

When the policy fails, the output will look something like this:

{
"deny": [{
"id": "b8ad4443-d875-427b-9eda-b4b2fb1d6212",
"domain": "malicious",
"severity": 4,
"tag": "CM0004"
}]
}

When Phylum sees this output from the policy, it will block the job and generate a report naming the package and describing the issue.

Blocking a dependencyโ€‹

You may also block on a dependency-level characteristic using a dependency rule.

The following policy blocks packages belonging to a namespace. Note: This is just an example, there is already a policy for blocking copyleft licenses.

package policy.v1

import rego.v1

# METADATA
# title: AGPL licensed software is not allowed.
deny contains dependency if {
some dependency in data.dependencies
regex.match("(?i)\\bAGPL\\b", dependency.license)
}

Given the following input:

{
"dependencies": [{
"ecosystem": "npm",
"id": "4cc36d79-b8ce-5b7d-89c1-6f6a31f59819",
"issues": [],
"issues_complete": true,
"license": "AGPL-3.0",
"name": "example-package",
"version": "1.0.0"
}]
}

When the policy fails, the output will look something like this:

{
"deny": [{
"ecosystem": "npm",
"id": "4cc36d79-b8ce-5b7d-89c1-6f6a31f59819",
"issues": [],
"issues_complete": true,
"license": "AGPL-3.0",
"name": "example-package",
"version": "1.0.0"
}]
}

When Phylum sees this output from the policy, it will block the job and generate a report naming the package and providing this message in the output.