diff --git a/.github/workflows/apply-exemptions.yml b/.github/workflows/apply-exemptions.yml
new file mode 100644
index 0000000..8996f2a
--- /dev/null
+++ b/.github/workflows/apply-exemptions.yml
@@ -0,0 +1,39 @@
+name: Apply EPM Exemptions
+
+on:
+ push:
+ branches: [epm-baseline-refactor, 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/.github/workflows/opa-lint.yml b/.github/workflows/opa-lint.yml
new file mode 100644
index 0000000..a48a2d3
--- /dev/null
+++ b/.github/workflows/opa-lint.yml
@@ -0,0 +1,62 @@
+name: OPA Lint & Validate
+
+on:
+ pull_request:
+ push:
+ branches: [main, epm-baseline-refactor]
+
+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: |
+ 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: 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 | \
+ while IFS= read -r -d '' file; do
+ 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/.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/.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
diff --git a/README.md b/README.md
index b28f95f..229fcc6 100644
--- a/README.md
+++ b/README.md
@@ -1,537 +1,163 @@
# 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"
-```
+This repository contains curated, production-ready Open Policy Agent (OPA) policies for use with Cloudsmith Enterprise Policy Management (EPM).
-Note: If you have a tagging response action attached to your policy, you could tag the package with ```non-compliant-license``` for further review:
+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
-### 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
-```
+These policies are intended to be readable, predictable, and suitable for enterprise environments.
-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```:
+## Repository Structure
```
-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"
+baseline/
+advanced/
+legacy/
+exemptions/
+ allow.json
+ update_policy.py
+ templates/
+ allowlist.rego.tpl
+.github/workflows/
+ opa-lint.yml
+ apply-exemptions.yml
```
-***
+### baseline/
-### 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
-```
+Recommended secure defaults for production environments.
-If a package has ```upstream``` and not ```approved``` --> allowed
-
-If a package has ```approved``` --> blocked (even if upstream is present)
+These policies address common supply chain security requirements such as:
+- Malware blocking
+- High-risk vulnerability control (CVSS / EPSS)
+- License compliance
+- Workflows using package age
+- Explicit allowlist and blocklist handling
-***
+If you are deploying EPM in a new workspace, start here.
-### 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.
+### advanced/
-```
-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"
-```
+Optional or format-specific policies that provide deeper governance controls.
-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:
+These may include:
-
+- Base image origin enforcement
+- SBOM-based controls
+- Model governance policies
+- Specialized workflow patterns
+Advanced policies are production-ready but not universally required.
-***
+---
-### 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
-```
+### legacy/
-This CVE was published on 24 April 2025 - much older than the 30 day threshold specified in the policy.
+Historical recipes and experimental policies retained for reference.
-```
-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"
-```
-
-***
+Policies in this directory:
+- May use older patterns
+- May not reflect current schema or best practices
+- Are not recommended for new deployments
-### 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
-```
+They are preserved for documentation history and migration reference.
-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
+### 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 by GitHub Actions when changes are pushed to the `epm-baseline-refactor` branch.
-***
+See the [Managing Exemptions](#managing-exemptions-gitops-workflow) section for details.
+---
-### 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
-```
+## Policy Ordering & Precedence
-***
+Cloudsmith EPM evaluates policies in precedence order (lowest precedence runs first).
-### 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
-```
+All policies in this repository are designed to be non-terminal and composable.
-```
-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"
-```
+A recommended precedence pattern for baseline deployments is:
-
+1. Package age restore (make eligible packages available again)
+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)
+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.
-***
+For full EPM documentation, see:
+https://docs.cloudsmith.com/supply-chain-security/epm
+---
-### 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
-```
+## Managing Exemptions (GitOps Workflow)
-```
-pip download
-cloudsmith push python $CLOUDSMITH_ORG/$CLOUDSMITH_REPO .whl -k "$CLOUDSMITH_API_KEY"
-```
+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`:
-### 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
+```json
+[
+ "python:requests:2.6.4",
+ "npm:left-pad:1.3.0"
+]
```
-**Trade-off:** While the tag remains, new CVEs on that package won’t trigger quarantine. You'll need to review those tags regularly.
+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
-### 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.
-
-***
+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.
-### 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
-```
-***
+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.
-### 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
+## Deployment
-This policy lets you maintain an **exact deny list** of suspicious or malicious packages across formats using the key pattern:
+Policies can be deployed using the Cloudsmith API or CLI.
-`::` (for example: `npm:@alloc/quick-lru:5.2.0`, `python:requests:2.6.0`).
+Refer to the official documentation for EPM policy management and simulation:
-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.
+https://docs.cloudsmith.com/supply-chain-security/epm
-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
+This repository is the single source of truth for:
-### Hugging Face recipes
+- Policy templates
+- Documentation examples
+- Secure baseline recommendations
+- Enterprise EPM enablement guidance
-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/).
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/advanced/huggingface-recipes/model_card.rego b/advanced/huggingface-recipes/model_card.rego
new file mode 100644
index 0000000..ad540cd
--- /dev/null
+++ b/advanced/huggingface-recipes/model_card.rego
@@ -0,0 +1,12 @@
+package cloudsmith
+
+default match := false
+
+pkg := input.v0.package
+
+hf_pkg if pkg.format == "huggingface"
+
+match if {
+ hf_pkg
+ "HuggingFaceTB/smollm-corpus" in pkg.card.datasets
+}
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/huggingface-recipes/risky_files.rego b/advanced/huggingface-recipes/risky_files.rego
similarity index 55%
rename from huggingface-recipes/risky_files.rego
rename to advanced/huggingface-recipes/risky_files.rego
index fce69a8..2e81dc1 100644
--- a/huggingface-recipes/risky_files.rego
+++ b/advanced/huggingface-recipes/risky_files.rego
@@ -1,12 +1,10 @@
package cloudsmith
-import rego.v1
-
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"
@@ -22,11 +20,16 @@ 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
- 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/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/huggingface-recipes/security_scan.rego b/advanced/huggingface-recipes/security_scan.rego
similarity index 64%
rename from huggingface-recipes/security_scan.rego
rename to advanced/huggingface-recipes/security_scan.rego
index b239629..8c4d5b3 100644
--- a/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
@@ -11,15 +9,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
+ input.v0.package.format == "huggingface"
+ is_upstream_pkg
+ incomplete_or_unsafe
}
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/huggingface-recipes/trusted_publishers.rego b/advanced/huggingface-recipes/trusted_publishers.rego
similarity index 74%
rename from huggingface-recipes/trusted_publishers.rego
rename to advanced/huggingface-recipes/trusted_publishers.rego
index dc05d6c..3570ab7 100644
--- a/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
@@ -12,7 +10,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
+ input.v0.package.format == "huggingface"
+ is_upstream_pkg
+ publisher in verified_publishers
}
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.rego b/baseline/exact-allowlist-exemption.rego
new file mode 100644
index 0000000..1932118
--- /dev/null
+++ b/baseline/exact-allowlist-exemption.rego
@@ -0,0 +1,27 @@
+package cloudsmith
+
+default match := false
+
+pkg := input.v0.package
+
+allowlist := {
+ "python:example-lib:1.2.3",
+ "npm:example-ui:4.5.6",
+}
+
+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/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.rego b/baseline/exact-blocklist.rego
new file mode 100644
index 0000000..2ffea4d
--- /dev/null
+++ b/baseline/exact-blocklist.rego
@@ -0,0 +1,27 @@
+package cloudsmith
+
+default match := false
+
+pkg := input.v0.package
+
+blocklist := {
+ "python:malicious-lib:0.1.0",
+ "npm:compromised-ui:9.9.9",
+}
+
+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
+}
+
+reason[msg] if {
+ match
+ msg := sprintf(
+ "Blocked by explicit deny list: %s",
+ [pkg_key],
+ )
+}
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
new file mode 100644
index 0000000..22524a8
--- /dev/null
+++ b/baseline/high-risk-vulnerability.rego
@@ -0,0 +1,82 @@
+# 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
+
+min_cvss := 7.0
+
+ignored := {}
+
+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 contains k if {
+ some k, _ in aliased_records
+ not k in ignored
+ max_cvss_for_key(k) >= min_cvss
+ has_fix_for_key(k)
+}
+
+cvss_scores_for_record(v) := top_level | affected_level if {
+ top_level := {sev.numerical_score |
+ some sev in v.severity
+ sev.numerical_score != null
+ }
+ affected_level := {sev.numerical_score |
+ some a in v.affected
+ some sev in a.severity
+ sev.numerical_score != null
+ }
+}
+
+max_cvss_for_key(k) := max(scores) if {
+ scores := {s |
+ some v in aliased_records[k]
+ some s in cvss_scores_for_record(v)
+ }
+ count(scores) > 0
+} else := 0.0
+
+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
+ 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)
+ 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..f1e89c8
--- /dev/null
+++ b/baseline/high-risk-vulnerability_test.rego
@@ -0,0 +1,101 @@
+package cloudsmith_test
+
+# ── 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.rego b/baseline/license-compliance.rego
new file mode 100644
index 0000000..6a9a38b
--- /dev/null
+++ b/baseline/license-compliance.rego
@@ -0,0 +1,55 @@
+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",
+ "SSPL-1.0",
+}
+
+match if {
+ 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
+}
+
+reason[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],
+ )
+}
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.rego b/baseline/malware-block.rego
new file mode 100644
index 0000000..94a8d00
--- /dev/null
+++ b/baseline/malware-block.rego
@@ -0,0 +1,22 @@
+package cloudsmith
+
+default match := false
+
+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[msg] if {
+ match
+ msg := sprintf(
+ "Detected %d malicious vulnerability ID(s): %v",
+ [count(malicious_packages), malicious_packages],
+ )
+}
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.rego b/baseline/package-age-quarantine.rego
new file mode 100644
index 0000000..e4d33ea
--- /dev/null
+++ b/baseline/package-age-quarantine.rego
@@ -0,0 +1,29 @@
+package cloudsmith
+
+default match := false
+
+pkg := input.v0.package
+
+within_past_days := 3
+
+match if below_minimum_release_age
+
+below_minimum_release_age if {
+ pkg.upstream_metadata != null
+ pkg.upstream_metadata.published_at != null
+
+ publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
+
+ days_ago := 0 - within_past_days
+ cutoff := time.add_date(time.now_ns(), 0, 0, days_ago)
+
+ publish_date >= cutoff
+}
+
+reason[msg] if {
+ below_minimum_release_age
+ msg := sprintf(
+ "Package published within last %v days — below minimum release age",
+ [within_past_days],
+ )
+}
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.rego b/baseline/package-age-restore.rego
new file mode 100644
index 0000000..0b30ce3
--- /dev/null
+++ b/baseline/package-age-restore.rego
@@ -0,0 +1,29 @@
+package cloudsmith
+
+default match := false
+
+pkg := input.v0.package
+
+within_past_days := 3
+
+match if above_minimum_release_age
+
+above_minimum_release_age if {
+ pkg.upstream_metadata != null
+ pkg.upstream_metadata.published_at != null
+
+ publish_date := time.parse_rfc3339_ns(pkg.upstream_metadata.published_at)
+
+ days_ago := 0 - within_past_days
+ cutoff := time.add_date(time.now_ns(), 0, 0, days_ago)
+
+ publish_date < cutoff
+}
+
+reason[msg] if {
+ above_minimum_release_age
+ msg := sprintf(
+ "Package older than %v days — meets minimum release age",
+ [within_past_days],
+ )
+}
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
+}
diff --git a/exemptions/allow.json b/exemptions/allow.json
new file mode 100644
index 0000000..d2b1bb3
--- /dev/null
+++ b/exemptions/allow.json
@@ -0,0 +1,11 @@
+[
+ "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",
+ "python:time:3.4.5"]
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..6a08213
--- /dev/null
+++ b/exemptions/update_policy.py
@@ -0,0 +1,147 @@
+import os
+import json
+import requests
+from pathlib import Path
+from requests.adapters import HTTPAdapter
+from urllib3.util.retry import Retry
+
+# --------------------------------------------------
+# 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",
+}
+
+# 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
+# --------------------------------------------------
+
+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 _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()
+
+ # 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)
+
+
+# --------------------------------------------------
+# CLOUDSMITH API
+# --------------------------------------------------
+
+def fetch_policy():
+ r = _session.get(POLICY_URL, headers=HEADERS, timeout=REQUEST_TIMEOUT)
+ 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 = _session.put(POLICY_URL, headers=HEADERS, json=payload, timeout=REQUEST_TIMEOUT)
+ 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()
diff --git a/huggingface-recipes/model_card.rego b/huggingface-recipes/model_card.rego
deleted file mode 100644
index 3d412fb..0000000
--- a/huggingface-recipes/model_card.rego
+++ /dev/null
@@ -1,14 +0,0 @@
-package cloudsmith
-
-import rego.v1
-
-default match := false
-
-pkg := input.v0.package
-
-hf_pkg if "huggingface" == pkg.format
-
-match if {
- hf_pkg
- "HuggingFaceTB/smollm-corpus" in pkg.card.datasets
-}
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