npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wade-development/security-node

v1.0.1

Published

Trusted server-side SDK for the Company security monitoring platform.

Downloads

332

Readme

@wade-development/security-node

Trusted server-side SDK for the Company security monitoring platform.

Send security-relevant events from your backend. The platform resolves which client, application and environment you are from your API key — you never send tenant identifiers, and you cannot claim to be someone else.

New to the platform? Start with docs/sdk-quick-start.md — it walks an application from "no key" to "reporting incidents" in order. This file is the reference for what the SDK does.

Install

npm install @wade-development/security-node

Quick start

import { Security } from '@wade-development/security-node';

Security.init({
  apiKey: process.env.SECURITY_API_KEY!,
  endpoint: process.env.SECURITY_ENDPOINT!,
});

// later, wherever something security-relevant happens
Security.auth.loginFailed({
  actor: { id: attemptedUserId },
  network: { sourceIp },
  metadata: { reason: 'invalid_credentials' },
});

Get SECURITY_API_KEY from the security console: Clients → your client → application → environment → Generate API key. The key is shown once.

The one rule: never await it

Security.auth.loginSucceeded({ actor: { id: user.id } });  // correct
await Security.auth.loginSucceeded({ ... });                // pointless — returns void

track() and every helper return void. They buffer in memory and send in the background. If the security platform is down, slow, or misconfigured, your application keeps working — events are buffered, retried, and eventually dropped rather than ever blocking a login or a payment.

The SDK does not throw. A malformed event is reported through onError and the debug log, never as an exception in your request handler.

Configuration

Security.init({
  apiKey: process.env.SECURITY_API_KEY!,
  endpoint: 'https://security.company.com/ingest/v1',

  batchSize: 50,          // send when this many events are buffered
  flushIntervalMs: 1000,  // ...or after this long, whichever comes first
  timeoutMs: 1500,        // per-request timeout
  maxQueueSize: 10_000,   // hard cap; beyond this the drop policy engages
  overflowStrategy: 'drop-oldest',
  maxRetries: 5,

  heartbeat: { enabled: true, intervalMs: 300_000, applicationVersion: '1.4.2' },

  debug: false,
  onError: (error, context) => myLogger.warn('security sdk', { error, context }),
});

Every value is clamped to a sane range, so a typo cannot produce a 10-second timeout on your login path.

Environment variables

| Variable | Purpose | | --- | --- | | SECURITY_API_KEY | Used when apiKey is not passed to init() | | SECURITY_ENDPOINT | Used when endpoint is not passed | | SECURITY_DEBUG | true enables verbose SDK logging |

Request context

Set the actor and network details once per request; everything tracked inside inherits them.

Security.withContext(
  {
    actor: { id: user.id, role: user.role },
    session: { id: sessionId },
    network: { sourceIp, userAgent },
    request: { requestId },
  },
  async () => {
    await handleRequest();      // any Security.* call in here gets the context
  },
);

Explicit values always win, so an admin acting on another account still records the right actor.

Nuxt / Nitro

// server/plugins/security.ts
import { Security } from '@wade-development/security-node';

export default defineNitroPlugin(() => {
  Security.init({
    apiKey: process.env.SECURITY_API_KEY!,
    endpoint: process.env.SECURITY_ENDPOINT!,
  });
});
// server/middleware/security-context.ts
import { securityRequestMiddleware } from '@wade-development/security-node/nitro';

export default defineEventHandler(
  securityRequestMiddleware({
    // Only enable when a proxy really is in front of you — otherwise callers
    // can forge their own source IP.
    trustProxy: true,
    resolveActor: (event) => {
      const user = event.context.user;
      return user ? { id: user.id, role: user.role } : undefined;
    },
  }),
);

Express

app.use((req, res, next) => {
  Security.withContext(
    {
      actor: req.user ? { id: req.user.id, role: req.user.role } : undefined,
      network: { sourceIp: req.ip, userAgent: req.get('user-agent') },
      request: { requestId: req.id },
    },
    () => next(),
  );
});

Fastify

fastify.addHook('onRequest', (request, _reply, done) => {
  Security.withContext(
    {
      network: { sourceIp: request.ip, userAgent: request.headers['user-agent'] },
      request: { requestId: request.id },
    },
    () => done(),
  );
});

Event catalogue

Helpers exist for the events the platform's detection rules match on. Prefer them over raw track() — they cannot get the event type wrong.

| Helper | Event type | Feeds | | --- | --- | --- | | Security.auth.loginFailed() | authentication.login.failed | AUTH-001, AUTH-002, AUTH-003 | | Security.auth.loginSucceeded() | authentication.login.succeeded | AUTH-004, AUTH-005, AUTH-006 | | Security.auth.logout() | authentication.logout | | | Security.auth.mfaFailed() | authentication.mfa.failed | MFA-001 | | Security.auth.mfaDisabled() | authentication.mfa.disabled | MFA-002, MFA-003 | | Security.identity.passwordChanged() | identity.password.changed | | | Security.identity.roleChanged() | authorization.role.changed | IAM-001, IAM-002, IAM-004 | | Security.authorization.denied() | authorization.access.denied | API-001 | | Security.admin.action() | administration.setting.changed | IAM-002 | | Security.admin.securityFeatureDisabled() | administration.security_feature.disabled | ADMIN-001 | | Security.admin.auditLoggingDisabled() | administration.audit_logging.disabled | ADMIN-002 | | Security.data.bulkExport() | data_access.bulk_export | IAM-003, DATA-001, DATA-002 | | Security.data.bulkDelete() | data_change.bulk_delete | DATA-003 | | Security.session.newDevice() | session.new_device | DATA-002 | | Security.session.reusedAfterRevocation() | session.reused_after_revocation | SESSION-002 | | Security.api.rateLimitExceeded() | api_security.rate_limit.exceeded | API-001 | | Security.api.validationFailed() | api_security.input_validation.failed | API-003 | | Security.secret.apiKeyCreated() | secret.api_key.created | SECRET-001 |

For anything not listed, use Security.track({ type: 'category.object.action' }). Types must be lowercase, dot-separated, and start with one of the 14 platform categories.

What not to send

The SDK strips these before anything leaves your process, but do not put them in metadata in the first place:

passwords, access and refresh tokens, session cookies, API keys, authorization headers, payment card data, and personal data you do not need for an investigation.

Send identifiers, not payloads: { recordId: 4821 }, not the record.

Graceful shutdown

process.on('SIGTERM', async () => {
  await Security.close();   // stops timers, makes a final flush attempt
  await server.close();
});

The SDK also flushes on beforeExit, and all its timers are unref'd — it will never hold your process open.

Health

const health = Security.health();
// { initialized, queueSize, eventsSent, eventsDropped, eventsFailed, retries, lastError }

Worth exposing on your own /health endpoint: a rising eventsDropped means the platform is unreachable and you are losing security signal.

Retry behaviour

| Response | Behaviour | | --- | --- | | 202 | Delivered | | 408, 429, 5xx, network error | Retried with exponential backoff and jitter (250ms → 5s), bounded by maxRetries. Retry-After is honoured | | 400, 401, 403, 422 | Not retried — the batch is discarded and reported through onError |

A wrong API key produces one clear error, not an infinite retry loop.