
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.
safe-timeouts
Advanced tools
Deadline-based timeouts for async code in Node.js. Enforce end-to-end execution deadlines with automatic propagation and AbortSignal support.
Deadline-based timeouts for async Node.js code with AbortSignal support.
Promise-based deadline enforcement for async code in Node.js. safe-timeouts helps you apply a single execution deadline across async functions, services, and external calls using standard AbortSignal semantics.
In real backend systems, timeouts are end-to-end, not per-function:
Most timeout utilities fail here because they:
AbortSignalsafe-timeouts solves this correctly.
npm install safe-timeouts
Node.js >= 16 is required.
import { withTimeout, TimeoutError, safeAxios } from "safe-timeouts";
import axios from "axios";
try {
const resultWithSafeAxios = await withTimeout(2000, async () => {
const res = await safeAxios.get("https://api.example.com/users"); // no signal to be passed.
return res.data;
});
const resultWithAxios = await withTimeout(2000, async (signal) => { // signal to be taken.
const res = await axios.get("https://api.example.com/users", {signal}); // signal to be passed.
return res.data;
});
} catch (err) {
if (err instanceof TimeoutError) {
console.error("Request timed out");
}
}
What happens:
A 2s deadline is created
An AbortController is started internally
If the deadline is exceeded:
TimeoutErrorAbortSignal is abortedDeadlines propagate and compose automatically.
await withTimeout(3000, async () => {
await serviceA(); // uses part of the budget
await withTimeout(5000, async () => {
await serviceB(); // still limited by the original 3s
});
});
The inner timeout cannot extend the outer deadline.
This makes time budgets safe and deterministic.
safeAxios is a convenience wrapper around Axios that automatically integrates with safe-timeouts.
When used inside withTimeout, HTTP requests are automatically cancellable. When used outside withTimeout, it behaves exactly like a normal Axios instance.
Example
import { withTimeout, safeAxios } from "safe-timeouts";
await withTimeout(2000, async () => {
const res = await safeAxios.get("/users");
return res.data;
});
import { withTimeout, createSafeAxios } from "safe-timeouts";
const api = createSafeAxios({
baseURL: "https://api.example.com",
});
await withTimeout(1000, async () => {
await api.post("/sync");
});
safe-timeouts uses AsyncLocalStorage to propagate timeout context across async boundaries.
Example flow
await withTimeout(2000, async () => {
await controller();
});
async function controller() {
return serviceA();
}
async function serviceA() {
return serviceB();
}
async function serviceB() {
return safeAxios.get("/users");
}
Context flow diagram
withTimeout
└─ Async context (deadline + AbortController)
├─ controller()
│ └─ serviceA()
│ └─ serviceB()
│ └─ safeAxios.get()
│ └─ axios(request + signal)
The timeout context is created once Node automatically propagates it across async calls safeAxios reads the context at request time When the deadline expires, the request is aborted
import axios from "axios";
await withTimeout(2000, async (signal) => {
await controller(signal);
});
async function controller(signal) {
await serviceA(signal);
}
async function serviceA(signal) {
await serviceB(signal);
}
async function serviceB(signal) {
const res = await axios.get("/users", { signal });
return res.data;
}
All functions share the same deadline by passing the same AbortSignal down the call chain.
These stop execution as soon as the deadline is exceeded:
fetch (Node 18+)axios (with { signal })fs/promises (partial)stream.pipelinetimers/promisesExample:
// GET
await safeAxios.get(url); // 👈 No AbortSignal needed
// POST
await safeAxios.post(
url,
{ name: "Aryan", role: "admin" },
{
// 👈 No AbortSignal goes here
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_TOKEN",
},
})
// GET
await axios.get(url, { signal }); // 👈 AbortSignal goes here
// POST
await axios.post(
url,
{ name: "Aryan", role: "admin" },
{
signal, // 👈 AbortSignal goes here
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_TOKEN",
},
})
These cannot be forcibly stopped:
setTimeout / sleepFor these, safe-timeouts:
JavaScript cannot forcibly stop non-abort-aware operations (like setTimeout, Sequelize queries, or CPU-bound work).
When such operations exceed the deadline:
safe-timeouts rejects the outer promiseTo keep control flow predictable:
This design avoids hidden global checks while remaining honest about JavaScript limitations.
safe-timeouts works with Axios by passing the provided AbortSignal to the request.
import axios from "axios";
import { withTimeout } from "safe-timeouts";
await withTimeout(2000, async (signal) => {
const res = await axios.get("/users", { signal });
return res.data;
});
Axios is abort-aware:
This explicit integration keeps cancellation predictable and avoids hidden behavior.
safe-timeouts does NOT doIt is important to be explicit about limitations:
This matches the realities of Node.js and modern async runtimes.
setTimeout| Feature | setTimeout | safe-timeouts |
|---|---|---|
| End-to-end deadline | ❌ | ✅ |
| Nested composition | ❌ | ✅ |
| AbortSignal support | ❌ | ✅ |
| Context propagation | ❌ | ✅ |
| Concurrency-safe | ❌ | ✅ |
setTimeout works locally. safe-timeouts works across your entire async call graph.
withTimeout(ms, fn)Runs an async function with a deadline.
withTimeout<T>(ms: number, fn: (signal: AbortSignal) => Promise<T>): Promise<T>
Rejects with TimeoutError when the deadline is exceeded.
TimeoutErrorError thrown when the deadline is exceeded.
instanceof TimeoutError === true
Use safe-timeouts when:
Do not use it as a replacement for DB-level query timeouts.
MIT
FAQs
Deadline-based timeouts for async code in Node.js. Enforce end-to-end execution deadlines with automatic propagation and AbortSignal support.
We found that safe-timeouts 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.