@noidmejs/atomkit-http
v0.4.0
Published
Declarative HTTP/backend connector: call any API by config (all verbs, headers, query, body, auth, retries, cache, transforms). Secrets stay server-only via a proxy with an SSRF host allow-list + per-redirect-hop re-validation.
Maintainers
Readme
@noidmejs/atomkit-http
Declarative HTTP / backend connector. Call any API by config — every verb, headers, query, body, auth, retries, cache, response transforms — and keep secrets server-only behind a proxy with an SSRF host allow-list.
npm i @noidmejs/atomkit-httpDefine a source
const listUsers = {
id: 'listUsers',
method: 'GET',
url: 'https://api.example.com/users',
query: { q: '{{params.q}}' },
auth: { type: 'bearer', token: '{{secret.API_TOKEN}}' },
retry: { attempts: 3, backoffMs: 300 }, // GET/HEAD/OPTIONS only, unless you opt in
cache: { ttlMs: 30000 },
timeoutMs: 10000, // default; never unbounded
maxBytes: 5_000_000, // default response cap
expose: true, // REQUIRED to be reachable via handleRequest — default false
transform: 'data.items',
};Server: the proxy (secrets never reach the client)
import { createProxy } from '@noidmejs/atomkit-http';
const proxy = createProxy({
sources: { listUsers },
secrets: { API_TOKEN: process.env.API_TOKEN! },
allowHosts: ['api.example.com'], // SSRF guard: where a request may GO
authorize: async (req, sourceId, params) => isLoggedIn(req), // WHO may trigger it
});
// Next route handler / Cloudflare Worker:
export const POST = (req: Request) => proxy.handleRequest(req);The client POSTs { sourceId, params }; the proxy injects secrets, enforces the
host allow-list, calls the API, and returns the (transformed) JSON. Tokens stay
on the server.
handleRequestauthenticates nobody. The allow-list governs where a request goes, never who may trigger it, and every call spends your server's secret-authenticated upstream quota with caller-chosen params. A source is therefore unreachable unless it setsexpose: true, and any source returning personal data needsauthorize(or host auth in front of the route) plus rate limiting, which this package does not provide.
Client: run a public source directly
import { runSource } from '@noidmejs/atomkit-http';
const res = await runSource(publicSource, { params: { q: 'hi' } });
// res: { ok, status, data, error }Config
DataSource: method, url, headers, query, body, auth
(bearer / basic / apiKey), timeoutMs, retry, cache, transform
(JSON path). {{params.x}} interpolates caller params; {{secret.X}} resolves
server-side only, in the proxy.
Security status (v0.2 — read this)
Server-only secrets + a hostname allow-list SSRF tier that now re-validates
every redirect hop (an allow-listed host can't 30x-escape to a private IP),
strips credentials on cross-host redirects, redacts upstream errors to the
client (full detail → your onError sink), and proto-guarded templating. See
SECURITY.md.
It is not yet pen-tested, and it does not pin DNS/IPs — a hostname you allow-list (or redirect to) whose DNS resolves to a private IP is still reached. Do not expose it to untrusted multi-tenant input without adding IP-pinning and a security review. Report vulnerabilities via a GitHub Security Advisory.
MIT © noidmejs
