WebAssembly Builds
This guide explains how to build and test the WebAssembly packages for abac-wasm and hbac-wasm.
Prerequisites
Rust with rustup
The WASM targets require rustup for toolchain management. If you have Homebrew-installed Rust, you’ll need to switch to rustup:
# Remove Homebrew Rust (if installed)
brew uninstall rust
# Install rustup
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Add WASM target
rustup target add wasm32-unknown-unknown
wasm-pack
Install wasm-pack for building WebAssembly packages:
cargo install wasm-pack
Node.js
Node.js is required for running WASM tests:
# macOS
brew install node
# Or use your preferred package manager
Building
Quick Build
Use the provided script to build both WASM crates:
./scripts/wasm-build.sh
This will build:
crates/abac-wasm/pkg/- Web targetcrates/abac-wasm/pkg-node/- Node.js targetcrates/hbac-wasm/pkg/- Web targetcrates/hbac-wasm/pkg-node/- Node.js target
Manual Build
Build individual packages:
# abac-wasm for web
cd crates/abac-wasm
wasm-pack build --target web --release
# abac-wasm for Node.js
wasm-pack build --target nodejs --release --out-dir pkg-node
# hbac-wasm for web
cd ../hbac-wasm
wasm-pack build --target web --release
# hbac-wasm for Node.js
wasm-pack build --target nodejs --release --out-dir pkg-node
Cargo Aliases
You can also use the cargo aliases defined in .cargo/config.toml:
cargo wasm-build-abac
cargo wasm-build-hbac
Testing
Quick Test
Run all WASM tests:
./scripts/wasm-test.sh
Manual Testing
# Test in Node.js
cd crates/abac-wasm
wasm-pack test --node
# Test in browser (requires Firefox)
wasm-pack test --headless --firefox
Dependency Compatibility
WASM-Compatible Dependencies
The following dependencies are verified to work with WASM:
- acls-rs (zero dependencies)
- lru (with
default-features = falseon wasm32) - ipnetwork
- thiserror
- log
- ahash (with
no-rngfeature on wasm32) - serde, serde_json
- getrandom (with
wasm_jsfeature) - js-sys, web-sys
- console_error_panic_hook
Excluded Features
Cranelift JIT: The jit feature of hbac-rs is NOT WASM-compatible and is explicitly excluded from hbac-wasm builds.
Troubleshooting
Homebrew Rust
If you’re using Homebrew-installed Rust (not rustup), you cannot add WASM targets. The error will be:
error[E0463]: can't find crate for `core`
= note: the `wasm32-unknown-unknown` target may not be installed
= help: consider downloading the target with `rustup target add wasm32-unknown-unknown`
Solution: Switch to rustup-based Rust installation as described in Prerequisites.
Bloom Filter on WASM
The bloom feature (backed by fastbloom) is disabled for WASM builds.
abac-wasm depends on abac-rs with default-features = false, which excludes
the bloom and smallvec dependencies. Bloom filter pre-screening is only
available in native builds.
WASM API Features
Structured Errors
Both abac-wasm and hbac-wasm return structured error objects instead of plain strings. Every error thrown by the WASM bindings is a JavaScript object with two fields:
try {
policy.addRuleFromJson(invalidJson);
} catch (e) {
console.log(e.type); // "JsonError", "PolicyError", or "ValidationError"
console.log(e.message); // human-readable description
}
Custom Matchers (abac-wasm)
AbacPolicyWasm.registerMatcher(dimension, matchFn) installs a JavaScript
callback for a named dimension. The ABAC engine calls this function
during evaluation instead of its built-in matching logic:
// Threshold matcher: true when request value >= rule threshold
policy.registerMatcher("temperature_high", (ruleValue, requestValue, requestGroups) => {
if (ruleValue === "all") return true;
if (!Array.isArray(ruleValue)) return false;
const actual = requestValue.type === "Float" ? requestValue.value : null;
if (actual == null) return false;
return ruleValue.some(t => {
const threshold = t.type === "Float" ? t.value : null;
return threshold != null && actual >= threshold;
});
});
Arguments are serde-serialized: ruleValue is either "all" or an array of
{type, value} objects; requestValue and requestGroups follow the same
{type, value} shape.
Wildcard Dimensions
Rule dimensions accept the literal string "all" as a wildcard. The
custom deserializer rejects any other string value — only "all"
(case-insensitive) is valid:
{
"name": "match-all-locations",
"dimensions": {
"sensor_type": [{"type": "String", "value": "temperature"}],
"location": "all"
},
"rule_type": "Allow",
"enabled": true
}
The AbacRuleBuilderWasm.addDimensionAll(dimension) method provides a
type-safe way to set a wildcard dimension from the builder API.
Deterministic JSON Output
getAllRulesJson() and rule serialization use BTreeMap for dimension
ordering, so the JSON output is deterministic across calls. This makes
snapshot testing and diff-based comparisons reliable.
Bundle Sizes
Target bundle sizes (gzipped):
- abac-wasm: ~150-200 KB
- hbac-wasm: ~120-150 KB
Check actual sizes after building:
# After running wasm-build.sh
du -h crates/abac-wasm/pkg/*.wasm
du -h crates/hbac-wasm/pkg/*.wasm
CI/CD
The CI workflow (.github/workflows/ci.yml) runs two WASM jobs:
wasm-build — builds both crates for web and Node.js targets:
- Installs
wasm-pack@0.13.1(pinned version) - Verifies hbac-wasm has no cranelift dependency via
cargo tree - Builds abac-wasm and hbac-wasm for
--target weband--target nodejs - Caches the cargo registry and git checkouts between runs
wasm-test — runs WASM tests after a successful build:
- Installs build tooling (
gcc,make,pkgconf-pkg-config) - Installs
wasm-pack@0.13.1 - Runs
wasm-pack test --nodefor both crates - Caches the cargo registry and git checkouts
The cranelift check in wasm-build captures cargo tree output and
validates both the command exit code and the absence of cranelift in the
dependency tree. This prevents the JIT feature from accidentally leaking
into hbac-wasm builds.