Experimental Feature - WASM Policy EngineThese examples are specifically for writing WASM policies using Chainloop’s experimental WASM policy engine. The WASM policy engine is NOT the default policy engine in Chainloop.
- Default engine: Rego-based (recommended for most users)
- WASM engine: Experimental alternative for Go/JavaScript policies
- Currently supported languages: Go and JavaScript only
- Status: Experimental preview - APIs may change in future releases
Common Patterns
Required Fields Validation
Ensure critical fields are present and non-empty.func validateRequiredFields(comp Component) chainloop.Result {
result := chainloop.Success()
if comp.Name == "" {
result.AddViolation("name is required")
}
if comp.Version == "" {
result.AddViolation("version is required")
}
return result
}
function validateRequiredFields(comp) {
const result = success();
if (!comp.name || comp.name === "") {
result.addViolation("name is required");
}
if (!comp.version || comp.version === "") {
result.addViolation("version is required");
}
return result;
}
Allowlist/Blocklist Validation
Check values against approved or forbidden lists.func validateLicense(license string, approved, forbidden []string) chainloop.Result {
result := chainloop.Success()
// Check blocklist first
for _, blocked := range forbidden {
if license == blocked {
result.AddViolationf("%s is forbidden", license)
return result
}
}
// Check allowlist
if len(approved) > 0 {
found := false
for _, allowed := range approved {
if license == allowed {
found = true
break
}
}
if !found {
result.AddViolationf("%s not approved", license)
}
}
return result
}
function validateLicense(license, approved, forbidden) {
const result = success();
// Check blocklist
if (forbidden.includes(license)) {
result.addViolation(`${license} is forbidden`);
return result;
}
// Check allowlist
if (approved.length > 0 && !approved.includes(license)) {
result.addViolation(`${license} not approved`);
}
return result;
}
Nested Structure Validation
Validate arrays of objects (SBOM components, attestation subjects).func validateComponents(components []Component) chainloop.Result {
result := chainloop.Success()
if len(components) == 0 {
result.AddViolation("must contain at least one component")
}
for i, comp := range components {
if comp.Name == "" {
result.AddViolationf("component %d missing name", i)
}
if comp.Version == "" {
result.AddViolationf("component %d missing version", i)
}
}
return result
}
function validateComponents(components) {
const result = success();
if (!components || components.length === 0) {
result.addViolation("must contain at least one component");
}
components.forEach((comp, i) => {
if (!comp.name) result.addViolation(`component ${i} missing name`);
if (!comp.version) result.addViolation(`component ${i} missing version`);
});
return result;
}
External API Validation
Verify data against external sources.func validateWithAPI(name, version string) chainloop.Result {
url := fmt.Sprintf("https://registry.npmjs.org/%s", name)
var data RegistryResponse
err := chainloop.HTTPGetJSON(url, &data)
if err != nil {
return chainloop.Skipf("API unavailable: %v", err)
}
result := chainloop.Success()
if _, exists := data.Versions[version]; !exists {
result.AddViolationf("version %s not found", version)
}
return result
}
function validateWithAPI(name, version) {
try {
const url = `https://registry.npmjs.org/${name}`;
const data = httpGetJSON(url);
const result = success();
if (!data.versions[version]) {
result.addViolation(`version ${version} not found`);
}
return result;
} catch (e) {
return skip(`API unavailable: ${e.message}`);
}
}
Important HTTP Request Notes:
- Always use
skip()when external APIs are unavailable - don’t fail policies due to network issues - All domains are blocked by default except
www.chainloop.devandwww.cisa.gov - Use
--allowed-hostnamesflag to allowlist domains:--allowed-hostnames registry.npmjs.org
Complete Examples
SBOM License Validation
Validate component licenses in a CycloneDX SBOM.package main
import (
"fmt"
chainloop "github.com/chainloop-dev/chainloop/labs/wasm-policy-sdk/go"
"github.com/extism/go-pdk"
)
type SBOM struct {
Components []Component `json:"components"`
}
type Component struct {
Name string `json:"name"`
Version string `json:"version"`
Licenses []License `json:"licenses"`
}
type License struct {
ID string `json:"id"`
}
//export Execute
func Execute() int32 {
return chainloop.Run(validate)
}
func validate() error {
var sbom SBOM
if err := chainloop.GetMaterialJSON(&sbom); err != nil {
return chainloop.Skip("Not a valid CycloneDX SBOM")
}
approved := []string{"MIT", "Apache-2.0", "BSD-3-Clause"}
forbidden := []string{"GPL-3.0", "AGPL-3.0"}
result := chainloop.Success()
for _, comp := range sbom.Components {
for _, lic := range comp.Licenses {
// Check forbidden
for _, f := range forbidden {
if lic.ID == f {
result.AddViolationf("component '%s' uses forbidden license: %s",
comp.Name, lic.ID)
}
}
// Check approved
approved := false
for _, a := range approved {
if lic.ID == a {
approved = true
}
}
if !approved {
result.AddViolationf("component '%s' license not approved: %s",
comp.Name, lic.ID)
}
}
}
return chainloop.OutputResult(result)
}
func main() {}
const {
getMaterialJSON,
success,
skip,
outputResult,
run
} = require('@chainloop-dev/policy-sdk');
function Execute() {
return run(() => {
const sbom = getMaterialJSON();
if (!sbom.components) {
outputResult(skip("Not a valid CycloneDX SBOM"));
return;
}
const approved = ["MIT", "Apache-2.0", "BSD-3-Clause"];
const forbidden = ["GPL-3.0", "AGPL-3.0"];
const result = success();
sbom.components.forEach(comp => {
(comp.licenses || []).forEach(lic => {
// Check forbidden
if (forbidden.includes(lic.id)) {
result.addViolation(
`component '${comp.name}' uses forbidden license: ${lic.id}`
);
}
// Check approved
if (!approved.includes(lic.id)) {
result.addViolation(
`component '${comp.name}' license not approved: ${lic.id}`
);
}
});
});
outputResult(result);
});
}
module.exports = { Execute };
sbom.json):
{
"components": [
{
"name": "lodash",
"version": "4.17.21",
"licenses": [{ "id": "MIT" }]
},
{
"name": "gpl-package",
"version": "1.0.0",
"licenses": [{ "id": "GPL-3.0" }]
}
]
}
chainloop policy devel eval \
--policy policy.yaml \
--material sbom.json \
--kind SBOM_CYCLONEDX_JSON
Attestation Signature Validation
Validate in-toto attestation has git commit subjects.package main
import (
chainloop "github.com/chainloop-dev/chainloop/labs/wasm-policy-sdk/go"
"github.com/extism/go-pdk"
)
type Attestation struct {
Subject []Subject `json:"subject"`
}
type Subject struct {
Name string `json:"name"`
}
//export Execute
func Execute() int32 {
return chainloop.Run(validate)
}
func validate() error {
var att Attestation
if err := chainloop.GetMaterialJSON(&att); err != nil {
return chainloop.Skip("Not a valid attestation")
}
result := chainloop.Success()
if len(att.Subject) == 0 {
result.AddViolation("attestation must have at least one subject")
}
for _, subj := range att.Subject {
if subj.Name == "" {
result.AddViolation("subject missing name field")
}
}
return chainloop.OutputResult(result)
}
func main() {}
const {
getMaterialJSON,
success,
skip,
outputResult,
run
} = require('@chainloop-dev/policy-sdk');
function Execute() {
return run(() => {
const att = getMaterialJSON();
if (!att.subject) {
outputResult(skip("Not a valid attestation"));
return;
}
const result = success();
if (att.subject.length === 0) {
result.addViolation("attestation must have at least one subject");
}
att.subject.forEach(subj => {
if (!subj.name || subj.name === "") {
result.addViolation("subject missing name field");
}
});
outputResult(result);
});
}
module.exports = { Execute };
HTTP API Integration
Verify package version exists in npm registry.Domain Allowlisting RequiredBy default, all HTTP requests are blocked except for:In production, configure allowed hostnames in your policy engine settings.
www.chainloop.devwww.cisa.gov
--allowed-hostnames flag:chainloop policy devel eval \
--policy policy.yaml \
--material data.json \
--kind EVIDENCE \
--allowed-hostnames registry.npmjs.org,api.example.com
package main
import (
"fmt"
chainloop "github.com/chainloop-dev/chainloop/labs/wasm-policy-sdk/go"
"github.com/extism/go-pdk"
)
type PackageData struct {
Name string `json:"name"`
Version string `json:"version"`
}
type RegistryResponse struct {
Versions map[string]interface{} `json:"versions"`
}
//export Execute
func Execute() int32 {
return chainloop.Run(validate)
}
func validate() error {
var pkg PackageData
if err := chainloop.GetMaterialJSON(&pkg); err != nil {
return chainloop.Skip("Invalid JSON")
}
url := fmt.Sprintf("https://registry.npmjs.org/%s", pkg.Name)
var registry RegistryResponse
if err := chainloop.HTTPGetJSON(url, ®istry); err != nil {
return chainloop.Skipf("npm registry unavailable: %v", err)
}
result := chainloop.Success()
if _, exists := registry.Versions[pkg.Version]; !exists {
result.AddViolationf("version %s not found in registry", pkg.Version)
}
return chainloop.OutputResult(result)
}
func main() {}
const {
getMaterialJSON,
success,
skip,
outputResult,
httpGetJSON,
run
} = require('@chainloop-dev/policy-sdk');
function Execute() {
return run(() => {
const pkg = getMaterialJSON();
try {
const url = `https://registry.npmjs.org/${pkg.name}`;
const registry = httpGetJSON(url);
const result = success();
if (!registry.versions[pkg.version]) {
result.addViolation(`version ${pkg.version} not found in registry`);
}
outputResult(result);
} catch (e) {
outputResult(skip(`npm registry unavailable: ${e.message}`));
}
});
}
module.exports = { Execute };
chainloop policy devel eval \
--policy policy.yaml \
--material package.json \
--kind EVIDENCE \
--allowed-hostnames registry.npmjs.org
Artifact Discovery
Check if related attestations have policy violations.package main
import (
"fmt"
chainloop "github.com/chainloop-dev/chainloop/labs/wasm-policy-sdk/go"
"github.com/extism/go-pdk"
)
type ContainerImage struct {
ChainloopMetadata struct {
Digest struct {
SHA256 string `json:"sha256"`
} `json:"digest"`
} `json:"chainloop_metadata"`
}
//export Execute
func Execute() int32 {
return chainloop.Run(validate)
}
func validate() error {
var img ContainerImage
if err := chainloop.GetMaterialJSON(&img); err != nil {
return chainloop.Skip("Not a container image")
}
digest := fmt.Sprintf("sha256:%s", img.ChainloopMetadata.Digest.SHA256)
discoverResult, err := chainloop.Discover(digest, "")
if err != nil {
return chainloop.Skipf("Discovery unavailable: %v", err)
}
result := chainloop.Success()
for _, ref := range discoverResult.References {
if ref.Kind == "ATTESTATION" {
if ref.Metadata["hasPolicyViolations"] == "true" {
result.AddViolationf(
"attestation %s contains policy violations", ref.Digest)
}
}
}
return chainloop.OutputResult(result)
}
func main() {}
const {
getMaterialJSON,
success,
skip,
outputResult,
discover,
logInfo,
run
} = require('@chainloop-dev/policy-sdk');
function Execute() {
return run(() => {
const img = getMaterialJSON();
if (!img.chainloop_metadata?.digest?.sha256) {
outputResult(skip("Not a container image"));
return;
}
const digest = `sha256:${img.chainloop_metadata.digest.sha256}`;
let discoverResult;
try {
discoverResult = discover(digest, "");
} catch (e) {
outputResult(skip(`Discovery unavailable: ${e.message}`));
return;
}
const result = success();
discoverResult.references.forEach(ref => {
if (ref.kind === "ATTESTATION") {
if (ref.metadata.hasPolicyViolations === "true") {
result.addViolation(
`attestation ${ref.digest} contains policy violations`
);
}
}
});
outputResult(result);
});
}
module.exports = { Execute };
Next Steps
Go SDK Guide
Learn how to use the Go SDK
JavaScript SDK Guide
Learn how to use the JavaScript SDK
