Skip to content

WASM Plugin System

Artifact Keeper features a powerful plugin system built on WebAssembly (WASM) that allows you to extend functionality without modifying core code.

Overview

The WASM plugin system enables:

  • Custom format handlers: Support new package formats beyond the built-in 30+
  • Metadata extractors: Parse and index custom artifact metadata
  • Validation hooks: Implement custom artifact validation logic
  • Post-processing: Transform artifacts on upload (virus scanning, compression, etc.)
  • Hot-reloading: Install, update, and remove plugins without server restart

Architecture

Built on industry-standard WebAssembly technologies:

  • wasmtime 21.0+: High-performance WASM runtime
  • WASI: WebAssembly System Interface for secure system access
  • wit-bindgen: Interface type definitions for plugin contracts

Security Model

Plugins run in a sandboxed environment with:

  • No direct filesystem access (only via provided APIs)
  • No network access (unless explicitly granted)
  • Limited memory allocation
  • CPU time limits per operation
  • Isolated from other plugins

Plugin Lifecycle

1. Installation

Install plugins from a ZIP file or Git repository:

Terminal window
# From local file
curl -X POST https://registry.example.com/api/v1/plugins \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-F "file=@my-plugin.zip"
# From Git repository
curl -X POST https://registry.example.com/api/v1/plugins \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source": "git",
"repository": "https://github.com/example/plugin-repo.git",
"ref": "v1.0.0"
}'
# From plugin registry
curl -X POST https://registry.example.com/api/v1/plugins \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source": "registry",
"name": "format-handler-conan",
"version": "1.2.0"
}'

2. Enabling

Installed plugins are disabled by default:

Terminal window
curl -X POST https://registry.example.com/api/v1/plugins/plugin-123/enable \
-H "Authorization: Bearer $ADMIN_TOKEN"

3. Configuration

Configure plugin settings:

Terminal window
curl -X PUT https://registry.example.com/api/v1/plugins/plugin-123/config \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"max_file_size_mb": 500,
"allowed_extensions": [".nupkg", ".snupkg"],
"index_metadata": true
}'

4. Disabling

Temporarily disable without uninstalling:

Terminal window
curl -X POST https://registry.example.com/api/v1/plugins/plugin-123/disable \
-H "Authorization: Bearer $ADMIN_TOKEN"

5. Updating

Update to a new version:

Terminal window
curl -X PUT https://registry.example.com/api/v1/plugins/plugin-123 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-F "file=@my-plugin-v2.zip"

6. Uninstalling

Remove plugin completely:

Terminal window
curl -X DELETE https://registry.example.com/api/v1/plugins/plugin-123 \
-H "Authorization: Bearer $ADMIN_TOKEN"

Plugin Types

Format Handler Plugins

Add support for new package formats:

plugin.wit
interface format-handler {
// Parse artifact metadata
parse-metadata: func(data: list<u8>) -> result<metadata, error>
// Validate artifact structure
validate: func(data: list<u8>) -> result<validation-result, error>
// Extract dependencies
extract-dependencies: func(data: list<u8>) -> result<list<dependency>, error>
// Generate index data
index: func(data: list<u8>) -> result<index-data, error>
}
record metadata {
name: string,
version: string,
description: option<string>,
authors: list<string>,
license: option<string>,
keywords: list<string>,
}
record dependency {
name: string,
version-requirement: string,
optional: bool,
}

Example: Conan package handler

use artifact_keeper_plugin::*;
#[plugin_export]
fn parse_metadata(data: &[u8]) -> Result<Metadata, Error> {
let conanfile = parse_conanfile(data)?;
Ok(Metadata {
name: conanfile.name,
version: conanfile.version,
description: Some(conanfile.description),
authors: vec![conanfile.author],
license: Some(conanfile.license),
keywords: conanfile.topics,
})
}
#[plugin_export]
fn validate(data: &[u8]) -> Result<ValidationResult, Error> {
// Validate conanfile.py structure
// Check for required fields
// Verify package contents
}

Metadata Extractor Plugins

Extract custom metadata for indexing:

interface metadata-extractor {
extract: func(artifact-id: string, data: list<u8>) -> result<key-value-pairs, error>
}
type key-value-pairs = list<tuple<string, string>>

Example: Extract ML model metadata

#[plugin_export]
fn extract(artifact_id: &str, data: &[u8]) -> Result<Vec<(String, String)>, Error> {
let model = load_model(data)?;
Ok(vec![
("model_type".into(), model.architecture.clone()),
("framework".into(), "pytorch".into()),
("input_shape".into(), format!("{:?}", model.input_shape)),
("parameters".into(), model.num_parameters.to_string()),
])
}

Validation Hook Plugins

Implement custom validation logic:

interface validation-hook {
// Called before artifact is accepted
validate-upload: func(
artifact: artifact-info,
data: list<u8>
) -> result<validation-result, error>
}
record artifact-info {
name: string,
version: string,
format: string,
size-bytes: u64,
uploader: string,
}
record validation-result {
allowed: bool,
message: option<string>,
warnings: list<string>,
}

Example: Size and naming policy

