Security building blocks for Node.js and TypeScript services.
Zero runtime dependencies · modular imports · guarded outbound fetches · framework adapters · SARIF-ready scanning
AxiomGuard collects backend security controls that are easy to rewrite badly and annoying to install as a dozen unrelated packages: API keys, webhook verification, replay protection, secure cookies, CSRF tokens, CORS policy, defensive headers, SSRF-oriented URL checks and outbound fetches, rate limiting, environment validation, secret-safe logging, path safety and repository scanning.
It stays intentionally small. Core and adapters have no runtime npm dependencies, and security assumptions are documented next to the feature instead of hidden behind a generic “secure by default” claim.
GitHub Packages:
# ~/.npmrc
@axiomnode-lab:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKENnpm install @axiomnode-lab/guardThe repository also contains an npmjs publishing path for public distribution. It remains disabled until the npm scope and trusted publisher are configured; see docs/RELEASE.md.
| Import | Purpose |
|---|---|
/api-keys |
High-entropy API keys, digests, verification and masking |
/webhooks |
Generic HMAC, GitHub, Stripe-style signed timestamps and replay stores |
/cookies |
Secure cookie serialization and prefix invariants |
/cors |
Strict framework-neutral origin/preflight policy |
/csrf |
Signed, expiring, optionally session-bound CSRF tokens |
/headers |
CSP/nonces, HSTS, cross-origin and defensive headers |
/presets |
Explicit api, web, and isolated header presets |
/web |
SSRF-oriented URL/DNS checks and redirect allowlists |
/fetch |
Redirect-aware guarded Fetch API wrapper with per-hop validation |
/rate-limit |
Fixed-window limiter, Redis adapters and response-header helpers |
/logging |
Key-, pattern- and path-based secret redaction plus PII masking |
/env |
Typed environment parsing, defaults, ranges and allowlists |
/filesystem |
Traversal-safe paths and filename sanitization |
/scanner |
Programmatic secret scanning, baselines, fingerprints and SARIF |
/adapters/* |
Express, Fastify, Hono and Redis integration layers |
axiomguard |
CLI scanner |
Use the root export for convenience or subpath imports for a smaller, clearer dependency surface.
import {
MemoryReplayStore,
verifyGitHubWebhook,
verifyStripeWebhook,
} from '@axiomnode-lab/guard/webhooks';
const githubOk = verifyGitHubWebhook(
rawBody,
req.headers['x-hub-signature-256'],
process.env.GITHUB_WEBHOOK_SECRET!,
);
const stripeResult = await verifyStripeWebhook(
rawBody,
req.headers['stripe-signature'],
process.env.STRIPE_WEBHOOK_SECRET!,
{
toleranceSeconds: 300,
replayStore: new MemoryReplayStore(),
},
);For multiple service instances, replace MemoryReplayStore with a shared store. AxiomGuard includes node-redis and ioredis adapters that use atomic NX + PX claims.
Adapters combine AxiomGuard's header and CORS primitives without taking a runtime dependency on the framework.
import { createExpressSecurityMiddleware } from '@axiomnode-lab/guard/adapters/express';
app.use(createExpressSecurityMiddleware({
cors: {
origins: ['https://app.example.com'],
allowCredentials: true,
allowMethods: ['GET', 'POST'],
},
}));Equivalent adapters are available for Fastify and Hono. See docs/ADAPTERS.md for framework and Redis examples.
import {
MemoryRateLimitStore,
checkRateLimit,
createRateLimitHeaders,
} from '@axiomnode-lab/guard/rate-limit';
const store = new MemoryRateLimitStore();
const result = await checkRateLimit(`ip:${clientIp}`, {
limit: 60,
windowMs: 60_000,
store,
});
const headers = createRateLimitHeaders(result, {
policyName: 'api',
});
if (!result.allowed) {
// Return 429 and include the generated Retry-After/RateLimit fields.
}The helper can emit the current IETF draft RateLimit-Policy/RateLimit fields together with widely deployed compatibility fields. The draft is not treated as a finalized RFC. The memory store is single-process; shared deployments can use Redis adapters.
Presets are opt-in and deployment-conscious. HSTS is not silently enabled because it is a deployment commitment; cross-origin isolation is a separate preset because it can break resources that are not prepared for it.
import { createPresetSecurityHeaders } from '@axiomnode-lab/guard/presets';
const webHeaders = createPresetSecurityHeaders('web');
const isolatedHeaders = createPresetSecurityHeaders('isolated');import { serializeCookie } from '@axiomnode-lab/guard/cookies';
import { createCsrfToken, verifyCsrfToken } from '@axiomnode-lab/guard/csrf';
const cookie = serializeCookie('__Host-session', sessionToken, {
sameSite: 'Lax',
maxAge: 3600,
});
const csrf = createCsrfToken(process.env.CSRF_SECRET!, { sessionId: session.id });
const csrfOk = verifyCsrfToken(csrf, process.env.CSRF_SECRET!, {
sessionId: session.id,
maxAgeSeconds: 7200,
});Cookie prefix, SameSite, Secure and partitioned-cookie invariants are validated instead of being emitted in contradictory combinations.
import { requireEnv } from '@axiomnode-lab/guard/env';
import { redactSecrets } from '@axiomnode-lab/guard/logging';
const env = requireEnv({
PORT: { type: 'port', default: 3000 },
API_URL: 'url',
MODE: { type: 'string', allowed: ['development', 'staging', 'production'] },
});
const safeEvent = redactSecrets(event, {
paths: ['req.headers.x-api-key', 'users.*.profile'],
});Validated configuration is frozen. Redaction never mutates the source object and supports secret-key heuristics, credential patterns and explicit wildcard paths.
import { assertSafeResolvedUrl } from '@axiomnode-lab/guard/web';
const target = await assertSafeResolvedUrl(userInput, {
protocols: ['https:'],
allowedHosts: ['api.example.com'],
});This blocks common localhost/private/link-local/reserved targets at validation time. It does not eliminate DNS rebinding or time-of-check/time-of-use risk.
safeFetch() turns the URL checks into a practical outbound-request primitive. It validates the initial target and every followed redirect, limits redirect depth, applies a total timeout, strips sensitive credentials on cross-origin redirects, and refuses transport-header overrides and unsafe body replay.
import { safeFetch } from '@axiomnode-lab/guard/fetch';
const response = await safeFetch(userSuppliedUrl, {
protocols: ['https:'],
allowedHosts: ['api.example.com'],
maxRedirects: 2,
timeoutMs: 5_000,
headers: { accept: 'application/json' },
});The underlying Fetch implementation can still resolve DNS again when opening the connection, so this is not a complete DNS-rebinding/TOCTOU boundary. High-risk fetchers still need outbound network controls. See docs/SAFE_FETCH.md.
axiomguard scan .
axiomguard scan . --json
axiomguard scan . --sarif --output axiomguard.sarif
axiomguard scan . --write-baseline .axiomguard-baseline.jsonThe scanner reports rule, file, line and a non-secret fingerprint but never the matched credential value. Config files and reviewed baselines allow teams to roll it out without permanently hiding moved/new findings. See docs/SCANNER.md.
Programmatic use:
import { findingsToSarif, scanSecrets } from '@axiomnode-lab/guard/scanner';
const findings = await scanSecrets('.');
const sarif = findingsToSarif(findings);- uses: actions/checkout@v6
- id: axiomguard
uses: AxiomNode-lab/AxiomGuard@v0.5.0
with:
path: .
fail-on-findings: 'true'The action exposes a SARIF path that can be uploaded with github/codeql-action/upload-sarif@v4. Full workflow: docs/GITHUB_ACTION.md.
docker run --rm \
-v "$PWD:/workspace:ro" \
ghcr.io/axiomnode-lab/axiomguard:edge scan /workspaceEvery pull request qualifies Node.js 20, 22 and 24 with type checking, regression tests, Node 24 coverage, package dry-run, published-subpath import smoke tests and a self scan. CI also runs the repository's composite GitHub Action against itself and validates its SARIF output.
Delivery paths:
main
├── @axiomnode-lab/guard → GitHub Packages
└── ghcr.io/axiomnode-lab/axiomguard:edge → GHCR
GitHub Release
├── GHCR semver/latest + SBOM + provenance
└── npmjs publish (only when explicitly enabled/configured)
The GitHub Packages workflow can also be started manually for diagnostics/retry and reads the exact version back after publishing before it reports success.
- a WAF, secrets manager, identity provider or authorization framework
- Argon2/scrypt/bcrypt password hashing
- egress firewalling, cloud metadata protection, or destination-pinned networking
- a dedicated distributed abuse-prevention platform
- a complete SAST or secrets-scanning platform
- a security review of the application using it
The package is useful when those boundaries are acceptable and explicit.
git clone https://github.com/AxiomNode-lab/AxiomGuard.git
cd AxiomGuard
npm ci
npm run typecheck
npm test
npm run test:coverage
npm pack --dry-run
npm run scan:selfRead RESEARCH.md, SECURITY.md, docs/ADAPTERS.md, docs/SCANNER.md, docs/SAFE_FETCH.md, and docs/RELEASE.md before changing security-sensitive behavior.
Security-sensitive changes should include positive tests, negative tests and a written failure boundary. See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
MIT — see LICENSE.