Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 104 additions & 102 deletions js-wasm/README.md
Original file line number Diff line number Diff line change
@@ -1,130 +1,132 @@
# yara-x-wasm
# @virustotal/yara-x

Browser-focused WebAssembly packaging for [`yara-x`](../lib) with an
object-oriented JavaScript API built around `Compiler`, `Rules`, and
`Scanner`.
JavaScript bindings for [YARA-X](https://github.com/VirusTotal/yara-x) using WebAssembly.

## Package surface

- `Compiler`, `Rules`, and `Scanner`

## Build

From [`js-wasm`](.) run:

```bash
cargo install --locked wasm-pack --version 0.14.0
cargo install --locked wasm-bindgen-cli --version 0.2.113
wasm-pack build --target web --release --mode no-install --no-pack
```

This produces the standard browser package in `pkg/`.

For the full package layout, including the preserved no-modules bundles in
`dist/`:

```bash
cargo run --release --features release-tools --bin build_web_release --
```

or:
## Installation

```bash
cargo build-web-release
npm install @virustotal/yara-x
```

This produces:
## Quick Start

- `pkg/yara-x-wasm.js`
- `pkg/yara-x-wasm_bg.wasm`
- `dist/yara-x-wasm-bundle.js`
- `dist/yara-x-wasm-bundle.min.js`
```js
import init, { Compiler } from "@virustotal/yara-x";

The no-modules bundle reuses the shared `pkg/yara-x-wasm_bg.wasm` binary
instead of shipping a second copy under `dist/`.
// Initialize the WebAssembly module
await init();

SIMD is enabled for `wasm32-unknown-unknown` via `js-wasm/.cargo/config.toml`.
// Compile a rule
const compiler = new Compiler();
compiler.addSource('rule test { strings: $a = "abc" condition: $a }');

## Tests
const rules = compiler.build();

Rust/browser API coverage:
// Scan data
const payload = new Uint8Array([0x61, 0x62, 0x63, 0x64]); // "abcd"
const result = rules.scan(payload);

```bash
npm run test:wasm-node
if (result.valid && result.matches.length > 0) {
console.log("Rule matched!");
}
```

Headless browser coverage for browser-specific behavior such as console output:

```bash
CHROME_BIN=/path/to/chrome \
CHROMEDRIVER=/path/to/chromedriver \
npm run test:wasm-browser
## API Reference

### `Compiler`

The `Compiler` translates YARA-X rules into a executable format.

- `addSource(source: string)`: Compiles a rule string. Throws an error if compilation fails.
- `build(): Rules`: Finalizes compilation and returns a `Rules` object.
- `defineGlobal(identifier: string, value: any)`: Defines an external variable used in conditions (boolean, number, string).
- `newNamespace(namespace: string)`: Scopes subsequent rules to a specific namespace.
- `errors: string[]`: Array of compilation error messages.
- `warnings: string[]`: Array of compilation warning messages.

### `Rules`

The `Rules` object represents the compiled set of rules ready for scanning.

- `scan(payload: Uint8Array): ScanResult`: Scans the payload using the default scanner profile.
- `scanner(): Scanner`: Spawns a dedicated `Scanner` for advanced configuration.
- `warnings: string[]`: Warnings inherited from the compiler.

### `Scanner`

The `Scanner` provides fine-grained control over scanning operations.

- `scan(payload: Uint8Array): ScanResult`: Scans the payload.
- `setGlobal(identifier: string, value: any)`: Overrides values defined by `compiler.defineGlobal()`.
- `setMaxMatchesPerPattern(n: number)`: Limits reporting to first `n` matches per pattern.
- `setTimeoutMs(timeout_ms: number)`: Sets a hard timeout for scanning.

---

## Scan Results Structure

The `.scan(...)` methods return an object detailing matches and diagnostics:

```json
{
"valid": true,
"matches": [
{
"identifier": "rule_name",
"namespace": "default",
"isPrivate": false,
"isGlobal": false,
"tags": ["tag_a"],
"metadata": [
{ "identifier": "key", "value": "value" }
],
"patterns": [
{
"identifier": "$a",
"kind": "text",
"isPrivate": false,
"matches": [
{ "offset": 2, "length": 3 }
]
}
]
}
],
"warnings": []
}
```

JS end-to-end coverage against the generated `pkg/` + `dist/` outputs:
---

```bash
npm run test:js
```
## Resource Management

Package validation:
Objects generated in WebAssembly live in the Wasm heap, which is separate from the JavaScript garbage-collected
heap. If you do not manually free these objects, they will leak memory in the WebAssembly space.

```bash
npm run pack:dry-run
```

## Usage

### ES module usage:
Ensure you call `.free()` on objects when no longer needed:

```js
import init, { Compiler } from "yara-x-wasm";

await init();

const compiler = new Compiler();
compiler.addSource('rule x { strings: $a = "abc" condition: $a }');

const rules = compiler.build();
const result = rules.scan(new Uint8Array([0x61, 0x62, 0x63]));
try {
compiler.addSource('rule x { condition: true }');
const rules = compiler.build();
const payload = new Uint8Array([0x00]); // Self-contained payload
rules.scan(payload);
rules.free();
} finally {
compiler.free();
}
```

### No-modules bundle usage:

```html
<script src="./dist/yara-x-wasm-bundle.js"></script>
<script>
(async () => {
await YaraWasm();
### Modern JavaScript (Explicit Resource Management)

const compiler = new YaraWasm.Compiler();
compiler.addSource('rule x { strings: $a = "abc" condition: $a }');

const rules = compiler.build();
const result = rules.scan(new Uint8Array([0x61, 0x62, 0x63]));

console.log(result.matches);
})();
</script>
```

Use `dist/yara-x-wasm-bundle.min.js` instead of `dist/yara-x-wasm-bundle.js` for
production deployments. By default the bundle loads its wasm from
`pkg/yara-x-wasm_bg.wasm`, so keep the standard package layout intact when
serving it.

### Object-style API with scanner configuration:
If your environment supports the new JavaScript `using` keyword (Explicit Resource Management), you can let the
runtime handle it automatically because the types implement `[Symbol.dispose]`:

```js
import init, { Compiler, Scanner } from "yara-x-wasm";

await init();

const compiler = new Compiler();
compiler.defineGlobal("threshold", 7);
compiler.addSource('rule x { condition: threshold == 7 }');

const rules = compiler.build();
const scanner = new Scanner(rules);
scanner.setGlobal("threshold", 9);
{
using compiler = new Compiler();
compiler.addSource('rule x { condition: true }');
} // compiler.free() is called automatically here!
```

Loading