@litko/yara-x 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +426 -0
  2. package/index.d.ts +268 -0
  3. package/index.js +321 -0
  4. package/package.json +58 -0
package/README.md ADDED
@@ -0,0 +1,426 @@
1
+ # @litko/yara-x
2
+
3
+ **v0.1.0**
4
+
5
+ ## Features
6
+
7
+ - High Performance: Built with [napi-rs](https://napi-rs.com) and [VirusTotal/yara-x](https://github.com/VirusTotal/yara-x)
8
+ - Async Support: First-class support for asynchronous scanning
9
+ - WASM Compilation: Compile rules to WebAssembly for portable execution
10
+ - Zero Dependencies: No external runtime dependencies
11
+
12
+ ## Usage
13
+
14
+ ### Installation
15
+
16
+ ```bash
17
+ npm install @litko/yara-x
18
+ ```
19
+
20
+ ### Basic Example
21
+
22
+ ```javascript
23
+ import { compile } from "yara-x";
24
+
25
+ // Compile yara rules
26
+ const rules = compile(`
27
+ rule test_rule {
28
+ strings:
29
+ $a = "hello world"
30
+ condition:
31
+ $a
32
+ }
33
+ `);
34
+
35
+ // Scan a buffer
36
+ const buffer = Buffer.from("This is a test with hello world in it");
37
+ const matches = rules.scan(buffer);
38
+
39
+ // Process matches
40
+ if (matches.length > 0) {
41
+ console.log(`Found ${matches.length} matching rules:`);
42
+ matches.forEach((match) => {
43
+ console.log(`- Rule: ${match.ruleIdentifier}`);
44
+ match.matches.forEach((stringMatch) => {
45
+ console.log(
46
+ ` * Match at offset ${stringMatch.offset}: ${stringMatch.data}`,
47
+ );
48
+ });
49
+ });
50
+ } else {
51
+ console.log("No matches found");
52
+ }
53
+ ```
54
+
55
+ ## Scanning Files
56
+
57
+ ```javascript
58
+ import { fromFile, compile } from "yara-x";
59
+ import { readFileSync } from "fs";
60
+
61
+ // Load rules from a file
62
+ const rules = fromFile("./rules/malware_rules.yar");
63
+
64
+ try {
65
+ // Scan a file directly
66
+ const matches = rules.scanFile("./samples/suspicious_file.exe");
67
+
68
+ console.log(`Found ${matches.length} matching rules`);
69
+ } catch (error) {
70
+ console.error(`Scanning error: ${error.message}`);
71
+ }
72
+ ```
73
+
74
+ ## Asynchronous Scanning
75
+
76
+ ```javascript
77
+ import { compile } from "yara-x";
78
+
79
+ async function scanLargeFile() {
80
+ const rules = compile(`rule large_file_rule {
81
+ strings:
82
+ $a = "sensitive data"
83
+ condition:
84
+ $a
85
+ }
86
+ `);
87
+
88
+ try {
89
+ // Scan a file asynchronously
90
+ const matches = await rules.scanFileAsync("./samples/large_file.bin");
91
+ console.log(`Found ${matches.length} matching rules`);
92
+ } catch (error) {
93
+ console.error(`Async scanning error: ${error.message}`);
94
+ }
95
+ }
96
+
97
+ scanLargeFile();
98
+ ```
99
+
100
+ ## Variables
101
+
102
+ ```javascript
103
+ import { compile } from "yara-x";
104
+
105
+ // Create a scanner with variables
106
+ const rules = compile(
107
+ `
108
+ rule variable_rule {
109
+ condition:
110
+ string_var contains "secret" and int_var > 10
111
+ }
112
+ `,
113
+ {
114
+ defineVariables: {
115
+ string_var: "this is a secret message",
116
+ int_var: "20",
117
+ },
118
+ },
119
+ );
120
+
121
+ // Scan with default variables
122
+ let matches = rules.scan(Buffer.from("test data"));
123
+ console.log(`Matches with default variables: ${matches.length}`);
124
+
125
+ // Override variables at scan time
126
+ matches = rules.scan(Buffer.from("test data"), {
127
+ string_var: "no secrets here",
128
+ int_var: 5, // Note: variables at scan time can be numbers as well
129
+ });
130
+ console.log(`Matches with overridden variables: ${matches.length}`);
131
+ ```
132
+
133
+ ## WASM Compilation
134
+
135
+ ```javascript
136
+ import { compile, compileToWasm } from "yara-x";
137
+
138
+ // Compile rules to WASM
139
+ const rule = `
140
+ rule wasm_test {
141
+ strings:
142
+ $a = "compile to wasm"
143
+ condition:
144
+ $a
145
+ }
146
+ `;
147
+
148
+ // Static compilation
149
+ compileToWasm(rule, "./output/rules.wasm");
150
+
151
+ // Or from a compiled rules instance
152
+ const compiledRules = compile(rule);
153
+ compiledRules.emitWasmFile("./output/instance_rules.wasm");
154
+
155
+ // Async compilation
156
+ await compiledRules.emitWasmFileAsync("./output/async_rules.wasm");
157
+ ```
158
+
159
+ ## Incremental Rule Building
160
+
161
+ ```javascript
162
+ import { create } from "yara-x";
163
+
164
+ // Create an empty scanner
165
+ const scanner = create();
166
+
167
+ // Add rules incrementally
168
+ scanner.addRuleSource(`
169
+ wrule first_rule {
170
+ strings:
171
+ $a = "first pattern"
172
+ condition:
173
+ $a
174
+ }
175
+ `);
176
+
177
+ // Add rules from a file
178
+ scanner.addRuleFile("./rules/more_rules.yar");
179
+
180
+ // Add another rule
181
+ scanner.addRuleSource(`
182
+ rule another_rule {
183
+ strings:
184
+ $a = "another pattern"
185
+ condition:
186
+ $a
187
+ }
188
+ `);
189
+
190
+ // Now scan with all the rules
191
+ const matches = scanner.scan(Buffer.from("test data with first pattern"));
192
+ ```
193
+
194
+ ## Rule Validation
195
+
196
+ ```javascript
197
+ import { validate } from "yara-x";
198
+
199
+ // Validate rules without executing them
200
+ const result = validate(`
201
+ rule valid_rule {
202
+ strings:
203
+ $a = "valid"
204
+ condition:
205
+ $a
206
+ }
207
+ `);
208
+
209
+ if (result.errors.length === 0) {
210
+ console.log("Rules are valid!");
211
+ } else {
212
+ console.error("Rule validation failed:");
213
+ result.errors.forEach((error) => {
214
+ console.error(`- ${error.code}: ${error.message}`);
215
+ });
216
+ }
217
+ ```
218
+
219
+ ## Advanced Options
220
+
221
+ ```javascript
222
+ import { compile } from "yara-x";
223
+
224
+ // Create a scanner with advanced options
225
+ const rules = compile(
226
+ `
227
+ rule advanced_rule {
228
+ strings:
229
+ $a = /hello[[:space:]]world/ // Using POSIX character class
230
+ condition:
231
+ $a and test_var > 10
232
+ }
233
+ `,
234
+ {
235
+ // Define variables
236
+ defineVariables: {
237
+ test_var: "20",
238
+ },
239
+
240
+ // Enable relaxed regular expression syntax
241
+ relaxedReSyntax: true,
242
+
243
+ // Enable condition optimization
244
+ conditionOptimization: true,
245
+
246
+ // Ignore specific modules
247
+ ignoreModules: ["pe"],
248
+
249
+ : // Error on potentially slow patterns
250
+ errorOnSlowPattern: true,
251
+
252
+ // Error on potentially slow loops
253
+ errorOnSlowLoop: true,
254
+ },
255
+ );
256
+ ```
257
+
258
+ ## Error Handling
259
+
260
+ ### Compilation Errors
261
+
262
+ ```javascript
263
+ import { compile } from "yara-x";
264
+
265
+ try {
266
+ // This will throw an error due to invalid syntax
267
+ const rules = compile(`
268
+ rule invalid_rule {
269
+ strings:
270
+ $a = "unclosed string
271
+ condition:
272
+ $a
273
+ }
274
+ `);
275
+ } catch (error) {
276
+ console.error(`Compilation error: ${error.message}`);
277
+ // Output: Compilation error: error[E001]: syntax error
278
+ // --> line:3:28
279
+ // |
280
+ // 3 | $a = "unclosed string
281
+ // | ^ expecting `"`, found end of file
282
+ // 278: }
283
+ }
284
+ ```
285
+
286
+ ### Scanning errors
287
+
288
+ ```javascript
289
+ import { compile } from "yara-x";
290
+
291
+ const rules = compile(`
292
+ rule test_rule {
293
+ condition:
294
+ true
295
+ }
296
+ `);
297
+
298
+ try {
299
+ // This will throw if the file doesn't exist
300
+ rules.scanFile("/path/to/nonexistent/file.bin");
301
+ } catch (error) {
302
+ console.error(`Scanning error: ${error.message}`);
303
+ // Output: Scanning error: Error reading file: No such file or directory (os error 2)
304
+ }
305
+ ```
306
+
307
+ ### Async Errors
308
+
309
+ ```javascript
310
+ import { compile, compileToWasm } from "yara-x";
311
+
312
+ async function handleAsyncErrors() {
313
+ const rules = compile(`
314
+ rule test_rule {
315
+ condition:
316
+ true
317
+ }
318
+ `);
319
+
320
+ try {
321
+ await rules.scanFileAsync("/path/to/nonexistent/file.bin");
322
+ } catch (error) {
323
+ console.error(`Async scanning error: ${error.message}`);
324
+ }
325
+
326
+ try {
327
+ await compileToWasm(
328
+ "rule test { condition: true }",
329
+ "/invalid/path/rules.wasm",
330
+ );
331
+ } catch (error) {
332
+ console.error(`WASM compilation error: ${error.message}`);
333
+ }
334
+ }
335
+
336
+ handleAsyncErrors();
337
+ ```
338
+
339
+ ## Compiler Warnings
340
+
341
+ ```javascript
342
+ import { compile } from "yara-x";
343
+
344
+ // Create a scanner with a rule that generates warnings
345
+ const rules = compile(`
346
+ rule warning_rule {
347
+ strings:
348
+ $a = "unused string"
349
+ condition:
350
+ true // Warning: invariant expression
351
+ }
352
+ `);
353
+
354
+ // Get and display warnings
355
+ const warnings = rules.getWarnings();
356
+ if (warnings.length > 0) {
357
+ console.log("Compiler warnings:");
358
+ warnings.forEach((warning) => {
359
+ console.log(`- ${warning.code}: ${warning.message}`);
360
+ });
361
+ }
362
+ ```
363
+
364
+ ## Performance Benchmarks
365
+
366
+ **Test Setup:**
367
+
368
+ - **Hardware:** MacBook Pro (M3 Max, 36GB RAM)
369
+ - **Test Data:** Generated data of varying sizes (small: 64 bytes, medium: 100KB, large: 10MB). See `__test__/benchmark.mjs` for data generation and benchmarking code.
370
+ - The Large test file (10MB) is auto-generated, to prevent bloating the size of the repository.
371
+
372
+ **Key Metrics (Averages):**
373
+
374
+ | Operation | Average Time | Iterations | p50 | p95 | p99 |
375
+ | :---------------------------------------------- | -----------: | ---------: | -------: | -------: | -------: |
376
+ | Scanner Creation (Simple Rule) | 1.675 ms | 100 | 1.547 ms | 2.318 ms | 2.657 ms |
377
+ | Scanner Creation (Complex Rule) | 1.878 ms | 100 | 1.848 ms | 2.005 ms | 2.865 ms |
378
+ | Scanner Creation (Regex Rule) | 2.447 ms | 100 | 2.444 ms | 2.473 ms | 2.569 ms |
379
+ | Scanner Creation (Multiple Rules) | 1.497 ms | 100 | 1.488 ms | 1.547 ms | 1.819 ms |
380
+ | Scanning Small Data (64 bytes, Simple Rule) | 0.145 ms | 1000 | 0.143 ms | 0.156 ms | 0.169 ms |
381
+ | Scanning Medium Data (100KB, Simple Rule) | 0.151 ms | 100 | 0.146 ms | 0.179 ms | 0.205 ms |
382
+ | Scanning Large Data (10MB, Simple Rule) | 0.347 ms | 10 | 0.340 ms | 0.394 ms | 0.394 ms |
383
+ | Scanning Medium Data (100KB, Complex Rule) | 0.219 ms | 100 | 0.215 ms | 0.254 ms | 0.269 ms |
384
+ | Scanning Medium Data (100KB, Regex Rule) | 0.156 ms | 100 | 0.152 ms | 0.182 ms | 0.210 ms |
385
+ | Scanning Medium Data (100KB, Multiple Rules) | 0.218 ms | 100 | 0.212 ms | 0.261 ms | 0.353 ms |
386
+ | Async Scanning Medium Data (100KB, Simple Rule) | 0.012 ms | 100 | 0.011 ms | 0.016 ms | 0.027ms |
387
+ | Scanning with Variables | 0.143 ms | 1000 | 0.140 ms | 0.155 ms | 0.166 ms |
388
+ | Scanning with Variables (Override at Scan Time) | 0.144 ms | 1000 | 0.142 ms | 0.158 ms | 0.175 ms |
389
+
390
+ # API Reference
391
+
392
+ ### Functions
393
+
394
+ - `compile(ruleSource: string, options?: CompilerOptions)` - Compiles yara rules from a string.
395
+ - `compileToWasm(ruleSource: string, outputPath: string, options?: CompilerOptions)` - Compiles yara rules from a string to WASM file.
396
+ - `compileFileToWasm(rulesPath: string, outputPath: string, options?: CompilerOptions)` - Compiles yara rules from a file to WASM file.
397
+ - `validate(ruleSource: string, options?: CompilerOptions)` - Validates yara rules without executing them.
398
+ - `create(options?: CompilerOptions)` - Creates an empty rules scanner to add rules incrementally.
399
+ - `fromFile(rulePath: string, options?: CompilerOptions)` - Compiles yara rules from a file.
400
+
401
+ ### yarax Methods
402
+
403
+ - `getWarnings()` - Get compiler warnings.
404
+ - `scan(data: Buffer, variables?: Record<string, string | number>)` - Scan a buffer.
405
+ - `scanFile(filePath: string, variables?: Record<string, string | number>)` - Scan a file.
406
+ - `scanAsync(data: Buffer, variables?: Record<string, object | undefined | null>)` - Scan a buffer asynchronously.
407
+ - `scanFileAsync(filePath: string, variables?: Record<string, object | undefined | null>)` - Scan a file asynchronously.
408
+ - `emitWasmFile(filePath: string)` - Emit compiled rules to WASM file synchronously.
409
+ - `emitWasmFileAsync(filePath: string)` - Emit compiled rules to WASM file asynchronously.
410
+ - `addRuleSource(rules: string)` - Add rules from a string to an existing scanner.
411
+ - `addRuleFile(filePath: string)` - Add rules from a file to an existing scanner.
412
+
413
+ ### Rule Validation
414
+
415
+ - `validate(rules: string, options?: CompilerOptions)` - Validate yara rules without executing them.
416
+
417
+ ## Licenses
418
+
419
+ This project incorporates code under two distinct licenses:
420
+
421
+ - **MIT License:**
422
+ - The node.js bindings and other code specific to this module are licensed under the MIT license.
423
+ - See `LICENSE-MIT` for the full text.
424
+ - **BSD-3-Clause License:**
425
+ - The included yara-x library is licensed under the BSD-3-Clause license.
426
+ - See `LICENSE-BSD-3-Clause` for the full text.
package/index.d.ts ADDED
@@ -0,0 +1,268 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ /** MatchData struct represents a match found by a YARA rule. */
7
+ export interface MatchData {
8
+ /** The offset of the match in the scanned data. */
9
+ offset: number
10
+ /** The length of the matched data. */
11
+ length: number
12
+ /** The matched data as a string. */
13
+ data: string
14
+ /** The identifier of the pattern that matched. */
15
+ identifier: string
16
+ }
17
+ /**
18
+ * RuleMatch struct represents a matching rule found during scanning.
19
+ *
20
+ * See [yara_::Match](https://docs.rs/yara-x/latest/yara_x/struct.Match.html) for more details.
21
+ */
22
+ export interface RuleMatch {
23
+ /** The identifier of the rule that matched. */
24
+ ruleIdentifier: string
25
+ /** The namespace of the rule that matched. */
26
+ namespace: string
27
+ /** The metadata associated with the rule that matched. */
28
+ meta: object
29
+ /** The tags associated with the rule that matched. */
30
+ tags: Array<string>
31
+ /** The matches found by the rule. */
32
+ matches: Array<MatchData>
33
+ }
34
+ /**
35
+ * CompilerOptions struct represents the options for the YARA compiler.
36
+ *
37
+ * See [yara_x::Compiler](https://docs.rs/yara-x/latest/yara_x/struct.Compiler.html) for more
38
+ * details.
39
+ */
40
+ export interface CompilerOptions {
41
+ /** Defines global variables for the YARA rules. */
42
+ defineVariables?: object
43
+ /** A list of module names to ignore during compilation. */
44
+ ignoreModules?: Array<string>
45
+ /** A list of banned modules that cannot be used in the YARA rules. */
46
+ bannedModules?: Array<BannedModule>
47
+ /** A list of features to enable for the YARA rules. */
48
+ features?: Array<string>
49
+ /** Whether to use relaxed regular expression syntax. */
50
+ relaxedReSyntax?: boolean
51
+ /** Whether to optimize conditions in the YARA rules. */
52
+ conditionOptimization?: boolean
53
+ /** Whether to raise an error on slow patterns. */
54
+ errorOnSlowPattern?: boolean
55
+ /** Whether to raise an error on slow loops. */
56
+ errorOnSlowLoop?: boolean
57
+ }
58
+ /** BannedModule struct represents a module that is banned from being used in YARA rules. */
59
+ export interface BannedModule {
60
+ /** The name of the banned module. */
61
+ name: string
62
+ /** The title of the error message if the module is used. */
63
+ errorTitle: string
64
+ /** The error message if the module is used. */
65
+ errorMessage: string
66
+ }
67
+ /**
68
+ * CompilerWarning struct represents a warning generated by the YARA compiler.
69
+ *
70
+ * See [yara_x::CompilerWarning](https://docs.rs/yara-x/latest/yara_x/warnings/enum.Warning.html)
71
+ * for more details.
72
+ */
73
+ export interface CompilerWarning {
74
+ /** The code of the warning. */
75
+ code: string
76
+ /** The message of the warning. */
77
+ message: string
78
+ /** The source of the warning, if available. */
79
+ source?: string
80
+ /** The line number where the warning occurred, if available. */
81
+ line?: number
82
+ /** The column number where the warning occurred, if available. */
83
+ column?: number
84
+ }
85
+ /**
86
+ * CompilerError struct represents an error generated by the YARA compiler.
87
+ *
88
+ * See
89
+ * [yara_x::CompileError](https://docs.rs/yara-x/latest/yara_x/errors/enum.CompileError.html)
90
+ * for more details.
91
+ */
92
+ export interface CompilerError {
93
+ /** The code of the error. */
94
+ code: string
95
+ /** The message of the error. */
96
+ message: string
97
+ /** The source of the error, if available. */
98
+ source?: string
99
+ /** The line number where the error occurred, if available. */
100
+ line?: number
101
+ /** The column number where the error occurred, if available. */
102
+ column?: number
103
+ }
104
+ /**
105
+ * CompileResult struct represents the result of compiling YARA rules.
106
+ * It contains any warnings or errors generated during the compilation process.
107
+ */
108
+ export interface CompileResult {
109
+ /** Any warnings generated during the compilation process. */
110
+ warnings: Array<CompilerWarning>
111
+ /** Any errors generated during the compilation process. */
112
+ errors: Array<CompilerError>
113
+ }
114
+ /**
115
+ * Compiles a YARA rule source string and returns any warnings or errors generated during the
116
+ * compilation process.
117
+ *
118
+ * Exported as `validate` in the NAPI interface.
119
+ *
120
+ * Example
121
+ *
122
+ * ```javascript
123
+ * const { validate } = require('your_yara_module');
124
+ * const result = validate('rule example { strings: $a = "example" condition: $a }');
125
+ * ```
126
+ */
127
+ export declare function validate(ruleSource: string, options?: CompilerOptions | undefined | null): CompileResult
128
+ /**
129
+ * Compiles a YARA rule source string and returns a YaraX instance with the compiled rules.
130
+ *
131
+ * Exported as `compile` in the NAPI interface.
132
+ *
133
+ * Example
134
+ *
135
+ * ```javascript
136
+ * const { compile } = require('your_yara_module');
137
+ * const yarax = compile('rule example { strings: $a = "example" condition: $a }');
138
+ * ```
139
+ */
140
+ export declare function compile(ruleSource: string, options?: CompilerOptions | undefined | null): YaraX
141
+ /**
142
+ * Creates a new YaraX instance with empty rules and no source code.
143
+ *
144
+ * Exported as `create` in the NAPI interface.
145
+ *
146
+ * Example
147
+ *
148
+ * ```javascript
149
+ * const { create } = require('your_yara_module');
150
+ * const yarax = create();
151
+ *
152
+ * // Now you can add rules or compile them later
153
+ *
154
+ * yarax.addRuleSource('rule example { strings: $a = "example" condition: $a }');
155
+ * yarax.addRuleFile('path/to/rule_file.yar');
156
+ * yarax.defineVariable('myVar', 'myValue');
157
+ * ```
158
+ */
159
+ export declare function create(): YaraX
160
+ /**
161
+ * Creates a new YaraX instance from a file containing YARA rules.
162
+ *
163
+ * Exported as `fromFile` in the NAPI interface.
164
+ *
165
+ * Example
166
+ *
167
+ * ```javascript
168
+ * const { fromFile } = require('your_yara_module');
169
+ * const yarax = fromFile('path/to/rule_file.yar');
170
+ * ```
171
+ */
172
+ export declare function fromFile(rulePath: string, options?: CompilerOptions | undefined | null): YaraX
173
+ /**
174
+ * Compiles a YARA rule source string to a WASM file.
175
+ *
176
+ * Exported as `compileToWasm` in the NAPI interface.
177
+ *
178
+ * Example
179
+ *
180
+ * ```javascript
181
+ * const { compileToWasm } = require('your_yara_module');
182
+ * compileToWasm('rule example { strings: $a = "example" condition: $a }', 'output.wasm');
183
+ * ```
184
+ */
185
+ export declare function compileToWasm(ruleSource: string, outputPath: string, options?: CompilerOptions | undefined | null): void
186
+ /**
187
+ * Compiles a YARA rule file to a WASM file.
188
+ *
189
+ * Exported as `compileFileToWasm` in the NAPI interface.
190
+ *
191
+ * Example
192
+ *
193
+ * ```javascript
194
+ * const { compileFileToWasm } = require('your_yara_module');
195
+ * compileFileToWasm('path/to/rule_file.yar', 'output.wasm');
196
+ * ```
197
+ */
198
+ export declare function compileFileToWasm(rulePath: string, outputPath: string, options?: CompilerOptions | undefined | null): void
199
+ /**
200
+ * YaraX struct represents the YARA rules and their associated data.
201
+ * It contains the compiled rules, source code, warnings, and variables.
202
+ *
203
+ * See [yara_x::Rules](https://docs.rs/yara-x/latest/yara_x/struct.Rules.html) for more details.
204
+ */
205
+ export declare class YaraX {
206
+ getWarnings(): Array<CompilerWarning>
207
+ /**
208
+ * Scans the provided data using the compiled YARA rules.
209
+ * This function takes the scanned data and an optional object of variables,
210
+ * and returns a vector of RuleMatch representing the matching rules found during the scan.
211
+ */
212
+ scan(data: Buffer, variables?: Record<string, string | number>): Array<RuleMatch>
213
+ /**
214
+ * Scans a file using the compiled YARA rules.
215
+ * This function takes the file path and an optional object of variables,
216
+ * and returns a vector of RuleMatch representing the matching rules found during the scan.
217
+ */
218
+ scanFile(filePath: string, variables?: Record<string, string | number>): Array<RuleMatch>
219
+ /**
220
+ * Emits a WASM file from the compiled YARA rules.
221
+ * This function takes the output path and writes the compiled rules to a WASM file.
222
+ */
223
+ emitWasmFile(outputPath: string): void
224
+ /**
225
+ * Scans the provided data asynchronously using the compiled YARA rules.
226
+ * This function takes the scanned data and an optional object of variables,
227
+ * and returns an AsyncTask that will resolve to a vector of RuleMatch representing the matching
228
+ * rules found during the scan.
229
+ *
230
+ * This allows for non-blocking scanning of data, which can be useful for large datasets or
231
+ * performance-critical applications.
232
+ */
233
+ scanAsync(data: Buffer, variables?: object | undefined | null): Promise<unknown>
234
+ /**
235
+ * Scans a file asynchronously using the compiled YARA rules.
236
+ * This function takes the file path and an optional object of variables,
237
+ * and returns an AsyncTask that will resolve to a vector of RuleMatch representing the matching
238
+ * rules found during the scan.
239
+ *
240
+ * This allows for non-blocking scanning of files, which can be useful for large files or
241
+ * performance-critical applications.
242
+ */
243
+ scanFileAsync(filePath: string, variables?: object | undefined | null): Promise<unknown>
244
+ /**
245
+ * Emits a WASM file asynchronously from the compiled YARA rules.
246
+ * This function takes the output path
247
+ * and returns an AsyncTask that will resolve when the WASM file is successfully emitted.
248
+ */
249
+ emitWasmFileAsync(outputPath: string): Promise<unknown>
250
+ /**
251
+ * Adds a rule source to the YARA compiler.
252
+ * This function takes a rule source string,
253
+ * compiles it, and updates the YaraX instance with the new rules.
254
+ */
255
+ addRuleSource(ruleSource: string): void
256
+ /**
257
+ * Adds a rule file to the YARA compiler.
258
+ * This function takes a file path,
259
+ * reads the file content, and adds it to the YaraX instance.
260
+ */
261
+ addRuleFile(filePath: string): void
262
+ /**
263
+ * Defines a variable for the YARA compiler.
264
+ * This function takes a variable name and value,
265
+ * and adds it to the YaraX instance.
266
+ */
267
+ defineVariable(name: string, value: string): void
268
+ }
package/index.js ADDED
@@ -0,0 +1,321 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ /* prettier-ignore */
4
+
5
+ /* auto-generated by NAPI-RS */
6
+
7
+ const { existsSync, readFileSync } = require('fs')
8
+ const { join } = require('path')
9
+
10
+ const { platform, arch } = process
11
+
12
+ let nativeBinding = null
13
+ let localFileExisted = false
14
+ let loadError = null
15
+
16
+ function isMusl() {
17
+ // For Node 10
18
+ if (!process.report || typeof process.report.getReport !== 'function') {
19
+ try {
20
+ const lddPath = require('child_process').execSync('which ldd').toString().trim()
21
+ return readFileSync(lddPath, 'utf8').includes('musl')
22
+ } catch (e) {
23
+ return true
24
+ }
25
+ } else {
26
+ const { glibcVersionRuntime } = process.report.getReport().header
27
+ return !glibcVersionRuntime
28
+ }
29
+ }
30
+
31
+ switch (platform) {
32
+ case 'android':
33
+ switch (arch) {
34
+ case 'arm64':
35
+ localFileExisted = existsSync(join(__dirname, 'yara-x.android-arm64.node'))
36
+ try {
37
+ if (localFileExisted) {
38
+ nativeBinding = require('./yara-x.android-arm64.node')
39
+ } else {
40
+ nativeBinding = require('@litko/yara-x-android-arm64')
41
+ }
42
+ } catch (e) {
43
+ loadError = e
44
+ }
45
+ break
46
+ case 'arm':
47
+ localFileExisted = existsSync(join(__dirname, 'yara-x.android-arm-eabi.node'))
48
+ try {
49
+ if (localFileExisted) {
50
+ nativeBinding = require('./yara-x.android-arm-eabi.node')
51
+ } else {
52
+ nativeBinding = require('@litko/yara-x-android-arm-eabi')
53
+ }
54
+ } catch (e) {
55
+ loadError = e
56
+ }
57
+ break
58
+ default:
59
+ throw new Error(`Unsupported architecture on Android ${arch}`)
60
+ }
61
+ break
62
+ case 'win32':
63
+ switch (arch) {
64
+ case 'x64':
65
+ localFileExisted = existsSync(
66
+ join(__dirname, 'yara-x.win32-x64-msvc.node')
67
+ )
68
+ try {
69
+ if (localFileExisted) {
70
+ nativeBinding = require('./yara-x.win32-x64-msvc.node')
71
+ } else {
72
+ nativeBinding = require('@litko/yara-x-win32-x64-msvc')
73
+ }
74
+ } catch (e) {
75
+ loadError = e
76
+ }
77
+ break
78
+ case 'ia32':
79
+ localFileExisted = existsSync(
80
+ join(__dirname, 'yara-x.win32-ia32-msvc.node')
81
+ )
82
+ try {
83
+ if (localFileExisted) {
84
+ nativeBinding = require('./yara-x.win32-ia32-msvc.node')
85
+ } else {
86
+ nativeBinding = require('@litko/yara-x-win32-ia32-msvc')
87
+ }
88
+ } catch (e) {
89
+ loadError = e
90
+ }
91
+ break
92
+ case 'arm64':
93
+ localFileExisted = existsSync(
94
+ join(__dirname, 'yara-x.win32-arm64-msvc.node')
95
+ )
96
+ try {
97
+ if (localFileExisted) {
98
+ nativeBinding = require('./yara-x.win32-arm64-msvc.node')
99
+ } else {
100
+ nativeBinding = require('@litko/yara-x-win32-arm64-msvc')
101
+ }
102
+ } catch (e) {
103
+ loadError = e
104
+ }
105
+ break
106
+ default:
107
+ throw new Error(`Unsupported architecture on Windows: ${arch}`)
108
+ }
109
+ break
110
+ case 'darwin':
111
+ localFileExisted = existsSync(join(__dirname, 'yara-x.darwin-universal.node'))
112
+ try {
113
+ if (localFileExisted) {
114
+ nativeBinding = require('./yara-x.darwin-universal.node')
115
+ } else {
116
+ nativeBinding = require('@litko/yara-x-darwin-universal')
117
+ }
118
+ break
119
+ } catch {}
120
+ switch (arch) {
121
+ case 'x64':
122
+ localFileExisted = existsSync(join(__dirname, 'yara-x.darwin-x64.node'))
123
+ try {
124
+ if (localFileExisted) {
125
+ nativeBinding = require('./yara-x.darwin-x64.node')
126
+ } else {
127
+ nativeBinding = require('@litko/yara-x-darwin-x64')
128
+ }
129
+ } catch (e) {
130
+ loadError = e
131
+ }
132
+ break
133
+ case 'arm64':
134
+ localFileExisted = existsSync(
135
+ join(__dirname, 'yara-x.darwin-arm64.node')
136
+ )
137
+ try {
138
+ if (localFileExisted) {
139
+ nativeBinding = require('./yara-x.darwin-arm64.node')
140
+ } else {
141
+ nativeBinding = require('@litko/yara-x-darwin-arm64')
142
+ }
143
+ } catch (e) {
144
+ loadError = e
145
+ }
146
+ break
147
+ default:
148
+ throw new Error(`Unsupported architecture on macOS: ${arch}`)
149
+ }
150
+ break
151
+ case 'freebsd':
152
+ if (arch !== 'x64') {
153
+ throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
154
+ }
155
+ localFileExisted = existsSync(join(__dirname, 'yara-x.freebsd-x64.node'))
156
+ try {
157
+ if (localFileExisted) {
158
+ nativeBinding = require('./yara-x.freebsd-x64.node')
159
+ } else {
160
+ nativeBinding = require('@litko/yara-x-freebsd-x64')
161
+ }
162
+ } catch (e) {
163
+ loadError = e
164
+ }
165
+ break
166
+ case 'linux':
167
+ switch (arch) {
168
+ case 'x64':
169
+ if (isMusl()) {
170
+ localFileExisted = existsSync(
171
+ join(__dirname, 'yara-x.linux-x64-musl.node')
172
+ )
173
+ try {
174
+ if (localFileExisted) {
175
+ nativeBinding = require('./yara-x.linux-x64-musl.node')
176
+ } else {
177
+ nativeBinding = require('@litko/yara-x-linux-x64-musl')
178
+ }
179
+ } catch (e) {
180
+ loadError = e
181
+ }
182
+ } else {
183
+ localFileExisted = existsSync(
184
+ join(__dirname, 'yara-x.linux-x64-gnu.node')
185
+ )
186
+ try {
187
+ if (localFileExisted) {
188
+ nativeBinding = require('./yara-x.linux-x64-gnu.node')
189
+ } else {
190
+ nativeBinding = require('@litko/yara-x-linux-x64-gnu')
191
+ }
192
+ } catch (e) {
193
+ loadError = e
194
+ }
195
+ }
196
+ break
197
+ case 'arm64':
198
+ if (isMusl()) {
199
+ localFileExisted = existsSync(
200
+ join(__dirname, 'yara-x.linux-arm64-musl.node')
201
+ )
202
+ try {
203
+ if (localFileExisted) {
204
+ nativeBinding = require('./yara-x.linux-arm64-musl.node')
205
+ } else {
206
+ nativeBinding = require('@litko/yara-x-linux-arm64-musl')
207
+ }
208
+ } catch (e) {
209
+ loadError = e
210
+ }
211
+ } else {
212
+ localFileExisted = existsSync(
213
+ join(__dirname, 'yara-x.linux-arm64-gnu.node')
214
+ )
215
+ try {
216
+ if (localFileExisted) {
217
+ nativeBinding = require('./yara-x.linux-arm64-gnu.node')
218
+ } else {
219
+ nativeBinding = require('@litko/yara-x-linux-arm64-gnu')
220
+ }
221
+ } catch (e) {
222
+ loadError = e
223
+ }
224
+ }
225
+ break
226
+ case 'arm':
227
+ if (isMusl()) {
228
+ localFileExisted = existsSync(
229
+ join(__dirname, 'yara-x.linux-arm-musleabihf.node')
230
+ )
231
+ try {
232
+ if (localFileExisted) {
233
+ nativeBinding = require('./yara-x.linux-arm-musleabihf.node')
234
+ } else {
235
+ nativeBinding = require('@litko/yara-x-linux-arm-musleabihf')
236
+ }
237
+ } catch (e) {
238
+ loadError = e
239
+ }
240
+ } else {
241
+ localFileExisted = existsSync(
242
+ join(__dirname, 'yara-x.linux-arm-gnueabihf.node')
243
+ )
244
+ try {
245
+ if (localFileExisted) {
246
+ nativeBinding = require('./yara-x.linux-arm-gnueabihf.node')
247
+ } else {
248
+ nativeBinding = require('@litko/yara-x-linux-arm-gnueabihf')
249
+ }
250
+ } catch (e) {
251
+ loadError = e
252
+ }
253
+ }
254
+ break
255
+ case 'riscv64':
256
+ if (isMusl()) {
257
+ localFileExisted = existsSync(
258
+ join(__dirname, 'yara-x.linux-riscv64-musl.node')
259
+ )
260
+ try {
261
+ if (localFileExisted) {
262
+ nativeBinding = require('./yara-x.linux-riscv64-musl.node')
263
+ } else {
264
+ nativeBinding = require('@litko/yara-x-linux-riscv64-musl')
265
+ }
266
+ } catch (e) {
267
+ loadError = e
268
+ }
269
+ } else {
270
+ localFileExisted = existsSync(
271
+ join(__dirname, 'yara-x.linux-riscv64-gnu.node')
272
+ )
273
+ try {
274
+ if (localFileExisted) {
275
+ nativeBinding = require('./yara-x.linux-riscv64-gnu.node')
276
+ } else {
277
+ nativeBinding = require('@litko/yara-x-linux-riscv64-gnu')
278
+ }
279
+ } catch (e) {
280
+ loadError = e
281
+ }
282
+ }
283
+ break
284
+ case 's390x':
285
+ localFileExisted = existsSync(
286
+ join(__dirname, 'yara-x.linux-s390x-gnu.node')
287
+ )
288
+ try {
289
+ if (localFileExisted) {
290
+ nativeBinding = require('./yara-x.linux-s390x-gnu.node')
291
+ } else {
292
+ nativeBinding = require('@litko/yara-x-linux-s390x-gnu')
293
+ }
294
+ } catch (e) {
295
+ loadError = e
296
+ }
297
+ break
298
+ default:
299
+ throw new Error(`Unsupported architecture on Linux: ${arch}`)
300
+ }
301
+ break
302
+ default:
303
+ throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
304
+ }
305
+
306
+ if (!nativeBinding) {
307
+ if (loadError) {
308
+ throw loadError
309
+ }
310
+ throw new Error(`Failed to load native binding`)
311
+ }
312
+
313
+ const { YaraX, validate, compile, create, fromFile, compileToWasm, compileFileToWasm } = nativeBinding
314
+
315
+ module.exports.YaraX = YaraX
316
+ module.exports.validate = validate
317
+ module.exports.compile = compile
318
+ module.exports.create = create
319
+ module.exports.fromFile = fromFile
320
+ module.exports.compileToWasm = compileToWasm
321
+ module.exports.compileFileToWasm = compileFileToWasm
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@litko/yara-x",
3
+ "version": "0.1.0",
4
+ "main": "index.js",
5
+ "types": "index.d.ts",
6
+ "napi": {
7
+ "name": "yara-x",
8
+ "triples": {
9
+ "additional": [
10
+ "x86_64-apple-darwin",
11
+ "aarch64-apple-darwin",
12
+ "x86_64-unknown-linux-gnu",
13
+ "aarch64-unknown-linux-gnu"
14
+ ]
15
+ }
16
+ },
17
+ "license": "MIT",
18
+ "devDependencies": {
19
+ "@napi-rs/cli": "^2.18.4",
20
+ "@napi-rs/package-template": "^1.0.0",
21
+ "@types/node": "^22.13.10"
22
+ },
23
+ "engines": {
24
+ "node": ">= 20"
25
+ },
26
+ "publishConfig": {
27
+ "registry": "https://registry.npmjs.org/",
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "artifacts": "napi artifacts",
32
+ "build": "napi build --platform --release",
33
+ "build:debug": "napi build --platform",
34
+ "universal": "napi universal",
35
+ "version": "napi version",
36
+ "test": "node --test __test__/index.spec.mjs",
37
+ "benchmark": "node __test__/benchmark.mjs"
38
+ },
39
+ "files": [
40
+ "index.js",
41
+ "index.d.ts"
42
+ ],
43
+ "keywords": [
44
+ "yara",
45
+ "yara-x",
46
+ "malware",
47
+ "detection",
48
+ "napi-rs",
49
+ "rust"
50
+ ],
51
+ "optionalDependencies": {
52
+ "@litko/yara-x-win32-x64-msvc": "0.1.0",
53
+ "@litko/yara-x-darwin-x64": "0.1.0",
54
+ "@litko/yara-x-linux-x64-gnu": "0.1.0",
55
+ "@litko/yara-x-darwin-arm64": "0.1.0",
56
+ "@litko/yara-x-linux-arm64-gnu": "0.1.0"
57
+ }
58
+ }