
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
Experimental MQTT broker for Node.js,
powered by Rust RMQTT + Neon.
Ultra-low latency · Hook-aware · Multi-protocol
| ⚡️ High performance core Built on Rust + the battle-tested RMQTT engine. | 🧠 TypeScript-first API Clean, promise-based ergonomics with strict typing. |
| 🌐 Multi-protocol listeners Run TCP, TLS, WebSocket, and WSS concurrently. | 🔌 Hook-driven extensibility Real-time auth, ACL, and lifecycle callbacks. |
| 📦 Full QoS & retention QoS 0/1/2 plus retained-message support end-to-end. | 🛡️ Production-ready controls Pluggable auth and subscribe QoS overrides. |
[!IMPORTANT] Designed for teams that demand predictable latency, graceful restarts, and deep observability hooks.
npm install rmqtt-js
[!NOTE] Ships with prebuilt binaries for Linux, macOS, and Windows; falls back to source builds when needed.
This package ships prebuilt binaries for common platforms (linux-x64/arm64, darwin-x64/arm64, win32-x64). If a prebuild for your platform isn’t available, install will build from source:
xcode-select --install)build-essential, pkg-config[!TIP] Combine
rmqtt-jswith your favorite MQTT client or cloud infrastructure for instant telemetry pipelines.
TypeScript
import { MqttServer, QoS } from "rmqtt-js";
const server = new MqttServer();
// Optional: simple demo auth (password must be "demo")
server.setHooks({
onClientAuthenticate: ({ username, password }) => ({
allow: Boolean(username) && password === "demo",
superuser: false,
}),
});
await server.start(MqttServer.createBasicConfig(1883));
// Publish a message
await server.publish("sensor/temperature", "23.7", { qos: QoS.AtLeastOnce });
JavaScript (CommonJS)
const { MqttServer, QoS } = require("rmqtt-js");
(async () => {
const server = new MqttServer();
await server.start(MqttServer.createBasicConfig(1883));
await server.publish("hello/world", Buffer.from("hi"), { qos: QoS.AtMostOnce });
})();
Listen to broker events in real time — only invoked if you register them.
| Hook | Type | When it fires | JS return | Default if not set | Timeout/error |
|---|---|---|---|---|---|
onClientConnect(info) | notification | Server receives CONNECT | void | — | — |
onClientAuthenticate(auth) | decision | A client connects | { allow, superuser?, reason? } | RMQTT defaults | deny |
onClientSubscribeAuthorize(session, sub) | decision | Client subscribes | { allow, qos?, reason? } | RMQTT defaults | deny |
onClientPublishAuthorize(session, packet) | decision + mutation | Client publishes | { allow, topic?, payload?, qos?, reason? } | allow (except $SYS/* denied) | deny |
onMessagePublish(session, from, msg) | notification | Any publish observed | void | — | — |
onClientSubscribe(session, sub) | notification | Client subscribes | void | — | — |
onClientUnsubscribe(session, unsub) | notification | Client unsubscribes | void | — | — |
onMessageDelivered(session, from, msg) | notification | Before delivering a message to a client | void | — | — |
onMessageAcked(session, from, msg) | notification | After client acknowledges a delivered message | void | — | — |
onMessageDropped(session, from?, msg, info?) | notification | Message could not be delivered (dropped) | void | — | — |
onSessionCreated(session) | notification | Session created | void | — | — |
onClientConnected(session) | notification | Client connected | void | — | — |
onClientConnack(info) | notification | CONNACK (success/fail) | void | — | — |
onClientDisconnected(session, info?) | notification | Client disconnected | void | — | — |
onSessionSubscribed(session, sub) | notification | Client subscribed | void | — | — |
onSessionUnsubscribed(session, unsub) | notification | Client unsubscribed | void | — | — |
onSessionTerminated(session, info?) | notification | Session terminated | void | — | — |
Auth (JS → Rust decision flow)
server.setHooks({
onClientAuthenticate: ({ username, password, clientId, remoteAddr }) => {
const allow = Boolean(username) && password === "demo";
return { allow, superuser: false, reason: allow ? "ok" : "bad creds" };
},
});
Subscribe ACLs with QoS override
server.setHooks({
onClientSubscribeAuthorize: (session, sub) => {
const user = session?.username ?? "";
const allowed = sub.topicFilter.startsWith(`${user}/`) || sub.topicFilter.startsWith("server/public/");
return allowed ? { allow: true, qos: 1 } : { allow: false, reason: "not authorized" };
},
});
Promise-based Subscribe ACL
server.setHooks({
onClientSubscribeAuthorize: async (session, sub) => {
// e.g., async lookup against a policy service
await new Promise(r => setTimeout(r, 50));
const user = session?.username ?? "";
const allowed = sub.topicFilter.startsWith(`${user}/`);
// You can also override QoS (0/1/2); invalid values are ignored and a WARN is logged.
return allowed ? { allow: true, qos: 1 } : { allow: false };
},
});
Publish/Subscribe notifications
server.setHooks({
onMessagePublish: (_session, from, msg) => {
console.log(`Message ${msg.topic}`, msg.payload.toString());
},
onClientSubscribe: (_session, sub) => console.log("SUB", sub),
onClientUnsubscribe: (_session, unsub) => console.log("UNSUB", unsub),
});
Message delivery notifications
server.setHooks({
onMessageDelivered: (_session, from, message) => {
console.log(`Delivered: ${message.topic} (from ${from.type})`);
},
onMessageAcked: (_session, from, message) => {
console.log(`Acked: ${message.topic} (from ${from.type})`);
},
onMessageDropped: (_session, from, message, info) => {
console.warn(`Dropped: ${message.topic} (from ${from?.type ?? 'unknown'}) reason=${info?.reason ?? 'n/a'}`);
},
});
Lifecycle notifications
server.setHooks({
onClientConnected: (session) => console.log("CONNECTED", session.clientId),
// Emitted when broker sends CONNACK (success or failure). Filter non-success if desired.
onClientConnack: (info) => console.log("CONNACK", info.clientId, info.connAck),
onClientDisconnected: (session, info) => console.log("DISCONNECTED", session.clientId, info?.reason),
// Subscribe/session lifecycle
onSessionSubscribed: (session, sub) => console.log("SUBSCRIBED", session.clientId, sub.topicFilter),
});
Notes:
onClientConnack fires for both success and non-success outcomes. To handle errors only, check info.connAck !== "Success".connAck values include: "Success", "BadUsernameOrPassword", "NotAuthorized", and other broker reasons depending on protocol version.node field currently uses a single-node placeholder value of 1. If/when multi-node/clustering is introduced, this will reflect the real node id.Publish ACL with optional mutation
server.setHooks({
onClientPublishAuthorize: (session, packet) => {
// Deny system topics unless explicitly allowed
if (packet.topic.startsWith("$SYS")) {
return { allow: false, reason: "system topic" };
}
// Example: rewrite topic and uppercase payload
return {
allow: true,
topic: `users/${session?.username ?? "anon"}/out`,
payload: Buffer.from(packet.payload.toString("utf8").toUpperCase()),
qos: 0,
};
},
});
Hook semantics:
Quick helpers
// Single TCP listener
const basic = MqttServer.createBasicConfig(1883);
// Multi‑protocol listeners (TLS/WSS require cert+key)
const multi = MqttServer.createMultiProtocolConfig({
tcpPort: 1883,
wsPort: 8080,
tlsPort: 8883,
wssPort: 8443,
address: "0.0.0.0",
// tlsCert: "/path/to/cert.pem",
// tlsKey: "/path/to/key.pem",
allowAnonymous: true,
});
Full shape (TypeScript)
interface ListenerConfig {
name: string;
address: string; // e.g. "0.0.0.0"
port: number; // 1–65535
protocol: "tcp" | "tls" | "ws" | "wss";
tlsCert?: string; // required for tls/wss
tlsKey?: string; // required for tls/wss
allowAnonymous?: boolean; // default: true
}
interface ServerConfig {
listeners: ListenerConfig[];
pluginsConfigDir?: string;
pluginsDefaultStartups?: string[];
}
await server.publish(topic: string, payload: string | Buffer, options?: {
qos?: QoS; // 0 | 1 | 2; default 0
retain?: boolean // default false
});
Notes:
This repo ships with a comprehensive example showing auth, ACLs, and server‑side publishing.
Run it:
npm run example
What it demonstrates:
The full source lives in examples/simple-server.ts.
See also: examples/CHEATSHEET.md for quick mqtt.js and Python client snippets (TCP/WS/TLS/WSS).
export enum QoS { AtMostOnce = 0, AtLeastOnce = 1, ExactlyOnce = 2 }
export interface SessionInfo { node: number; remoteAddr: string | null; clientId: string; username: string | null; }
export interface MessageFrom { type: "client" | "system" | "bridge" | "admin" | "lastwill" | "custom"; node: number; remoteAddr: string | null; clientId: string; username: string | null; }
export interface MessageInfo { dup: boolean; qos: QoS; retain: boolean; topic: string; payload: Buffer; createTime: number; }
export interface SubscriptionInfo { topicFilter: string; qos: QoS }
export interface UnsubscriptionInfo { topicFilter: string }
export interface MqttMessage {
topic: string;
payload: Buffer;
qos: QoS;
retain: boolean;
}
export interface AuthenticationRequest { clientId: string; username: string | null; password: string | null; protocolVersion: number; remoteAddr: string; keepAlive: number; cleanSession: boolean; }
export interface AuthenticationResult { allow: boolean; superuser?: boolean; reason?: string }
export interface SubscribeAuthorizeResult { allow: boolean; qos?: number; reason?: string }
export interface PublishAuthorizeResult { allow: boolean; topic?: string; payload?: Buffer; qos?: number; reason?: string }
export interface HookCallbacks {
onClientAuthenticate?(auth: AuthenticationRequest): AuthenticationResult | Promise<AuthenticationResult>;
onClientSubscribeAuthorize?(session: SessionInfo | null, sub: SubscriptionInfo): SubscribeAuthorizeResult | Promise<SubscribeAuthorizeResult>;
onClientPublishAuthorize?(session: SessionInfo | null, packet: MqttMessage): PublishAuthorizeResult | Promise<PublishAuthorizeResult>;
onMessagePublish?(session: SessionInfo | null, from: MessageFrom, msg: MessageInfo): void;
onClientSubscribe?(session: SessionInfo | null, sub: SubscriptionInfo): void;
onClientUnsubscribe?(session: SessionInfo | null, unsub: UnsubscriptionInfo): void;
onMessageDelivered?(session: SessionInfo | null, from: MessageFrom, message: MessageInfo): void;
onMessageAcked?(session: SessionInfo | null, from: MessageFrom, message: MessageInfo): void;
onMessageDropped?(session: SessionInfo | null, from: MessageFrom | null, message: MessageInfo, info?: { reason?: string }): void;
onClientConnect?(info: ConnectInfo): void;
onClientConnack?(info: ConnackInfo): void;
onClientConnected?(session: SessionInfo): void;
onClientDisconnected?(session: SessionInfo, info?: { reason?: string }): void;
onSessionCreated?(session: SessionInfo): void;
onSessionSubscribed?(session: SessionInfo, subscription: SubscriptionInfo): void;
onSessionUnsubscribed?(session: SessionInfo, unsubscription: UnsubscriptionInfo): void;
onSessionTerminated?(session: SessionInfo, info?: { reason?: string }): void;
}
// Connection lifecycle payloads
export interface ConnectInfo {
node: number;
remoteAddr: string | null;
clientId: string;
username: string | null;
keepAlive: number;
protoVer: number;
cleanSession?: boolean; // MQTT 3.1/3.1.1
cleanStart?: boolean; // MQTT 5.0
}
export interface ConnackInfo extends ConnectInfo {
connAck: string; // e.g., "Success", "BadUsernameOrPassword", "NotAuthorized"
}
Implementation notes:
clientId and username more precisely. If multi-node is planned, threading the real node id into SessionInfo/MessageFrom early will avoid downstream assumptions.npm test
What the suite covers:
start() before publish()tlsCert and tlsKeycreateMultiProtocolConfig, only enable tls/wss when both tlsCert and tlsKey are presentonClientAuthenticate on the JS side to enforce your auth model
onClientSubscribeAuthorize (e.g., <username>/*)See CHANGELOG.md for release notes.
MIT
FAQs
High-performance MQTT server for Node.js built with Rust and Neon
We found that rmqtt-js demonstrated a healthy version release cadence and project activity because the last version was released less than 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.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.