
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@xan105/ffi
Advanced tools
Foreign Function Interface (FFI) helper. Provides a friendly abstraction/API for:
Syntax is inspired by Deno FFI. The goal was to be able to easily switch from ffi-napi to koffi or vice versa.
📦 Scoped @xan105 packages are for my own personal use but feel free to use them.
Loading a library with Deno like syntax
import { dlopen } from "@xan105/ffi/[ napi | koffi ]";
const lib = dlopen("libm", {
ceil: {
result: "double",
parameters: [ "double" ]
}
});
lib.ceil(1.5); // 2
Asynchronous calling
import { dlopen } from "@xan105/ffi/[ napi | koffi ]";
const lib = dlopen("libm", {
ceil: {
result: "double",
parameters: [ "double" ],
nonblocking: true
}
});
await lib.ceil(1.5); // 2
Calling directly from a library
import { load, types } from "@xan105/ffi/[ napi | koffi ]";
const lib = load("user32.dll", { abi: "stdcall" });
const MessageBoxA = lib("MessageBoxA", "int", [
"void *",
types.win32.LPCSTR,
types.win32.LPCSTR,
"uint"
]);
const MB_ICONINFORMATION = 0x40;
MessageBoxA(null, "Hello World!", "Message", MB_ICONINFORMATION);
Callback with Deno like syntax
import { dlopen, Callback} from "@xan105/ffi/koffi";
const lib = dlopen(
"./callback.so",
{
set_status_callback: {
parameters: ["function"],
result: "void"
},
start_long_operation: {
parameters: [],
result: "void"
}
}
);
const callback = new Callback(
{
parameters: ["u8"],
result: "void",
},
(success) => {}
);
lib.set_status_callback(callback.pointer);
lib.start_long_operation();
callback.close();
npm install @xan105/ffi
Please note that ffi-napi and koffi are optional peer dependencies.
Install the one you wish to use yourself (or both 🙃).
⚠️ NB: As of this writing ffi-napi does not work with Electron >= 21.x.
Due to Electron and the V8 Memory Cage.
⚠️ This module is only available as an ECMAScript module (ESM).
💡 This lib doesn't have a default entry point. Choose the export corresponding to your liking.
import ... from "@xan105/ffi/napi";
//OR
import ... from "@xan105/ffi/koffi";
load(path: string, option?: object): functionLoad the given library path and return an handle function to call library's symbol(s).
⚙️ Option
ignoreLoadingFail?: boolean (false)When set to true the handle function will silently fail if the given library couldn't be loaded and return undefined in such case.
ignoreMissingSymbol?: boolean (false)When set to true the handle function will silently fail if the given library doesn't have the called symbol and return undefined in such case.
lazy?: boolean (false)When set to true use RTLD_LAZY (lazy-binding) on POSIX platforms otherwise use RTLD_NOW.
global?: boolean (false)When set to true use RTLD_GLOBAL on POSIX platforms otherwise use RTLD_LOCAL.
abi?: string (koffi: "func" | ffi-napi: "default_abi")ABI convention to use. Use this when you need to.
ex: Win32 API (x86) requires "stdcall".
[
"cdecl", "ms_cdecl", //koffi & ffi-napi
"stdcall", //koffi & ffi-napi
"fastcall", //koffi & ffi-napi
"thiscall", //koffi & ffi-napi
"win64", //ffi-napi
"unix64", //ffi-napi
"sysv", //ffi-napi
"vfp" //ffi-napi
]
integrity?: string (none)Subresource Integrity.
Return
An handle function to call library's symbol(s).
function(symbol: string | number, result: unknown, parameters: unknown[]): unknown
💡 Koffi can call by ordinal (symbol:number)
See the corresponding FFI library for more information on what to pass for result and parameters as they have string type parser, structure/array/pointer interface, ... and other features.
❌ Throws on error
Example:
import { load } from "@xan105/ffi/[ napi | koffi ]";
const lib = load("libm");
const ceil = lib("ceil", "double", ["double"]);
ceil(1.5); //2
dlopen(path: string, symbols: object, option?: object): objectOpen library and define exported symbols. This is a friendly wrapper to load() inspired by Deno FFI dlopen syntax.
If you ever use ffi-napi ffi.Library() this will be familiar.
Param
path: string
Library path to load.
symbols: object
Symbol(s) definition:
{
name: {
symbol?: string | number,
result?: unknown,
parameters?: unknown[],
nonblocking?: boolean,
stub?: boolean
},
...
}
By default the property name is used for symbol. Use symbol if you are using a symbol name different than the given property name or if you want to call by ordinal (Koffi).
result and parameters are the same as for the returned handle from load().
If omitted, result is set to "void" and parameters to an empty array.
See the corresponding FFI library for more information on what to pass for result and parameters as they have string type parser, structure/array/pointer interface, ... and other features.
When nonblocking is true the corresponding symbol will return the promisified async() method (asynchronous calling). 💡 If set, this superseed the "global" nonblocking option (see below).
When stub is true the corresponding symbol will return a no-op if its missing.
💡 If set, this superseed the "global" stub option (see below).
⚙️ option?: object
Same as load() (see above) in addition to the following:
errorAtRuntime?: boolean (false)
When set to true, initialisation error will be thrown on symbol invocation.
nonblocking?: boolean (false)
When set to true, every symbols will return the corresponding promisified async() method (asynchronous calling).
💡 This can be overriden per symbol (see symbol definition above).
stub?: boolean (false)
When set to true, every missing symbols will return a no-op.
💡 This can be overriden per symbol (see symbol definition above).
Return
An object with the given symbol(s) as properties.
❌ Throws on error.
Example
import { dlopen, types } from "@xan105/ffi/[ napi | koffi ]";
const { BOOL } = types.win32;
const lib = dlopen("xinput1_4", {
"XInputEnable": {
parameters: [BOOL],
nonblocking: true
}
}, { abi: "stdcall" });
await lib.XInputEnable(1);
const types: objectThe FFI Library's primitive types as well as corresponding alias are exposed for convenience. Such as Deno types (rust) and Windows specific types (DWORD,...).
💡 Windows specific types are grouped together under win32.
import { types } from "@xan105/ffi/[ napi | koffi ]";
const { DWORD, LPCSTR } = types.win32;
💡 When using koffi alias are also set with koffi.alias() so you can use them as string.
import { load } from "@xan105/ffi/koffi";
const lib = load("user32.dll", { abi: "stdcall" });
const MessageBoxA = lib("MessageBoxA", "int", ["void *", "LPCSTR", "LPCSTR", "uint"]);
⚠️ Types are not exposed under their own namespace because some words are illegal or already in use in JavaScript. You can still use destructuring if needed as long as the name is "allowed".
❌ No
import { i32 } from "@xan105/ffi/koffi/types"
✔️ Yes
import { types } from "@xan105/ffi/koffi"
const { i32 } = types;
🚫 Forbidden
import { types } from "@xan105/ffi/napi"
const { function } = types;
class CallbackCreate a callback to be called at a later time (registered callback).
This is a class wrapper to the FFI library's callback function(s) inspired by Deno FFI UnsafeCallback class syntax.
(definition: { result: unknown, parameters: unknown[], abi?: string }, callback?: Function | null)
pointer: unknown (read only)The pointer to the callback.
address: number | BigInt | null (read only)The memory address of the pointer.
type: unknown (read only)The type of the callback.
close(): voidDispose of the callback. Remove function pointer associated with this instance.
register(callback?: Function): voidRegister the callback. If a callback was already registered with this instance it will be disposed of.
import { dlopen, types, Callback } from "@xan105/ffi/[ napi | koffi ]";
const library = dlopen("./callback.so", {
setCallback: {
parameters: [types.function],
result: "void",
},
doSomething(): {
parameters: [],
result: "void",
},
});
const callback = new Callback(
{ parameters: [], result: "void" },
() => {},
);
library.setCallback(callback.pointer);
library.doSomething();
// After callback is no longer needed
callback.close();
You can also register the callback at a later time:
import { dlopen, Callback } from "@xan105/ffi/[ napi | koffi ]";
const callback = new Callback(
{ parameters: [], result: "void" }
);
const library = dlopen("./callback.so", {
setCallback: {
parameters: [callback.type],
result: "void",
},
doSomething(): {
parameters: [],
result: "void",
},
});
callback.register(()=>{});
library.setCallback(callback.pointer);
library.doSomething();
// After callback is no longer needed
callback.close();
pointer(value: unknown, direction?: string): unknownJust a shorthand to define a pointer.
import { dlopen, types, pointer } from "@xan105/ffi/[ napi | koffi ]";
const dylib = dlopen("shell32.dll", {
SHQueryUserNotificationState: {
result: types.win32.HRESULT,
parameters: [
pointer(types.win32.ENUM, "out")
]
}
}, { abi: "stdcall" });
struct(schema: unknown): unknownJust a shorthand to define a structure.
import { dlopen, types, struct, pointer } from "@xan105/ffi/[ napi | koffi ]";
const POINT = struct({ //define struct
x: types.win32.LONG,
y: types.win32.LONG
});
const dylib = dlopen("user32.dll", { //lib loading
GetCursorPos: {
result: types.win32.BOOL,
parameters: [ pointer(POINT, "out") ] //struct pointer
}
}, { abi: "stdcall" });
⚠️ NB: Struct are use differently afterwards:
const cursorPos = {};
GetCursorPos(cursorPos);
console.log(cursorPos)
//{ x: 0, y: 0 }
const cursorPos = new POINT();
GetCursorPos(cursorPos.ref());
//access the properties directly
console.log({ x: cursorPos.x, y: cursorPos.y }); //{ x: 0, y: 0 }
//or call .toObject()/.toJSON() (alias) to get a JS Object
console.log(cursorPos.toObject()); //{ x: 0, y: 0 }
structEx(schema: object): object💡 It is worth noting that while the goal of this lib is to write the same code with different FFI libraries;
when using Koffi you can just use Koffi's struct() function as Koffi converts JS objects to C structs, and vice-versa.
Define a structure. The returned object has 2 properties:
type: unknownThe type of the struct.
create: ()=> Class instanceReturn an instance of a class wrapper to the FFI library's struct functions.
pointer: unknown (read only)The pointer to the struct.
values: objectGet or set the values of the struct.
import { dlopen, types, struct, pointer } from "@xan105/ffi/[ napi | koffi ]";
const POINT = struct({ //define struct
x: types.win32.LONG,
y: types.win32.LONG
});
const dylib = dlopen("user32.dll", { //lib loading
GetCursorPos: {
result: types.win32.BOOL,
parameters: [ pointer(POINT.type, "out") ] //struct pointer
}
}, { abi: "stdcall" });
const cursorPos = POINT.create();
GetCursorPos(cursorPos.pointer);
console.log(cursorPos.values) //{ x: 0, y: 0 }
alloc(type: unknown): { pointer: Buffer, get: ()=> unknown }Allocate a buffer and get the corresponding data when passing a pointer to allow the called function to manipulate memory.
import { dlopen, alloc } from "@xan105/ffi/[ napi | koffi ]";
const dylib = dlopen(...); //lib loading
const number = alloc("int"); //allocate Buffer for the output data
dylib.manipulate_number(number.pointer);
const result = number.get();
lastError(option?: object): string[] | numberShorthand to errno (POSIX) and GetLastError (win32).
⚙️ Option
translate?: boolean (true)When an error code is known it will be 'translated' to its corresponding message and code values as
[message: string, code?: string]. If you only want the raw numerical code set it to false.
ex:
if(result !== 0){ //something went wrong
console.log(lastError())
//['No such file or directory', 'ENOENT']
console.log(lastError({ translate: false }));
// 2
}
FAQs
Friendly abstraction/API for FFI with a Deno like syntax.
The npm package @xan105/ffi receives a total of 42 weekly downloads. As such, @xan105/ffi popularity was classified as not popular.
We found that @xan105/ffi demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.