JSPM

@litko/yara-x

0.1.2
    • ESM via JSPM
    • ES Module Entrypoint
    • Export Map
    • Keywords
    • License
    • Repository URL
    • TypeScript Types
    • README
    • Created
    • Published
    • Downloads 5
    • Score
      100M100P100Q42672F
    • License MIT

    Package Exports

    • @litko/yara-x
    • @litko/yara-x/index.js

    This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@litko/yara-x) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

    Readme

    @litko/yara-x

    v0.1.1

    Features

    • High Performance: Built with napi-rs and VirusTotal/yara-x
    • Async Support: First-class support for asynchronous scanning
    • WASM Compilation: Compile rules to WebAssembly for portable execution
    • Zero Dependencies: No external runtime dependencies

    Usage

    Installation

    npm install @litko/yara-x

    Basic Example

    import { compile } from "@litko/yara-x";
    
    // Compile yara rules
    const rules = compile(`
      rule test_rule {
        strings:
          $a = "hello world"
        condition:
          $a
      }
    `);
    
    // Scan a buffer
    const buffer = Buffer.from("This is a test with hello world in it");
    const matches = rules.scan(buffer);
    
    // Process matches
    if (matches.length > 0) {
      console.log(`Found ${matches.length} matching rules:`);
      matches.forEach((match) => {
        console.log(`- Rule: ${match.ruleIdentifier}`);
        match.matches.forEach((stringMatch) => {
          console.log(
            `  * Match at offset ${stringMatch.offset}: ${stringMatch.data}`,
          );
        });
      });
    } else {
      console.log("No matches found");
    }

    Scanning Files

    import { fromFile, compile } from "@litko/yara-x";
    import { readFileSync } from "fs";
    
    // Load rules from a file
    const rules = fromFile("./rules/malware_rules.yar");
    
    try {
      // Scan a file directly
      const matches = rules.scanFile("./samples/suspicious_file.exe");
    
      console.log(`Found ${matches.length} matching rules`);
    } catch (error) {
      console.error(`Scanning error: ${error.message}`);
    }

    Asynchronous Scanning

    import { compile } from "@litko/yara-x";
    
    async function scanLargeFile() {
      const rules = compile(`rule large_file_rule {
          strings:
            $a = "sensitive data"
          condition:
            $a
        }
      `);
    
      try {
        // Scan a file asynchronously
        const matches = await rules.scanFileAsync("./samples/large_file.bin");
        console.log(`Found ${matches.length} matching rules`);
      } catch (error) {
        console.error(`Async scanning error: ${error.message}`);
      }
    }
    
    scanLargeFile();

    Variables

    import { compile } from "@litko/yara-x";
    
    // Create a scanner with variables
    const rules = compile(
      `
      rule variable_rule {
        condition:
          string_var contains "secret" and int_var > 10
      }
    `,
      {
        defineVariables: {
          string_var: "this is a secret message",
          int_var: "20",
        },
      },
    );
    
    // Scan with default variables
    let matches = rules.scan(Buffer.from("test data"));
    console.log(`Matches with default variables: ${matches.length}`);
    
    // Override variables at scan time
    matches = rules.scan(Buffer.from("test data"), {
      string_var: "no secrets here",
      int_var: 5, // Note: variables at scan time can be numbers as well
    });
    console.log(`Matches with overridden variables: ${matches.length}`);

    WASM Compilation

    import { compile, compileToWasm } from "@litko/yara-x";
    
    // Compile rules to WASM
    const rule = `
      rule wasm_test {
        strings:
          $a = "compile to wasm"
        condition:
          $a
      }
    `;
    
    // Static compilation
    compileToWasm(rule, "./output/rules.wasm");
    
    // Or from a compiled rules instance
    const compiledRules = compile(rule);
    compiledRules.emitWasmFile("./output/instance_rules.wasm");
    
    // Async compilation
    await compiledRules.emitWasmFileAsync("./output/async_rules.wasm");

    Incremental Rule Building

    import { create } from "@litko/yara-x";
    
    // Create an empty scanner
    const scanner = create();
    
    // Add rules incrementally
    scanner.addRuleSource(`
      wrule first_rule {
        strings:
          $a = "first pattern"
        condition:
          $a
      }
    `);
    
    // Add rules from a file
    scanner.addRuleFile("./rules/more_rules.yar");
    
    // Add another rule
    scanner.addRuleSource(`
      rule another_rule {
        strings:
          $a = "another pattern"
        condition:
          $a
      }
    `);
    
    // Now scan with all the rules
    const matches = scanner.scan(Buffer.from("test data with first pattern"));

    Rule Validation

    import { validate } from "@litko/yara-x";
    
    // Validate rules without executing them
    const result = validate(`
      rule valid_rule {
        strings:
          $a = "valid"
        condition:
          $a
        }
    `);
    
    if (result.errors.length === 0) {
      console.log("Rules are valid!");
    } else {
      console.error("Rule validation failed:");
      result.errors.forEach((error) => {
        console.error(`- ${error.code}: ${error.message}`);
      });
    }

    Advanced Options

    import { compile } from "@litko/yara-x";
    
    // Create a scanner with advanced options
    const rules = compile(
      `
      rule advanced_rule {
        strings:
          $a = /hello[[:space:]]world/ // Using POSIX character class
        condition:
          $a and test_var > 10
      }
    `,
      {
        // Define variables
        defineVariables: {
          test_var: "20",
        },
    
        // Enable relaxed regular expression syntax
        relaxedReSyntax: true,
    
        // Enable condition optimization
        conditionOptimization: true,
    
        // Ignore specific modules
        ignoreModules: ["pe"],
    
      :  // Error on potentially slow patterns
        errorOnSlowPattern: true,
    
        // Error on potentially slow loops
        errorOnSlowLoop: true,
      },
    );

    Error Handling

    Compilation Errors

    import { compile } from "@litko/yara-x";
    
    try {
      // This will throw an error due to invalid syntax
      const rules = compile(`
        rule invalid_rule {
          strings:
            $a = "unclosed string
          condition:
            $a
        }
      `);
    } catch (error) {
      console.error(`Compilation error: ${error.message}`);
      // Output: Compilation error: error[E001]: syntax error
      //  --> line:3:28
      //   |
      // 3 |         $a = "unclosed string
      //   |                            ^ expecting `"`, found end of file
      // 278:  }
    }

    Scanning errors

    import { compile } from "@litko/yara-x";
    
    const rules = compile(`
      rule test_rule {
        condition:
          true
      }
    `);
    
    try {
      // This will throw if the file doesn't exist
      rules.scanFile("/path/to/nonexistent/file.bin");
    } catch (error) {
      console.error(`Scanning error: ${error.message}`);
      // Output: Scanning error: Error reading file: No such file or directory (os error 2)
    }

    Async Errors

    import { compile, compileToWasm } from "@litko/yara-x";
    
    async function handleAsyncErrors() {
      const rules = compile(`
        rule test_rule {
          condition:
            true
        }
      `);
    
      try {
        await rules.scanFileAsync("/path/to/nonexistent/file.bin");
      } catch (error) {
        console.error(`Async scanning error: ${error.message}`);
      }
    
      try {
        await compileToWasm(
          "rule test { condition: true }",
          "/invalid/path/rules.wasm",
        );
      } catch (error) {
        console.error(`WASM compilation error: ${error.message}`);
      }
    }
    
    handleAsyncErrors();

    Compiler Warnings

    import { compile } from "@litko/yara-x";
    
    // Create a scanner with a rule that generates warnings
    const rules = compile(`
      rule warning_rule {
        strings:
          $a = "unused string"
        condition:
          true  // Warning: invariant expression
        }
    `);
    
    // Get and display warnings
    const warnings = rules.getWarnings();
    if (warnings.length > 0) {
      console.log("Compiler warnings:");
      warnings.forEach((warning) => {
        console.log(`- ${warning.code}: ${warning.message}`);
      });
    }

    Performance Benchmarks

    Test Setup:

    • Hardware: MacBook Pro (M3 Max, 36GB RAM)
    • Test Data: Generated data of varying sizes (small: 64 bytes, medium: 100KB, large: 10MB). See __test__/benchmark.mjs for data generation and benchmarking code.
    • The Large test file (10MB) is auto-generated, to prevent bloating the size of the repository.

    Key Metrics (Averages):

    Operation Average Time Iterations p50 p95 p99
    Scanner Creation (Simple Rule) 1.675 ms 100 1.547 ms 2.318 ms 2.657 ms
    Scanner Creation (Complex Rule) 1.878 ms 100 1.848 ms 2.005 ms 2.865 ms
    Scanner Creation (Regex Rule) 2.447 ms 100 2.444 ms 2.473 ms 2.569 ms
    Scanner Creation (Multiple Rules) 1.497 ms 100 1.488 ms 1.547 ms 1.819 ms
    Scanning Small Data (64 bytes, Simple Rule) 0.145 ms 1000 0.143 ms 0.156 ms 0.169 ms
    Scanning Medium Data (100KB, Simple Rule) 0.151 ms 100 0.146 ms 0.179 ms 0.205 ms
    Scanning Large Data (10MB, Simple Rule) 0.347 ms 10 0.340 ms 0.394 ms 0.394 ms
    Scanning Medium Data (100KB, Complex Rule) 0.219 ms 100 0.215 ms 0.254 ms 0.269 ms
    Scanning Medium Data (100KB, Regex Rule) 0.156 ms 100 0.152 ms 0.182 ms 0.210 ms
    Scanning Medium Data (100KB, Multiple Rules) 0.218 ms 100 0.212 ms 0.261 ms 0.353 ms
    Async Scanning Medium Data (100KB, Simple Rule) 0.012 ms 100 0.011 ms 0.016 ms 0.027ms
    Scanning with Variables 0.143 ms 1000 0.140 ms 0.155 ms 0.166 ms
    Scanning with Variables (Override at Scan Time) 0.144 ms 1000 0.142 ms 0.158 ms 0.175 ms

    API Reference

    Functions

    • compile(ruleSource: string, options?: CompilerOptions) - Compiles yara rules from a string.
    • compileToWasm(ruleSource: string, outputPath: string, options?: CompilerOptions) - Compiles yara rules from a string to WASM file.
    • compileFileToWasm(rulesPath: string, outputPath: string, options?: CompilerOptions) - Compiles yara rules from a file to WASM file.
    • validate(ruleSource: string, options?: CompilerOptions) - Validates yara rules without executing them.
    • create(options?: CompilerOptions) - Creates an empty rules scanner to add rules incrementally.
    • fromFile(rulePath: string, options?: CompilerOptions) - Compiles yara rules from a file.

    yarax Methods

    • getWarnings() - Get compiler warnings.
    • scan(data: Buffer, variables?: Record<string, string | number>) - Scan a buffer.
    • scanFile(filePath: string, variables?: Record<string, string | number>) - Scan a file.
    • scanAsync(data: Buffer, variables?: Record<string, object | undefined | null>) - Scan a buffer asynchronously.
    • scanFileAsync(filePath: string, variables?: Record<string, object | undefined | null>) - Scan a file asynchronously.
    • emitWasmFile(filePath: string) - Emit compiled rules to WASM file synchronously.
    • emitWasmFileAsync(filePath: string) - Emit compiled rules to WASM file asynchronously.
    • addRuleSource(rules: string) - Add rules from a string to an existing scanner.
    • addRuleFile(filePath: string) - Add rules from a file to an existing scanner.

    Rule Validation

    • validate(rules: string, options?: CompilerOptions) - Validate yara rules without executing them.

    Licenses

    This project incorporates code under two distinct licenses:

    • MIT License:
      • The node.js bindings and other code specific to this module are licensed under the MIT license.
      • See LICENSE-MIT for the full text.
    • BSD-3-Clause License:
      • The included yara-x library is licensed under the BSD-3-Clause license.
      • See LICENSE-BSD-3-Clause for the full text.