Skip to main content
Experimental Feature - WASM Policy EngineThis SDK is 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
  • Status: Experimental preview - APIs may change in future releases
For the default Rego-based policy engine, see Writing Custom Policies.

Prerequisites

The Chainloop JavaScript SDK for WASM policies is built on top of the Extism JavaScript PDK, which enables JavaScript code to run inside WebAssembly with QuickJS. Required tools:
For package management and building.
# Verify installation
node --version  # Should be v16+
Compiles JavaScript to WebAssembly.
# Install globally
npm install -g @extism/js-pdk

# Verify installation
extism-js --version
Dependencies:
  • @chainloop-dev/policy-sdk - Chainloop WASM Policy SDK (npm package)
  • @extism/js-pdk - Extism JavaScript PDK (auto-installed)
  • esbuild - JavaScript bundler (dev dependency)
The Extism JS PDK handles JavaScript-to-WASM compilation using QuickJS, while Chainloop’s WASM Policy SDK provides policy-specific APIs for material validation.

Project Setup

Install Dependencies

npm init -y
npm install @chainloop-dev/policy-sdk
npm install --save-dev esbuild

Configure Build

esbuild.js:
const esbuild = require('esbuild');

esbuild.build({
  entryPoints: ['policy.js'],
  outdir: 'dist',
  bundle: true,
  format: 'cjs',
  target: ['es2020'],
  platform: 'node',
}).catch(() => process.exit(1));
package.json:
{
  "name": "my-policy",
  "scripts": {
    "build": "node esbuild.js && extism-js dist/policy.js -i policy.d.ts -o policy.wasm"
  },
  "dependencies": {
    "@chainloop-dev/policy-sdk": "^0.1.0"
  },
  "devDependencies": {
    "esbuild": "^0.19.0"
  }
}
policy.d.ts:
declare module "main" {
  export function Execute(): I32;
}
policy.yaml:
apiVersion: workflowcontract.chainloop.dev/v1
kind: Policy
metadata:
  name: my-js-policy
  description: My custom JavaScript policy
spec:
  policies:
    - kind: EVIDENCE
      path: policy.wasm

Complete Example

const {
  getMaterialJSON,
  success,
  skip,
  outputResult,
  logInfo,
  logError,
  run
} = require('@chainloop-dev/policy-sdk');

function Execute() {
  return run(() => {
    // Parse the SBOM material
    const sbom = getMaterialJSON();

    if (!sbom.components) {
      outputResult(skip("Not a valid CycloneDX SBOM"));
      return;
    }

    logInfo(`Validating SBOM with ${sbom.components.length} components`);

    const result = success();
    const approved = ["MIT", "Apache-2.0", "BSD-3-Clause", "ISC"];

    // Validate each component
    sbom.components.forEach((component, index) => {
      // Check version
      if (!component.version || component.version === "") {
        result.addViolation(`component '${component.name}' missing version`);
      }

      // Check licenses
      if (!component.licenses || component.licenses.length === 0) {
        result.addViolation(`component '${component.name}' missing license`);
        return;
      }

      // Validate licenses
      component.licenses.forEach(license => {
        const licenseID = license.id || license.name;
        if (!approved.includes(licenseID)) {
          result.addViolation(
            `component '${component.name}' has unapproved license: ${licenseID}`
          );
        }
      });
    });

    if (result.hasViolations()) {
      logError(`SBOM validation failed with ${result.violations.length} violations`);
    } else {
      logInfo("SBOM validation passed");
    }

    outputResult(result);
  });
}

module.exports = { Execute };

API Quick Reference

The JavaScript SDK provides functions for:
  • Execution: run() - Entry point wrapper
  • Material Extraction: getMaterialJSON(), getMaterialString(), getMaterialBytes()
  • Arguments: getArgs(), getArgString(), getArgStringDefault()
  • Results: success(), fail(), skip(), outputResult(), addViolation(), hasViolations()
  • Logging: logInfo(), logDebug(), logWarn(), logError()
  • HTTP Requests: httpGet(), httpGetJSON(), httpPost(), httpPostJSON()
  • Artifact Discovery: discover(), discoverByDigest()

Building

npm run build
This runs:
  1. esbuild - Bundles policy.js and SDK
  2. extism-js - Compiles bundle to WASM
Output:
  • dist/policy.js - Bundled JavaScript (~7KB)
  • policy.wasm - Compiled WASM (~2.1MB, includes QuickJS runtime)

Testing

test.sh:
#!/bin/bash

npm run build

echo "Testing valid SBOM..."
chainloop policy devel eval \
  --policy policy.yaml \
  --material test-data/valid-sbom.json \
  --kind SBOM_CYCLONEDX_JSON

echo "Testing invalid SBOM..."
chainloop policy devel eval \
  --policy policy.yaml \
  --material test-data/invalid-sbom.json \
  --kind SBOM_CYCLONEDX_JSON

JavaScript Compatibility

Supported:
  • ✅ ES2020 JavaScript features
  • ✅ JSON parsing and manipulation
  • ✅ String operations and regex
  • ✅ Arrays, objects, and basic types
  • ✅ Synchronous operations
Not Supported:
  • ❌ async/await (no Promise support)
  • ❌ Node.js built-ins (fs, path, http)
  • ❌ ES modules (use CommonJS)
  • ❌ Browser APIs (fetch, localStorage)
  • ❌ setTimeout/setInterval
  • ❌ Symbols and WeakMaps
Recommended:
// ✅ Good: Simple objects
const component = {
  name: "lodash",
  version: "4.17.21"
};

// ✅ Good: Synchronous operations
components.forEach(comp => {
  if (!comp.name) {
    result.addViolation("Missing name");
  }
});

// ❌ Avoid: Async operations
// async function validate() {  // Not supported
//   const data = await fetch(...);
// }

Best Practices

  1. Use simple data structures - Plain objects and arrays
  2. Validate early - Check material format first
  3. Clear violation messages - Include specific details
  4. Use skip appropriately - Don’t fail for wrong material types
  5. Test with real data - Use actual artifacts
  6. Log progress - Aid debugging
  7. Handle errors - Use try-catch for parsing

Next Steps