javascript-strict — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited javascript-strict (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.
Rules extracted from production Node.js services (non-TypeScript).
// BAD
var count = 0;
var items = [];
// GOOD
const VALID_MODES = ['bypass', 'browser', 'capsolver']; // Never changes
let activeCount = 0; // Reassigned in loop
let currentDelay = baseDelay; // Mutated by logicNo var anywhere.
// BAD
const name = config.name;
const port = config.port;
const mode = config.mode;
// GOOD
const { name, port, mode } = config;
// GOOD: with defaults
const { mode = 'both', delay = 1000 } = options;// BAD
try { await save(); } catch (e) {}
// BAD: logs but no recovery
try { await save(); } catch (e) { console.log(e); }
// GOOD: context + action
try {
await save();
} catch (err) {
console.error(`[Monitor] ${this.monitorId} Save failed:`, err.message);
await this.notifyError('save_failure', err.message);
// Decide: retry, skip, or throw
}// BAD
console.error(err.message);
// GOOD
console.error(`[Monitor] ${this.monitorId} Error:`, err.message);
console.error(`[TokenBank] ${this.baseHost} Persist failed:`, err.message);
console.warn(`[DEPRECATED] MONITOR_BYPASS is deprecated, use TMPT_MODE`);Format: [Module] ${identifier} Action: message
// BAD: infinite recursion on persistent failure
async getTmpt() {
try {
const token = await this.tmptBank.getTmpt();
if (!token) { await sleep(1000); return this.getTmpt(); }
return token;
} catch {
await sleep(1000);
return this.getTmpt(); // STACK OVERFLOW on persistent failure
}
}
// GOOD: bounded retries
async getTmpt(retries = 10) {
for (let i = 0; i < retries; i++) {
try {
const token = await this.tmptBank.getTmpt();
if (token) return token;
} catch (err) {
console.error(`[Monitor] getTmpt attempt ${i + 1}/${retries}:`, err.message);
}
await sleep(1000 * Math.min(i + 1, 5)); // Backoff
}
throw new Error(`Failed to get TMPT after ${retries} attempts`);
}// BAD
function fetchData() {
return fetch(url).then(r => r.json()).then(data => process(data)).catch(handleError);
}
// GOOD
async function fetchData() {
const response = await fetch(url);
const data = await response.json();
return process(data);
}function withTimeout(promise, ms, message = 'Timeout') {
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => reject(new Error(message)), ms);
});
return Promise.race([promise, timeoutPromise])
.finally(() => clearTimeout(timeoutId)); // ALWAYS clean up
}
// Usage
const data = await withTimeout(fetch(url), 5000, 'Request timed out');Always .finally() to clear the timeout: prevents dangling timers.
// BAD: blocks event loop 5-50ms
fs.writeFileSync(path, JSON.stringify(data, null, 2));
// GOOD
await fs.promises.writeFile(path, JSON.stringify(data, null, 2));
// BETTER: skip if unchanged
const json = JSON.stringify(data, null, 2);
const hash = crypto.createHash('md5').update(json).digest('hex');
if (hash !== this._lastHash) {
await fs.promises.writeFile(path, json);
this._lastHash = hash;
}// Single class export
class Monitor { ... }
module.exports = Monitor;
// Named function exports
module.exports = {
addMonitor: async (data) => { ... },
getMonitor: async (id) => { ... },
updateMonitor: async (id, updates) => { ... },
};
// Utility exports
const { sleep, withTimeout, shouldRun } = require('./utils');
module.exports = { sleep, withTimeout, shouldRun };If A requires B and B requires A, extract shared logic into C.
// GOOD: class for stateful service
class Monitor {
constructor(monitorData, tmptBank, mode = 'both') {
this.monitorId = monitorData.id;
this.tmptBank = tmptBank;
this.mode = mode;
this.stopped = false;
this.delay = 100;
this.notifiedListings = new Set(); // Use Set, not Array
}
async start() { ... }
async stop() { this.stopped = true; }
}
// GOOD: plain functions for stateless utilities
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function formatProxy(line) {
const [host, port, user, pass] = line.split(':');
return `http://${user}:${pass}@${host}:${port}`;
}/**
* Makes an HTTP request with TMPT error handling and retry logic.
* @param {Object} config - Request configuration
* @param {string} config.url - Target URL
* @param {string} [config.method='GET'] - HTTP method
* @param {Object} [config.headers] - Additional headers
* @param {number} [config.timeout=5000] - Timeout in milliseconds
* @returns {Promise<Object>} Response object with data and status
* @throws {Error} If request fails after retries
*/
async makeRequest(config) { ... }Essential for non-TypeScript codebases: provides IDE autocomplete and documentation.
// BAD: O(n) per check
const newIds = currentIds.filter(x => !savedIds.includes(x));
// GOOD: O(1) per check
const savedSet = new Set(savedIds);
const newIds = currentIds.filter(x => !savedSet.has(x));// BAD: 3 passes, 3 arrays
const available = tokens.filter(t => !t.expired);
available.sort((a, b) => a.lastUsed - b.lastUsed);
const best = available[0];
// GOOD: 1 pass, 0 arrays
let best = null;
for (const t of tokens) {
if (t.expired) continue;
if (!best || t.lastUsed < best.lastUsed) best = t;
}// BAD: regex + split every call
function getProxy() {
const proxies = source.split(/\r?\n/);
return proxies[Math.floor(Math.random() * proxies.length)].split(':');
}
// GOOD: parse once
class ProxyPool {
constructor(source) {
this.urls = source.split(/\r?\n/).filter(Boolean).map(formatProxy);
}
random() { return this.urls[Math.floor(Math.random() * this.urls.length)]; }
}// BAD: O(n) find per lookup
function findToken(value) {
return this.tokens.find(t => t.value === value);
}
// GOOD: O(1) Map lookup
this.tokenIndex = new Map(tokens.map(t => [t.value, t]));
function findToken(value) {
return this.tokenIndex.get(value);
}// BAD: code injection
eval(userInput);
new Function(userInput)();
// GOOD: use safe alternatives
JSON.parse(jsonString);// BAD: XSS
element.innerHTML = `<p>${userInput}</p>`;
// GOOD
element.textContent = userInput;// BAD: in source code
const API_KEY = 'sk-abc123...';
// GOOD
require('dotenv').config();
const API_KEY = process.env.API_KEY;
if (!API_KEY) throw new Error('API_KEY required');// BAD
console.log(`Token: ${token}`);
// GOOD
console.log(`Token: ${token.slice(0, 8)}...`);// DANGER: disables SSL verification globally
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
// BETTER: per-request agent with custom CA
const agent = new https.Agent({ rejectUnauthorized: false });
// Only use for proxy connections, never for production APIs// BAD: no way to cancel; the fetch leaks if the caller goes away
const data = await fetch(url).then(r => r.json());
// GOOD: timeout + caller-driven cancellation
async function fetchJSON(url, { signal, timeoutMs = 5000 } = {}) {
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(new Error('timeout')), timeoutMs);
signal?.addEventListener('abort', () => ctrl.abort(signal.reason));
try {
const res = await fetch(url, { signal: ctrl.signal });
return await res.json();
} finally {
clearTimeout(timer);
}
}Replaces the Promise.race timeout pattern in modern Node.
crypto.randomBytes for tokens, never Math.random// BAD: predictable, seedable, not for security
const token = Math.random().toString(36).slice(2);
// GOOD: cryptographically random
const { randomBytes } = require('node:crypto');
const token = randomBytes(32).toString('hex');Same rule for session IDs, password reset tokens, nonces, and salts.
--experimental-transform-types still required for enums/namespaces (which erasableSyntaxOnly in TS bans anyway).| Replace | With (Node 22+) |
|---|---|
dotenv | node --env-file=.env, or process.loadEnvFile() |
node-fetch, axios (for simple cases) | global fetch, Request, Response, Headers |
ws (server) | global WebSocket (client) / node:ws patterns |
mocha, jest (for libs without React) | node:test + node --test |
glob | fs.glob / fs.globSync |
better-sqlite3 (for simple use) | node:sqlite |
Smaller node_modules, less supply-chain surface, fewer compatibility bugs across runtimes.
--permission for sandboxed processesnode --permission \
--allow-fs-read=/app \
--allow-fs-write=/app/tmp \
--allow-net=api.example.com \
server.jsRestricts what filesystem paths and network targets the process can touch. Useful for plugin runners, untrusted code, or hardening prod entrypoints. Still flagged experimental in Node 24, treat as defense-in-depth.
// BAD: loads entire file into memory
const data = await fs.promises.readFile('huge.csv');
process(data);
// GOOD: stream + pipeline (handles backpressure and errors)
const { pipeline } = require('node:stream/promises');
const { createReadStream, createWriteStream } = require('node:fs');
await pipeline(
createReadStream('huge.csv'),
parseCsv(),
transform(),
createWriteStream('out.json'),
);Anything over a few MB or coming from the network should stream.
var declarations (const/let only)eval() or new Function()innerHTML with user-controlled datawriteFileSync in event loop (use async)NODE_TLS_REJECT_UNAUTHORIZED only for proxy, never production~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.