Custom Policies with OPA Rego
Hextrap's built-in security features — allow lists, deny lists, soak time, typosquat detection — cover the most common supply chain security needs. But every organization has unique requirements. Custom policies let you write your own filtering rules using Open Policy Agent (OPA) Rego, a purpose-built policy language used across the cloud-native ecosystem.
Rego is a declarative policy language designed by the Open Policy Agent project. It's used by Kubernetes admission controllers, Terraform Cloud, Conftest, and hundreds of other tools. If you've written policies for any of these, you already know Rego.
What can you do with custom policies?
- Enforce a license allowlist (only permit MIT, Apache-2.0, BSD-3-Clause, etc.)
- Set minimum security, quality, or maintenance score thresholds
- Block packages with unresolved security advisories (CVEs)
- Limit transitive dependency count to reduce supply chain surface area
- Require SLSA build provenance for all packages
- Block packages below a weekly download threshold
- Combine any of the above into a single, cohesive policy
Getting Started
- Navigate to your firewall in the dashboard
- Click the Custom Policy tab
- Toggle Custom Policy Status to enabled
- Write your Rego policy in the built-in editor (with syntax highlighting)
- Click Save Policy
Once enabled, every package installation request through this firewall is evaluated against your policy.
Policies deny by default — if allow evaluates to false, the package is blocked
and the result is recorded in the activity log.
You can write and save a policy while the toggle is off. The policy is only evaluated when the toggle is enabled. This lets you draft and refine policies before activating them.
How Policies Work
Every custom policy must follow these conventions:
- The package declaration must be
package hextrap.firewall - Import the Rego v1 syntax with
import rego.v1 - Include
default allow := falseso packages are denied unless explicitly permitted - Write
allowrules that evaluate totruewhen a package should be permitted
When a package install request comes through your firewall:
- Hextrap constructs an input document containing the package's metadata, scores, dependencies, and community data
- Your Rego policy is evaluated with this input
- If
allowevaluates totrue, the package is allowed (by this rule — other firewall rules still apply) - If
allowevaluates tofalse, the package is blocked with statusrego_policy_blocked
Minimal Policy Structure
package hextrap.firewall
import rego.v1
default allow := false
# Allow packages with a security score of 50 or higher
allow if {
input.scores.security >= 50
}
# Also allow packages that haven't been scored yet
allow if {
input.scores.security == 0
}
Each allow rule is a separate check. Multiple allow rules act as an OR —
if any rule evaluates to true, the package is allowed. Conditions within a single rule act as an AND —
all conditions must be true for the rule to grant access.
Input Reference
Your policy receives a structured input document with four top-level categories. Every field listed below is available for use in your policy conditions.
Package Metadata
Basic information about the package being installed.
| Field | Type | Description |
|---|---|---|
input.package.name |
string | Package name (e.g., "requests") |
input.package.version |
string | Version being installed (e.g., "2.31.0") |
input.package.registry |
string | Registry: "pypi", "npm", or "go" |
input.package.published |
string | ISO 8601 publish timestamp |
input.package.license |
string | SPDX license identifier (e.g., "MIT", "Apache-2.0") |
input.package.is_deprecated |
bool | Package has been deprecated by its author |
input.package.is_malicious |
bool | Package identified as malicious |
input.package.is_unmaintained |
bool | Package appears unmaintained or abandoned |
input.package.first_release_date |
string | ISO 8601 date of the package's first release |
input.package.last_release_date |
string | ISO 8601 date of the most recent release |
Scores
Computed scores based on deps.dev data, OpenSSF Scorecards, and Hextrap analysis. All scores range from 0 to 100 unless noted.
| Field | Type | Description |
|---|---|---|
input.scores.security |
number | Security score (0-100). Based on advisories, SLSA provenance, vulnerability checks, and dependency risk. |
input.scores.quality |
number | Code quality score (0-100). Based on metadata completeness, OpenSSF code review checks, CI/CD practices. |
input.scores.maintenance |
number | Maintenance score (0-100). Based on release recency, commit activity, issue response ratio, project longevity. |
input.scores.overall |
number | Weighted overall score: security × 0.40 + quality × 0.25 + maintenance × 0.35 |
input.scores.scorecard_overall |
number | OpenSSF Scorecard score (0-10 scale, not 0-100) |
Dependencies
Dependency tree and supply chain provenance data.
| Field | Type | Description |
|---|---|---|
input.deps.dependency_count |
number | Number of direct dependencies |
input.deps.transitive_dependency_count |
number | Total transitive dependencies (full dependency tree) |
input.deps.advisory_count |
number | Number of known security advisories (CVEs) affecting this package |
input.deps.has_slsa_provenance |
bool | Package has verified SLSA build provenance |
Community
Community adoption and activity signals.
| Field | Type | Description |
|---|---|---|
input.community.weekly_downloads |
number | Weekly download count from the registry |
input.community.github_stars |
number | GitHub star count (0 if no linked repository) |
If a package hasn't been enriched with metadata yet, numeric fields will be 0,
string fields will be "", and boolean fields will be false.
Since policies deny by default, make sure your allow rules account for unenriched packages.
For example, include a rule like allow if { input.scores.security == 0 } to permit
packages that haven't been scored yet, rather than blocking them.
Example Policies
License Allowlist
Only allow packages with approved open-source licenses. Packages with unknown licenses (not yet enriched) are also permitted.
package hextrap.firewall
import rego.v1
default allow := false
allowed_licenses := {"MIT", "Apache-2.0", "BSD-3-Clause", "BSD-2-Clause", "ISC", "MPL-2.0"}
# Allow packages with an approved license
allow if {
input.package.license in allowed_licenses
}
# Allow packages where license is not yet known
allow if {
input.package.license == ""
}
Minimum Security Score
Require packages to meet a minimum security score threshold.
package hextrap.firewall
import rego.v1
default allow := false
minimum_security_score := 60
# Allow if security score meets the threshold
allow if {
input.scores.security >= minimum_security_score
}
# Allow packages that haven't been scored yet
allow if {
input.scores.security == 0
}
Block Packages with Advisories
Block any package that has unresolved security advisories (CVEs).
package hextrap.firewall
import rego.v1
default allow := false
# Allow packages with no known advisories
allow if {
input.deps.advisory_count == 0
}
Limit Transitive Dependencies
Reduce supply chain surface area by capping the total number of transitive dependencies.
package hextrap.firewall
import rego.v1
default allow := false
max_transitive_deps := 100
# Allow if transitive dependencies are within limit
allow if {
input.deps.transitive_dependency_count <= max_transitive_deps
}
Require SLSA Provenance
Ensure all packages have verified build provenance attestations.
package hextrap.firewall
import rego.v1
default allow := false
# Allow packages with SLSA build provenance
allow if {
input.deps.has_slsa_provenance
}
Minimum Download Threshold
Block packages with very low adoption, which may indicate abandoned, experimental, or malicious packages.
package hextrap.firewall
import rego.v1
default allow := false
minimum_weekly_downloads := 100
# Allow packages with sufficient adoption
allow if {
input.community.weekly_downloads >= minimum_weekly_downloads
}
# Allow packages where download data is not yet available
allow if {
input.community.weekly_downloads == 0
}
Combining Rules
Real-world policies often combine multiple criteria. Multiple allow rules act as an OR —
if any rule evaluates to true, the package is allowed. Conditions within a single rule act as an AND —
all conditions must be true. Since the default is false, a package must satisfy at least one allow rule to pass.
Enterprise Security Policy
A comprehensive policy that requires a package to pass license compliance, score thresholds, advisory checks, and dependency limits — all at once.
package hextrap.firewall
import rego.v1
default allow := false
approved_licenses := {
"MIT", "Apache-2.0", "BSD-3-Clause", "BSD-2-Clause",
"ISC", "MPL-2.0", "Unlicense", "0BSD"
}
# A package must pass ALL of these conditions to be allowed
allow if {
# License compliance
license_ok
# Security score
security_ok
# No known vulnerabilities
input.deps.advisory_count == 0
# Dependency limits
input.deps.transitive_dependency_count <= 200
# Minimum adoption
adoption_ok
# Overall quality gate
quality_ok
}
# Also allow unenriched packages (no scores yet)
allow if {
input.scores.security == 0
input.scores.overall == 0
}
# === Helper rules ===
license_ok if {
input.package.license in approved_licenses
}
license_ok if {
input.package.license == ""
}
security_ok if {
input.scores.security >= 50
}
adoption_ok if {
input.community.weekly_downloads >= 50
}
adoption_ok if {
input.community.weekly_downloads == 0
}
quality_ok if {
input.scores.overall >= 40
}
Registry-Specific Rules
Apply different criteria depending on the package registry.
package hextrap.firewall
import rego.v1
default allow := false
# npm: stricter dependency limits
allow if {
input.package.registry == "npm"
input.deps.transitive_dependency_count <= 50
}
# PyPI: require higher security score
allow if {
input.package.registry == "pypi"
input.scores.security >= 70
}
# PyPI: allow if not yet scored
allow if {
input.package.registry == "pypi"
input.scores.security == 0
}
# Go: require SLSA provenance
allow if {
input.package.registry == "go"
input.deps.has_slsa_provenance
}
Testing Your Policies
Always test your policies before enabling them on a production firewall. The OPA Rego Playground lets you evaluate policies against sample input in your browser.
How to Test
- Open the OPA Rego Playground
- Paste your policy into the Policy editor
- Paste sample input (below) into the Input field
- Set the query to:
data.hextrap.firewall.allow - Click Evaluate
If the result is true, the package would be allowed.
If the result is false, the package would be blocked.
Sample Input: Healthy Package
This input represents a well-maintained, popular package that should pass most policies.
{
"package": {
"name": "requests",
"version": "2.31.0",
"registry": "pypi",
"published": "2024-05-29T12:00:00Z",
"license": "Apache-2.0",
"is_deprecated": false,
"is_malicious": false,
"is_unmaintained": false,
"first_release_date": "2011-02-14T00:00:00Z",
"last_release_date": "2024-05-29T00:00:00Z"
},
"scores": {
"security": 85,
"quality": 72,
"maintenance": 90,
"overall": 83,
"scorecard_overall": 7.2
},
"deps": {
"dependency_count": 5,
"transitive_dependency_count": 12,
"advisory_count": 0,
"has_slsa_provenance": true
},
"community": {
"weekly_downloads": 5000000,
"github_stars": 50000
}
}
Sample Input: Risky Package
This input represents a suspicious, low-quality package that most policies should block.
{
"package": {
"name": "sketchy-lib",
"version": "0.0.1",
"registry": "pypi",
"published": "2026-02-18T12:00:00Z",
"license": "WTFPL",
"is_deprecated": false,
"is_malicious": false,
"is_unmaintained": true,
"first_release_date": "2026-02-18T00:00:00Z",
"last_release_date": "2026-02-18T00:00:00Z"
},
"scores": {
"security": 25,
"quality": 10,
"maintenance": 5,
"overall": 15,
"scorecard_overall": 1.2
},
"deps": {
"dependency_count": 45,
"transitive_dependency_count": 312,
"advisory_count": 3,
"has_slsa_provenance": false
},
"community": {
"weekly_downloads": 12,
"github_stars": 0
}
}
Always verify that your policy correctly allows legitimate packages and correctly blocks risky ones. Use both sample inputs above to check both paths.
Best Practices
1. Account for unenriched packages
Since policies deny by default, packages that haven't been enriched yet (all zero values)
will be blocked unless you explicitly allow them. Add a separate allow rule
for the unenriched case.
package hextrap.firewall
import rego.v1
default allow := false
# Allow packages that meet the score threshold
allow if {
input.scores.security >= 60
}
# Also allow packages that haven't been scored yet
allow if {
input.scores.security == 0
}
2. Use helper rules for readability
When combining multiple criteria in a single allow rule, extract conditions
into named helper rules. This makes complex policies easier to read and maintain.
3. Start permissive, then tighten
Begin with lenient thresholds and monitor which packages would be blocked in your activity log. Gradually increase thresholds as you gain confidence in the policy.
4. Draft before enabling
Save your policy with the toggle set to disabled. Test it in the OPA Playground against real packages your team uses. Only enable the toggle when you're confident the policy won't block legitimate packages.
5. Always include the required declarations
Always start your policy with package hextrap.firewall, import rego.v1,
and default allow := false.
The package declaration is required for the policy engine to locate your rules,
the import enables modern Rego syntax (the if keyword), and the default
ensures packages are denied unless an allow rule explicitly permits them.
6. Document your rules
Use Rego comments (#) to explain the intent behind each rule. Future team members
will thank you.
Rego Quick Reference
Key Rego concepts for writing Hextrap policies:
| Concept | Syntax | Description |
|---|---|---|
| Assignment | x := "value" |
Assign a value to a variable |
| Comparison | x == y, x != y, x < y |
Compare values |
| Negation | not condition |
True when condition is false |
| Set literal | {"a", "b", "c"} |
Define a set of values |
| Set membership | x in set |
Check if x is in a set |
| String format | sprintf("msg %s %d", [s, n]) |
Format a string with values |
| Comments | # comment |
Single-line comments |
For complete Rego documentation, see the OPA Policy Language Reference.