#[plugin_export]
fn validate_upload(artifact: ArtifactInfo, data: &[u8]) -> Result<ValidationResult, Error> {
let mut warnings = Vec::new();
// Check size limit
if artifact.size_bytes > 1_000_000_000 {
return Ok(ValidationResult {
allowed: false,
message: Some("Artifact exceeds 1GB limit".into()),
warnings,
});
}
// Check naming convention
if !artifact.name.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') {
warnings.push("Artifact name contains non-standard characters".into());
}
Ok(ValidationResult {
allowed: true,
message: None,
warnings,
})
}

Post-Processing Plugins

Transform artifacts after upload:

interface post-processor {
process: func(
artifact-id: string,
data: list<u8>
) -> result<processed-data, error>
}
record processed-data {
data: list<u8>,
modified: bool,
metadata: key-value-pairs,
}

Example: Virus scanning integration

#[plugin_export]
fn process(artifact_id: &str, data: &[u8]) -> Result<ProcessedData, Error> {
// Scan with ClamAV or similar
let scan_result = scan_for_viruses(data)?;
if scan_result.threats_found > 0 {
return Err(Error::SecurityThreat(scan_result.description));
}
Ok(ProcessedData {
data: data.to_vec(),
modified: false,
metadata: vec![
("scanned_at".into(), Utc::now().to_string()),
("scanner_version".into(), scan_result.version),
],
})
}

Developing Plugins

Prerequisites

Terminal window
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add WASM target
rustup target add wasm32-wasi
# Install wit-bindgen
cargo install wit-bindgen-cli

Project Structure

my-plugin/
├── Cargo.toml
├── plugin.wit # Interface definition
├── src/
│ └── lib.rs # Plugin implementation
└── plugin.toml # Plugin manifest

Plugin Manifest (plugin.toml)

[plugin]
name = "format-handler-conan"
version = "1.0.0"
description = "Conan package format support"
author = "Your Name <you@example.com>"
license = "MIT"
[plugin.capabilities]
format_handler = true
extensions = [".conan"]
[plugin.config]
# Default configuration
max_file_size_mb = 100
validate_recipes = true

Build Plugin

Terminal window
# Build WASM module
cargo build --target wasm32-wasi --release
# Package plugin
zip my-plugin.zip \
target/wasm32-wasi/release/my_plugin.wasm \
plugin.toml \
plugin.wit \
README.md

Testing Plugins Locally

Terminal window
# Test with artifact-keeper CLI
artifact-keeper plugin test \
--plugin my-plugin.zip \
--artifact test-package.conan

Plugin Management API

List Plugins

Terminal window
curl https://registry.example.com/api/v1/plugins \
-H "Authorization: Bearer $TOKEN"

Response:

{
"plugins": [
{
"id": "plugin-123",
"name": "format-handler-conan",
"version": "1.0.0",
"enabled": true,
"capabilities": ["format_handler"],
"config": {
"max_file_size_mb": 100
}
}
]
}

Get Plugin Details

Terminal window
curl https://registry.example.com/api/v1/plugins/plugin-123 \
-H "Authorization: Bearer $TOKEN"

Plugin Logs

Terminal window
curl https://registry.example.com/api/v1/plugins/plugin-123/logs \
-H "Authorization: Bearer $TOKEN"

Configuration

System-Wide Settings

Terminal window
# Enable plugin system
PLUGINS_ENABLED=true
# Plugin storage path
PLUGINS_PATH=/var/lib/artifact-keeper/plugins
# Resource limits per plugin
PLUGIN_MAX_MEMORY_MB=256
PLUGIN_MAX_CPU_MS=5000
PLUGIN_MAX_EXECUTION_TIME_MS=30000
# Allow network access for plugins
PLUGIN_ALLOW_NETWORK=false

Plugin Security

Terminal window
# Require signature verification
PLUGIN_REQUIRE_SIGNATURE=true
PLUGIN_TRUSTED_KEYS=/etc/artifact-keeper/trusted-keys/
# Restrict plugin sources
PLUGIN_ALLOWED_SOURCES=https://plugins.artifact-keeper.io

Best Practices

Performance

  • Keep plugin logic lightweight
  • Cache expensive computations
  • Stream large files instead of loading into memory
  • Use async operations where possible

Error Handling

  • Return meaningful error messages
  • Don’t crash on invalid input
  • Validate inputs thoroughly
  • Log errors for debugging

Security

  • Never trust input data
  • Validate all external data
  • Use safe parsing libraries
  • Limit resource consumption

Troubleshooting

Plugin Won’t Load

Check plugin logs:

Terminal window
curl https://registry.example.com/api/v1/plugins/plugin-123/logs

Verify WASM module:

Terminal window
wasm-validate target/wasm32-wasi/release/my_plugin.wasm

Plugin Crashes

Review resource limits:

Terminal window
PLUGIN_MAX_MEMORY_MB=512
PLUGIN_MAX_EXECUTION_TIME_MS=60000

Enable debug logging:

Terminal window
PLUGIN_LOG_LEVEL=debug

Performance Issues

Profile plugin execution:

Terminal window
artifact-keeper plugin profile \
--plugin plugin-123 \
--artifact test-data.bin

Community Plugins

Browse available plugins at: https://plugins.artifact-keeper.io

Popular plugins:

  • format-handler-conan: Conan C++ package support
  • format-handler-conda: Conda package support
  • scanner-trivy: Trivy security scanning integration
  • metadata-sbom: SBOM generation (SPDX, CycloneDX)
  • transform-compress: Automatic compression/decompression
  • validator-license: License compliance checking