From 5b65c26a58461e1d92896eab58e50cc40ac81ae1 Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Wed, 18 Feb 2026 21:49:22 +0000
Subject: [PATCH 01/35] refactor(epm): introduce baseline policies.
---
README.md | 647 +++---------------
.../huggingface-recipes}/README.md | 0
.../huggingface-recipes}/model_card.rego | 0
.../huggingface-recipes}/risky_files.rego | 0
.../huggingface-recipes}/security_scan.rego | 0
.../trusted_publishers.rego | 0
baseline/cooldown-restore.rego | 27 +
baseline/cooldown.rego | 23 +
baseline/exact-allowlist-exemption.rego | 29 +
baseline/exact-blocklist.rego | 29 +
baseline/high-risk-vulnerability.rego | 134 ++++
baseline/license-compliance.rego | 118 ++++
baseline/malware-block.rego | 18 +
{recipe-1 => legacy/recipe-1}/policy.rego | 0
{recipe-10 => legacy/recipe-10}/policy.rego | 0
{recipe-11 => legacy/recipe-11}/policy.rego | 0
{recipe-12 => legacy/recipe-12}/policy.rego | 0
{recipe-13 => legacy/recipe-13}/policy.rego | 0
{recipe-14 => legacy/recipe-14}/policy.rego | 0
{recipe-15 => legacy/recipe-15}/policy.rego | 0
{recipe-16 => legacy/recipe-16}/policy.rego | 0
{recipe-17 => legacy/recipe-17}/policy.rego | 0
{recipe-18 => legacy/recipe-18}/policy.rego | 0
{recipe-19 => legacy/recipe-19}/policy.rego | 0
{recipe-2 => legacy/recipe-2}/policy.rego | 0
{recipe-20 => legacy/recipe-20}/policy.rego | 0
{recipe-21 => legacy/recipe-21}/policy.rego | 0
{recipe-3 => legacy/recipe-3}/policy.rego | 0
{recipe-4 => legacy/recipe-4}/policy.rego | 0
{recipe-5 => legacy/recipe-5}/policy.rego | 0
{recipe-6 => legacy/recipe-6}/policy.rego | 0
{recipe-7 => legacy/recipe-7}/policy.rego | 0
{recipe-8 => legacy/recipe-8}/policy.rego | 0
{recipe-9 => legacy/recipe-9}/policy.rego | 0
34 files changed, 489 insertions(+), 536 deletions(-)
rename {huggingface-recipes => advanced/huggingface-recipes}/README.md (100%)
rename {huggingface-recipes => advanced/huggingface-recipes}/model_card.rego (100%)
rename {huggingface-recipes => advanced/huggingface-recipes}/risky_files.rego (100%)
rename {huggingface-recipes => advanced/huggingface-recipes}/security_scan.rego (100%)
rename {huggingface-recipes => advanced/huggingface-recipes}/trusted_publishers.rego (100%)
create mode 100644 baseline/cooldown-restore.rego
create mode 100644 baseline/cooldown.rego
create mode 100644 baseline/exact-allowlist-exemption.rego
create mode 100644 baseline/exact-blocklist.rego
create mode 100644 baseline/high-risk-vulnerability.rego
create mode 100644 baseline/license-compliance.rego
create mode 100644 baseline/malware-block.rego
rename {recipe-1 => legacy/recipe-1}/policy.rego (100%)
rename {recipe-10 => legacy/recipe-10}/policy.rego (100%)
rename {recipe-11 => legacy/recipe-11}/policy.rego (100%)
rename {recipe-12 => legacy/recipe-12}/policy.rego (100%)
rename {recipe-13 => legacy/recipe-13}/policy.rego (100%)
rename {recipe-14 => legacy/recipe-14}/policy.rego (100%)
rename {recipe-15 => legacy/recipe-15}/policy.rego (100%)
rename {recipe-16 => legacy/recipe-16}/policy.rego (100%)
rename {recipe-17 => legacy/recipe-17}/policy.rego (100%)
rename {recipe-18 => legacy/recipe-18}/policy.rego (100%)
rename {recipe-19 => legacy/recipe-19}/policy.rego (100%)
rename {recipe-2 => legacy/recipe-2}/policy.rego (100%)
rename {recipe-20 => legacy/recipe-20}/policy.rego (100%)
rename {recipe-21 => legacy/recipe-21}/policy.rego (100%)
rename {recipe-3 => legacy/recipe-3}/policy.rego (100%)
rename {recipe-4 => legacy/recipe-4}/policy.rego (100%)
rename {recipe-5 => legacy/recipe-5}/policy.rego (100%)
rename {recipe-6 => legacy/recipe-6}/policy.rego (100%)
rename {recipe-7 => legacy/recipe-7}/policy.rego (100%)
rename {recipe-8 => legacy/recipe-8}/policy.rego (100%)
rename {recipe-9 => legacy/recipe-9}/policy.rego (100%)
diff --git a/README.md b/README.md
index b28f95f..e6a2819 100644
--- a/README.md
+++ b/README.md
@@ -1,537 +1,112 @@
# Cloudsmith EPM Recipes
-A curated collection of Enterprise Policy Management (EPM) recipes for Cloudsmith - combining practical OPA Rego policy samples, action configurations, and testing guides to help you define, simulate, and enforce package lifecycle rules across your repositories.
-
-This repository includes:
-- Rego policies tailored for Cloudsmith’s EPM engine, using the supported input schema and policy format.
-- Policy action configurations (```SetPackageState```, ```AddPackageTags```, etc.) with examples showing how to order and associate them by precedence.
-- Simulation and deployment instructions using Cloudsmith’s API, including how to safely test policies before enforcement.
-- Helper scripts and workflows to automate policy testing and promotion.
-
-These recipes are designed to be modular, auditable, and production-ready - with a strong emphasis on policy-as-code best practices.
-
-***
-
-### Table of Rego Samples
-
-| Name | Description |
-| --- | --- |
-| [Enforcing Signed Packages](https://github.com/cloudsmith-io/rego-recipes?tab=readme-ov-file#recipe-1---enforcing-signed-packages) | This policy enforces mandatory ```GPG/DSA signature``` checks on packages during their sync/import into Cloudsmith |
-| [Restriction Based on Tags](https://github.com/cloudsmith-io/rego-recipes?tab=readme-ov-file#recipe-2---restricting-package-based-on-tags) | This policy checks whether a package includes specific ```deprecated``` tag and marks it as match if present |
-| [Copy-Left licensing](https://github.com/cloudsmith-io/rego-recipes?tab=readme-ov-file#recipe-3---copyleft-or-restrictive-oss-licenses) | This policy is designed to detect a broad range of copyleft licenses, including free-text and SPDX variants |
-| [Quarantine Debug Builds](https://github.com/cloudsmith-io/rego-recipes?tab=readme-ov-file#recipe-4---restricting-package-based-on-tags) | Identify and quarantine packages that look like debug/test artifacts based on filename or metadata patterns |
-| [Limit Tag Sprawl](https://github.com/cloudsmith-io/rego-recipes?tab=readme-ov-file#recipe-5---limit-tag-sprawl) | Flag any packages that have more than a threshold number of tags to avoid ungoverned tagging behaviours |
-| [Enforce Consistent Filename](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-6---enforce-consistent-filename-convention) | Validate whether the filename convention matches a semantic or naming pattern via Regular Expressions |
-| [Approved Upstreams based on Tags](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-7---approved-upstreams-based-on-tags) | Only packages from explicitly approved upstream sources are permitted, helping to prevent the propagation of unvetted or insecure dependencies. |
-| [CVSS Policy with Fix Available](https://github.com/cloudsmith-io/rego-recipes?tab=readme-ov-file#recipe-8---cvss-with-fix-available) | Match packages in a specific repo that have high/critical fixed vulnerabilities, excluding specific known CVEs. |
-| [Time-Based CVSS Policy](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-9---time-based-cvss-policy) | Evaluate CVEs older than 30 days. Checks CVSS threshold ≥ 7. Filters for a specific repo. Ignores certain CVE |
-| CVSS with EPSS context | Combines High scoring CVSS vulnerability with EPSS scoring context that go above a specific threshold. |
-| [Architecture allow list](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-11---architecture-specific-allow-list) | Policy that only allows ```amd64``` architecture packages and blocks others like arm64. |
-| [Block package if version over 0.16.0](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-12---block-package-xyz-if-version--0160) | ```semver.compare(pkg.version, "0.16.0") == -1``` should check if the version is less than ```0.16.0``` using semver-aware comparison. |
-| [Block specific CVE numbers](https://github.com/cloudsmith-io/rego-recipes?tab=readme-ov-file#recipe-15---block-specifically-based-on-cves) | Blocks a specific package based on known CVE numbers |
-| [Enforce Upload Time Window](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-16---suspicious-package-upload-window) | Allow uploads during business hours (9 AM – 5 PM UTC), to catch anomalous behaviour like late-night uploads |
-| [Tag-based bypass Exception](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-17---tag-based-exception-policy) | This is a simple tag-based exception. |
-| [Exact allowlist with CVSS limit exemption](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-18---exact-allowlist-exception-policy-with-cvss-ceiling) | Use when you want tight control per version, but still prevent exemptions if a CVSS exceeds a ceiling. |
-| [Malware advisory](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-19---malware-advisory) | Match for malware advisory. |
-| [npm last published date](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-20---npm-last-published-date) | Use when you want to tag or stop devs from using the latest npm package. |
-| [Exact blocklist by format/name/version](https://github.com/cloudsmith-io/rego-recipes/tree/main?tab=readme-ov-file#recipe-21---exact-blocklist) | Blocks packages that appear on a known-bad exact list across formats (e.g. npm/python) before your upstream removes them. |
-| [Huggingface Recipes](https://github.com/cloudsmith-io/rego-recipes/blob/main/huggingface-recipes/README.md/) | Policies relating to Hugging Face models/datasets. |
-
-***
-
-### Recipe 1 - Enforcing Signed Packages
-This policy enforces mandatory GPG/DSA signature checks on packages during their sync/import into Cloudsmith
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-1/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Enforcing Signed Packages",
- "description": "This policy enforces mandatory GPG/DSA signature checks on packages during their sync/import into Cloudsmith.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 1
-}
-EOF
-```
-
-Once the policy is created, here is how you can perform a controlled test.
-Create a simple dummy python package locally that we know for certain is unsigned.
-Your policy will trigger, since ```input.v0.package.signed``` will not be present or will default to ```false```.
-
-```
-mkdir dummy_unsigned
-cd dummy_unsigned
-echo "from setuptools import setup; setup(name='dummy_unsigned', version='0.0.1')" > setup.py
-python3 -m pip install --upgrade build
-apt install python3.10-venv
-python3 -m build # produces dist/dummy_unsigned-0.0.1.tar.gz
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO dist/dummy_unsigned-0.0.1.tar.gz -k "$CLOUDSMITH_API_KEY"
-```
-
-***
-
-### Recipe 2 - Restricting Package Based on Tags
-This policy checks whether a package includes a specific ```deprecated``` tag and marks it as a match if it does.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-2/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Restricting Package Based on Tags",
- "description": "This policy checks whether a package includes a specific DEPRECATED tag.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 2
-}
-EOF
-```
-
-Once ready, download a random package, in this case a python package called ```h11``` and push the package to Cloudsmith with the ```deprecated``` tag to cause the policy violation:
-```
-pip download h11==0.14.0
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO h11-0.14.0-py3-none-any.whl -k "$CLOUDSMITH_API_KEY" --tags deprecated
-```
-
-***
-
-### Recipe 3 - Copyleft or restrictive OSS licenses
-This policy is designed to flag packages that use ```copyleft``` or ```restrictive``` open-source licenses, particularly those unsuitable for production use without legal review or approval.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-3/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Copyleft licensing policy",
- "description": "This policy checks packages that are unsuitable for production.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 3
-}
-EOF
-```
-
-Once ready, download the Python ```Gitlab v.3.1.1``` package that we know has a ```LGPLv3 license``` that should trigger the policy:
-```
-pip download python-gitlab==3.1.1
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO python_gitlab-3.1.1-py3-none-any.whl -k "$CLOUDSMITH_API_KEY" --tags lgplv3license
-cloudsmith list packages acme-corporation/acme-repo-one -k "$CLOUDSMITH_API_KEY" -q "format:python AND tag:lgplv3license"
-```
-
-Note: If you have a tagging response action attached to your policy, you could tag the package with ```non-compliant-license``` for further review:
-
-
-
-
-
-
-***
-
-### Recipe 4 - Quarantine Debug Builds
-This policy checks whether a package includes specific ```debug```, ```test```, or ```temp``` descriptors in the filename.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-4/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Quarantine Debug Builds",
- "description": "Identify and quarantine packages that look like debug/test artifacts based on filename or metadata patterns.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 4
-}
-EOF
-```
-
-Once ready, download ```debugpy``` Python package and push it to Cloudsmith to cause the policy violation:
-```
-pip download --no-deps --dest . debugpy
-&& cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO debugpy-1.8.14-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl -k "$CLOUDSMITH_API_KEY"
-```
-
-***
-
-### Recipe 5 - Limit Tag Sprawl
-This policy checks whether a package already includes ```5``` or more assigned tags - conidered as sprawl by some orgs.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-5/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Limit Tag Sprawl",
- "description": "Flag packages that have more than a threshold number of tags to avoid ungoverned tagging behaviours.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 5
-}
-EOF
-```
-
-Once ready, download ```transformers``` Python and assign it ```5 tags``` during the Cloudsmith push process to cause the policy violation:
-```
-pip download transformers --no-deps
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO transformers-4.53.1-py3-none-any.whl -k "$CLOUDSMITH_API_KEY" --tags TAG1,TAG2,TAG3,TAG4,TAG5
-```
-
-***
-
-### Recipe 6 - Enforce Consistent Filename Convention
-Validate filename matches a semantic or naming pattern where ```MAJOR```.```MINOR```, and ```PATCH``` are all numeric.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-6/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Enforce Consistent Filename Convention",
- "description": "Validate filename matches a semantic or naming pattern.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 6
-}
-EOF
-```
-
-A straightforward way to test this policy is to take a package that already has a valid SemVer-compliant filename and rename it by replacing the version number with a placeholder like ```test```:
-
-```
-pip download h11==0.14.0
-mv h11-0.14.0-py3-none-any.whl h11-test.whl
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO h11-test.whl -k "$CLOUDSMITH_API_KEY"
-```
-
-***
-
-### Recipe 7 - Approved Upstreams based on Tags
-Simply put, this approves packages based on the specified upstream source.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-7/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Approved Upstreams based on Tags",
- "description": "Only packages from explicitly approved upstream sources are permitted, helping to prevent the propagation of unvetted or insecure dependencies.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 7
-}
-EOF
-```
-
-If a package has ```upstream``` and not ```approved``` --> allowed
-
-If a package has ```approved``` --> blocked (even if upstream is present)
-
-
-***
-
-### Recipe 8 - CVSS with Fix Available
-This policy is designed to match packages in a specific repository (```acme-repo-one```) that have ```high``` or ```critical``` with a ```Fixed version available```, excluding specific ```known CVEs```.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-8/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "CVSS with Fix Available",
- "description": "Matched packages from a specific repository that have high or critical vulnerabilities that can be patched.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 8
-}
-EOF
-```
-
-To demonstrate this policy, you can use the ```requests``` Python package, which has a known vulnerability with a high CVSS score.
-
-Vulnerability Details:
-
-- Package: h11
-- Affected Version: 0.14.0
-- Fixed In: 0.16.0
-- CVE Identifier: [CVE-2025-43859](https://access.redhat.com/security/cve/cve-2025-43859)
-- CVSS Context: This CVE record has been marked for NVD enrichment efforts.
-- Description: An HTTP request smuggling vulnerability in python-h11.
-
-```
-pip download h11==0.14.0
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO h11-0.14.0-py3-none-any.whl -k "$CLOUDSMITH_API_KEY"
-```
-
-You'll probably want to enable a [Quarantine](https://help.cloudsmith.io/docs/package-quarantine) action for policies dealing with critical vulnerabilities that can be fixed:
-
-
-
-
-***
-
-### Recipe 9 - Time-based CVSS Policy
-This policy is designed to detect and flag packages in a specific repo that contain serious, outdated, but fixed vulnerabilities.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-9/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Time-based CVSS policy",
- "description": "Only matches if the vulnerability was published more than 30 days ago.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 9
-}
-EOF
-```
-
-This CVE was published on 24 April 2025 - much older than the 30 day threshold specified in the policy.
-
-```
-pip download h11==0.14.0
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO h11-0.14.0-py3-none-any.whl -k "$CLOUDSMITH_API_KEY"
-```
-
-***
-
-
-### Recipe 11 - Architecture-specific allow list
-This policy only allows ```amd64``` architecture packages and blocks others like ```arm64```.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-11/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Architecture-specific allow list",
- "description": "Policy that only allows amd64 architecture packages and blocks others like arm64",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 11
-}
-EOF
-```
-
-To trigger the above policy (which blocks all architectures except ```amd64```), you'd want to upload or sync a Python package that is built for a ```non-amd64``` architecture - like ```arm64```.
-
-However, pip by default fetches packages built for your local system architecture, so you typically won't download architecture-specific wheels unless they're explicitly tagged.
-
-Here's a command to download a known Python package with an ARM-specific wheel using pip download:
-```
-pip download numpy --platform manylinux2014_aarch64 --only-binary=:all: --python-version 38 --implementation cp --abi cp38
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -k "$CLOUDSMITH_API_KEY"
-```
-
-- ```--platform manylinux2014_aarch64```: targets ```arm64``` (```aarch64```)
-- ```--only-binary=:all:```: avoid source dist, force binary wheel
-- ```--python-version 38``` and ```--abi cp38```: simulate Python 3.8 CPython ABI
-
-
-
-
-***
-
-
-### Recipe 12 - Block Package XYZ if version < 0.16.0
-This policy matches any ```h11``` packages with a version older than ```0.16.0```:
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-12/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Forced Upgrade of Package Versions",
- "description": "Forces teams to periodically upgrade versions of certain package dependencies, so they don't fall too far behind.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 12
-}
-EOF
-```
-
-***
-
-### Recipe 15 - Block specifically based on CVEs
-Again, the Python package ```requests``` version ```2.6.0``` has the known vulnerability ```CVE-2018-18074```:
-We have commented out the other CVEs in this policy, but feel free remove those comments and add additional CVEs as a list.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-15/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-
-cat < payload.json
-{
- "name": "Block specific CVE numbers",
- "description": "This policy is only blocking CVE-2018-18074 specifically",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 15
-}
-EOF
-```
-
-```
-pip download requests==2.6.0
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO requests-2.6.0-py2.py3-none-any.whl -k "$CLOUDSMITH_API_KEY"
-```
-
-
-
-
-***
-
-
-### Recipe 16 - Suspicious Package Upload Window
-Assuming your organisation don't expect packages to be uploaded or modified at specific times, the below policy can detect package uploads at ```9am-11am UTC``` - regardless of the package name.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-16/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-
-cat < payload.json
-{
- "name": "Package Upload Window",
- "description": "Flag any package uploaded outside of working hours",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 16
-}
-EOF
-```
-
-```
-pip download
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO .whl -k "$CLOUDSMITH_API_KEY"
-```
-
-***
-
-
-### Recipe 17 - Tag-Based Exception Policy
-Use this policy when you need a quick, time-boxed exception. For example, policy can quarantine by severity; where a separate terminal exemption policy matches if a package has an exempt tag and stops further evaluation.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-17/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-
-cat < payload.json
-{
- "name": "Tag based exception",
- "description": "Exception policy based on basic tagging",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 17
-}
-EOF
-```
-
-**Trade-off:** While the tag remains, new CVEs on that package won’t trigger quarantine. You'll need to review those tags regularly.
-
-***
-
-### Recipe 18 - Exact allowlist exception policy with CVSS ceiling
-Use when you want tight control per version, but still prevent exemptions if a CVSS exceeds a ceiling.
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-18/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-
-cat < payload.json
-{
- "name": "Specific allowlist exception",
- "description": "Controls based on exact version but still prevent exemptions if a CVSS exceeds a ceiling.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": false,
- "precedence": 18
-}
-EOF
-```
-
-**Trade-off:** While the tag remains, new CVEs on that package won’t trigger quarantine. You'll need to review those tags regularly.
-
-***
-
-### Recipe 19 - Malware advisory
-Use when you want to match based on malware advisory
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-19/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-
-cat < payload.json
-{
- "name": "Malware",
- "description": "Control malware ingestion.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": true,
- "precedence": 1
-}
-EOF
-```
-***
-
-### Recipe 20 - npm last published date
-Use when you want to match based on the npm last published date on npm upstream
-Download the ```policy.rego``` and create the associated ```payload.json``` with the below command:
-```
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-20/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-
-cat < payload.json
-{
- "name": "npm last published date on npm upstream",
- "description": "Match if the publish date comes after the date of the set number of days ago.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": true,
- "precedence": 1
-}
-EOF
-```
-***
-
-### Recipe 21 - Exact blocklist
-
-This policy lets you maintain an **exact deny list** of suspicious or malicious packages across formats using the key pattern:
-
-`::` (for example: `npm:@alloc/quick-lru:5.2.0`, `python:requests:2.6.0`).
-
-It’s especially useful for incident response scenarios like Shai-Hulud-style attacks, where you receive a CSV or text list of impacted packages and need to block them immediately — even before upstream registries have removed them.
-
-Download the `policy.rego` and create the associated `payload.json` with:
-
-```bash
-wget https://raw.githubusercontent.com/cloudsmith-io/rego-recipes/refs/heads/main/recipe-21/policy.rego
-escaped_policy=$(jq -Rs . < policy.rego)
-cat < payload.json
-{
- "name": "Exact blocklist by format/name/version",
- "description": "Blocks packages that appear on an external suspicious or malicious list, using exact format:name:version matches.",
- "rego": $escaped_policy,
- "enabled": true,
- "is_terminal": true,
- "precedence": 1
-}
-EOF
-
-### Hugging Face recipes
-
-For policies relating to Hugging Face models and datasets, see [Hugging Face Recipes](https://github.com/cloudsmith-io/rego-recipes/blob/main/huggingface-recipes/README.md/).
+
+This repository contains curated, production-ready Open Policy Agent (OPA) policies for use with Cloudsmith Enterprise Policy Management (EPM).
+
+The goal of this repository is to define a clear, recommended secure baseline for Cloudsmith workspaces, along with a smaller set of advanced governance patterns.
+
+---
+
+## Design Principles
+
+All policies in this repository:
+
+- Are WASM-compatible
+- Use only supported Cloudsmith EPM builtins
+- Avoid deprecated syntax (e.g. `import rego.v1`)
+- Follow OPA style guidelines
+- Are structured for composability using precedence
+- Are safe for production use
+
+These policies are intended to be readable, predictable, and suitable for enterprise environments.
+
+---
+
+## Repository Structure
+baseline/
+advanced/
+legacy/
+
+### baseline/
+
+Recommended secure defaults for production environments.
+
+These policies address common supply chain security requirements such as:
+
+- Malware blocking
+- High-risk vulnerability control (CVSS / EPSS)
+- License compliance
+- Controlled cooldown workflows
+- Explicit allowlist and blocklist handling
+
+If you are deploying EPM in a new workspace, start here.
+
+---
+
+### advanced/
+
+Optional or format-specific policies that provide deeper governance controls.
+
+These may include:
+
+- Base image origin enforcement
+- SBOM-based controls
+- Model governance policies
+- Specialised workflow patterns
+
+Advanced policies are production-ready but not universally required.
+
+---
+
+### legacy/
+
+Historical recipes and experimental policies retained for reference.
+
+Policies in this directory:
+
+- May use older patterns
+- May not reflect current schema or best practices
+- Are not recommended for new deployments
+
+They are preserved for documentation history and migration reference.
+
+---
+
+## Policy Ordering & Precedence
+
+Cloudsmith EPM evaluates policies in precedence order (lowest precedence runs first).
+
+All policies in this repository are designed to be non-terminal and composable.
+
+A recommended precedence pattern for baseline deployments is:
+
+1. Cooldown Restore (make eligible packages available again)
+2. Cooldown (time-based quarantine)
+3. License policy (tagging or governance)
+4. High-risk vulnerability policy (quarantine based on thresholds)
+5. Exact allowlist exemption (explicit override)
+6. Malware block (final quarantine safeguard)
+
+All matched policy actions are applied within a single transaction.
+The package state visible to users reflects the final committed result.
+
+For full EPM documentation, see:
+https://docs.cloudsmith.com/supply-chain-security/epm
+
+---
+
+## Deployment
+
+Policies can be deployed using the Cloudsmith API or CLI.
+
+Refer to the official documentation for EPM policy management and simulation:
+
+https://docs.cloudsmith.com/supply-chain-security/epm
+
+---
+
+This repository is the single source of truth for:
+
+- Policy templates
+- Documentation examples
+- Secure baseline recommendations
+- Enterprise EPM enablement guidance
diff --git a/huggingface-recipes/README.md b/advanced/huggingface-recipes/README.md
similarity index 100%
rename from huggingface-recipes/README.md
rename to advanced/huggingface-recipes/README.md
diff --git a/huggingface-recipes/model_card.rego b/advanced/huggingface-recipes/model_card.rego
similarity index 100%
rename from huggingface-recipes/model_card.rego
rename to advanced/huggingface-recipes/model_card.rego
diff --git a/huggingface-recipes/risky_files.rego b/advanced/huggingface-recipes/risky_files.rego
similarity index 100%
rename from huggingface-recipes/risky_files.rego
rename to advanced/huggingface-recipes/risky_files.rego
diff --git a/huggingface-recipes/security_scan.rego b/advanced/huggingface-recipes/security_scan.rego
similarity index 100%
rename from huggingface-recipes/security_scan.rego
rename to advanced/huggingface-recipes/security_scan.rego
diff --git a/huggingface-recipes/trusted_publishers.rego b/advanced/huggingface-recipes/trusted_publishers.rego
similarity index 100%
rename from huggingface-recipes/trusted_publishers.rego
rename to advanced/huggingface-recipes/trusted_publishers.rego
diff --git a/baseline/cooldown-restore.rego b/baseline/cooldown-restore.rego
new file mode 100644
index 0000000..da6ba82
--- /dev/null
+++ b/baseline/cooldown-restore.rego
@@ -0,0 +1,27 @@
+package cloudsmith
+
+default match := false
+
+pkg := input.v0.package
+
+within_past_days := 4
+required_tag := "cooldown"
+
+match if count(reason) > 0
+
+reason[msg] if {
+ pkg.upstream_metadata.published_at != null
+
+ some t in pkg.tags
+ t.name == required_tag
+
+ publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
+ cutoff := time.add_date(time.now_ns(), 0, 0, 0 - within_past_days)
+
+ publish_date < cutoff
+
+ msg := sprintf(
+ "Package older than %v days and tagged '%s' — restoring availability",
+ [within_past_days, required_tag],
+ )
+}
diff --git a/baseline/cooldown.rego b/baseline/cooldown.rego
new file mode 100644
index 0000000..563bd96
--- /dev/null
+++ b/baseline/cooldown.rego
@@ -0,0 +1,23 @@
+package cloudsmith
+
+default match := false
+
+pkg := input.v0.package
+
+within_past_days := 3
+
+match if count(reason) > 0
+
+reason[msg] if {
+ pkg.upstream_metadata.published_at != null
+
+ publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
+ cutoff := time.add_date(time.now_ns(), 0, 0, 0 - within_past_days)
+
+ publish_date >= cutoff
+
+ msg := sprintf(
+ "Package published within last %v days — applying cooldown",
+ [within_past_days],
+ )
+}
diff --git a/baseline/exact-allowlist-exemption.rego b/baseline/exact-allowlist-exemption.rego
new file mode 100644
index 0000000..5a8ba87
--- /dev/null
+++ b/baseline/exact-allowlist-exemption.rego
@@ -0,0 +1,29 @@
+package cloudsmith
+
+default match := false
+
+#
+# Explicit exemption allowlist
+# Format: "::"
+#
+
+allowlist := {
+ "python:example-lib:1.2.3",
+ "npm:example-ui:4.5.6",
+}
+
+pkg := input.v0.package
+
+pkg_key := sprintf("%s:%s:%s", [pkg.format, pkg.name, pkg.version])
+
+match if {
+ pkg_key in allowlist
+}
+
+reason[msg] if {
+ match
+ msg := sprintf(
+ "Explicit exemption approved: %s",
+ [pkg_key],
+ )
+}
diff --git a/baseline/exact-blocklist.rego b/baseline/exact-blocklist.rego
new file mode 100644
index 0000000..ab1574b
--- /dev/null
+++ b/baseline/exact-blocklist.rego
@@ -0,0 +1,29 @@
+package cloudsmith
+
+default match := false
+
+#
+# Explicit blocklist
+# Format: "::"
+#
+
+blocklist := {
+ "python:malicious-lib:0.1.0",
+ "npm:compromised-ui:9.9.9",
+}
+
+pkg := input.v0.package
+
+pkg_key := sprintf("%s:%s:%s", [pkg.format, pkg.name, pkg.version])
+
+match if {
+ pkg_key in blocklist
+}
+
+reason[msg] if {
+ match
+ msg := sprintf(
+ "Blocked by explicit deny list: %s",
+ [pkg_key],
+ )
+}
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
new file mode 100644
index 0000000..2d5cc5f
--- /dev/null
+++ b/baseline/high-risk-vulnerability.rego
@@ -0,0 +1,134 @@
+package cloudsmith
+
+# -----------------------------------------------------------------------------
+# This policy blocks vulnerabilities based on both severity and exploitability:
+#
+# • CVSS ≥ 9.0 → always block (critical severity)
+# • CVSS ≥ 7.0 AND EPSS ≥ 0.1 → block if likely to be exploited
+#
+# EPSS represents the probability of real-world exploitation, helping focus on
+# vulnerabilities that attackers are actively targeting.
+#
+# Only vulnerabilities with a fix available are blocked.
+#
+# -----------------------------------------------------------------------------
+#
+
+default match := false
+
+#
+# POLICY THRESHOLDS
+#
+# Critical severity: always block
+critical_cvss := 9.0
+
+# High severity: block if also likely exploited
+high_cvss := 7.0
+
+# EPSS exploit probability threshold
+# 0.1 = 10% probability of exploitation in wild
+min_epss := 0.1
+
+#
+# OPTIONAL: explicitly ignored CVEs
+#
+ignored_cves := {
+ # Example:
+ # "CVE-2023-12345"
+}
+
+#
+# MAIN MATCH CONDITION
+#
+match if {
+ some vuln in input.v0.vulnerabilities
+
+ not ignored(vuln)
+ has_fix(vuln)
+
+ should_block(vuln)
+}
+
+#
+# BLOCKING LOGIC
+#
+should_block(vuln) if {
+ critical_severity(vuln)
+}
+
+should_block(vuln) if {
+ exploitable_high_severity(vuln)
+}
+
+#
+# CRITICAL: CVSS >= 9 (always block)
+#
+critical_severity(vuln) if {
+ some _, score in vuln.cvss
+ score.V3Score >= critical_cvss
+}
+
+#
+# HIGH + EXPLOITED: CVSS >= 7 AND EPSS >= threshold
+#
+exploitable_high_severity(vuln) if {
+ some _, score in vuln.cvss
+
+ score.V3Score >= high_cvss
+
+ vuln.epss.score != null
+ to_number(vuln.epss.score) >= min_epss
+}
+
+#
+# ONLY BLOCK IF FIX EXISTS
+#
+has_fix(vuln) if {
+ vuln.patched_versions
+ count(vuln.patched_versions) > 0
+}
+
+#
+# IGNORE SPECIFIC CVES
+#
+ignored(vuln) if {
+ vuln.identifier in ignored_cves
+}
+
+#
+# OPTIONAL: HUMAN-READABLE REASON
+#
+reason[msg] if {
+ some vuln in input.v0.vulnerabilities
+
+ not ignored(vuln)
+ has_fix(vuln)
+
+ critical_severity(vuln)
+
+ msg := sprintf(
+ "Blocking %v: Critical vulnerability (CVSS %.1f)",
+ [
+ vuln.identifier,
+ vuln.cvss[_].V3Score
+ ]
+ )
+}
+
+reason[msg] if {
+ some vuln in input.v0.vulnerabilities
+
+ not ignored(vuln)
+ has_fix(vuln)
+
+ exploitable_high_severity(vuln)
+
+ msg := sprintf(
+ "Blocking %v: High severity (CVSS %.1f) and actively exploited (EPSS %.3f)",
+ [
+ vuln.identifier,
+ vuln.cvss[_].V3Score,
+ to_number(vuln.epss.score)
+ ]
+ )
+}
\ No newline at end of file
diff --git a/baseline/license-compliance.rego b/baseline/license-compliance.rego
new file mode 100644
index 0000000..7903e7c
--- /dev/null
+++ b/baseline/license-compliance.rego
@@ -0,0 +1,118 @@
+package cloudsmith
+
+default match := false
+
+# GNU General Public License (GPL) variants
+gpl_licenses := {
+ "GPL-1.0-only",
+ "GPL-1.0-or-later",
+ "GPL-2.0",
+ "GPL-2.0-only",
+ "GPL-2.0-or-later",
+ "GPL-3.0",
+ "GPL-3.0-only",
+ "GPL-3.0-or-later",
+}
+
+# GNU Lesser General Public License (LGPL) variants
+lgpl_licenses := {
+ "LGPL-2.0",
+ "LGPL-2.0-only",
+ "LGPL-2.0-or-later",
+ "LGPL-2.1",
+ "LGPL-2.1-only",
+ "LGPL-2.1-or-later",
+ "LGPL-3.0",
+ "LGPL-3.0-only",
+ "LGPL-3.0-or-later",
+}
+
+# GNU Affero General Public License (AGPL) variants
+agpl_licenses := {
+ "AGPL-1.0",
+ "AGPL-1.0-only",
+ "AGPL-1.0-or-later",
+ "AGPL-3.0",
+ "AGPL-3.0-only",
+ "AGPL-3.0-or-later",
+}
+
+# Mozilla Public License (MPL) variants
+mpl_licenses := {
+ "MPL-1.0",
+ "MPL-1.1",
+ "MPL-2.0",
+}
+
+# Common Development and Distribution License (CDDL) variants
+cddl_licenses := {
+ "CDDL-1.0",
+ "CDDL-1.1",
+}
+
+# Eclipse Public License (EPL) variants
+epl_licenses := {
+ "EPL-1.0",
+ "EPL-2.0",
+}
+
+# Open Software License (OSL) variants
+osl_licenses := {
+ "OSL-1.0",
+ "OSL-2.0",
+ "OSL-3.0",
+}
+
+# GNU Free Documentation License (GFDL) variants
+gfdl_licenses := {
+ "GFDL-1.1-only",
+ "GFDL-1.1-or-later",
+ "GFDL-1.2-only",
+ "GFDL-1.2-or-later",
+ "GFDL-1.3-only",
+ "GFDL-1.3-or-later",
+}
+
+# Creative Commons Share Alike (CC-BY-SA) variants
+cc_by_sa_licenses := {
+ "CC-BY-SA-1.0",
+ "CC-BY-SA-2.0",
+ "CC-BY-SA-2.5",
+ "CC-BY-SA-3.0",
+ "CC-BY-SA-4.0",
+}
+
+# Other copyleft licenses
+other_copyleft_licenses := {
+ "QPL-1.0",
+ "Sleepycat",
+ "SSPL-1.0",
+ "copyleft-next-0.3.0",
+}
+
+# Combined copyleft license set
+copyleft := gpl_licenses
+ | lgpl_licenses
+ | agpl_licenses
+ | mpl_licenses
+ | cddl_licenses
+ | epl_licenses
+ | osl_licenses
+ | gfdl_licenses
+ | cc_by_sa_licenses
+ | other_copyleft_licenses
+
+# Main policy rule
+match if {
+ input.v0.package.license.oss_license.spdx_identifier in copyleft
+}
+
+# Explanation for decision logs / error text
+reason contains msg if {
+ match
+ lic := input.v0.package.license.oss_license.spdx_identifier
+ msg := sprintf(
+ "Copyleft license detected (%s). Package blocked/quarantined per license policy.",
+ [lic],
+ )
+}
\ No newline at end of file
diff --git a/baseline/malware-block.rego b/baseline/malware-block.rego
new file mode 100644
index 0000000..c20e7ca
--- /dev/null
+++ b/baseline/malware-block.rego
@@ -0,0 +1,18 @@
+package cloudsmith
+
+default match := false
+
+match if count(malicious_packages) > 0
+
+malicious_packages := [vulnerability.id |
+ some vulnerability in input.v0.osv
+ startswith(vulnerability.id, "MAL-")
+]
+
+reason contains msg if {
+ match
+ msg := sprintf(
+ "Detected %d malicious vulnerability ID(s): %v",
+ [count(malicious_packages), malicious_packages],
+ )
+}
\ No newline at end of file
diff --git a/recipe-1/policy.rego b/legacy/recipe-1/policy.rego
similarity index 100%
rename from recipe-1/policy.rego
rename to legacy/recipe-1/policy.rego
diff --git a/recipe-10/policy.rego b/legacy/recipe-10/policy.rego
similarity index 100%
rename from recipe-10/policy.rego
rename to legacy/recipe-10/policy.rego
diff --git a/recipe-11/policy.rego b/legacy/recipe-11/policy.rego
similarity index 100%
rename from recipe-11/policy.rego
rename to legacy/recipe-11/policy.rego
diff --git a/recipe-12/policy.rego b/legacy/recipe-12/policy.rego
similarity index 100%
rename from recipe-12/policy.rego
rename to legacy/recipe-12/policy.rego
diff --git a/recipe-13/policy.rego b/legacy/recipe-13/policy.rego
similarity index 100%
rename from recipe-13/policy.rego
rename to legacy/recipe-13/policy.rego
diff --git a/recipe-14/policy.rego b/legacy/recipe-14/policy.rego
similarity index 100%
rename from recipe-14/policy.rego
rename to legacy/recipe-14/policy.rego
diff --git a/recipe-15/policy.rego b/legacy/recipe-15/policy.rego
similarity index 100%
rename from recipe-15/policy.rego
rename to legacy/recipe-15/policy.rego
diff --git a/recipe-16/policy.rego b/legacy/recipe-16/policy.rego
similarity index 100%
rename from recipe-16/policy.rego
rename to legacy/recipe-16/policy.rego
diff --git a/recipe-17/policy.rego b/legacy/recipe-17/policy.rego
similarity index 100%
rename from recipe-17/policy.rego
rename to legacy/recipe-17/policy.rego
diff --git a/recipe-18/policy.rego b/legacy/recipe-18/policy.rego
similarity index 100%
rename from recipe-18/policy.rego
rename to legacy/recipe-18/policy.rego
diff --git a/recipe-19/policy.rego b/legacy/recipe-19/policy.rego
similarity index 100%
rename from recipe-19/policy.rego
rename to legacy/recipe-19/policy.rego
diff --git a/recipe-2/policy.rego b/legacy/recipe-2/policy.rego
similarity index 100%
rename from recipe-2/policy.rego
rename to legacy/recipe-2/policy.rego
diff --git a/recipe-20/policy.rego b/legacy/recipe-20/policy.rego
similarity index 100%
rename from recipe-20/policy.rego
rename to legacy/recipe-20/policy.rego
diff --git a/recipe-21/policy.rego b/legacy/recipe-21/policy.rego
similarity index 100%
rename from recipe-21/policy.rego
rename to legacy/recipe-21/policy.rego
diff --git a/recipe-3/policy.rego b/legacy/recipe-3/policy.rego
similarity index 100%
rename from recipe-3/policy.rego
rename to legacy/recipe-3/policy.rego
diff --git a/recipe-4/policy.rego b/legacy/recipe-4/policy.rego
similarity index 100%
rename from recipe-4/policy.rego
rename to legacy/recipe-4/policy.rego
diff --git a/recipe-5/policy.rego b/legacy/recipe-5/policy.rego
similarity index 100%
rename from recipe-5/policy.rego
rename to legacy/recipe-5/policy.rego
diff --git a/recipe-6/policy.rego b/legacy/recipe-6/policy.rego
similarity index 100%
rename from recipe-6/policy.rego
rename to legacy/recipe-6/policy.rego
diff --git a/recipe-7/policy.rego b/legacy/recipe-7/policy.rego
similarity index 100%
rename from recipe-7/policy.rego
rename to legacy/recipe-7/policy.rego
diff --git a/recipe-8/policy.rego b/legacy/recipe-8/policy.rego
similarity index 100%
rename from recipe-8/policy.rego
rename to legacy/recipe-8/policy.rego
diff --git a/recipe-9/policy.rego b/legacy/recipe-9/policy.rego
similarity index 100%
rename from recipe-9/policy.rego
rename to legacy/recipe-9/policy.rego
From b82b6e3d194eca0970365c4186ff19381a9ace3a Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Thu, 19 Feb 2026 09:47:36 +0000
Subject: [PATCH 02/35] Add OPA test
---
.github/opa-lint.yml | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
create mode 100644 .github/opa-lint.yml
diff --git a/.github/opa-lint.yml b/.github/opa-lint.yml
new file mode 100644
index 0000000..c8ee442
--- /dev/null
+++ b/.github/opa-lint.yml
@@ -0,0 +1,35 @@
+name: OPA Lint & Validate
+
+on:
+ pull_request:
+ push:
+ branches: [main]
+
+jobs:
+ opa:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install OPA
+ run: |
+ curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static
+ chmod +x opa
+ sudo mv opa /usr/local/bin/
+
+ - name: Show OPA Version
+ run: opa version
+
+ - name: Format Check
+ run: |
+ [ -d baseline ] && opa fmt --fail baseline || echo "No baseline folder"
+ [ -d advanced ] && opa fmt --fail advanced || echo "No advanced folder"
+
+ - name: Validate Policies
+ run: |
+ find baseline advanced -name "*.rego" -print0 | \
+ while IFS= read -r -d '' file; do
+ echo "Checking $file"
+ opa check "$file"
+ done
From ddfb6979f992a46cb3f2181d7de9858dea617b89 Mon Sep 17 00:00:00 2001
From: Ciara Carey
Date: Thu, 19 Feb 2026 09:51:41 +0000
Subject: [PATCH 03/35] fix formatting opa errors
---
advanced/huggingface-recipes/model_card.rego | 6 +-
advanced/huggingface-recipes/risky_files.rego | 12 +-
.../huggingface-recipes/security_scan.rego | 10 +-
.../trusted_publishers.rego | 6 +-
baseline/cooldown-restore.rego | 20 +--
baseline/cooldown.rego | 16 +--
baseline/exact-allowlist-exemption.rego | 16 +--
baseline/exact-blocklist.rego | 16 +--
baseline/high-risk-vulnerability.rego | 92 ++++++-------
baseline/license-compliance.rego | 123 ++++++++----------
baseline/malware-block.rego | 16 +--
11 files changed, 162 insertions(+), 171 deletions(-)
diff --git a/advanced/huggingface-recipes/model_card.rego b/advanced/huggingface-recipes/model_card.rego
index 3d412fb..fd34be9 100644
--- a/advanced/huggingface-recipes/model_card.rego
+++ b/advanced/huggingface-recipes/model_card.rego
@@ -9,6 +9,6 @@ pkg := input.v0.package
hf_pkg if "huggingface" == pkg.format
match if {
- hf_pkg
- "HuggingFaceTB/smollm-corpus" in pkg.card.datasets
-}
+ hf_pkg
+ "HuggingFaceTB/smollm-corpus" in pkg.card.datasets
+}
diff --git a/advanced/huggingface-recipes/risky_files.rego b/advanced/huggingface-recipes/risky_files.rego
index fce69a8..75e8dd8 100644
--- a/advanced/huggingface-recipes/risky_files.rego
+++ b/advanced/huggingface-recipes/risky_files.rego
@@ -22,11 +22,11 @@ is_upstream_pkg if input.v0.package.uploader.slug == "cloudsmith-o6v"
# SavedModel (.pb)
# GGUF (.gguf)
-risky_file_extensions := {".h5", ".hdf5", ".pdparams", ".keras", ".bin", ".pkl", ".dat", ".pt", ".pth", ".ckpt", ".npy", ".joblib", ".dill", ".pb", ".gguf", ".zip",}
+risky_file_extensions := {".h5", ".hdf5", ".pdparams", ".keras", ".bin", ".pkl", ".dat", ".pt", ".pth", ".ckpt", ".npy", ".joblib", ".dill", ".pb", ".gguf", ".zip"}
match if {
- hf_pkg
- is_upstream_pkg
- some file in pkg.files
- file.file_extension in risky_file_extensions
-}
\ No newline at end of file
+ hf_pkg
+ is_upstream_pkg
+ some file in pkg.files
+ file.file_extension in risky_file_extensions
+}
diff --git a/advanced/huggingface-recipes/security_scan.rego b/advanced/huggingface-recipes/security_scan.rego
index b239629..9a42e65 100644
--- a/advanced/huggingface-recipes/security_scan.rego
+++ b/advanced/huggingface-recipes/security_scan.rego
@@ -11,15 +11,15 @@ is_upstream_pkg if input.v0.package.uploader.slug == "cloudsmith-o6v"
# find any problematic content.
# Users an incremental rule to express OR.
incomplete_or_unsafe if {
- input.v0.model_security.availability != "COMPLETE"
+ input.v0.model_security.availability != "COMPLETE"
}
incomplete_or_unsafe if {
- input.v0.model_security.scan_summary != "SAFE"
+ input.v0.model_security.scan_summary != "SAFE"
}
match if {
- "huggingface" == input.v0.package.format
- is_upstream_pkg
- incomplete_or_unsafe
+ "huggingface" == input.v0.package.format
+ is_upstream_pkg
+ incomplete_or_unsafe
}
diff --git a/advanced/huggingface-recipes/trusted_publishers.rego b/advanced/huggingface-recipes/trusted_publishers.rego
index dc05d6c..f7a44c6 100644
--- a/advanced/huggingface-recipes/trusted_publishers.rego
+++ b/advanced/huggingface-recipes/trusted_publishers.rego
@@ -12,7 +12,7 @@ verified_publishers := {"amazon", "apple", "facebook", "FacebookAI", "google", "
publisher := split(input.v0.package.name, "/")[0]
match if {
- "huggingface" == input.v0.package.format
- is_upstream_pkg
- publisher in verified_publishers
+ "huggingface" == input.v0.package.format
+ is_upstream_pkg
+ publisher in verified_publishers
}
diff --git a/baseline/cooldown-restore.rego b/baseline/cooldown-restore.rego
index da6ba82..700bd07 100644
--- a/baseline/cooldown-restore.rego
+++ b/baseline/cooldown-restore.rego
@@ -10,18 +10,18 @@ required_tag := "cooldown"
match if count(reason) > 0
reason[msg] if {
- pkg.upstream_metadata.published_at != null
+ pkg.upstream_metadata.published_at != null
- some t in pkg.tags
- t.name == required_tag
+ some t in pkg.tags
+ t.name == required_tag
- publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
- cutoff := time.add_date(time.now_ns(), 0, 0, 0 - within_past_days)
+ publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
+ cutoff := time.add_date(time.now_ns(), 0, 0, 0 - within_past_days)
- publish_date < cutoff
+ publish_date < cutoff
- msg := sprintf(
- "Package older than %v days and tagged '%s' — restoring availability",
- [within_past_days, required_tag],
- )
+ msg := sprintf(
+ "Package older than %v days and tagged '%s' — restoring availability",
+ [within_past_days, required_tag],
+ )
}
diff --git a/baseline/cooldown.rego b/baseline/cooldown.rego
index 563bd96..218ed7b 100644
--- a/baseline/cooldown.rego
+++ b/baseline/cooldown.rego
@@ -9,15 +9,15 @@ within_past_days := 3
match if count(reason) > 0
reason[msg] if {
- pkg.upstream_metadata.published_at != null
+ pkg.upstream_metadata.published_at != null
- publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
- cutoff := time.add_date(time.now_ns(), 0, 0, 0 - within_past_days)
+ publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
+ cutoff := time.add_date(time.now_ns(), 0, 0, 0 - within_past_days)
- publish_date >= cutoff
+ publish_date >= cutoff
- msg := sprintf(
- "Package published within last %v days — applying cooldown",
- [within_past_days],
- )
+ msg := sprintf(
+ "Package published within last %v days — applying cooldown",
+ [within_past_days],
+ )
}
diff --git a/baseline/exact-allowlist-exemption.rego b/baseline/exact-allowlist-exemption.rego
index 5a8ba87..d388317 100644
--- a/baseline/exact-allowlist-exemption.rego
+++ b/baseline/exact-allowlist-exemption.rego
@@ -8,8 +8,8 @@ default match := false
#
allowlist := {
- "python:example-lib:1.2.3",
- "npm:example-ui:4.5.6",
+ "python:example-lib:1.2.3",
+ "npm:example-ui:4.5.6",
}
pkg := input.v0.package
@@ -17,13 +17,13 @@ pkg := input.v0.package
pkg_key := sprintf("%s:%s:%s", [pkg.format, pkg.name, pkg.version])
match if {
- pkg_key in allowlist
+ pkg_key in allowlist
}
reason[msg] if {
- match
- msg := sprintf(
- "Explicit exemption approved: %s",
- [pkg_key],
- )
+ match
+ msg := sprintf(
+ "Explicit exemption approved: %s",
+ [pkg_key],
+ )
}
diff --git a/baseline/exact-blocklist.rego b/baseline/exact-blocklist.rego
index ab1574b..cc19257 100644
--- a/baseline/exact-blocklist.rego
+++ b/baseline/exact-blocklist.rego
@@ -8,8 +8,8 @@ default match := false
#
blocklist := {
- "python:malicious-lib:0.1.0",
- "npm:compromised-ui:9.9.9",
+ "python:malicious-lib:0.1.0",
+ "npm:compromised-ui:9.9.9",
}
pkg := input.v0.package
@@ -17,13 +17,13 @@ pkg := input.v0.package
pkg_key := sprintf("%s:%s:%s", [pkg.format, pkg.name, pkg.version])
match if {
- pkg_key in blocklist
+ pkg_key in blocklist
}
reason[msg] if {
- match
- msg := sprintf(
- "Blocked by explicit deny list: %s",
- [pkg_key],
- )
+ match
+ msg := sprintf(
+ "Blocked by explicit deny list: %s",
+ [pkg_key],
+ )
}
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index 2d5cc5f..6c72db3 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -32,103 +32,103 @@ min_epss := 0.1
#
# OPTIONAL: explicitly ignored CVEs
#
-ignored_cves := {
- # Example:
- # "CVE-2023-12345"
-}
+ignored_cves := {}
+
+# Example:
+# "CVE-2023-12345"
#
# MAIN MATCH CONDITION
#
match if {
- some vuln in input.v0.vulnerabilities
+ some vuln in input.v0.vulnerabilities
- not ignored(vuln)
- has_fix(vuln)
+ not ignored(vuln)
+ has_fix(vuln)
- should_block(vuln)
+ should_block(vuln)
}
#
# BLOCKING LOGIC
#
should_block(vuln) if {
- critical_severity(vuln)
+ critical_severity(vuln)
}
should_block(vuln) if {
- exploitable_high_severity(vuln)
+ exploitable_high_severity(vuln)
}
#
# CRITICAL: CVSS >= 9 (always block)
#
critical_severity(vuln) if {
- some _, score in vuln.cvss
- score.V3Score >= critical_cvss
+ some _, score in vuln.cvss
+ score.V3Score >= critical_cvss
}
#
# HIGH + EXPLOITED: CVSS >= 7 AND EPSS >= threshold
#
exploitable_high_severity(vuln) if {
- some _, score in vuln.cvss
+ some _, score in vuln.cvss
- score.V3Score >= high_cvss
+ score.V3Score >= high_cvss
- vuln.epss.score != null
- to_number(vuln.epss.score) >= min_epss
+ vuln.epss.score != null
+ to_number(vuln.epss.score) >= min_epss
}
#
# ONLY BLOCK IF FIX EXISTS
#
has_fix(vuln) if {
- vuln.patched_versions
- count(vuln.patched_versions) > 0
+ vuln.patched_versions
+ count(vuln.patched_versions) > 0
}
#
# IGNORE SPECIFIC CVES
#
ignored(vuln) if {
- vuln.identifier in ignored_cves
+ vuln.identifier in ignored_cves
}
#
# OPTIONAL: HUMAN-READABLE REASON
#
reason[msg] if {
- some vuln in input.v0.vulnerabilities
+ some vuln in input.v0.vulnerabilities
- not ignored(vuln)
- has_fix(vuln)
+ not ignored(vuln)
+ has_fix(vuln)
- critical_severity(vuln)
+ critical_severity(vuln)
- msg := sprintf(
- "Blocking %v: Critical vulnerability (CVSS %.1f)",
- [
- vuln.identifier,
- vuln.cvss[_].V3Score
- ]
- )
+ msg := sprintf(
+ "Blocking %v: Critical vulnerability (CVSS %.1f)",
+ [
+ vuln.identifier,
+ vuln.cvss[_].V3Score,
+ ],
+ )
}
reason[msg] if {
- some vuln in input.v0.vulnerabilities
-
- not ignored(vuln)
- has_fix(vuln)
-
- exploitable_high_severity(vuln)
-
- msg := sprintf(
- "Blocking %v: High severity (CVSS %.1f) and actively exploited (EPSS %.3f)",
- [
- vuln.identifier,
- vuln.cvss[_].V3Score,
- to_number(vuln.epss.score)
- ]
- )
-}
\ No newline at end of file
+ some vuln in input.v0.vulnerabilities
+
+ not ignored(vuln)
+ has_fix(vuln)
+
+ exploitable_high_severity(vuln)
+
+ msg := sprintf(
+ "Blocking %v: High severity (CVSS %.1f) and actively exploited (EPSS %.3f)",
+ [
+ vuln.identifier,
+ vuln.cvss[_].V3Score,
+ to_number(vuln.epss.score),
+ ],
+ )
+}
diff --git a/baseline/license-compliance.rego b/baseline/license-compliance.rego
index 7903e7c..1355c29 100644
--- a/baseline/license-compliance.rego
+++ b/baseline/license-compliance.rego
@@ -4,115 +4,106 @@ default match := false
# GNU General Public License (GPL) variants
gpl_licenses := {
- "GPL-1.0-only",
- "GPL-1.0-or-later",
- "GPL-2.0",
- "GPL-2.0-only",
- "GPL-2.0-or-later",
- "GPL-3.0",
- "GPL-3.0-only",
- "GPL-3.0-or-later",
+ "GPL-1.0-only",
+ "GPL-1.0-or-later",
+ "GPL-2.0",
+ "GPL-2.0-only",
+ "GPL-2.0-or-later",
+ "GPL-3.0",
+ "GPL-3.0-only",
+ "GPL-3.0-or-later",
}
# GNU Lesser General Public License (LGPL) variants
lgpl_licenses := {
- "LGPL-2.0",
- "LGPL-2.0-only",
- "LGPL-2.0-or-later",
- "LGPL-2.1",
- "LGPL-2.1-only",
- "LGPL-2.1-or-later",
- "LGPL-3.0",
- "LGPL-3.0-only",
- "LGPL-3.0-or-later",
+ "LGPL-2.0",
+ "LGPL-2.0-only",
+ "LGPL-2.0-or-later",
+ "LGPL-2.1",
+ "LGPL-2.1-only",
+ "LGPL-2.1-or-later",
+ "LGPL-3.0",
+ "LGPL-3.0-only",
+ "LGPL-3.0-or-later",
}
# GNU Affero General Public License (AGPL) variants
agpl_licenses := {
- "AGPL-1.0",
- "AGPL-1.0-only",
- "AGPL-1.0-or-later",
- "AGPL-3.0",
- "AGPL-3.0-only",
- "AGPL-3.0-or-later",
+ "AGPL-1.0",
+ "AGPL-1.0-only",
+ "AGPL-1.0-or-later",
+ "AGPL-3.0",
+ "AGPL-3.0-only",
+ "AGPL-3.0-or-later",
}
# Mozilla Public License (MPL) variants
mpl_licenses := {
- "MPL-1.0",
- "MPL-1.1",
- "MPL-2.0",
+ "MPL-1.0",
+ "MPL-1.1",
+ "MPL-2.0",
}
# Common Development and Distribution License (CDDL) variants
cddl_licenses := {
- "CDDL-1.0",
- "CDDL-1.1",
+ "CDDL-1.0",
+ "CDDL-1.1",
}
# Eclipse Public License (EPL) variants
epl_licenses := {
- "EPL-1.0",
- "EPL-2.0",
+ "EPL-1.0",
+ "EPL-2.0",
}
# Open Software License (OSL) variants
osl_licenses := {
- "OSL-1.0",
- "OSL-2.0",
- "OSL-3.0",
+ "OSL-1.0",
+ "OSL-2.0",
+ "OSL-3.0",
}
# GNU Free Documentation License (GFDL) variants
gfdl_licenses := {
- "GFDL-1.1-only",
- "GFDL-1.1-or-later",
- "GFDL-1.2-only",
- "GFDL-1.2-or-later",
- "GFDL-1.3-only",
- "GFDL-1.3-or-later",
+ "GFDL-1.1-only",
+ "GFDL-1.1-or-later",
+ "GFDL-1.2-only",
+ "GFDL-1.2-or-later",
+ "GFDL-1.3-only",
+ "GFDL-1.3-or-later",
}
# Creative Commons Share Alike (CC-BY-SA) variants
cc_by_sa_licenses := {
- "CC-BY-SA-1.0",
- "CC-BY-SA-2.0",
- "CC-BY-SA-2.5",
- "CC-BY-SA-3.0",
- "CC-BY-SA-4.0",
+ "CC-BY-SA-1.0",
+ "CC-BY-SA-2.0",
+ "CC-BY-SA-2.5",
+ "CC-BY-SA-3.0",
+ "CC-BY-SA-4.0",
}
# Other copyleft licenses
other_copyleft_licenses := {
- "QPL-1.0",
- "Sleepycat",
- "SSPL-1.0",
- "copyleft-next-0.3.0",
+ "QPL-1.0",
+ "Sleepycat",
+ "SSPL-1.0",
+ "copyleft-next-0.3.0",
}
# Combined copyleft license set
-copyleft := gpl_licenses
- | lgpl_licenses
- | agpl_licenses
- | mpl_licenses
- | cddl_licenses
- | epl_licenses
- | osl_licenses
- | gfdl_licenses
- | cc_by_sa_licenses
- | other_copyleft_licenses
+copyleft := ((((((((gpl_licenses | lgpl_licenses) | agpl_licenses) | mpl_licenses) | cddl_licenses) | epl_licenses) | osl_licenses) | gfdl_licenses) | cc_by_sa_licenses) | other_copyleft_licenses
# Main policy rule
match if {
- input.v0.package.license.oss_license.spdx_identifier in copyleft
+ input.v0.package.license.oss_license.spdx_identifier in copyleft
}
# Explanation for decision logs / error text
reason contains msg if {
- match
- lic := input.v0.package.license.oss_license.spdx_identifier
- msg := sprintf(
- "Copyleft license detected (%s). Package blocked/quarantined per license policy.",
- [lic],
- )
-}
\ No newline at end of file
+ match
+ lic := input.v0.package.license.oss_license.spdx_identifier
+ msg := sprintf(
+ "Copyleft license detected (%s). Package blocked/quarantined per license policy.",
+ [lic],
+ )
+}
diff --git a/baseline/malware-block.rego b/baseline/malware-block.rego
index c20e7ca..c9bd0d8 100644
--- a/baseline/malware-block.rego
+++ b/baseline/malware-block.rego
@@ -5,14 +5,14 @@ default match := false
match if count(malicious_packages) > 0
malicious_packages := [vulnerability.id |
- some vulnerability in input.v0.osv
- startswith(vulnerability.id, "MAL-")
+ some vulnerability in input.v0.osv
+ startswith(vulnerability.id, "MAL-")
]
reason contains msg if {
- match
- msg := sprintf(
- "Detected %d malicious vulnerability ID(s): %v",
- [count(malicious_packages), malicious_packages],
- )
-}
\ No newline at end of file
+ match
+ msg := sprintf(
+ "Detected %d malicious vulnerability ID(s): %v",
+ [count(malicious_packages), malicious_packages],
+ )
+}
From 2747c245af6b749dd10c9b948aae1bf0a1eb2d2d Mon Sep 17 00:00:00 2001
From: Ciara Carey
Date: Thu, 19 Feb 2026 10:26:48 +0000
Subject: [PATCH 04/35] Improving the rego and moving test to workflows
---
.github/{ => workflows}/opa-lint.yml | 0
baseline/cooldown-restore.rego | 12 ++-
baseline/cooldown.rego | 12 ++-
baseline/exact-allowlist-exemption.rego | 10 +--
baseline/exact-blocklist.rego | 10 +--
baseline/high-risk-vulnerability.rego | 64 ++------------
baseline/license-compliance.rego | 110 ++++--------------------
baseline/malware-block.rego | 8 +-
8 files changed, 55 insertions(+), 171 deletions(-)
rename .github/{ => workflows}/opa-lint.yml (100%)
diff --git a/.github/opa-lint.yml b/.github/workflows/opa-lint.yml
similarity index 100%
rename from .github/opa-lint.yml
rename to .github/workflows/opa-lint.yml
diff --git a/baseline/cooldown-restore.rego b/baseline/cooldown-restore.rego
index 700bd07..20e3f75 100644
--- a/baseline/cooldown-restore.rego
+++ b/baseline/cooldown-restore.rego
@@ -7,19 +7,25 @@ pkg := input.v0.package
within_past_days := 4
required_tag := "cooldown"
-match if count(reason) > 0
+match if restored
-reason[msg] if {
+restored if {
+ pkg.upstream_metadata != null
pkg.upstream_metadata.published_at != null
some t in pkg.tags
t.name == required_tag
publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
- cutoff := time.add_date(time.now_ns(), 0, 0, 0 - within_past_days)
+
+ days_ago := 0 - within_past_days
+ cutoff := time.add_date(time.now_ns(), 0, 0, days_ago)
publish_date < cutoff
+}
+reason[msg] if {
+ restored
msg := sprintf(
"Package older than %v days and tagged '%s' — restoring availability",
[within_past_days, required_tag],
diff --git a/baseline/cooldown.rego b/baseline/cooldown.rego
index 218ed7b..033e376 100644
--- a/baseline/cooldown.rego
+++ b/baseline/cooldown.rego
@@ -6,16 +6,22 @@ pkg := input.v0.package
within_past_days := 3
-match if count(reason) > 0
+match if cooldown_needed
-reason[msg] if {
+cooldown_needed if {
+ pkg.upstream_metadata != null
pkg.upstream_metadata.published_at != null
publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
- cutoff := time.add_date(time.now_ns(), 0, 0, 0 - within_past_days)
+
+ days_ago := 0 - within_past_days
+ cutoff := time.add_date(time.now_ns(), 0, 0, days_ago)
publish_date >= cutoff
+}
+reason[msg] if {
+ cooldown_needed
msg := sprintf(
"Package published within last %v days — applying cooldown",
[within_past_days],
diff --git a/baseline/exact-allowlist-exemption.rego b/baseline/exact-allowlist-exemption.rego
index d388317..1932118 100644
--- a/baseline/exact-allowlist-exemption.rego
+++ b/baseline/exact-allowlist-exemption.rego
@@ -2,21 +2,19 @@ package cloudsmith
default match := false
-#
-# Explicit exemption allowlist
-# Format: "::"
-#
+pkg := input.v0.package
allowlist := {
"python:example-lib:1.2.3",
"npm:example-ui:4.5.6",
}
-pkg := input.v0.package
-
pkg_key := sprintf("%s:%s:%s", [pkg.format, pkg.name, pkg.version])
match if {
+ pkg.format != null
+ pkg.name != null
+ pkg.version != null
pkg_key in allowlist
}
diff --git a/baseline/exact-blocklist.rego b/baseline/exact-blocklist.rego
index cc19257..2ffea4d 100644
--- a/baseline/exact-blocklist.rego
+++ b/baseline/exact-blocklist.rego
@@ -2,21 +2,19 @@ package cloudsmith
default match := false
-#
-# Explicit blocklist
-# Format: "::"
-#
+pkg := input.v0.package
blocklist := {
"python:malicious-lib:0.1.0",
"npm:compromised-ui:9.9.9",
}
-pkg := input.v0.package
-
pkg_key := sprintf("%s:%s:%s", [pkg.format, pkg.name, pkg.version])
match if {
+ pkg.format != null
+ pkg.name != null
+ pkg.version != null
pkg_key in blocklist
}
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index 6c72db3..c4dabfd 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -1,57 +1,22 @@
package cloudsmith
-# -----------------------------------------------------------------------------
-# This policy blocks vulnerabilities based on both severity and exploitability:
-#
-# • CVSS ≥ 9.0 → always block (critical severity)
-# • CVSS ≥ 7.0 AND EPSS ≥ 0.1 → block if likely to be exploited
-#
-# EPSS represents the probability of real-world exploitation, helping focus on
-# vulnerabilities that attackers are actively targeting.
-#
-# Only vulnerabilities with a fix available are blocked.
-#
-# -----------------------------------------------------------------------------
-#
-
default match := false
-#
-# POLICY THRESHOLDS
-#
-# Critical severity: always block
critical_cvss := 9.0
-
-# High severity: block if also likely exploited
high_cvss := 7.0
-
-# EPSS exploit probability threshold
-# 0.1 = 10% probability of exploitation in wild
min_epss := 0.1
-#
-# OPTIONAL: explicitly ignored CVEs
-#
ignored_cves := {}
-# Example:
-# "CVE-2023-12345"
-
-#
-# MAIN MATCH CONDITION
-#
match if {
+ input.v0.vulnerabilities != null
some vuln in input.v0.vulnerabilities
not ignored(vuln)
has_fix(vuln)
-
should_block(vuln)
}
-#
-# BLOCKING LOGIC
-#
should_block(vuln) if {
critical_severity(vuln)
}
@@ -60,67 +25,48 @@ should_block(vuln) if {
exploitable_high_severity(vuln)
}
-#
-# CRITICAL: CVSS >= 9 (always block)
-#
critical_severity(vuln) if {
some _, score in vuln.cvss
score.V3Score >= critical_cvss
}
-#
-# HIGH + EXPLOITED: CVSS >= 7 AND EPSS >= threshold
-#
exploitable_high_severity(vuln) if {
some _, score in vuln.cvss
-
score.V3Score >= high_cvss
-
+ vuln.epss != null
vuln.epss.score != null
to_number(vuln.epss.score) >= min_epss
}
-#
-# ONLY BLOCK IF FIX EXISTS
-#
has_fix(vuln) if {
vuln.patched_versions
count(vuln.patched_versions) > 0
}
-#
-# IGNORE SPECIFIC CVES
-#
ignored(vuln) if {
vuln.identifier in ignored_cves
}
-#
-# OPTIONAL: HUMAN-READABLE REASON
-#
reason[msg] if {
+ input.v0.vulnerabilities != null
some vuln in input.v0.vulnerabilities
not ignored(vuln)
has_fix(vuln)
-
critical_severity(vuln)
msg := sprintf(
"Blocking %v: Critical vulnerability (CVSS %.1f)",
- [
- vuln.identifier,
- vuln.cvss[_].V3Score,
- ],
+ [vuln.identifier, vuln.cvss[_].V3Score],
)
}
reason[msg] if {
+ input.v0.vulnerabilities != null
some vuln in input.v0.vulnerabilities
not ignored(vuln)
has_fix(vuln)
-
exploitable_high_severity(vuln)
msg := sprintf(
diff --git a/baseline/license-compliance.rego b/baseline/license-compliance.rego
index 1355c29..e629aa5 100644
--- a/baseline/license-compliance.rego
+++ b/baseline/license-compliance.rego
@@ -2,104 +2,30 @@ package cloudsmith
default match := false
-# GNU General Public License (GPL) variants
-gpl_licenses := {
- "GPL-1.0-only",
- "GPL-1.0-or-later",
- "GPL-2.0",
- "GPL-2.0-only",
- "GPL-2.0-or-later",
- "GPL-3.0",
- "GPL-3.0-only",
- "GPL-3.0-or-later",
-}
-
-# GNU Lesser General Public License (LGPL) variants
-lgpl_licenses := {
- "LGPL-2.0",
- "LGPL-2.0-only",
- "LGPL-2.0-or-later",
- "LGPL-2.1",
- "LGPL-2.1-only",
- "LGPL-2.1-or-later",
- "LGPL-3.0",
- "LGPL-3.0-only",
- "LGPL-3.0-or-later",
-}
-
-# GNU Affero General Public License (AGPL) variants
-agpl_licenses := {
- "AGPL-1.0",
- "AGPL-1.0-only",
- "AGPL-1.0-or-later",
- "AGPL-3.0",
- "AGPL-3.0-only",
- "AGPL-3.0-or-later",
-}
-
-# Mozilla Public License (MPL) variants
-mpl_licenses := {
- "MPL-1.0",
- "MPL-1.1",
- "MPL-2.0",
-}
-
-# Common Development and Distribution License (CDDL) variants
-cddl_licenses := {
- "CDDL-1.0",
- "CDDL-1.1",
-}
-
-# Eclipse Public License (EPL) variants
-epl_licenses := {
- "EPL-1.0",
- "EPL-2.0",
-}
-
-# Open Software License (OSL) variants
-osl_licenses := {
- "OSL-1.0",
- "OSL-2.0",
- "OSL-3.0",
-}
-
-# GNU Free Documentation License (GFDL) variants
-gfdl_licenses := {
- "GFDL-1.1-only",
- "GFDL-1.1-or-later",
- "GFDL-1.2-only",
- "GFDL-1.2-or-later",
- "GFDL-1.3-only",
- "GFDL-1.3-or-later",
-}
-
-# Creative Commons Share Alike (CC-BY-SA) variants
-cc_by_sa_licenses := {
- "CC-BY-SA-1.0",
- "CC-BY-SA-2.0",
- "CC-BY-SA-2.5",
- "CC-BY-SA-3.0",
- "CC-BY-SA-4.0",
-}
-
-# Other copyleft licenses
-other_copyleft_licenses := {
- "QPL-1.0",
- "Sleepycat",
+copyleft := {
+ "GPL-1.0-only", "GPL-1.0-or-later", "GPL-2.0", "GPL-2.0-only",
+ "GPL-2.0-or-later", "GPL-3.0", "GPL-3.0-only", "GPL-3.0-or-later",
+ "LGPL-2.0", "LGPL-2.0-only", "LGPL-2.0-or-later",
+ "LGPL-2.1", "LGPL-2.1-only", "LGPL-2.1-or-later",
+ "LGPL-3.0", "LGPL-3.0-only", "LGPL-3.0-or-later",
+ "AGPL-3.0", "AGPL-3.0-only", "AGPL-3.0-or-later",
+ "MPL-1.0", "MPL-1.1", "MPL-2.0",
+ "CDDL-1.0", "CDDL-1.1",
+ "EPL-1.0", "EPL-2.0",
+ "OSL-1.0", "OSL-2.0", "OSL-3.0",
"SSPL-1.0",
- "copyleft-next-0.3.0",
}
-# Combined copyleft license set
-copyleft := ((((((((gpl_licenses | lgpl_licenses) | agpl_licenses) | mpl_licenses) | cddl_licenses) | epl_licenses) | osl_licenses) | gfdl_licenses) | cc_by_sa_licenses) | other_copyleft_licenses
-
-# Main policy rule
match if {
- input.v0.package.license.oss_license.spdx_identifier in copyleft
+ input.v0.package.license != null
+ input.v0.package.license.oss_license != null
+
+ lic := input.v0.package.license.oss_license.spdx_identifier
+ lic != null
+ lic in copyleft
}
-# Explanation for decision logs / error text
-reason contains msg if {
+reason[msg] if {
match
lic := input.v0.package.license.oss_license.spdx_identifier
msg := sprintf(
diff --git a/baseline/malware-block.rego b/baseline/malware-block.rego
index c9bd0d8..94a8d00 100644
--- a/baseline/malware-block.rego
+++ b/baseline/malware-block.rego
@@ -2,14 +2,18 @@ package cloudsmith
default match := false
-match if count(malicious_packages) > 0
+match if {
+ input.v0.osv != null
+ count(malicious_packages) > 0
+}
malicious_packages := [vulnerability.id |
+ input.v0.osv != null
some vulnerability in input.v0.osv
startswith(vulnerability.id, "MAL-")
]
-reason contains msg if {
+reason[msg] if {
match
msg := sprintf(
"Detected %d malicious vulnerability ID(s): %v",
From 809db09f1837d49f50d4ea57a236b7c36c0262f0 Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Thu, 19 Feb 2026 15:02:37 +0000
Subject: [PATCH 05/35] Update opa-lint.yml
---
.github/workflows/opa-lint.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/opa-lint.yml b/.github/workflows/opa-lint.yml
index c8ee442..92bfd4c 100644
--- a/.github/workflows/opa-lint.yml
+++ b/.github/workflows/opa-lint.yml
@@ -3,7 +3,7 @@ name: OPA Lint & Validate
on:
pull_request:
push:
- branches: [main]
+ branches: [epm-baseline-refactor]
jobs:
opa:
From cbb09cdc36b63d17355f991b23154a367d498203 Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Wed, 25 Feb 2026 14:03:18 +0000
Subject: [PATCH 06/35] Refactor vulnerability matching and scoring logic
---
baseline/high-risk-vulnerability.rego | 115 +++++++++++++++++---------
1 file changed, 74 insertions(+), 41 deletions(-)
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index c4dabfd..87f9116 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -9,72 +9,105 @@ min_epss := 0.1
ignored_cves := {}
match if {
- input.v0.vulnerabilities != null
- some vuln in input.v0.vulnerabilities
+ input.v0.vulnerabilities != null
+ some vuln in input.v0.vulnerabilities
- not ignored(vuln)
- has_fix(vuln)
- should_block(vuln)
+ not ignored(vuln)
+ has_fix(vuln)
+ should_block(vuln)
}
should_block(vuln) if {
- critical_severity(vuln)
+ critical_severity(vuln)
}
should_block(vuln) if {
- exploitable_high_severity(vuln)
+ exploitable_high_severity(vuln)
}
critical_severity(vuln) if {
- some _, score in vuln.cvss
- score.V3Score >= critical_cvss
+ score := cvss_v3_score(vuln)
+ score >= critical_cvss
}
exploitable_high_severity(vuln) if {
- some _, score in vuln.cvss
- score.V3Score >= high_cvss
- vuln.epss != null
- vuln.epss.score != null
- to_number(vuln.epss.score) >= min_epss
+ score := cvss_v3_score(vuln)
+ score >= high_cvss
+
+ epss := epss_score(vuln)
+ epss >= min_epss
}
has_fix(vuln) if {
- vuln.patched_versions
- count(vuln.patched_versions) > 0
+ vuln.patched_versions
+ count(vuln.patched_versions) > 0
}
ignored(vuln) if {
- vuln.identifier in ignored_cves
+ vuln.identifier in ignored_cves
+}
+
+cvss_v3_score(vuln) := s if {
+ vuln.cvss != null
+ vuln.cvss.redhat != null
+ vuln.cvss.redhat.V3Score != null
+ s := vuln.cvss.redhat.V3Score
+} else := s if {
+ vuln.cvss != null
+ vuln.cvss.ghsa != null
+ vuln.cvss.ghsa.V3Score != null
+ s := vuln.cvss.ghsa.V3Score
+} else := s if {
+ vuln.cvss != null
+ vuln.cvss.nvd != null
+ vuln.cvss.nvd.V3Score != null
+ s := vuln.cvss.nvd.V3Score
+} else := s if {
+ # fallback: first non-null V3Score found in any source
+ vuln.cvss != null
+ some _, src in vuln.cvss
+ src != null
+ src.V3Score != null
+ s := src.V3Score
}
+epss_score(vuln) := s if {
+ vuln.epss != null
+ vuln.epss.score != null
+ s := to_number(vuln.epss.score)
+} else := 0.0
+
reason[msg] if {
- input.v0.vulnerabilities != null
- some vuln in input.v0.vulnerabilities
+ input.v0.vulnerabilities != null
+ some vuln in input.v0.vulnerabilities
+
+ not ignored(vuln)
+ has_fix(vuln)
- not ignored(vuln)
- has_fix(vuln)
- critical_severity(vuln)
+ score := cvss_v3_score(vuln)
+ score >= critical_cvss
- msg := sprintf(
- "Blocking %v: Critical vulnerability (CVSS %.1f)",
- [vuln.identifier, vuln.cvss[_].V3Score],
- )
+ msg := sprintf(
+ "Blocking %v: Critical vulnerability (CVSS %.1f)",
+ [vuln.identifier, score],
+ )
}
reason[msg] if {
- input.v0.vulnerabilities != null
- some vuln in input.v0.vulnerabilities
-
- not ignored(vuln)
- has_fix(vuln)
- exploitable_high_severity(vuln)
-
- msg := sprintf(
- "Blocking %v: High severity (CVSS %.1f) and actively exploited (EPSS %.3f)",
- [
- vuln.identifier,
- vuln.cvss[_].V3Score,
- to_number(vuln.epss.score),
- ],
- )
+ input.v0.vulnerabilities != null
+ some vuln in input.v0.vulnerabilities
+
+ not ignored(vuln)
+ has_fix(vuln)
+
+ score := cvss_v3_score(vuln)
+ score >= high_cvss
+
+ epss := epss_score(vuln)
+ epss >= min_epss
+
+ msg := sprintf(
+ "Blocking %v: High severity (CVSS %.1f) and actively exploited (EPSS %.3f)",
+ [vuln.identifier, score, epss],
+ )
}
From f9d53384226c315bc32392afbebd9c999487c7d1 Mon Sep 17 00:00:00 2001
From: Ciara Carey
Date: Wed, 25 Feb 2026 14:23:45 +0000
Subject: [PATCH 07/35] opa formatting fixes
---
baseline/high-risk-vulnerability.rego | 118 +++++++++++++-------------
1 file changed, 59 insertions(+), 59 deletions(-)
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index 87f9116..2e1ab66 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -9,105 +9,105 @@ min_epss := 0.1
ignored_cves := {}
match if {
- input.v0.vulnerabilities != null
- some vuln in input.v0.vulnerabilities
+ input.v0.vulnerabilities != null
+ some vuln in input.v0.vulnerabilities
- not ignored(vuln)
- has_fix(vuln)
- should_block(vuln)
+ not ignored(vuln)
+ has_fix(vuln)
+ should_block(vuln)
}
should_block(vuln) if {
- critical_severity(vuln)
+ critical_severity(vuln)
}
should_block(vuln) if {
- exploitable_high_severity(vuln)
+ exploitable_high_severity(vuln)
}
critical_severity(vuln) if {
- score := cvss_v3_score(vuln)
- score >= critical_cvss
+ score := cvss_v3_score(vuln)
+ score >= critical_cvss
}
exploitable_high_severity(vuln) if {
- score := cvss_v3_score(vuln)
- score >= high_cvss
+ score := cvss_v3_score(vuln)
+ score >= high_cvss
- epss := epss_score(vuln)
- epss >= min_epss
+ epss := epss_score(vuln)
+ epss >= min_epss
}
has_fix(vuln) if {
- vuln.patched_versions
- count(vuln.patched_versions) > 0
+ vuln.patched_versions
+ count(vuln.patched_versions) > 0
}
ignored(vuln) if {
- vuln.identifier in ignored_cves
+ vuln.identifier in ignored_cves
}
cvss_v3_score(vuln) := s if {
- vuln.cvss != null
- vuln.cvss.redhat != null
- vuln.cvss.redhat.V3Score != null
- s := vuln.cvss.redhat.V3Score
+ vuln.cvss != null
+ vuln.cvss.redhat != null
+ vuln.cvss.redhat.V3Score != null
+ s := vuln.cvss.redhat.V3Score
} else := s if {
- vuln.cvss != null
- vuln.cvss.ghsa != null
- vuln.cvss.ghsa.V3Score != null
- s := vuln.cvss.ghsa.V3Score
+ vuln.cvss != null
+ vuln.cvss.ghsa != null
+ vuln.cvss.ghsa.V3Score != null
+ s := vuln.cvss.ghsa.V3Score
} else := s if {
- vuln.cvss != null
- vuln.cvss.nvd != null
- vuln.cvss.nvd.V3Score != null
- s := vuln.cvss.nvd.V3Score
+ vuln.cvss != null
+ vuln.cvss.nvd != null
+ vuln.cvss.nvd.V3Score != null
+ s := vuln.cvss.nvd.V3Score
} else := s if {
- # fallback: first non-null V3Score found in any source
- vuln.cvss != null
- some _, src in vuln.cvss
- src != null
- src.V3Score != null
- s := src.V3Score
+ # fallback: first non-null V3Score found in any source
+ vuln.cvss != null
+ some _, src in vuln.cvss
+ src != null
+ src.V3Score != null
+ s := src.V3Score
}
epss_score(vuln) := s if {
- vuln.epss != null
- vuln.epss.score != null
- s := to_number(vuln.epss.score)
+ vuln.epss != null
+ vuln.epss.score != null
+ s := to_number(vuln.epss.score)
} else := 0.0
reason[msg] if {
- input.v0.vulnerabilities != null
- some vuln in input.v0.vulnerabilities
+ input.v0.vulnerabilities != null
+ some vuln in input.v0.vulnerabilities
- not ignored(vuln)
- has_fix(vuln)
+ not ignored(vuln)
+ has_fix(vuln)
- score := cvss_v3_score(vuln)
- score >= critical_cvss
+ score := cvss_v3_score(vuln)
+ score >= critical_cvss
- msg := sprintf(
- "Blocking %v: Critical vulnerability (CVSS %.1f)",
- [vuln.identifier, score],
- )
+ msg := sprintf(
+ "Blocking %v: Critical vulnerability (CVSS %.1f)",
+ [vuln.identifier, score],
+ )
}
reason[msg] if {
- input.v0.vulnerabilities != null
- some vuln in input.v0.vulnerabilities
+ input.v0.vulnerabilities != null
+ some vuln in input.v0.vulnerabilities
- not ignored(vuln)
- has_fix(vuln)
+ not ignored(vuln)
+ has_fix(vuln)
- score := cvss_v3_score(vuln)
- score >= high_cvss
+ score := cvss_v3_score(vuln)
+ score >= high_cvss
- epss := epss_score(vuln)
- epss >= min_epss
+ epss := epss_score(vuln)
+ epss >= min_epss
- msg := sprintf(
- "Blocking %v: High severity (CVSS %.1f) and actively exploited (EPSS %.3f)",
- [vuln.identifier, score, epss],
- )
+ msg := sprintf(
+ "Blocking %v: High severity (CVSS %.1f) and actively exploited (EPSS %.3f)",
+ [vuln.identifier, score, epss],
+ )
}
From 8bde0eb6903402492bf2706b370df588af48db6e Mon Sep 17 00:00:00 2001
From: Ciara Carey
Date: Wed, 4 Mar 2026 12:59:51 +0000
Subject: [PATCH 08/35] update vulnerability policy to use osv object
---
baseline/high-risk-vulnerability.rego | 159 +++++++++++---------------
1 file changed, 68 insertions(+), 91 deletions(-)
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index 2e1ab66..78484dc 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -2,112 +2,89 @@ package cloudsmith
default match := false
-critical_cvss := 9.0
-high_cvss := 7.0
-min_epss := 0.1
-
-ignored_cves := {}
-
-match if {
- input.v0.vulnerabilities != null
- some vuln in input.v0.vulnerabilities
-
- not ignored(vuln)
- has_fix(vuln)
- should_block(vuln)
+match if count(reason) > 0
+
+min_cvss := 7.0
+ignored := {}
+
+# ── Blocking key set ──────────────────────────────────────────────────────────
+# Vulnerabilities are only blocked when a fix is available.
+# This prevents breaking builds with no recourse for the developer.
+# To block regardless of fix, remove has_fix_for_key(k) from this set.
+blocking_keys := {k |
+ v := input.v0.osv[_]
+ k := vuln_keys(v)[_]
+ not k in ignored
+ max_cvss_for_key(k) >= min_cvss
+ has_fix_for_key(k)
}
-should_block(vuln) if {
- critical_severity(vuln)
+# ── Key derivation ────────────────────────────────────────────────────────────
+vuln_keys(v) := {a | a := v.aliases[_]} if {
+ count(v.aliases) > 0
+} else := {v.id}
+
+# ── CVSS extraction ───────────────────────────────────────────────────────────
+# Check both top-level and per-affected severity — OSV records vary
+cvss_scores_for_record(v) := scores if {
+ top_level := {s |
+ sev := v.severity[_]
+ sev.numerical_score != null
+ s := sev.numerical_score
+ }
+ affected_level := {s |
+ a := v.affected[_]
+ sev := a.severity[_]
+ sev.numerical_score != null
+ s := sev.numerical_score
+ }
+ scores := top_level | affected_level
}
-should_block(vuln) if {
- exploitable_high_severity(vuln)
-}
+max_cvss_for_key(k) := s if {
+ scores := {s |
+ v := input.v0.osv[_]
+ k in vuln_keys(v)
+ s := cvss_scores_for_record(v)[_]
+ }
+ count(scores) > 0
+ s := max(scores)
+} else := 0.0
-critical_severity(vuln) if {
- score := cvss_v3_score(vuln)
- score >= critical_cvss
+# ── Fix detection ─────────────────────────────────────────────────────────────
+has_fix_for_key(k) if {
+ count(fixed_versions_for_key(k)) > 0
}
-exploitable_high_severity(vuln) if {
- score := cvss_v3_score(vuln)
- score >= high_cvss
-
- epss := epss_score(vuln)
- epss >= min_epss
+fixed_versions_for_key(k) := {f |
+ v := input.v0.osv[_]
+ k in vuln_keys(v)
+ r := v.affected[_].ranges[_]
+ e := r.events[_]
+ f := e.fixed
+ include_fix(f)
}
-has_fix(vuln) if {
- vuln.patched_versions
- count(vuln.patched_versions) > 0
+include_fix(f) if {
+ semver.is_valid(f)
+ semver.is_valid(input.v0.package.version)
+ semver.compare(f, input.v0.package.version) > 0
}
-ignored(vuln) if {
- vuln.identifier in ignored_cves
-}
+include_fix(f) if not semver.is_valid(f)
-cvss_v3_score(vuln) := s if {
- vuln.cvss != null
- vuln.cvss.redhat != null
- vuln.cvss.redhat.V3Score != null
- s := vuln.cvss.redhat.V3Score
-} else := s if {
- vuln.cvss != null
- vuln.cvss.ghsa != null
- vuln.cvss.ghsa.V3Score != null
- s := vuln.cvss.ghsa.V3Score
-} else := s if {
- vuln.cvss != null
- vuln.cvss.nvd != null
- vuln.cvss.nvd.V3Score != null
- s := vuln.cvss.nvd.V3Score
-} else := s if {
- # fallback: first non-null V3Score found in any source
- vuln.cvss != null
- some _, src in vuln.cvss
- src != null
- src.V3Score != null
- s := src.V3Score
+include_fix(f) if {
+ semver.is_valid(f)
+ not semver.is_valid(input.v0.package.version)
}
-epss_score(vuln) := s if {
- vuln.epss != null
- vuln.epss.score != null
- s := to_number(vuln.epss.score)
-} else := 0.0
-
+# ── Reason messages ───────────────────────────────────────────────────────────
reason[msg] if {
- input.v0.vulnerabilities != null
- some vuln in input.v0.vulnerabilities
-
- not ignored(vuln)
- has_fix(vuln)
-
- score := cvss_v3_score(vuln)
- score >= critical_cvss
-
- msg := sprintf(
- "Blocking %v: Critical vulnerability (CVSS %.1f)",
- [vuln.identifier, score],
- )
-}
-
-reason[msg] if {
- input.v0.vulnerabilities != null
- some vuln in input.v0.vulnerabilities
-
- not ignored(vuln)
- has_fix(vuln)
-
- score := cvss_v3_score(vuln)
- score >= high_cvss
-
- epss := epss_score(vuln)
- epss >= min_epss
-
+ k := blocking_keys[_]
+ score := max_cvss_for_key(k)
+ fixes := fixed_versions_for_key(k)
msg := sprintf(
- "Blocking %v: High severity (CVSS %.1f) and actively exploited (EPSS %.3f)",
- [vuln.identifier, score, epss],
+ "Blocking %v: CVSS %.1f, fix(es) available: %v",
+ [k, score, concat(", ", fixes)],
)
}
From 952298640bc127522a77277d37c9c816f1868176 Mon Sep 17 00:00:00 2001
From: Ciara Carey
Date: Wed, 4 Mar 2026 13:04:19 +0000
Subject: [PATCH 09/35] update vulnerability policy
---
baseline/high-risk-vulnerability.rego | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index 78484dc..b16981f 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -41,14 +41,14 @@ cvss_scores_for_record(v) := scores if {
scores := top_level | affected_level
}
-max_cvss_for_key(k) := s if {
+max_cvss_for_key(k) := result if {
scores := {s |
v := input.v0.osv[_]
k in vuln_keys(v)
s := cvss_scores_for_record(v)[_]
}
count(scores) > 0
- s := max(scores)
+ result := max(scores)
} else := 0.0
# ── Fix detection ─────────────────────────────────────────────────────────────
From 6bfd1ac279d01b438e1606f3d19258b8789023fb Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Thu, 5 Mar 2026 11:37:17 +0000
Subject: [PATCH 10/35] Update README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index e6a2819..fe9bfb7 100644
--- a/README.md
+++ b/README.md
@@ -84,7 +84,8 @@ A recommended precedence pattern for baseline deployments is:
3. License policy (tagging or governance)
4. High-risk vulnerability policy (quarantine based on thresholds)
5. Exact allowlist exemption (explicit override)
-6. Malware block (final quarantine safeguard)
+6. Exact blocklist (explicit deny)
+7. Malware block (final quarantine safeguard)
All matched policy actions are applied within a single transaction.
The package state visible to users reflects the final committed result.
From c300edbc5cda468264726dcf2b61df974eec4c29 Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Thu, 5 Mar 2026 11:37:37 +0000
Subject: [PATCH 11/35] Update .github/workflows/opa-lint.yml
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.github/workflows/opa-lint.yml | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/opa-lint.yml b/.github/workflows/opa-lint.yml
index 92bfd4c..f250690 100644
--- a/.github/workflows/opa-lint.yml
+++ b/.github/workflows/opa-lint.yml
@@ -23,8 +23,16 @@ jobs:
- name: Format Check
run: |
- [ -d baseline ] && opa fmt --fail baseline || echo "No baseline folder"
- [ -d advanced ] && opa fmt --fail advanced || echo "No advanced folder"
+ if [ -d baseline ]; then
+ opa fmt --fail baseline
+ else
+ echo "No baseline folder"
+ fi
+ if [ -d advanced ]; then
+ opa fmt --fail advanced
+ else
+ echo "No advanced folder"
+ fi
- name: Validate Policies
run: |
From aebc6ce5b7eec28591af81536e2b0f1931b9f85e Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Thu, 5 Mar 2026 11:37:57 +0000
Subject: [PATCH 12/35] Update .github/workflows/opa-lint.yml
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.github/workflows/opa-lint.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/opa-lint.yml b/.github/workflows/opa-lint.yml
index f250690..724ba7f 100644
--- a/.github/workflows/opa-lint.yml
+++ b/.github/workflows/opa-lint.yml
@@ -3,7 +3,7 @@ name: OPA Lint & Validate
on:
pull_request:
push:
- branches: [epm-baseline-refactor]
+ branches: [main, epm-baseline-refactor]
jobs:
opa:
From d1a8e18e5b419860a52835174bb5050b9e51da51 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 5 Mar 2026 11:43:05 +0000
Subject: [PATCH 13/35] fix(readme): wrap repository structure in fenced code
block (#16)
* Initial plan
* fix(readme): wrap repository structure in fenced code block
Co-authored-by: ciaracarey <84123925+ciaracarey@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ciaracarey <84123925+ciaracarey@users.noreply.github.com>
---
README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/README.md b/README.md
index fe9bfb7..9c8a7bc 100644
--- a/README.md
+++ b/README.md
@@ -22,9 +22,12 @@ These policies are intended to be readable, predictable, and suitable for enterp
---
## Repository Structure
+
+```
baseline/
advanced/
legacy/
+```
### baseline/
From 97fbe8f380d232b322486bac258c8bb4c4317262 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 5 Mar 2026 11:43:41 +0000
Subject: [PATCH 14/35] Fix Americanized spelling in README (#17)
* Initial plan
* Fix spelling: "Specialised" -> "Specialized" in README.md
Co-authored-by: ciaracarey <84123925+ciaracarey@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ciaracarey <84123925+ciaracarey@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 9c8a7bc..1cd0e79 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,7 @@ These may include:
- Base image origin enforcement
- SBOM-based controls
- Model governance policies
-- Specialised workflow patterns
+- Specialized workflow patterns
Advanced policies are production-ready but not universally required.
From 2c7cc2f943ae8bfc32de53ea195afc2671acc366 Mon Sep 17 00:00:00 2001
From: Copilot <198982749+Copilot@users.noreply.github.com>
Date: Thu, 5 Mar 2026 11:44:28 +0000
Subject: [PATCH 15/35] Remove `import rego.v1` from
advanced/huggingface-recipes policies (#18)
* Initial plan
* Remove import rego.v1 from advanced/huggingface-recipes policies
Co-authored-by: ciaracarey <84123925+ciaracarey@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ciaracarey <84123925+ciaracarey@users.noreply.github.com>
---
advanced/huggingface-recipes/model_card.rego | 2 --
advanced/huggingface-recipes/risky_files.rego | 2 --
advanced/huggingface-recipes/security_scan.rego | 2 --
advanced/huggingface-recipes/trusted_publishers.rego | 2 --
4 files changed, 8 deletions(-)
diff --git a/advanced/huggingface-recipes/model_card.rego b/advanced/huggingface-recipes/model_card.rego
index fd34be9..c34c280 100644
--- a/advanced/huggingface-recipes/model_card.rego
+++ b/advanced/huggingface-recipes/model_card.rego
@@ -1,7 +1,5 @@
package cloudsmith
-import rego.v1
-
default match := false
pkg := input.v0.package
diff --git a/advanced/huggingface-recipes/risky_files.rego b/advanced/huggingface-recipes/risky_files.rego
index 75e8dd8..b06a02c 100644
--- a/advanced/huggingface-recipes/risky_files.rego
+++ b/advanced/huggingface-recipes/risky_files.rego
@@ -1,7 +1,5 @@
package cloudsmith
-import rego.v1
-
default match := false
pkg := input.v0.package
diff --git a/advanced/huggingface-recipes/security_scan.rego b/advanced/huggingface-recipes/security_scan.rego
index 9a42e65..bfb8740 100644
--- a/advanced/huggingface-recipes/security_scan.rego
+++ b/advanced/huggingface-recipes/security_scan.rego
@@ -1,7 +1,5 @@
package cloudsmith
-import rego.v1
-
default match := false
# Upstream packages are fetched by a system user
diff --git a/advanced/huggingface-recipes/trusted_publishers.rego b/advanced/huggingface-recipes/trusted_publishers.rego
index f7a44c6..767c6ef 100644
--- a/advanced/huggingface-recipes/trusted_publishers.rego
+++ b/advanced/huggingface-recipes/trusted_publishers.rego
@@ -1,7 +1,5 @@
package cloudsmith
-import rego.v1
-
default match := false
# Upstream packages are fetched by a system user
From 0df6c6f3f3e968d40ff1b0768c315f224b1b5355 Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Thu, 5 Mar 2026 12:41:15 +0000
Subject: [PATCH 16/35] change name from cooldown to pacage age
---
README.md | 6 +++---
...cooldown.rego => package-age-quarantine.rego} | 8 ++++----
...own-restore.rego => package-age-restore.rego} | 16 ++++++----------
3 files changed, 13 insertions(+), 17 deletions(-)
rename baseline/{cooldown.rego => package-age-quarantine.rego} (71%)
rename baseline/{cooldown-restore.rego => package-age-restore.rego} (60%)
diff --git a/README.md b/README.md
index 1cd0e79..4c5bfb1 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@ These policies address common supply chain security requirements such as:
- Malware blocking
- High-risk vulnerability control (CVSS / EPSS)
- License compliance
-- Controlled cooldown workflows
+- Workflows using package age
- Explicit allowlist and blocklist handling
If you are deploying EPM in a new workspace, start here.
@@ -82,8 +82,8 @@ All policies in this repository are designed to be non-terminal and composable.
A recommended precedence pattern for baseline deployments is:
-1. Cooldown Restore (make eligible packages available again)
-2. Cooldown (time-based quarantine)
+1. Package age restore (make eligible packages available again)
+2. Packge age quarantine (time-based quarantine)
3. License policy (tagging or governance)
4. High-risk vulnerability policy (quarantine based on thresholds)
5. Exact allowlist exemption (explicit override)
diff --git a/baseline/cooldown.rego b/baseline/package-age-quarantine.rego
similarity index 71%
rename from baseline/cooldown.rego
rename to baseline/package-age-quarantine.rego
index 033e376..e4d33ea 100644
--- a/baseline/cooldown.rego
+++ b/baseline/package-age-quarantine.rego
@@ -6,9 +6,9 @@ pkg := input.v0.package
within_past_days := 3
-match if cooldown_needed
+match if below_minimum_release_age
-cooldown_needed if {
+below_minimum_release_age if {
pkg.upstream_metadata != null
pkg.upstream_metadata.published_at != null
@@ -21,9 +21,9 @@ cooldown_needed if {
}
reason[msg] if {
- cooldown_needed
+ below_minimum_release_age
msg := sprintf(
- "Package published within last %v days — applying cooldown",
+ "Package published within last %v days — below minimum release age",
[within_past_days],
)
}
diff --git a/baseline/cooldown-restore.rego b/baseline/package-age-restore.rego
similarity index 60%
rename from baseline/cooldown-restore.rego
rename to baseline/package-age-restore.rego
index 20e3f75..0b30ce3 100644
--- a/baseline/cooldown-restore.rego
+++ b/baseline/package-age-restore.rego
@@ -4,18 +4,14 @@ default match := false
pkg := input.v0.package
-within_past_days := 4
-required_tag := "cooldown"
+within_past_days := 3
-match if restored
+match if above_minimum_release_age
-restored if {
+above_minimum_release_age if {
pkg.upstream_metadata != null
pkg.upstream_metadata.published_at != null
- some t in pkg.tags
- t.name == required_tag
-
publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
days_ago := 0 - within_past_days
@@ -25,9 +21,9 @@ restored if {
}
reason[msg] if {
- restored
+ above_minimum_release_age
msg := sprintf(
- "Package older than %v days and tagged '%s' — restoring availability",
- [within_past_days, required_tag],
+ "Package older than %v days — meets minimum release age",
+ [within_past_days],
)
}
From a1e39c4a010f7a2c96ed5ffa0b716af632e19fdf Mon Sep 17 00:00:00 2001
From: Ciara Carey
Date: Tue, 10 Mar 2026 23:26:37 +0000
Subject: [PATCH 17/35] Simplify vulnerability policy
---
baseline/high-risk-vulnerability.rego | 88 ++++++++++-----------------
1 file changed, 31 insertions(+), 57 deletions(-)
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index b16981f..34d6363 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -1,90 +1,64 @@
+# title: High Risk Vulnerability
+# description: Blocks packages with a CVSS score >= X where a fix is available.
package cloudsmith
default match := false
-match if count(reason) > 0
-
min_cvss := 7.0
+
ignored := {}
-# ── Blocking key set ──────────────────────────────────────────────────────────
-# Vulnerabilities are only blocked when a fix is available.
-# This prevents breaking builds with no recourse for the developer.
-# To block regardless of fix, remove has_fix_for_key(k) from this set.
+match if count(reason) > 0
+
+vuln_keys(v) := {a | some a in v.aliases} if {
+ count(v.aliases) > 0
+} else := {v.id}
+
+aliased_records[k] := records if {
+ some v_seed in input.v0.osv
+ some k in vuln_keys(v_seed)
+ records := {v | some v in input.v0.osv; k in vuln_keys(v)}
+}
+
blocking_keys := {k |
- v := input.v0.osv[_]
- k := vuln_keys(v)[_]
+ some k, _ in aliased_records
not k in ignored
max_cvss_for_key(k) >= min_cvss
has_fix_for_key(k)
}
-# ── Key derivation ────────────────────────────────────────────────────────────
-vuln_keys(v) := {a | a := v.aliases[_]} if {
- count(v.aliases) > 0
-} else := {v.id}
-
-# ── CVSS extraction ───────────────────────────────────────────────────────────
-# Check both top-level and per-affected severity — OSV records vary
-cvss_scores_for_record(v) := scores if {
+cvss_scores_for_record(v) := top_level | affected_level if {
top_level := {s |
- sev := v.severity[_]
+ some sev in v.severity
sev.numerical_score != null
s := sev.numerical_score
}
affected_level := {s |
- a := v.affected[_]
- sev := a.severity[_]
+ some a in v.affected
+ some sev in a.severity
sev.numerical_score != null
s := sev.numerical_score
}
- scores := top_level | affected_level
}
-max_cvss_for_key(k) := result if {
+max_cvss_for_key(k) := max(scores) if {
scores := {s |
- v := input.v0.osv[_]
- k in vuln_keys(v)
- s := cvss_scores_for_record(v)[_]
+ some v in aliased_records[k]
+ some s in cvss_scores_for_record(v)
}
count(scores) > 0
- result := max(scores)
} else := 0.0
-# ── Fix detection ─────────────────────────────────────────────────────────────
has_fix_for_key(k) if {
- count(fixed_versions_for_key(k)) > 0
-}
-
-fixed_versions_for_key(k) := {f |
- v := input.v0.osv[_]
- k in vuln_keys(v)
- r := v.affected[_].ranges[_]
- e := r.events[_]
- f := e.fixed
- include_fix(f)
-}
-
-include_fix(f) if {
- semver.is_valid(f)
- semver.is_valid(input.v0.package.version)
- semver.compare(f, input.v0.package.version) > 0
-}
-
-include_fix(f) if not semver.is_valid(f)
-
-include_fix(f) if {
- semver.is_valid(f)
- not semver.is_valid(input.v0.package.version)
+ some v in aliased_records[k]
+ some a in v.affected
+ some r in a.ranges
+ some e in r.events
+ e.fixed
}
-# ── Reason messages ───────────────────────────────────────────────────────────
-reason[msg] if {
- k := blocking_keys[_]
+reason contains msg if {
+ some k in blocking_keys
score := max_cvss_for_key(k)
- fixes := fixed_versions_for_key(k)
- msg := sprintf(
- "Blocking %v: CVSS %.1f, fix(es) available: %v",
- [k, score, concat(", ", fixes)],
- )
+ msg := sprintf("Blocking %v: CVSS %.1f, fix available", [k, score])
}
From fcdb54084506651c7ea63fe6027f0bb734987b8c Mon Sep 17 00:00:00 2001
From: Ciara Carey
Date: Tue, 10 Mar 2026 23:54:21 +0000
Subject: [PATCH 18/35] adding the GitOps exemption workflow
---
.github/workflows/apply-exemptions.yml | 39 +++++
.gitignore | 208 ++++++++++++++++++++++++
README.md | 39 ++++-
exemptions/allow.json | 10 ++
exemptions/templates/allowlist.rego.tpl | 34 ++++
exemptions/update_policy.py | 104 ++++++++++++
6 files changed, 433 insertions(+), 1 deletion(-)
create mode 100644 .github/workflows/apply-exemptions.yml
create mode 100644 .gitignore
create mode 100644 exemptions/allow.json
create mode 100644 exemptions/templates/allowlist.rego.tpl
create mode 100644 exemptions/update_policy.py
diff --git a/.github/workflows/apply-exemptions.yml b/.github/workflows/apply-exemptions.yml
new file mode 100644
index 0000000..ac2be07
--- /dev/null
+++ b/.github/workflows/apply-exemptions.yml
@@ -0,0 +1,39 @@
+name: Apply EPM Exemptions
+
+on:
+ push:
+ branches: [main]
+ paths:
+ - "exemptions/allow.json"
+
+permissions:
+ contents: read
+ id-token: write
+
+jobs:
+ apply:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.11"
+
+ - name: Cloudsmith OIDC login
+ uses: cloudsmith-io/cloudsmith-cli-action@v1.0.2
+ with:
+ oidc-namespace: ${{ vars.CLOUDSMITH_WORKSPACE }}
+ oidc-service-slug: ${{ vars.CLOUDSMITH_SERVICE }}
+ oidc-auth-only: "true"
+
+ - name: Install dependencies
+ run: pip install requests
+
+ - name: Apply exemptions
+ env:
+ CLOUDSMITH_WORKSPACE: ${{ vars.CLOUDSMITH_WORKSPACE }}
+ ALLOW_POLICY_SLUG: ${{ secrets.ALLOW_POLICY_SLUG }}
+ CLOUDSMITH_TOKEN: ${{ env.CLOUDSMITH_API_KEY }}
+ run: python exemptions/update_policy.py
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fd09a7d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,208 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[codz]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py.cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# UV
+# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+#uv.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+#poetry.toml
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
+# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
+#pdm.lock
+#pdm.toml
+.pdm-python
+.pdm-build/
+
+# pixi
+# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
+#pixi.lock
+# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
+# in the .venv directory. It is recommended not to include this directory in version control.
+.pixi
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.envrc
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+exemptions/*.local.json
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+# Abstra
+# Abstra is an AI-powered process automation framework.
+# Ignore directories containing user credentials, local state, and settings.
+# Learn more at https://abstra.io/docs
+.abstra/
+
+# Visual Studio Code
+# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
+# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
+# and can be added to the global gitignore or merged into this file. However, if you prefer,
+# you could uncomment the following to ignore the entire vscode folder
+# .vscode/
+
+# Ruff stuff:
+.ruff_cache/
+
+# PyPI configuration file
+.pypirc
+
+# Cursor
+# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
+# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
+# refer to https://docs.cursor.com/context/ignore-files
+.cursorignore
+.cursorindexingignore
+
+# Marimo
+marimo/_static/
+marimo/_lsp/
+__marimo__/
diff --git a/README.md b/README.md
index 4c5bfb1..1c528f2 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,14 @@ These policies are intended to be readable, predictable, and suitable for enterp
baseline/
advanced/
legacy/
+exemptions/
+ allow.json
+ update_policy.py
+ templates/
+ allowlist.rego.tpl
+.github/workflows/
+ opa-lint.yml
+ apply-exemptions.yml
```
### baseline/
@@ -83,7 +91,7 @@ All policies in this repository are designed to be non-terminal and composable.
A recommended precedence pattern for baseline deployments is:
1. Package age restore (make eligible packages available again)
-2. Packge age quarantine (time-based quarantine)
+2. Package age quarantine (time-based quarantine)
3. License policy (tagging or governance)
4. High-risk vulnerability policy (quarantine based on thresholds)
5. Exact allowlist exemption (explicit override)
@@ -98,6 +106,34 @@ https://docs.cloudsmith.com/supply-chain-security/epm
---
+## Managing Exemptions (GitOps Workflow)
+
+The allowlist policy in `baseline/` supports a GitOps-based exemption workflow.
+Rather than editing policies manually, exemptions are stored in Git, reviewed via
+Pull Requests, and automatically applied to Cloudsmith on merge.
+
+### How it works
+
+1. Maintain an exemption list in the format `format:name:version`:
+
+```json
+[
+ "python:requests:2.6.4",
+ "npm:left-pad:1.3.0"
+]
+```
+
+2. Open a Pull Request for security/DevOps review.
+3. On merge, a CI step regenerates the allowlist Rego policy from the exemption list and uploads it to Cloudsmith via the API.
+
+### Why this approach
+
+EPM policies embed exemption data directly in Rego. Managing exemptions via Git provides auditability, an approval gate, rollback capability, and a scalable alternative to manual policy edits.
+
+The allowlist exemption policy should be placed at a higher precedence than the vulnerability policy (position 5 in the recommended ordering above) so that explicitly approved packages bypass security enforcement.
+
+---
+
## Deployment
Policies can be deployed using the Cloudsmith API or CLI.
@@ -114,3 +150,4 @@ This repository is the single source of truth for:
- Documentation examples
- Secure baseline recommendations
- Enterprise EPM enablement guidance
+
diff --git a/exemptions/allow.json b/exemptions/allow.json
new file mode 100644
index 0000000..e6f76b4
--- /dev/null
+++ b/exemptions/allow.json
@@ -0,0 +1,10 @@
+[
+ "python:requests:2.6.4",
+ "python:requests:2.6.1",
+ "npm:left-pad:1.3.0",
+ "python:botocore:1.42.53",
+ "maven:commons-compress:1.18",
+ "maven:jackson-core:2.19.1",
+ "maven:commons-io:2.11.0",
+ "maven:commons-io:2.6",
+ "maven:commons-io:2.13.0"]
diff --git a/exemptions/templates/allowlist.rego.tpl b/exemptions/templates/allowlist.rego.tpl
new file mode 100644
index 0000000..569f26f
--- /dev/null
+++ b/exemptions/templates/allowlist.rego.tpl
@@ -0,0 +1,34 @@
+package cloudsmith
+
+default match := false
+
+############################################################
+# GENERATED FILE — DO NOT EDIT MANUALLY
+# Managed by exemption workflow
+############################################################
+
+allowlist := {
+{{ENTRIES}}
+}
+
+pkg := input.v0.package
+
+pkg_key := sprintf(
+ "%s:%s:%s",
+ [pkg.format, pkg.name, pkg.version],
+)
+
+match if {
+ pkg.format != null
+ pkg.name != null
+ pkg.version != null
+ pkg_key in allowlist
+}
+
+reason[msg] if {
+ match
+ msg := sprintf(
+ "Explicit exemption approved: %s",
+ [pkg_key],
+ )
+}
diff --git a/exemptions/update_policy.py b/exemptions/update_policy.py
new file mode 100644
index 0000000..5a90caf
--- /dev/null
+++ b/exemptions/update_policy.py
@@ -0,0 +1,104 @@
+import os
+import json
+import requests
+from pathlib import Path
+
+# --------------------------------------------------
+# ENV CONFIG (GHA or local)
+# --------------------------------------------------
+
+WORKSPACE = os.environ["CLOUDSMITH_WORKSPACE"]
+ALLOW_POLICY_SLUG = os.environ["ALLOW_POLICY_SLUG"]
+API_TOKEN = os.environ["CLOUDSMITH_TOKEN"]
+
+BASE_DIR = Path(__file__).parent
+ALLOW_FILE = BASE_DIR / "allow.json"
+TEMPLATE_FILE = BASE_DIR / "templates" / "allowlist.rego.tpl"
+
+POLICY_URL = (
+ f"https://api.cloudsmith.io/v2/workspaces/"
+ f"{WORKSPACE}/policies/{ALLOW_POLICY_SLUG}/"
+)
+
+HEADERS = {
+ "Authorization": f"Bearer {API_TOKEN}",
+ "Content-Type": "application/json",
+}
+
+
+# --------------------------------------------------
+# LOAD DATA
+# --------------------------------------------------
+
+def load_allowlist():
+ if not ALLOW_FILE.exists():
+ raise Exception("allow.json missing")
+
+ data = json.loads(ALLOW_FILE.read_text())
+
+ if not isinstance(data, list):
+ raise Exception("allow.json must contain a list")
+
+ return sorted(set(data))
+
+
+# --------------------------------------------------
+# TEMPLATE RENDER
+# --------------------------------------------------
+
+def render_rego(entries):
+
+ template = TEMPLATE_FILE.read_text()
+
+ formatted = ",\n ".join(f'"{e}"' for e in entries)
+
+ return template.replace("{{ENTRIES}}", formatted)
+
+
+# --------------------------------------------------
+# CLOUDSMITH API
+# --------------------------------------------------
+
+def fetch_policy():
+ r = requests.get(POLICY_URL, headers=HEADERS)
+ r.raise_for_status()
+ return r.json()
+
+
+def update_policy(policy, rego):
+
+ payload = {
+ "name": policy["name"],
+ "description": policy.get("description"),
+ "rego": rego,
+ "enabled": policy["enabled"],
+ "precedence": policy["precedence"],
+ "is_terminal": policy["is_terminal"],
+ }
+
+ r = requests.put(POLICY_URL, headers=HEADERS, json=payload)
+ r.raise_for_status()
+
+
+# --------------------------------------------------
+# MAIN
+# --------------------------------------------------
+
+def main():
+
+ entries = load_allowlist()
+
+ if not entries:
+ raise Exception("Refusing to upload empty exemption list")
+
+ rego = render_rego(entries)
+
+ policy = fetch_policy()
+
+ update_policy(policy, rego)
+
+ print(f"✅ Applied {len(entries)} exemptions")
+
+
+if __name__ == "__main__":
+ main()
From 9094bee38c5e5f7d03bf57c521189b35f07d3a19 Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Wed, 11 Mar 2026 00:01:34 +0000
Subject: [PATCH 19/35] Change branch for exemption application workflow
---
.github/workflows/apply-exemptions.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/apply-exemptions.yml b/.github/workflows/apply-exemptions.yml
index ac2be07..ec06337 100644
--- a/.github/workflows/apply-exemptions.yml
+++ b/.github/workflows/apply-exemptions.yml
@@ -2,7 +2,7 @@ name: Apply EPM Exemptions
on:
push:
- branches: [main]
+ branches: [epm-baseline-refactor]
paths:
- "exemptions/allow.json"
From 1b326116ab16c6d9d47961467a352325b8d19c6b Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Wed, 11 Mar 2026 00:02:38 +0000
Subject: [PATCH 20/35] Add python:time:3.4.5 to allow.json
---
exemptions/allow.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/exemptions/allow.json b/exemptions/allow.json
index e6f76b4..d2b1bb3 100644
--- a/exemptions/allow.json
+++ b/exemptions/allow.json
@@ -7,4 +7,5 @@
"maven:jackson-core:2.19.1",
"maven:commons-io:2.11.0",
"maven:commons-io:2.6",
- "maven:commons-io:2.13.0"]
+ "maven:commons-io:2.13.0",
+ "python:time:3.4.5"]
From 783e2f6d2632a53a13381bbe5dd6574818ec594b Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Wed, 11 Mar 2026 00:10:50 +0000
Subject: [PATCH 21/35] Update high-risk-vulnerability.rego
---
baseline/high-risk-vulnerability.rego | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index 34d6363..72bc311 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -1,5 +1,6 @@
-# title: High Risk Vulnerability
-# description: Blocks packages with a CVSS score >= X where a fix is available.
+# METADATA
+# title: High-risk vulnerability block
+# description: Block packages with high-severity vulnerabilities (CVSS >= 7.0) when fixes are available
package cloudsmith
default match := false
From 65fcd3c2149a73c184949a24ae91bdef3e487cf0 Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Wed, 11 Mar 2026 09:56:31 +0000
Subject: [PATCH 22/35] Document GitOps workflow for policy exemptions
Added section on GitOps workflow for managing policy exemptions.
---
README.md | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/README.md b/README.md
index 1c528f2..87c46df 100644
--- a/README.md
+++ b/README.md
@@ -82,6 +82,16 @@ They are preserved for documentation history and migration reference.
---
+### exemptions/
+
+A GitOps workflow for managing policy exemptions.
+
+Rather than editing policies manually, exemptions are stored in `allow.json`, reviewed via Pull Requests, and automatically applied to Cloudsmith on merge via GitHub Actions.
+
+See the [Managing Exemptions](#managing-exemptions-gitops-workflow) section for details.
+
+---
+
## Policy Ordering & Precedence
Cloudsmith EPM evaluates policies in precedence order (lowest precedence runs first).
From 2d9911af5531ade77db8aea2f7bd39f052a617cf Mon Sep 17 00:00:00 2001
From: markmcmurray
Date: Tue, 10 Mar 2026 17:14:14 +0000
Subject: [PATCH 23/35] add regal step to lint rego
---
.github/workflows/opa-lint.yml | 9 +++++++++
.regal/config.yaml | 12 ++++++++++++
2 files changed, 21 insertions(+)
create mode 100644 .regal/config.yaml
diff --git a/.github/workflows/opa-lint.yml b/.github/workflows/opa-lint.yml
index 724ba7f..e4bbdc9 100644
--- a/.github/workflows/opa-lint.yml
+++ b/.github/workflows/opa-lint.yml
@@ -34,6 +34,15 @@ jobs:
echo "No advanced folder"
fi
+ - name: Install Regal
+ run: |
+ curl -L -o regal https://github.com/StyraInc/regal/releases/latest/download/regal_Linux_x86_64
+ chmod +x regal
+ sudo mv regal /usr/local/bin/regal
+
+ - name: Lint with Regal
+ run: regal lint baseline advanced
+
- name: Validate Policies
run: |
find baseline advanced -name "*.rego" -print0 | \
diff --git a/.regal/config.yaml b/.regal/config.yaml
new file mode 100644
index 0000000..53c769a
--- /dev/null
+++ b/.regal/config.yaml
@@ -0,0 +1,12 @@
+rules:
+ idiomatic:
+ directory-package-mismatch:
+ # All policies use `package cloudsmith` as required by the Cloudsmith EPM runtime
+ level: ignore
+ no-defined-entrypoint:
+ # Entrypoints are determined by the EPM system, not metadata annotations
+ level: ignore
+ style:
+ messy-rule:
+ # `default match := false` + `match if { ... }` is the required EPM policy interface
+ level: ignore
From ced740b91241690b546ee057799139f074bcd3da Mon Sep 17 00:00:00 2001
From: markmcmurray
Date: Tue, 10 Mar 2026 17:30:26 +0000
Subject: [PATCH 24/35] tidy rego based on regal output
---
advanced/huggingface-recipes/model_card.rego | 2 +-
advanced/huggingface-recipes/risky_files.rego | 9 +++++++--
advanced/huggingface-recipes/security_scan.rego | 2 +-
advanced/huggingface-recipes/trusted_publishers.rego | 2 +-
baseline/high-risk-vulnerability.rego | 4 ++--
baseline/malware-block.rego | 2 ++
6 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/advanced/huggingface-recipes/model_card.rego b/advanced/huggingface-recipes/model_card.rego
index c34c280..ad540cd 100644
--- a/advanced/huggingface-recipes/model_card.rego
+++ b/advanced/huggingface-recipes/model_card.rego
@@ -4,7 +4,7 @@ default match := false
pkg := input.v0.package
-hf_pkg if "huggingface" == pkg.format
+hf_pkg if pkg.format == "huggingface"
match if {
hf_pkg
diff --git a/advanced/huggingface-recipes/risky_files.rego b/advanced/huggingface-recipes/risky_files.rego
index b06a02c..2e81dc1 100644
--- a/advanced/huggingface-recipes/risky_files.rego
+++ b/advanced/huggingface-recipes/risky_files.rego
@@ -4,7 +4,7 @@ default match := false
pkg := input.v0.package
-hf_pkg if "huggingface" == pkg.format
+hf_pkg if pkg.format == "huggingface"
# Upstream packages are fetched by a system user
is_upstream_pkg if input.v0.package.uploader.slug == "cloudsmith-o6v"
@@ -20,7 +20,12 @@ is_upstream_pkg if input.v0.package.uploader.slug == "cloudsmith-o6v"
# SavedModel (.pb)
# GGUF (.gguf)
-risky_file_extensions := {".h5", ".hdf5", ".pdparams", ".keras", ".bin", ".pkl", ".dat", ".pt", ".pth", ".ckpt", ".npy", ".joblib", ".dill", ".pb", ".gguf", ".zip"}
+risky_file_extensions := {
+ ".bin", ".ckpt", ".dat", ".dill",
+ ".gguf", ".h5", ".hdf5", ".joblib",
+ ".keras", ".npy", ".pb", ".pdparams",
+ ".pkl", ".pt", ".pth", ".zip",
+}
match if {
hf_pkg
diff --git a/advanced/huggingface-recipes/security_scan.rego b/advanced/huggingface-recipes/security_scan.rego
index bfb8740..8c4d5b3 100644
--- a/advanced/huggingface-recipes/security_scan.rego
+++ b/advanced/huggingface-recipes/security_scan.rego
@@ -17,7 +17,7 @@ incomplete_or_unsafe if {
}
match if {
- "huggingface" == input.v0.package.format
+ input.v0.package.format == "huggingface"
is_upstream_pkg
incomplete_or_unsafe
}
diff --git a/advanced/huggingface-recipes/trusted_publishers.rego b/advanced/huggingface-recipes/trusted_publishers.rego
index 767c6ef..3570ab7 100644
--- a/advanced/huggingface-recipes/trusted_publishers.rego
+++ b/advanced/huggingface-recipes/trusted_publishers.rego
@@ -10,7 +10,7 @@ verified_publishers := {"amazon", "apple", "facebook", "FacebookAI", "google", "
publisher := split(input.v0.package.name, "/")[0]
match if {
- "huggingface" == input.v0.package.format
+ input.v0.package.format == "huggingface"
is_upstream_pkg
publisher in verified_publishers
}
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index 72bc311..eed26b5 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -3,6 +3,8 @@
# description: Block packages with high-severity vulnerabilities (CVSS >= 7.0) when fixes are available
package cloudsmith
+import rego.v1
+
default match := false
min_cvss := 7.0
@@ -32,13 +34,11 @@ cvss_scores_for_record(v) := top_level | affected_level if {
top_level := {s |
some sev in v.severity
sev.numerical_score != null
- s := sev.numerical_score
}
affected_level := {s |
some a in v.affected
some sev in a.severity
sev.numerical_score != null
- s := sev.numerical_score
}
}
diff --git a/baseline/malware-block.rego b/baseline/malware-block.rego
index 94a8d00..7abaddb 100644
--- a/baseline/malware-block.rego
+++ b/baseline/malware-block.rego
@@ -1,5 +1,7 @@
package cloudsmith
+import rego.v1
+
default match := false
match if {
From 10f6b28153b5c6d594da8d893247a10767f60701 Mon Sep 17 00:00:00 2001
From: markmcmurray
Date: Wed, 11 Mar 2026 10:26:58 +0000
Subject: [PATCH 25/35] relint high-risk-vulnerability
---
baseline/high-risk-vulnerability.rego | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index eed26b5..a90c45a 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -23,7 +23,7 @@ aliased_records[k] := records if {
records := {v | some v in input.v0.osv; k in vuln_keys(v)}
}
-blocking_keys := {k |
+blocking_keys contains k if {
some k, _ in aliased_records
not k in ignored
max_cvss_for_key(k) >= min_cvss
From 275f10a4f16d8a8de38d966cf8dc9846eac0f014 Mon Sep 17 00:00:00 2001
From: markmcmurray
Date: Wed, 11 Mar 2026 10:34:49 +0000
Subject: [PATCH 26/35] fix unsafe var introduced by linting fix
---
baseline/high-risk-vulnerability.rego | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index a90c45a..d388d77 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -31,11 +31,11 @@ blocking_keys contains k if {
}
cvss_scores_for_record(v) := top_level | affected_level if {
- top_level := {s |
+ top_level := {sev.numerical_score |
some sev in v.severity
sev.numerical_score != null
}
- affected_level := {s |
+ affected_level := {sev.numerical_score |
some a in v.affected
some sev in a.severity
sev.numerical_score != null
From d98b3453f362dc1b4b1807ec674f4a30b488953e Mon Sep 17 00:00:00 2001
From: Mark McMurray
Date: Wed, 11 Mar 2026 11:06:01 +0000
Subject: [PATCH 27/35] remove leftover rego.v1 imports
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
baseline/high-risk-vulnerability.rego | 2 --
baseline/malware-block.rego | 2 --
2 files changed, 4 deletions(-)
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index d388d77..6c0fc5d 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -3,8 +3,6 @@
# description: Block packages with high-severity vulnerabilities (CVSS >= 7.0) when fixes are available
package cloudsmith
-import rego.v1
-
default match := false
min_cvss := 7.0
diff --git a/baseline/malware-block.rego b/baseline/malware-block.rego
index 7abaddb..94a8d00 100644
--- a/baseline/malware-block.rego
+++ b/baseline/malware-block.rego
@@ -1,7 +1,5 @@
package cloudsmith
-import rego.v1
-
default match := false
match if {
From 804341be1404a58584622ce51dad3c07ca89bef0 Mon Sep 17 00:00:00 2001
From: markmcmurray
Date: Tue, 10 Mar 2026 20:10:12 +0000
Subject: [PATCH 28/35] add unit tests for rego
---
.github/workflows/opa-lint.yml | 10 ++
.../huggingface-recipes/model_card_test.rego | 36 ++++++
.../huggingface-recipes/risky_files_test.rego | 50 +++++++++
.../security_scan_test.rego | 45 ++++++++
.../trusted_publishers_test.rego | 39 +++++++
baseline/exact-allowlist-exemption_test.rego | 41 +++++++
baseline/exact-blocklist_test.rego | 41 +++++++
baseline/high-risk-vulnerability.rego | 27 ++++-
baseline/high-risk-vulnerability_test.rego | 103 ++++++++++++++++++
baseline/license-compliance_test.rego | 36 ++++++
baseline/malware-block_test.rego | 29 +++++
baseline/package-age-quarantine_test.rego | 26 +++++
baseline/package-age-restore_test.rego | 26 +++++
13 files changed, 505 insertions(+), 4 deletions(-)
create mode 100644 advanced/huggingface-recipes/model_card_test.rego
create mode 100644 advanced/huggingface-recipes/risky_files_test.rego
create mode 100644 advanced/huggingface-recipes/security_scan_test.rego
create mode 100644 advanced/huggingface-recipes/trusted_publishers_test.rego
create mode 100644 baseline/exact-allowlist-exemption_test.rego
create mode 100644 baseline/exact-blocklist_test.rego
create mode 100644 baseline/high-risk-vulnerability_test.rego
create mode 100644 baseline/license-compliance_test.rego
create mode 100644 baseline/malware-block_test.rego
create mode 100644 baseline/package-age-quarantine_test.rego
create mode 100644 baseline/package-age-restore_test.rego
diff --git a/.github/workflows/opa-lint.yml b/.github/workflows/opa-lint.yml
index e4bbdc9..a48a2d3 100644
--- a/.github/workflows/opa-lint.yml
+++ b/.github/workflows/opa-lint.yml
@@ -50,3 +50,13 @@ jobs:
echo "Checking $file"
opa check "$file"
done
+
+ - name: Unit Test Policies
+ run: |
+ find baseline advanced -name "*_test.rego" -print0 | \
+ while IFS= read -r -d '' test_file; do
+ policy="${test_file/_test/}"
+ echo "Testing $test_file"
+ opa test "$policy" "$test_file"
+ done
+
diff --git a/advanced/huggingface-recipes/model_card_test.rego b/advanced/huggingface-recipes/model_card_test.rego
new file mode 100644
index 0000000..e738108
--- /dev/null
+++ b/advanced/huggingface-recipes/model_card_test.rego
@@ -0,0 +1,36 @@
+package cloudsmith_test
+
+test_match_target_dataset if {
+ data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "huggingface",
+ "card": {"datasets": ["HuggingFaceTB/smollm-corpus"]},
+ }}}
+}
+
+test_match_dataset_among_multiple if {
+ data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "huggingface",
+ "card": {"datasets": ["other/dataset", "HuggingFaceTB/smollm-corpus"]},
+ }}}
+}
+
+test_no_match_wrong_format if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "python",
+ "card": {"datasets": ["HuggingFaceTB/smollm-corpus"]},
+ }}}
+}
+
+test_no_match_different_dataset if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "huggingface",
+ "card": {"datasets": ["some/other-dataset"]},
+ }}}
+}
+
+test_no_match_empty_datasets if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "huggingface",
+ "card": {"datasets": []},
+ }}}
+}
diff --git a/advanced/huggingface-recipes/risky_files_test.rego b/advanced/huggingface-recipes/risky_files_test.rego
new file mode 100644
index 0000000..c0e2b2c
--- /dev/null
+++ b/advanced/huggingface-recipes/risky_files_test.rego
@@ -0,0 +1,50 @@
+package cloudsmith_test
+
+_upstream_hf_pkg(files) := {"v0": {"package": {
+ "format": "huggingface",
+ "uploader": {"slug": "cloudsmith-o6v"},
+ "files": files,
+}}}
+
+test_match_bin_file if {
+ data.cloudsmith.match with input as _upstream_hf_pkg([{"file_extension": ".bin"}])
+}
+
+test_match_pkl_file if {
+ data.cloudsmith.match with input as _upstream_hf_pkg([{"file_extension": ".pkl"}])
+}
+
+test_match_gguf_file if {
+ data.cloudsmith.match with input as _upstream_hf_pkg([{"file_extension": ".gguf"}])
+}
+
+test_match_risky_among_safe_files if {
+ data.cloudsmith.match with input as _upstream_hf_pkg([
+ {"file_extension": ".txt"},
+ {"file_extension": ".bin"},
+ ])
+}
+
+test_no_match_safe_file_extension if {
+ not data.cloudsmith.match with input as _upstream_hf_pkg([{"file_extension": ".txt"}])
+}
+
+test_no_match_not_upstream if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "huggingface",
+ "uploader": {"slug": "other-user"},
+ "files": [{"file_extension": ".bin"}],
+ }}}
+}
+
+test_no_match_wrong_format if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "python",
+ "uploader": {"slug": "cloudsmith-o6v"},
+ "files": [{"file_extension": ".bin"}],
+ }}}
+}
+
+test_no_match_empty_files if {
+ not data.cloudsmith.match with input as _upstream_hf_pkg([])
+}
diff --git a/advanced/huggingface-recipes/security_scan_test.rego b/advanced/huggingface-recipes/security_scan_test.rego
new file mode 100644
index 0000000..6cf9191
--- /dev/null
+++ b/advanced/huggingface-recipes/security_scan_test.rego
@@ -0,0 +1,45 @@
+package cloudsmith_test
+
+_upstream_hf := {"format": "huggingface", "uploader": {"slug": "cloudsmith-o6v"}}
+
+test_match_incomplete_scan if {
+ data.cloudsmith.match with input as {"v0": {
+ "package": _upstream_hf,
+ "model_security": {"availability": "INCOMPLETE", "scan_summary": "SAFE"},
+ }}
+}
+
+test_match_unsafe_scan if {
+ data.cloudsmith.match with input as {"v0": {
+ "package": _upstream_hf,
+ "model_security": {"availability": "COMPLETE", "scan_summary": "UNSAFE"},
+ }}
+}
+
+test_match_incomplete_and_unsafe if {
+ data.cloudsmith.match with input as {"v0": {
+ "package": _upstream_hf,
+ "model_security": {"availability": "INCOMPLETE", "scan_summary": "UNSAFE"},
+ }}
+}
+
+test_no_match_complete_and_safe if {
+ not data.cloudsmith.match with input as {"v0": {
+ "package": _upstream_hf,
+ "model_security": {"availability": "COMPLETE", "scan_summary": "SAFE"},
+ }}
+}
+
+test_no_match_not_upstream if {
+ not data.cloudsmith.match with input as {"v0": {
+ "package": {"format": "huggingface", "uploader": {"slug": "other-user"}},
+ "model_security": {"availability": "INCOMPLETE", "scan_summary": "SAFE"},
+ }}
+}
+
+test_no_match_wrong_format if {
+ not data.cloudsmith.match with input as {"v0": {
+ "package": {"format": "python", "uploader": {"slug": "cloudsmith-o6v"}},
+ "model_security": {"availability": "INCOMPLETE", "scan_summary": "SAFE"},
+ }}
+}
diff --git a/advanced/huggingface-recipes/trusted_publishers_test.rego b/advanced/huggingface-recipes/trusted_publishers_test.rego
new file mode 100644
index 0000000..2ff67fc
--- /dev/null
+++ b/advanced/huggingface-recipes/trusted_publishers_test.rego
@@ -0,0 +1,39 @@
+package cloudsmith_test
+
+_upstream_hf_package(name) := {"v0": {"package": {
+ "format": "huggingface",
+ "uploader": {"slug": "cloudsmith-o6v"},
+ "name": name,
+}}}
+
+test_match_google_model if {
+ data.cloudsmith.match with input as _upstream_hf_package("google/gemma-2")
+}
+
+test_match_microsoft_model if {
+ data.cloudsmith.match with input as _upstream_hf_package("microsoft/phi-3")
+}
+
+test_match_openai_model if {
+ data.cloudsmith.match with input as _upstream_hf_package("openai/whisper-large")
+}
+
+test_no_match_unknown_publisher if {
+ not data.cloudsmith.match with input as _upstream_hf_package("unknown-org/some-model")
+}
+
+test_no_match_not_upstream if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "huggingface",
+ "uploader": {"slug": "other-user"},
+ "name": "google/gemma-2",
+ }}}
+}
+
+test_no_match_wrong_format if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "python",
+ "uploader": {"slug": "cloudsmith-o6v"},
+ "name": "google/some-package",
+ }}}
+}
diff --git a/baseline/exact-allowlist-exemption_test.rego b/baseline/exact-allowlist-exemption_test.rego
new file mode 100644
index 0000000..21e0580
--- /dev/null
+++ b/baseline/exact-allowlist-exemption_test.rego
@@ -0,0 +1,41 @@
+package cloudsmith_test
+
+test_match_allowlisted_python_package if {
+ data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "python",
+ "name": "example-lib",
+ "version": "1.2.3",
+ }}}
+}
+
+test_match_allowlisted_npm_package if {
+ data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "npm",
+ "name": "example-ui",
+ "version": "4.5.6",
+ }}}
+}
+
+test_no_match_wrong_version if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "python",
+ "name": "example-lib",
+ "version": "9.9.9",
+ }}}
+}
+
+test_no_match_wrong_format if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "ruby",
+ "name": "example-lib",
+ "version": "1.2.3",
+ }}}
+}
+
+test_reason_message if {
+ data.cloudsmith.reason["Explicit exemption approved: python:example-lib:1.2.3"] with input as {"v0": {"package": {
+ "format": "python",
+ "name": "example-lib",
+ "version": "1.2.3",
+ }}}
+}
diff --git a/baseline/exact-blocklist_test.rego b/baseline/exact-blocklist_test.rego
new file mode 100644
index 0000000..07ec90d
--- /dev/null
+++ b/baseline/exact-blocklist_test.rego
@@ -0,0 +1,41 @@
+package cloudsmith_test
+
+test_match_blocked_python_package if {
+ data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "python",
+ "name": "malicious-lib",
+ "version": "0.1.0",
+ }}}
+}
+
+test_match_blocked_npm_package if {
+ data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "npm",
+ "name": "compromised-ui",
+ "version": "9.9.9",
+ }}}
+}
+
+test_no_match_unlisted_package if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "python",
+ "name": "safe-lib",
+ "version": "1.0.0",
+ }}}
+}
+
+test_no_match_wrong_version if {
+ not data.cloudsmith.match with input as {"v0": {"package": {
+ "format": "python",
+ "name": "malicious-lib",
+ "version": "9.9.9",
+ }}}
+}
+
+test_reason_message if {
+ data.cloudsmith.reason["Blocked by explicit deny list: python:malicious-lib:0.1.0"] with input as {"v0": {"package": {
+ "format": "python",
+ "name": "malicious-lib",
+ "version": "0.1.0",
+ }}}
+}
diff --git a/baseline/high-risk-vulnerability.rego b/baseline/high-risk-vulnerability.rego
index 6c0fc5d..22524a8 100644
--- a/baseline/high-risk-vulnerability.rego
+++ b/baseline/high-risk-vulnerability.rego
@@ -48,16 +48,35 @@ max_cvss_for_key(k) := max(scores) if {
count(scores) > 0
} else := 0.0
-has_fix_for_key(k) if {
- some v in aliased_records[k]
+fixed_versions_for_key(k) := {e.fixed |
+ some v in input.v0.osv
+ k in vuln_keys(v)
some a in v.affected
some r in a.ranges
some e in r.events
- e.fixed
+ include_fix(e.fixed)
+}
+
+include_fix(f) if {
+ semver.is_valid(f)
+ semver.is_valid(input.v0.package.version)
+ semver.compare(f, input.v0.package.version) > 0
+}
+
+include_fix(f) if not semver.is_valid(f)
+
+include_fix(f) if {
+ semver.is_valid(f)
+ not semver.is_valid(input.v0.package.version)
+}
+
+has_fix_for_key(k) if {
+ count(fixed_versions_for_key(k)) > 0
}
reason contains msg if {
some k in blocking_keys
score := max_cvss_for_key(k)
- msg := sprintf("Blocking %v: CVSS %.1f, fix available", [k, score])
+ fixes := fixed_versions_for_key(k)
+ msg := sprintf("Blocking %v: CVSS %.1f, fix(es) available: %v", [k, score, concat(", ", fixes)])
}
diff --git a/baseline/high-risk-vulnerability_test.rego b/baseline/high-risk-vulnerability_test.rego
new file mode 100644
index 0000000..bce03bb
--- /dev/null
+++ b/baseline/high-risk-vulnerability_test.rego
@@ -0,0 +1,103 @@
+package cloudsmith_test
+
+import rego.v1
+
+# ── Shared fixtures ───────────────────────────────────────────────────────────
+
+_package := {"version": "1.0.0", "format": "python", "name": "example"}
+
+_vuln_with_fix(id, score) := {
+ "id": id,
+ "aliases": [],
+ "severity": [{"numerical_score": score}],
+ "affected": [{"severity": [], "ranges": [{"events": [{"fixed": "1.0.1"}]}]}],
+}
+
+_vuln_no_fix(id, score) := {
+ "id": id,
+ "aliases": [],
+ "severity": [{"numerical_score": score}],
+ "affected": [{"severity": [], "ranges": [{"events": []}]}],
+}
+
+# ── Blocking ──────────────────────────────────────────────────────────────────
+
+test_match_high_cvss_with_fix if {
+ data.cloudsmith.match with input as {"v0": {
+ "osv": [_vuln_with_fix("CVE-2021-1234", 9.0)],
+ "package": _package,
+ }}
+}
+
+test_match_at_cvss_threshold if {
+ data.cloudsmith.match with input as {"v0": {
+ "osv": [_vuln_with_fix("CVE-2021-1234", 7.0)],
+ "package": _package,
+ }}
+}
+
+test_match_cvss_from_affected_severity if {
+ data.cloudsmith.match with input as {"v0": {
+ "osv": [{
+ "id": "CVE-2021-1234",
+ "aliases": [],
+ "severity": [],
+ "affected": [{"severity": [{"numerical_score": 9.0}], "ranges": [{"events": [{"fixed": "1.0.1"}]}]}],
+ }],
+ "package": _package,
+ }}
+}
+
+test_match_uses_alias_as_key if {
+ data.cloudsmith.match with input as {"v0": {
+ "osv": [{
+ "id": "GHSA-xxxx-xxxx-xxxx",
+ "aliases": ["CVE-2021-9999"],
+ "severity": [{"numerical_score": 9.0}],
+ "affected": [{"severity": [], "ranges": [{"events": [{"fixed": "1.0.1"}]}]}],
+ }],
+ "package": _package,
+ }}
+}
+
+# ── Not blocking ──────────────────────────────────────────────────────────────
+
+test_no_match_below_cvss_threshold if {
+ not data.cloudsmith.match with input as {"v0": {
+ "osv": [_vuln_with_fix("CVE-2021-5678", 6.9)],
+ "package": _package,
+ }}
+}
+
+test_no_match_high_cvss_no_fix if {
+ not data.cloudsmith.match with input as {"v0": {
+ "osv": [_vuln_no_fix("CVE-2021-9999", 9.0)],
+ "package": _package,
+ }}
+}
+
+test_no_match_fix_already_applied if {
+ not data.cloudsmith.match with input as {"v0": {
+ "osv": [{
+ "id": "CVE-2021-0001",
+ "aliases": [],
+ "severity": [{"numerical_score": 9.0}],
+ "affected": [{"severity": [], "ranges": [{"events": [{"fixed": "0.9.0"}]}]}],
+ }],
+ "package": _package,
+ }}
+}
+
+test_no_match_empty_osv if {
+ not data.cloudsmith.match with input as {"v0": {"osv": [], "package": _package}}
+}
+
+# ── Reason messages ───────────────────────────────────────────────────────────
+
+test_reason_message if {
+ r := data.cloudsmith.reason with input as {"v0": {
+ "osv": [_vuln_with_fix("CVE-2021-1234", 9.0)],
+ "package": _package,
+ }}
+ r["Blocking CVE-2021-1234: CVSS 9.0, fix(es) available: 1.0.1"]
+}
diff --git a/baseline/license-compliance_test.rego b/baseline/license-compliance_test.rego
new file mode 100644
index 0000000..889e1b5
--- /dev/null
+++ b/baseline/license-compliance_test.rego
@@ -0,0 +1,36 @@
+package cloudsmith_test
+
+_input(spdx) := {"v0": {"package": {"license": {"oss_license": {"spdx_identifier": spdx}}}}}
+
+test_match_gpl3 if {
+ data.cloudsmith.match with input as _input("GPL-3.0-only")
+}
+
+test_match_agpl if {
+ data.cloudsmith.match with input as _input("AGPL-3.0-only")
+}
+
+test_match_lgpl if {
+ data.cloudsmith.match with input as _input("LGPL-2.1-only")
+}
+
+test_no_match_mit if {
+ not data.cloudsmith.match with input as _input("MIT")
+}
+
+test_no_match_apache if {
+ not data.cloudsmith.match with input as _input("Apache-2.0")
+}
+
+test_no_match_null_license if {
+ not data.cloudsmith.match with input as {"v0": {"package": {"license": null}}}
+}
+
+test_no_match_null_oss_license if {
+ not data.cloudsmith.match with input as {"v0": {"package": {"license": {"oss_license": null}}}}
+}
+
+test_reason_message if {
+ expected := "Copyleft license detected (GPL-3.0-only). Package blocked/quarantined per license policy."
+ data.cloudsmith.reason[expected] with input as _input("GPL-3.0-only")
+}
diff --git a/baseline/malware-block_test.rego b/baseline/malware-block_test.rego
new file mode 100644
index 0000000..5715bea
--- /dev/null
+++ b/baseline/malware-block_test.rego
@@ -0,0 +1,29 @@
+package cloudsmith_test
+
+test_match_malicious_osv_id if {
+ data.cloudsmith.match with input as {"v0": {"osv": [{"id": "MAL-1234"}]}}
+}
+
+test_match_multiple_osv_one_malicious if {
+ data.cloudsmith.match with input as {"v0": {"osv": [
+ {"id": "CVE-2021-0001"},
+ {"id": "MAL-5678"},
+ ]}}
+}
+
+test_no_match_non_malicious_osv if {
+ not data.cloudsmith.match with input as {"v0": {"osv": [{"id": "CVE-2021-1234"}]}}
+}
+
+test_no_match_empty_osv if {
+ not data.cloudsmith.match with input as {"v0": {"osv": []}}
+}
+
+test_no_match_null_osv if {
+ not data.cloudsmith.match with input as {"v0": {"osv": null}}
+}
+
+test_reason_message if {
+ r := data.cloudsmith.reason with input as {"v0": {"osv": [{"id": "MAL-1234"}]}}
+ count(r) > 0
+}
diff --git a/baseline/package-age-quarantine_test.rego b/baseline/package-age-quarantine_test.rego
new file mode 100644
index 0000000..4e270ff
--- /dev/null
+++ b/baseline/package-age-quarantine_test.rego
@@ -0,0 +1,26 @@
+package cloudsmith_test
+
+_input(published_at) := {"v0": {"package": {"upstream_metadata": {"published_at": published_at}}}}
+
+# "2099-01-01" is always in the future, so always within the past 3 days relative to now
+test_match_recently_published if {
+ data.cloudsmith.match with input as _input("2099-01-01T00:00:00Z")
+}
+
+# "2020-01-01" is well in the past, so always older than 3 days
+test_no_match_old_package if {
+ not data.cloudsmith.match with input as _input("2020-01-01T00:00:00Z")
+}
+
+test_no_match_null_upstream_metadata if {
+ not data.cloudsmith.match with input as {"v0": {"package": {"upstream_metadata": null}}}
+}
+
+test_no_match_null_published_at if {
+ not data.cloudsmith.match with input as {"v0": {"package": {"upstream_metadata": {"published_at": null}}}}
+}
+
+test_reason_message if {
+ r := data.cloudsmith.reason with input as _input("2099-01-01T00:00:00Z")
+ count(r) > 0
+}
diff --git a/baseline/package-age-restore_test.rego b/baseline/package-age-restore_test.rego
new file mode 100644
index 0000000..d37381b
--- /dev/null
+++ b/baseline/package-age-restore_test.rego
@@ -0,0 +1,26 @@
+package cloudsmith_test
+
+_input(published_at) := {"v0": {"package": {"upstream_metadata": {"published_at": published_at}}}}
+
+# "2020-01-01" is well in the past, so always older than 3 days
+test_match_old_package if {
+ data.cloudsmith.match with input as _input("2020-01-01T00:00:00Z")
+}
+
+# "2099-01-01" is always in the future, so always within the past 3 days relative to now
+test_no_match_recently_published if {
+ not data.cloudsmith.match with input as _input("2099-01-01T00:00:00Z")
+}
+
+test_no_match_null_upstream_metadata if {
+ not data.cloudsmith.match with input as {"v0": {"package": {"upstream_metadata": null}}}
+}
+
+test_no_match_null_published_at if {
+ not data.cloudsmith.match with input as {"v0": {"package": {"upstream_metadata": {"published_at": null}}}}
+}
+
+test_reason_message if {
+ r := data.cloudsmith.reason with input as _input("2020-01-01T00:00:00Z")
+ count(r) > 0
+}
From d934db41d0235effa76a208527d606e48d1d105b Mon Sep 17 00:00:00 2001
From: Mark McMurray
Date: Wed, 11 Mar 2026 11:20:39 +0000
Subject: [PATCH 29/35] Apply suggestion from @Copilot
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
baseline/high-risk-vulnerability_test.rego | 2 --
1 file changed, 2 deletions(-)
diff --git a/baseline/high-risk-vulnerability_test.rego b/baseline/high-risk-vulnerability_test.rego
index bce03bb..f1e89c8 100644
--- a/baseline/high-risk-vulnerability_test.rego
+++ b/baseline/high-risk-vulnerability_test.rego
@@ -1,7 +1,5 @@
package cloudsmith_test
-import rego.v1
-
# ── Shared fixtures ───────────────────────────────────────────────────────────
_package := {"version": "1.0.0", "format": "python", "name": "example"}
From 3e6137e8d6aac2a1d7bf0bf25c2b1b48b26fc7c6 Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Wed, 11 Mar 2026 11:38:02 +0000
Subject: [PATCH 30/35] Update baseline/license-compliance.rego
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
baseline/license-compliance.rego | 40 ++++++++++++++++++++++++--------
1 file changed, 30 insertions(+), 10 deletions(-)
diff --git a/baseline/license-compliance.rego b/baseline/license-compliance.rego
index e629aa5..6a9a38b 100644
--- a/baseline/license-compliance.rego
+++ b/baseline/license-compliance.rego
@@ -3,16 +3,36 @@ package cloudsmith
default match := false
copyleft := {
- "GPL-1.0-only", "GPL-1.0-or-later", "GPL-2.0", "GPL-2.0-only",
- "GPL-2.0-or-later", "GPL-3.0", "GPL-3.0-only", "GPL-3.0-or-later",
- "LGPL-2.0", "LGPL-2.0-only", "LGPL-2.0-or-later",
- "LGPL-2.1", "LGPL-2.1-only", "LGPL-2.1-or-later",
- "LGPL-3.0", "LGPL-3.0-only", "LGPL-3.0-or-later",
- "AGPL-3.0", "AGPL-3.0-only", "AGPL-3.0-or-later",
- "MPL-1.0", "MPL-1.1", "MPL-2.0",
- "CDDL-1.0", "CDDL-1.1",
- "EPL-1.0", "EPL-2.0",
- "OSL-1.0", "OSL-2.0", "OSL-3.0",
+ "GPL-1.0-only",
+ "GPL-1.0-or-later",
+ "GPL-2.0",
+ "GPL-2.0-only",
+ "GPL-2.0-or-later",
+ "GPL-3.0",
+ "GPL-3.0-only",
+ "GPL-3.0-or-later",
+ "LGPL-2.0",
+ "LGPL-2.0-only",
+ "LGPL-2.0-or-later",
+ "LGPL-2.1",
+ "LGPL-2.1-only",
+ "LGPL-2.1-or-later",
+ "LGPL-3.0",
+ "LGPL-3.0-only",
+ "LGPL-3.0-or-later",
+ "AGPL-3.0",
+ "AGPL-3.0-only",
+ "AGPL-3.0-or-later",
+ "MPL-1.0",
+ "MPL-1.1",
+ "MPL-2.0",
+ "CDDL-1.0",
+ "CDDL-1.1",
+ "EPL-1.0",
+ "EPL-2.0",
+ "OSL-1.0",
+ "OSL-2.0",
+ "OSL-3.0",
"SSPL-1.0",
}
From c85bc8a40723978a31ace1cf20ee6ac94dc4af57 Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Wed, 11 Mar 2026 11:38:36 +0000
Subject: [PATCH 31/35] Update exemptions/update_policy.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
exemptions/update_policy.py | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/exemptions/update_policy.py b/exemptions/update_policy.py
index 5a90caf..48a7a38 100644
--- a/exemptions/update_policy.py
+++ b/exemptions/update_policy.py
@@ -46,12 +46,29 @@ def load_allowlist():
# TEMPLATE RENDER
# --------------------------------------------------
+def _validate_allowlist_entry(entry):
+ """
+ Validate that an allowlist entry is a string in 'format:name:version' form.
+ """
+ if not isinstance(entry, str):
+ raise ValueError("Allowlist entries must be strings in 'format:name:version' form")
+
+ # Require exactly two ':' separators to roughly enforce 'format:name:version'.
+ if entry.count(":") != 2:
+ raise ValueError(f"Invalid allowlist entry '{entry}'; expected 'format:name:version'")
+
+ return entry
+
+
def render_rego(entries):
template = TEMPLATE_FILE.read_text()
- formatted = ",\n ".join(f'"{e}"' for e in entries)
+ # Validate entries and ensure they are in the expected shape.
+ validated_entries = [_validate_allowlist_entry(e) for e in entries]
+ # Use JSON encoding to safely escape values for inclusion in Rego.
+ formatted = ",\n ".join(json.dumps(e) for e in validated_entries)
return template.replace("{{ENTRIES}}", formatted)
From 191d72bf90a7e38a274346f773d1f179ed7c96b4 Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Wed, 11 Mar 2026 11:39:10 +0000
Subject: [PATCH 32/35] Update README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 87c46df..229fcc6 100644
--- a/README.md
+++ b/README.md
@@ -86,7 +86,7 @@ They are preserved for documentation history and migration reference.
A GitOps workflow for managing policy exemptions.
-Rather than editing policies manually, exemptions are stored in `allow.json`, reviewed via Pull Requests, and automatically applied to Cloudsmith on merge via GitHub Actions.
+Rather than editing policies manually, exemptions are stored in `allow.json`, reviewed via Pull Requests, and automatically applied to Cloudsmith by GitHub Actions when changes are pushed to the `epm-baseline-refactor` branch.
See the [Managing Exemptions](#managing-exemptions-gitops-workflow) section for details.
From 94e8c0e912c8ca408a72a42929a28fa5c68dfa8e Mon Sep 17 00:00:00 2001
From: Ciara Carey <84123925+ciaracarey@users.noreply.github.com>
Date: Wed, 11 Mar 2026 11:40:09 +0000
Subject: [PATCH 33/35] Update .github/workflows/apply-exemptions.yml
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.github/workflows/apply-exemptions.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/apply-exemptions.yml b/.github/workflows/apply-exemptions.yml
index ec06337..8996f2a 100644
--- a/.github/workflows/apply-exemptions.yml
+++ b/.github/workflows/apply-exemptions.yml
@@ -2,7 +2,7 @@ name: Apply EPM Exemptions
on:
push:
- branches: [epm-baseline-refactor]
+ branches: [epm-baseline-refactor, main]
paths:
- "exemptions/allow.json"
From 2cdfee80ad2d4a2a4eef6efa35e74ac27690d80f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 11 Mar 2026 11:49:04 +0000
Subject: [PATCH 34/35] Initial plan
From b4364900116e0df5c6597d6e0fec4581a96aa133 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 11 Mar 2026 11:52:08 +0000
Subject: [PATCH 35/35] Add explicit timeout and retry/backoff to Cloudsmith
API requests
Co-authored-by: ciaracarey <84123925+ciaracarey@users.noreply.github.com>
---
exemptions/update_policy.py | 30 ++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/exemptions/update_policy.py b/exemptions/update_policy.py
index 48a7a38..6a08213 100644
--- a/exemptions/update_policy.py
+++ b/exemptions/update_policy.py
@@ -2,6 +2,8 @@
import json
import requests
from pathlib import Path
+from requests.adapters import HTTPAdapter
+from urllib3.util.retry import Retry
# --------------------------------------------------
# ENV CONFIG (GHA or local)
@@ -25,6 +27,30 @@
"Content-Type": "application/json",
}
+# Seconds to wait for a response from the Cloudsmith API before giving up.
+REQUEST_TIMEOUT = 30
+
+# Retry up to 3 times on connection errors and 5xx responses, with exponential
+# backoff (0.5 s, 1 s, 2 s) so transient failures resolve quickly.
+_retry_strategy = Retry(
+ total=3,
+ backoff_factor=0.5,
+ status_forcelist=[500, 502, 503, 504],
+ allowed_methods=["GET", "PUT"],
+)
+
+
+def _build_session() -> requests.Session:
+ session = requests.Session()
+ adapter = HTTPAdapter(max_retries=_retry_strategy)
+ # Only mount on HTTPS to prevent tokens being sent in cleartext.
+ session.mount("https://", adapter)
+ return session
+
+
+# Single shared session so the retry adapter is not recreated per request.
+_session = _build_session()
+
# --------------------------------------------------
# LOAD DATA
@@ -77,7 +103,7 @@ def render_rego(entries):
# --------------------------------------------------
def fetch_policy():
- r = requests.get(POLICY_URL, headers=HEADERS)
+ r = _session.get(POLICY_URL, headers=HEADERS, timeout=REQUEST_TIMEOUT)
r.raise_for_status()
return r.json()
@@ -93,7 +119,7 @@ def update_policy(policy, rego):
"is_terminal": policy["is_terminal"],
}
- r = requests.put(POLICY_URL, headers=HEADERS, json=payload)
+ r = _session.put(POLICY_URL, headers=HEADERS, json=payload, timeout=REQUEST_TIMEOUT)
r.raise_for_status()