
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.
@tty-pt/ndc
Advanced tools
HTTP(S) + WS(S) + Terminal MUX
A cross-platform C library for building network daemons - HTTP servers, WebSocket servers, telnet-like services, terminal multiplexers, or custom network applications.
From NeverDark • Powers tty.pt
libndc - C library for building network daemons
ndc - Standalone server binary with HTTP/WS/terminal mux
Build telnet-like servers, custom protocol handlers, HTTP APIs, WebSocket apps, or anything that needs persistent network connections with an event loop.
| Platform | Status |
|---|---|
| Linux, macOS, BSD | ✅ Full support |
| Windows | ⚠️ HTTP/WS only (no PTY/CGI/privilege dropping) |
# Run simple HTTP server
ndc -d -p 8888
# With SSL (POSIX)
sudo ndc -C . -K certs.txt -d
| Option | Description |
|---|---|
-p PORT | Specify HTTP server port |
-s PORT | Specify HTTPS server port (POSIX) |
-C PATH | Change directory to PATH before starting |
-K PATH | Load SSL certificate mappings from file (POSIX) |
-k CERT | Add single SSL certificate mapping (POSIX) |
-d | Don't detach (run in foreground) |
-r | Root multiplex mode |
-? | Display help message |
example.com:cert.pem:key.pem
#include <ttypt/ndc.h>
int main(void) {
ndc_config.port = 8080;
ndc_register("GET", do_GET, CF_NOAUTH);
return ndc_main(); // Blocks, runs event loop
}
void cmd_echo(socket_t fd, int argc, char *argv[]) {
for (int i = 0; i < argc; i++)
ndc_writef(fd, "%s ", argv[i]);
ndc_writef(fd, "\n");
}
int ndc_connect(socket_t fd) {
ndc_writef(fd, "Welcome! Type 'echo hello'\n");
return 1; // Accept connection
}
int main(void) {
ndc_config.port = 2323;
ndc_register("echo", cmd_echo, CF_NOAUTH);
return ndc_main();
}
void my_handler(socket_t fd, char *body) {
// Handle raw data from client
ndc_write(fd, "RESPONSE", 8);
}
void ndc_command(socket_t fd, int argc, char *argv[]) {
// Called on every command
log_command(argv[0]);
}
void ndc_update(unsigned long long dt) {
// Periodic tick (game loops, etc)
}
| Function | Description | Return Value |
|---|---|---|
ndc_main() | Start event loop (blocking) | Exit code |
ndc_register(name, cb, flags) | Register command handler | - |
ndc_register_handler(path, handler) | Register HTTP handler for exact path | - |
ndc_write(fd, data, len) | Write raw bytes | Bytes written or -1 |
ndc_writef(fd, fmt, ...) | Write formatted data | Bytes written or -1 |
ndc_dwritef(fd, fmt, va) | Write formatted data with va_list | Bytes written or -1 |
ndc_close(fd) | Close connection | - |
ndc_wall(msg) | Broadcast message to all descriptors | - |
| Function | Description | Return Value |
|---|---|---|
ndc_header(fd, key, val) | Add response header (call before ndc_head) | - |
ndc_head(fd, code) | Send HTTP status and headers | - |
ndc_body(fd, body) | Send body and close connection | - |
ndc_sendfile(fd, path) | Serve static file with auto MIME type | - |
ndc_status_text(code) | Get HTTP status text for code | Status string |
| Function | Description | Return Value |
|---|---|---|
ndc_flags(fd) | Get descriptor flags | Flags bitmask |
ndc_set_flags(fd, flags) | Set descriptor flags | - |
| Function | Description | Return Value |
|---|---|---|
ndc_env_get(fd, target, key) | Get request environment value | 0 on success |
ndc_env_put(fd, key, value) | Set environment key/value | 0 on success |
ndc_env_clear(fd) | Clear request environment | - |
ndc_env(fd) | Get internal env handle (advanced) | Env handle |
| Function | Description | Return Value |
|---|---|---|
ndc_pty(fd, args[]) | Spawn PTY-backed command | - |
ndc_exec(fd, args[], cb, input, len) | Execute command with callback | - |
ndc_auth(fd, username) | Mark user as authenticated, drop privileges (POSIX) | 0 on success, 1 on failure |
ndc_cert_add(str) | Add cert mapping: domain:cert.pem:key.pem | - |
ndc_certs_add(fname) | Load certificate mappings from file | - |
ndc_mmap(mapped, file) | Map file into memory | File size or -1 |
ndc_mmap_iter(start, pos) | Iterate mapped lines separated by \n | Next line or NULL |
ndc_sendfile(fd, path) | Serve static file (POSIX uses sendfile syscall) | - |
| Handler | Description |
|---|---|
do_GET | HTTP GET request handler |
do_POST | HTTP POST request handler |
do_sh | Shell PTY handler (POSIX-only) |
Define these weak symbol hooks to customize behavior:
| Hook | Description | Return Value |
|---|---|---|
ndc_connect(socket_t fd) | Accept/reject WebSocket connections | Non-zero to accept |
ndc_disconnect(socket_t fd) | Cleanup on disconnect | - |
ndc_accept(socket_t fd) | Called on socket accept | Ignored |
ndc_command(socket_t fd, int argc, char *argv[]) | Before command execution | - |
ndc_flush(socket_t fd, int argc, char *argv[]) | After command execution | - |
ndc_vim(socket_t fd, int argc, char *argv[]) | Called when command not found | - |
ndc_update(unsigned long long dt) | Periodic updates (dt in milliseconds) | - |
ndc_auth_check(socket_t fd) | Custom auth hook: validate credentials, return username. Default: session file lookup in ./sessions/ | Username string or NULL |
Example:
int ndc_connect(socket_t fd) {
ndc_writef(fd, "Welcome!\n");
return 1; // Accept connection
}
void ndc_disconnect(socket_t fd) {
// Cleanup resources
}
void ndc_command(socket_t fd, int argc, char *argv[]) {
// Log or validate commands
}
void ndc_flush(socket_t fd, int argc, char *argv[]) {
// Post-command processing
}
void ndc_vim(socket_t fd, int argc, char *argv[]) {
ndc_writef(fd, "Unknown command: %s\n", argv[0]);
}
void ndc_update(unsigned long long dt) {
// Game loop, periodic tasks, etc.
}
char *ndc_auth_check(socket_t fd) {
// Check cookies, tokens, etc.
// Return username or NULL
return authenticated_user;
}
struct ndc_config {
char *chroot; // chroot directory (POSIX)
unsigned flags; // Server flags (see below)
unsigned port; // HTTP listen port
unsigned ssl_port; // HTTPS listen port (POSIX)
ndc_handler_t *default_handler; // Fallback HTTP handler
};
// Example usage
ndc_config.port = 8080; // HTTP on port 8080
ndc_config.ssl_port = 8443; // HTTPS on port 8443 (POSIX)
ndc_config.flags = NDC_SSL; // Enable TLS
ndc_config.chroot = "/var/www"; // chroot directory (POSIX)
ndc_config.default_handler = my_404; // Custom 404 handler
| Flag | Description |
|---|---|
NDC_WAKE | Wake on activity |
NDC_SSL | Enable TLS/SSL |
NDC_ROOT | Root multiplex mode |
NDC_SSL_ONLY | Redirect HTTP to HTTPS when SSL enabled |
NDC_DETACH | Detach into background (daemon mode) |
Use with ndc_register():
| Flag | Description |
|---|---|
CF_NOAUTH | Allow command without authentication |
CF_NOTRIM | Do not trim trailing CR from input |
Access with ndc_flags() and ndc_set_flags():
| Flag | Description |
|---|---|
DF_CONNECTED | Connection established and accepted |
DF_WEBSOCKET | WebSocket mode enabled |
DF_TO_CLOSE | Marked to close after remaining output |
DF_ACCEPTED | Accepted socket (pre-WebSocket) |
DF_AUTHENTICATED | User authenticated |
| Feature | POSIX | Windows |
|---|---|---|
| HTTP/WebSocket | ✅ | ✅ |
| Custom commands | ✅ | ✅ |
| PTY/Terminal | ✅ | ❌ |
| CGI execution | ✅ | ❌ |
| Authentication (privilege dropping) | ✅ | ❌ |
| SSL certs | ✅ | ❌ |
Windows build provides core networking only.
Create index.sh for dynamic pages:
#!/bin/sh
# CGI scripts output status line without "HTTP/1.1" prefix
printf "200 OK\r\n"
printf "Content-Type: text/plain\r\n"
printf "\r\n"
printf "Hello world\n"
printf "REQUEST_METHOD=%s\n" "$REQUEST_METHOD"
printf "QUERY_STRING=%s\n" "$QUERY_STRING"
Control access with serve.allow and serve.autoindex files.
Install the package:
npm install @tty-pt/ndc
JavaScript/TypeScript API:
import { create } from "@tty-pt/ndc";
// Create terminal instance
const term = create(document.getElementById("terminal"), {
proto: "ws", // or "wss" for secure
port: 4201,
sub: {
onOpen: (term, ws) => {
console.log("Connected to server");
},
onClose: (ws) => {
console.log("Disconnected, reconnecting...");
},
onMessage: (ev, arr) => {
// Return true to continue default processing
return true;
},
cols: 80,
rows: 25,
},
debug: false,
});
See types/ndc.d.ts for full TypeScript definitions.
man ndc and man ndc.3include/ttypt/ndc.hsrc/test-*.cLoad dynamic modules with dependency resolution via libndx:
// In your plugin module
const char *ndx_deps[] = { "dependency.so", NULL };
// In main application
ndx_load("plugin.so"); // Automatically loads dependencies
The binary automatically loads core.so at startup. Plugins can hook into lifecycle events (ndc_update, ndc_connect, etc.) to extend functionality.
Installation: See install docs
Entry points: src/ndc.c (native), ndc-cli.js (npm)
FAQs
ndc example project
We found that @tty-pt/ndc 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.