Docs / Firewall / Custom Policies

Custom Policies with OPA Rego

Write expressive security policies that evaluate package metadata, scores, dependencies, and community signals to enforce your organization's specific requirements.

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.

What is Rego?

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

  1. Navigate to your firewall in the dashboard
  2. Click the Custom Policy tab
  3. Toggle Custom Policy Status to enabled
  4. Write your Rego policy in the built-in editor (with syntax highlighting)
  5. 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.

Policy toggle is independent of policy text.

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:

  1. The package declaration must be package hextrap.firewall
  2. Import the Rego v1 syntax with import rego.v1
  3. Include default allow := false so packages are denied unless explicitly permitted
  4. Write allow rules that evaluate to true when a package should be permitted

When a package install request comes through your firewall:

  1. Hextrap constructs an input document containing the package's metadata, scores, dependencies, and community data
  2. Your Rego policy is evaluated with this input
  3. If allow evaluates to true, the package is allowed (by this rule — other firewall rules still apply)
  4. If allow evaluates to false, the package is blocked with status rego_policy_blocked

Minimal Policy Structure

Minimal custom policy
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)
Zero values for unenriched packages.

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.

License allowlist policy
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.

Minimum security score policy
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).

Block advisories policy
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.

Dependency limit policy
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.

SLSA provenance policy
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.

Minimum downloads policy
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.

Combined enterprise policy
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.

Registry-specific policy
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

  1. Open the OPA Rego Playground
  2. Paste your policy into the Policy editor
  3. Paste sample input (below) into the Input field
  4. Set the query to: data.hextrap.firewall.allow
  5. 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.

Sample input — should pass
{
    "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.

Sample input — should be blocked
{
    "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
    }
}
Tip: Test both directions.

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.

Handling unenriched packages
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.