
Security News
/Research
Fake Corepack Site Distributes Infostealer and Proxyware to Developers
A fake corepack.org site is impersonating the Node.js tool and delivers an infostealer and proxyware to developers who download it.
Tiny, cross-platform PTY library for Node.js, built in Zig, also usable as a standalone Zig package. Supports Linux, macOS, Android and Windows (via ConPTY).
Tiny, cross-platform PTY library for Node.js, built in Zig, also usable as a standalone Zig package. Supports Linux, macOS, Android and Windows (via ConPTY).
Drop-in replacement for node-pty. 350x smaller (43 KB vs 15.5 MB packed, 176 KB vs 64.4 MB installed), no node-gyp or C++ compiler needed, and ships musl prebuilds for Alpine.
Regular child_process.spawn() runs programs without a terminal attached. That means no colors, no cursor control, no prompts — programs like vim, top, htop, or interactive shells simply don't work. A PTY (pseudo-terminal) makes the subprocess think it's connected to a real terminal. Colors, line editing, full-screen TUIs, and terminal resizing all work as expected.
import { spawn } from "zigpty";
// auto-detects default shell ($SHELL on Unix, %COMSPEC% on Windows)
const pty = spawn(undefined, [], {
cols: 80,
rows: 24,
terminal: {
data(terminal, data: Uint8Array) {
process.stdout.write(data);
},
},
onExit(exitCode, signal) {
console.log("exited:", exitCode);
},
});
pty.write("echo hello\n");
pty.resize(120, 40);
await pty.exited; // Promise<number>
Terminal callbacks bypass Node.js streams and deliver raw Uint8Array directly from native code. You can also use the onData/onExit event listeners instead:
pty.onData((data) => process.stdout.write(data));
pty.onExit(({ exitCode }) => console.log("exited:", exitCode));
The Terminal class can be reused across multiple spawns and supports AsyncDisposable:
import { spawn, Terminal } from "zigpty";
await using terminal = new Terminal({
data(term, data) {
process.stdout.write(data);
},
});
const pty = spawn("/bin/sh", ["-c", "echo hello"], { terminal });
await pty.exited;
// terminal.close() called automatically by `await using`
spawn(file, args?, options?)Spawn a process inside a new PTY.
Options:
interface IPtyOptions {
cols?: number; // Default: 80
rows?: number; // Default: 24
cwd?: string; // Default: process.cwd()
env?: Record<string, string>; // Default: process.env
name?: string; // Sets TERM (e.g. "xterm-256color")
encoding?: BufferEncoding | null; // Default: "utf8", null for raw Buffer
uid?: number; // Unix user ID
gid?: number; // Unix group ID
handleFlowControl?: boolean; // Intercept XON/XOFF (default: false)
pipe?: boolean; // Force pipe-based fallback (default: false)
terminal?: TerminalOptions | Terminal; // Bun-compatible terminal callbacks
onExit?: (exitCode: number, signal: number) => void;
}
Returns:
interface IPty {
pid: number;
cols: number;
rows: number;
readonly process: string; // Foreground process name
readonly exited: Promise<number>; // Resolves with exit code
readonly exitCode: number | null; // Exit code or null if running
onData: (cb: (data: string | Buffer) => void) => IDisposable;
onExit: (cb: (e: { exitCode: number; signal: number }) => void) => IDisposable;
write(data: string): void;
resize(cols: number, rows: number): void;
kill(signal?: string): void; // Default: SIGHUP
pause(): void;
resume(): void;
close(): void;
waitFor(pattern: string, options?: { timeout?: number }): Promise<string>;
}
pty.waitFor(pattern, options?)Wait until the PTY output contains the given string. Returns all output collected so far. Useful for AI agents that need to read prompts before responding.
import { spawn, Terminal } from "zigpty";
// Terminal provides callback-based data handling and AsyncDisposable cleanup
await using terminal = new Terminal({
cols: 100,
rows: 30,
// Nice to meet you, zigpty! Zig is a great choice!
data: (_terminal, data) => process.stdout.write(data),
});
// spawn() attaches to the Terminal — data flows through terminal callbacks
const pty = spawn(
"python3",
[
"-c",
`
name = input("What is your name? ")
lang = input("Favorite language? ")
print(f"Nice to meet you, {name}! {lang} is a great choice!")
`,
],
{ terminal },
);
// waitFor() resolves when the output contains the pattern
await pty.waitFor("name?");
pty.write("zigpty\n");
await pty.waitFor("language?");
pty.write("Zig\n");
// exited returns a Promise<number> with the exit code
await pty.exited;
Options: { timeout?: number } — default 30 seconds. Throws if the pattern is not found within the timeout.
hasNativeBoolean — true when native Zig PTY bindings loaded successfully, false when running in pipe fallback mode.
open(options?)Create a PTY pair without spawning a process — useful when you need to control the child process yourself.
import { open } from "zigpty";
const { master, slave, pty } = open({ cols: 80, rows: 24 });
When native Zig PTY bindings can't load (missing prebuilds, sandboxed containers, WASM, minimal libc), spawn() automatically falls back to a pure-TypeScript pipe-based PTY instead of crashing. This covers containers without /dev/ptmx, CI environments without prebuilds, and restricted runtimes.
You can also force the pipe fallback explicitly with the pipe option:
import { spawn, hasNative } from "zigpty";
// Automatic — uses native if available, pipes otherwise
const pty = spawn("ls", ["-la"]);
// Explicit — force pipe mode even when native is available
const pty = spawn("ls", ["-la"], { pipe: true });
You can also use PipePty directly:
import { PipePty } from "zigpty";
const pty = new PipePty("/bin/sh", ["-c", "echo hello"]);
The pipe fallback emulates terminal behavior where possible:
^C→SIGINT, ^Z→SIGTSTP, ^\→SIGQUIT, ^D→EOF^W word erase, ^U line killhandleFlowControl is enabled)FORCE_COLOR=1 and COLORTERM=truecolorSIGWINCH to the child process as a best-effort hint/proc on LinuxRaw mode (no echo, no line buffering) is available via setRawMode() / setCanonicalMode() on PipePty instances.
Known limitations — programs see isatty() → false, no kernel-level TIOCSWINSZ, open() throws in fallback mode.
| Platform | Status |
|---|---|
| Linux x64 (glibc) | ✅ |
| Linux x64 (musl) | ✅ |
| Linux arm64 (glibc) | ✅ |
| Linux arm64 (musl) | ✅ |
| macOS x64 | ✅ |
| macOS arm64 | ✅ |
| Windows x64 | ✅ |
| Windows arm64 | ✅ |
All 8 platform binaries are prebuilt — no compiler needed at install time. On Linux, the native loader tries glibc first and falls back to musl automatically.
The PTY core is a standalone Zig package with no Node.js or NAPI dependency.
zig fetch --save git+https://github.com/pithings/zigpty.git
Wire it up in build.zig:
const zigpty = b.dependency("zigpty", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("zigpty", zigpty.module("zigpty"));
API:
const pty = @import("zigpty");
// Fork a process with a PTY
const result = try pty.forkPty(.{
.file = "/bin/bash",
.argv = &.{ "/bin/bash", null },
.envp = &.{ "TERM=xterm-256color", null },
.cwd = "/home/user",
.cols = 120,
.rows = 40,
});
// result.fd — PTY file descriptor (read/write)
// result.pid — child process ID
// Open a bare PTY pair (no process spawned)
const pair = try pty.openPty(80, 24);
// pair.master, pair.slave
// Resize
try pty.resize(result.fd, 80, 24, 0, 0);
// Foreground process name
var buf: [4096]u8 = undefined;
const name: ?[]const u8 = pty.getProcessName(result.fd, &buf);
// Block until child exits
const exit_info = pty.waitForExit(result.pid);
// exit_info.exit_code, exit_info.signal_code
Requires Zig 0.15.1+.
zig build # Build prebuilds (all targets)
zig build --release # Release build
bun run build # Build + bundle TypeScript
bun test # Run tests
API-compatible with node-pty. Terminal API inspired by Bun.
MIT
FAQs
Tiny, cross-platform PTY library for Node.js, built in Zig, also usable as a standalone Zig package. Supports Linux, macOS, Android and Windows (via ConPTY).
The npm package zigpty receives a total of 45,069 weekly downloads. As such, zigpty popularity was classified as popular.
We found that zigpty 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
/Research
A fake corepack.org site is impersonating the Node.js tool and delivers an infostealer and proxyware to developers who download it.

Research
/Security News
A large-scale campaign abused GitHub Actions in compromised repositories to exploit CVE-2026-41940 in cPanel and WHM and steal server credentials.

Security News
Five frontier LLMs generated the same nonexistent package names, leaving 53 available for potential slopsquatting across PyPI and npm.