@@ -28,6 +28,7 @@ import (
2828 "github.com/chainloop-dev/chainloop/app/controlplane/pkg/unmarshal"
2929 "github.com/chainloop-dev/chainloop/pkg/policies/engine"
3030 "github.com/chainloop-dev/chainloop/pkg/resourceloader"
31+ extism "github.com/extism/go-sdk"
3132 opaAst "github.com/open-policy-agent/opa/v1/ast"
3233 "github.com/open-policy-agent/opa/v1/format"
3334 "github.com/styrainc/regal/pkg/config"
@@ -158,12 +159,30 @@ func (p *PolicyToLint) loadReferencedPolicyFiles(baseDir string) error {
158159}
159160
160161func (p * PolicyToLint ) processFile (filePath string ) error {
162+ ext := strings .ToLower (filepath .Ext (filePath ))
163+
164+ // WASM files: validate magic bytes
165+ if ext == ".wasm" {
166+ content , err := os .ReadFile (filePath )
167+ if err != nil {
168+ return fmt .Errorf ("failed to read WASM file %s: %w" , filePath , err )
169+ }
170+
171+ // Verify magic bytes
172+ if engine .DetectPolicyType (content ) != engine .PolicyTypeWASM {
173+ return fmt .Errorf ("file has .wasm extension but is not a valid WASM file" )
174+ }
175+
176+ p .WASMFiles = append (p .WASMFiles , & File {Path : filePath , Content : content })
177+ return nil
178+ }
179+
180+ // Other files: read full content
161181 content , err := os .ReadFile (filePath )
162182 if err != nil {
163183 return err
164184 }
165185
166- ext := strings .ToLower (filepath .Ext (filePath ))
167186 switch ext {
168187 case ".yaml" , ".yml" :
169188 p .YAMLFiles = append (p .YAMLFiles , & File {
@@ -175,16 +194,6 @@ func (p *PolicyToLint) processFile(filePath string) error {
175194 Path : filePath ,
176195 Content : content ,
177196 })
178- case ".wasm" :
179- // Detect if file is actually WASM using magic bytes
180- policyType := engine .DetectPolicyType (content )
181- if policyType != engine .PolicyTypeWASM {
182- return fmt .Errorf ("file has .wasm extension but is not a valid WASM file" )
183- }
184- p .WASMFiles = append (p .WASMFiles , & File {
185- Path : filePath ,
186- Content : content ,
187- })
188197 default :
189198 return fmt .Errorf ("unsupported file extension %s, must be .yaml/.yml, .rego, or .wasm" , ext )
190199 }
@@ -198,8 +207,10 @@ func (p *PolicyToLint) Validate() {
198207 p .validateRegoFile (regoFile )
199208 }
200209
201- // WASM files are binary and cannot be linted
202- // Only verify they have valid WASM magic bytes (already done in processFile)
210+ // Validate WASM files
211+ for _ , wasmFile := range p .WASMFiles {
212+ p .validateWasmFile (wasmFile )
213+ }
203214}
204215
205216func (p * PolicyToLint ) validateRegoFile (file * File ) {
@@ -215,6 +226,35 @@ func (p *PolicyToLint) validateRegoFile(file *File) {
215226 }
216227}
217228
229+ // validateWasmFile validates a WASM policy file by checking that it exports the required Execute function
230+ func (p * PolicyToLint ) validateWasmFile (file * File ) {
231+ ctx := context .Background ()
232+
233+ // Create Extism manifest
234+ manifest := extism.Manifest {
235+ Wasm : []extism.Wasm {
236+ extism.WasmData {Data : file .Content },
237+ },
238+ }
239+
240+ cfg := extism.PluginConfig {
241+ EnableWasi : true ,
242+ }
243+
244+ // Create plugin
245+ plugin , err := extism .NewPlugin (ctx , manifest , cfg , []extism.HostFunction {})
246+ if err != nil {
247+ p .AddError (file .Path , fmt .Sprintf ("failed to load WASM module: %v" , err ), 0 )
248+ return
249+ }
250+ defer plugin .Close (ctx )
251+
252+ // Check if Execute function is exported
253+ if ! plugin .FunctionExists ("Execute" ) {
254+ p .AddError (file .Path , "WASM module missing required 'Execute' function export" , 0 )
255+ }
256+ }
257+
218258func (p * PolicyToLint ) validateAndFormatRego (content , path string ) string {
219259 // 1. Optionally format
220260 if p .Format {
0 commit comments