nodejs — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited nodejs (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
These guardrails apply to all Node.js code you write, review, or modify. They exist because LLM-generated Node.js tends to repeat the same mistakes: missing node: prefixes, callback-style APIs when promise versions exist, unsafe exec() calls, sync fs operations in async contexts, and JSON.parse(JSON.stringify()) for deep copies. Following these rules produces code that's modern, secure, and idiomatic for Node.js 20+.
The node: prefix makes it unambiguous that you're importing a built-in module, not a npm package that happens to share the name. Node.js has had this prefix since v16 and it should be used everywhere.
import fs from 'node:fs/promises', not import fs from 'fs'. This applies to every built-in: node:path, node:url, node:crypto, node:os, node:stream, node:child_process, node:util, node:events, etc.node:fs with callbacks. The promise-based API is cleaner and composes with async/await./. Paths are OS-dependent and path.join() / path.resolve() handle this correctly.Math.random() for anything security-related (tokens, IDs, secrets). crypto.randomUUID() and crypto.randomBytes() exist for this reason.Node.js is built around non-blocking I/O. Callbacks were the original API style, but modern Node.js has promise-based alternatives for everything. Using async/await makes code readable, debuggable, and composable.
.then() chains are harder to read, harder to debug (stack traces), and harder to compose with try/catch.Promise.all() rejects on the first failure and you lose the results of the others.let cancelled = false. AbortSignal is the standard cancellation mechanism and integrates with fetch, streams, child processes, and timers.import { setTimeout } from 'node:timers/promises' gives you an awaitable timer directly. Don't wrap setTimeout in new Promise().// wrong
await new Promise(resolve => setTimeout(resolve, 1000));
// correct
import { setTimeout } from 'node:timers/promises';
await setTimeout(1000);process.exit() skips cleanup, kills pending I/O, and makes code untestable.process.exit(1) when possible — setting the exit code lets Node.js finish pending operations before exiting naturally.JSON.parse(JSON.stringify()). The JSON trick silently drops undefined, functions, Date objects, Map, Set, RegExp, and circular references. structuredClone() handles all of these correctly.child_process for running JS code. Worker threads share memory (via SharedArrayBuffer) and avoid the overhead of spawning a new process.Sync fs operations block the event loop. In a server or any async context, this means every other request stalls while you wait for disk I/O. The only acceptable place for sync fs is CLI startup where nothing else is running yet.
readFileSync, writeFileSync, etc.) except during CLI startup/initialization.readFile(path, { encoding: 'utf-8' }), not readFile(path) which returns a Buffer.existsSync first and then create. The recursive option is idempotent and avoids race conditions./tmp. macOS uses /private/var/folders/..., Windows uses %TEMP%, and containers may mount tmpfs elsewhere.import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
await mkdir(join(tmpdir(), 'my-app'), { recursive: true });
const content = await readFile(configPath, { encoding: 'utf-8' });Streams are powerful but notoriously error-prone when piped manually. A leaked error handler means an unhandled rejection that crashes your process. pipeline() handles backpressure, error propagation, and cleanup for you.
.pipe() with manual error handling. pipeline() destroys all streams on error and returns a promise..text(), .json(), .buffer()) for consuming readable streams — these are built-in and handle encoding correctly.import { pipeline } from 'node:stream/promises';
import { createReadStream, createWriteStream } from 'node:fs';
import { createGzip } from 'node:zlib';
await pipeline(
createReadStream('input.txt'),
createGzip(),
createWriteStream('input.txt.gz')
);Spawning a shell is a common source of injection vulnerabilities. exec() passes the command through /bin/sh, which means shell metacharacters in user input become code execution. execFile() bypasses the shell entirely.
exec explicitly and sanitize inputs, but prefer execFile by default.kill.import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
const execFileAsync = promisify(execFile);
const controller = new AbortController();
const { stdout, stderr } = await execFileAsync('git', ['status'], {
timeout: 10_000,
maxBuffer: 1024 * 1024,
signal: controller.signal,
});"main". The "exports" field supports conditional exports (ESM/CJS), subpath exports, and blocks deep imports into internals.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.