﻿# SecureNow SDK â€” Agent Skill

Instrument any Node.js application with OpenTelemetry tracing, structured logging, request body capture, and a multi-layer IP firewall. Supports Express, Fastify, NestJS, Koa, Hapi, Next.js, Nuxt 3, Vite (browser), and raw `http.createServer` â€” with zero code changes for most setups.

**CLI parity:** every capability exposed below (redaction, CIDR matching, log/span emission, firewall preload, config inspection) has an equivalent `securenow` CLI command. See [SKILL-CLI.md](./SKILL-CLI.md) for the terminal surface.

**MCP parity (v7.5+):** `npx securenow mcp` starts a local stdio MCP server for Codex, Claude, and other MCP clients. It reuses the same `.securenow/credentials.json` file as the CLI/SDK and exposes SecureNow tools, bundled docs resources, and setup prompts to agents.

## Installation

```bash
npm install securenow@latest
```

### Install This Skill in Cursor

Save this file as `.cursor/skills/securenow-api/SKILL.md` in your project. Your AI agent will auto-discover it whenever you ask about integrating securenow, configuring tracing, setting up the firewall, or instrumenting any framework.

## Quick Start â€” Any Node.js Framework

### 1. Install, Login, And Init

```bash
npm install securenow@latest
node -p "require('./node_modules/securenow/package.json').version"
npx securenow version
npx securenow login
npx securenow init
```

Use `securenow@7.5.1` or newer. Start authentication with `npx securenow login`; do not manually open auth URLs, because the CLI generates the callback/state values. The login flow lets the user pick or create an app, enables the app firewall by default, writes `.securenow/credentials.json`, and `init` scaffolds the framework integration.

### 2. Run With Instrumentation

**Option A â€” CLI (recommended):**

```bash
npx securenow run src/index.js
```

**Option B â€” Node preload flag:**

```bash
node -r securenow/register src/index.js
```

**Option C â€” Auto-setup for Next.js:**

```bash
npx securenow init
```

That's it. Traces, logs, request body capture, multipart metadata capture, and firewall enforcement are on by default. No code changes for Express, Fastify, NestJS, Koa, Hapi, and raw Node.

### 3. Firewall Is Enabled by Default

Since v7.5.1, the browser login flow connects the firewall automatically after
the user picks or creates an app. The firewall key lives in your credentials
file â€” no env var required:

```bash
npx securenow login              # pick/create app; firewall key is minted automatically
# or, if you already have one:
npx securenow api-key set snk_live_abc123...
```

Both paths write the key to `.securenow/credentials.json` (gitignored via credential-file patterns, not a whole-directory `.securenow/` ignore) and the firewall activates on next start. Setting `SECURENOW_API_KEY=snk_live_...` in the environment still works for production/CI overrides and takes precedence.

The firewall syncs your blocklist and enforces it on every request â€” zero code changes.

---

## Import Map

| Import | Purpose | Type |
|--------|---------|------|
| `securenow` / `securenow/register` | Auto-register tracing + firewall (side-effect) | Preload (`-r`) |
| `securenow/tracing` | Core OTel SDK; exports `getLogger()`, `isLoggingEnabled()` | CJS |
| `securenow/nextjs` | Next.js instrumentation; exports `registerSecureNow(options?)` | CJS |
| `securenow/nextjs-webpack-config` | Next.js config wrapper; exports `withSecureNow()`, `getSecureNowWebpackConfig()`, `EXTERNAL_PACKAGES` | CJS |
| `securenow/nextjs-middleware` | Edge middleware body capture; exports `middleware()`, `redactSensitiveData()`, `DEFAULT_SENSITIVE_FIELDS` | CJS |
| `securenow/nextjs-wrapper` | Route handler wrappers; exports `withSecureNow()`, `withSecureNowAsync()`, `captureRequestBody()`, `redactSensitiveData()` | CJS |
| `securenow/nextjs-auto-capture` | Auto-patch Next request for body capture; exports `patchNextRequest()`, `safeBodyCapture()`, `redactSensitiveData()`, `isBodyCaptureEnabled()` | CJS |
| `securenow/nuxt` | Nuxt 3 module (add to `modules` array) | ESM |
| `securenow/firewall` | Standalone firewall; exports `init()`, `shutdown()`, `getStats()`, `getMatcher()`, `getAllowlistMatcher()` | CJS |
| `securenow/firewall-only` | Preload: dotenv + firewall only, no tracing | Preload (`-r`) |
| `securenow/cidr` | CIDR utilities; exports `createMatcher()`, `ipToInt()`, `parseCidr()`, `matchesCidr()` | CJS |
| `securenow/resolve-ip` | IP resolution; exports `resolveClientIp()`, `resolveSocketIp()`, `isFromTrustedProxy()` | CJS |
| `securenow/console-instrumentation` | Consoleâ†’OTLP bridge; exports `originalConsole`, `restoreConsole()` | CJS |
| `securenow/web-vite` | Browser OTel (document load, fetch, XHR, user interaction); default export `startSecurenowWeb()` | ESM |
| `securenow/register-vite` | CJS bridge for Vite preload | CJS |

---

## Framework Integration Guides

### Express / Fastify / NestJS / Koa / Hapi / Raw Node

No code changes. Use the preload:

```bash
node -r securenow/register app.js
```

Or with the CLI:

```bash
npx securenow run app.js
```

**PM2:**

```javascript
// ecosystem.config.cjs
module.exports = {
  apps: [{
    name: 'my-app',
    script: './app.js',
    instances: 4,
    node_args: '-r securenow/register',
    env: {
      SECURENOW_APPID: 'my-app',
      SECURENOW_INSTANCE: 'https://ingest.securenow.ai',
      SECURENOW_NO_UUID: '1',
    },
  }],
};
```

**Docker:**

```dockerfile
ENV SECURENOW_APPID=my-app
ENV SECURENOW_INSTANCE=https://ingest.securenow.ai
CMD ["node", "-r", "securenow/register", "app.js"]
```

---

### Next.js

Three files to touch:

**1. `next.config.js`** (or `.mjs` / `.ts`):

```javascript
const { withSecureNow } = require('securenow/nextjs-webpack-config');

module.exports = withSecureNow({
  // your existing config
});
```

`withSecureNow()` auto-detects Next.js version:
- **>=15**: sets `serverExternalPackages`
- **<15**: sets `experimental.serverComponentsExternalPackages` + `experimental.instrumentationHook` + webpack ignore rules

**2. `instrumentation.ts`** (or `.js`, can be in `src/`):

```typescript
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { registerSecureNow } = require('securenow/nextjs');
    registerSecureNow();
  }
}
```

`registerSecureNow(options?)` accepts:

```typescript
interface RegisterOptions {
  serviceName?: string;   // override SECURENOW_APPID
  endpoint?: string;      // override SECURENOW_INSTANCE
  noUuid?: boolean;       // override SECURENOW_NO_UUID
  captureBody?: boolean;  // override SECURENOW_CAPTURE_BODY
}
```

On Vercel it uses `@vercel/otel`; self-hosted uses vanilla `@opentelemetry/sdk-node`.

**3. `.env.local`:**

```bash
SECURENOW_APPID=my-nextjs-app
SECURENOW_INSTANCE=https://ingest.securenow.ai
```

#### Next.js Body Capture

**Option A â€” Auto-capture (recommended):**

Add to your `instrumentation.ts`:

```typescript
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { registerSecureNow } = require('securenow/nextjs');
    registerSecureNow({ captureBody: true });
    require('securenow/nextjs-auto-capture');
  }
}
```

**Option B â€” Middleware:**

```typescript
// middleware.ts
export { middleware } from 'securenow/nextjs-middleware';
export const config = { matcher: ['/api/:path*'] };
```

**Option C â€” Per-route wrapper:**

```typescript
import { withSecureNow } from 'securenow/nextjs-wrapper';

export const POST = withSecureNow(async (req) => {
  // handler
});
```

#### Next.js with `securenow init`

```bash
npx securenow login
npx securenow init
```

Auto-detects Next.js, creates `instrumentation.ts`, suggests `next.config` changes, and reuses the app, instance, and firewall key written by login to `.securenow/credentials.json`.

---

### Nuxt 3

**`nuxt.config.ts`:**

```typescript
export default defineNuxtConfig({
  modules: ['securenow/nuxt'],
  securenow: {
    // optional overrides (defaults come from env vars)
  },
});
```

**`.env`:**

```bash
SECURENOW_APPID=my-nuxt-app
SECURENOW_INSTANCE=https://ingest.securenow.ai
```

The Nuxt module auto-configures Nitro externals, runtime config, and a server plugin that sets up OTel tracing + logging + firewall.

---

### Vite / Browser

```javascript
import startSecurenowWeb from 'securenow/web-vite';

startSecurenowWeb({
  serviceName: 'my-frontend',
  endpoint: 'https://ingest.securenow.ai',
});
```

Instruments document load, fetch, XMLHttpRequest, and user interactions with browser-side OpenTelemetry.

---

## Firewall â€” Multi-Layer IP Blocking

The firewall auto-activates once an API key is resolvable and the app firewall toggle is on. Since **v7.5.1**, `npx securenow login` enables the selected app firewall by default and writes the scoped key to `.securenow/credentials.json`; `securenow api-key set` can still write/rotate the key later. The `SECURENOW_API_KEY` env var is only for production/CI overrides or when the credentials file is unavailable. Resolution order: env (must start with `snk_live_`) -> project `./.securenow/credentials.json` -> global `~/.securenow/credentials.json`.

```
Layer 4: Cloud/Edge WAF    â†’  blocked at CDN (Cloudflare, AWS WAF, GCP Cloud Armor)
Layer 3: OS Firewall       â†’  kernel-level DROP (iptables/nftables)
Layer 2: TCP Socket        â†’  socket.destroy() before HTTP parsing
Layer 1: HTTP Handler      â†’  403 JSON response (always active)
```

### Activate

```bash
# Zero-config (recommended) â€” writes the key to .securenow/credentials.json
npx securenow login              # pick/create app; firewall connects automatically
# or, if you already have a key:
npx securenow api-key set snk_live_abc123...

# Optional .env overrides for the stronger layers
SECURENOW_FIREWALL_TCP=1                  # opt-in Layer 2
SECURENOW_FIREWALL_IPTABLES=1             # opt-in Layer 3 (Linux, needs root)
SECURENOW_FIREWALL_CLOUD=cloudflare       # opt-in Layer 4
# SECURENOW_API_KEY=snk_live_...          # still honored; only wins if it starts with snk_live_
```

### Firewall-Only Mode (No Tracing Overhead)

```bash
node -r securenow/firewall-only app.js

# Or via the CLI (same effect)
securenow run --firewall-only app.js
```

Loads only dotenv + firewall. No OpenTelemetry, no tracing, no external packages needed.

### Programmatic Firewall API

```javascript
const firewall = require('securenow/firewall');

await firewall.init({
  apiKey: process.env.SECURENOW_API_KEY,
  apiUrl: 'https://api.securenow.ai',
  syncInterval: 3600,       // safety-net full sync every hour
  versionCheckInterval: 10, // lightweight ETag check every 10s
  failMode: 'open',         // 'open' or 'closed'
  statusCode: 403,
  log: true,
  tcp: false,
  iptables: false,
  cloud: null,              // 'cloudflare' | 'aws' | 'gcp'
});

const stats = firewall.getStats();
const matcher = firewall.getMatcher();       // (ip) => boolean
const allowMatcher = firewall.getAllowlistMatcher();

await firewall.shutdown();
```

### Cloud WAF Providers

**Cloudflare:**
```bash
SECURENOW_FIREWALL_CLOUD=cloudflare
CLOUDFLARE_API_TOKEN=your-token
CLOUDFLARE_ACCOUNT_ID=your-account-id
```

**AWS WAF:**
```bash
SECURENOW_FIREWALL_CLOUD=aws
AWS_WAF_IP_SET_ID=your-ip-set-id
# + standard AWS credentials (env, profile, or IAM role)
```
Requires peer dep: `npm install @aws-sdk/client-wafv2`

**GCP Cloud Armor:**
```bash
SECURENOW_FIREWALL_CLOUD=gcp
GCP_PROJECT_ID=your-project
GCP_SECURITY_POLICY=your-policy-name
```
Requires peer dep: `npm install @google-cloud/compute`

**Dry-run (log only, no actual WAF changes):**
```bash
SECURENOW_FIREWALL_CLOUD_DRY_RUN=1
```

---

## Logging

Logging is enabled by default (`SECURENOW_LOGGING_ENABLED=1`). Logs are exported to your OTLP collector alongside traces.

### Get a Logger

```javascript
const { getLogger } = require('securenow/tracing');

const logger = getLogger('my-module', '1.0.0');
if (logger) {
  logger.emit({ body: 'User login succeeded', severityText: 'INFO', attributes: { userId: '123' } });
}
```

**CLI equivalent** (for shell scripts, cron, CI):

```bash
securenow log send "User login succeeded" --level info --attrs userId=123
securenow test-span "ci.smoke-test"          # emit a span without booting the SDK
```

### Console Instrumentation

When tracing is active, `console.log/warn/error` are automatically patched to emit OTLP log records correlated with the active trace span. To access the original console:

```javascript
const { originalConsole, restoreConsole } = require('securenow/console-instrumentation');
originalConsole.log('This bypasses OTLP');
restoreConsole(); // undo the patch
```

---

## IP Resolution Utilities

```javascript
const { resolveClientIp, resolveSocketIp, isFromTrustedProxy } = require('securenow/resolve-ip');

// From an HTTP request (respects X-Forwarded-For from trusted proxies)
const clientIp = resolveClientIp(req);

// Direct socket IP
const socketIp = resolveSocketIp(req);

// Check if request comes from a trusted proxy
const trusted = isFromTrustedProxy(req);
```

### CIDR Matching

```javascript
const { createMatcher, ipToInt, parseCidr, matchesCidr } = require('securenow/cidr');

const isBlocked = createMatcher(['10.0.0.0/8', '192.168.1.0/24']);
isBlocked('10.0.0.5');   // true
isBlocked('8.8.8.8');    // false
```

**CLI equivalent:**

```bash
securenow cidr match 10.0.0.5 10.0.0.0/8,192.168.1.0/24    # exit 0 = match, 2 = miss
securenow cidr parse 10.0.0.0/8                             # network/broadcast/mask/size
```

---

## Sensitive Data Redaction

Available from multiple entry points (`securenow/nextjs-middleware`, `securenow/nextjs-wrapper`, `securenow/nextjs-auto-capture`):

```javascript
const { redactSensitiveData, DEFAULT_SENSITIVE_FIELDS } = require('securenow/nextjs-middleware');

const safe = redactSensitiveData({ username: 'alice', password: 's3cret', token: 'abc' });
// { username: 'alice', password: '[REDACTED]', token: '[REDACTED]' }
```

**Auto-redacted fields:** `password`, `passwd`, `pwd`, `secret`, `token`, `api_key`, `apikey`, `access_token`, `auth`, `credentials`, `mysql_pwd`, `stripeToken`, `card`, `cardnumber`, `ccv`, `cvc`, `cvv`, `ssn`, `pin`.

Add custom fields via `SECURENOW_SENSITIVE_FIELDS=field1,field2`.

**CLI equivalent** (for piping, scripts, debugging what a payload looks like post-redaction):

```bash
securenow redact '{"user":"alice","password":"s3cret"}'
securenow redact @request.json --fields internal_id,sessionHash
```

---

## Environment Variables â€” Complete Reference

### Required

| Variable | Description | Default |
|----------|-------------|---------|
| `SECURENOW_APPID` | Service name / app identifier | *(auto-generated with UUID)* |
| `SECURENOW_INSTANCE` | OTLP collector base URL | `https://ingest.securenow.ai` |

### Service Naming

| Variable | Description | Default |
|----------|-------------|---------|
| `OTEL_SERVICE_NAME` | OpenTelemetry standard; overrides `SECURENOW_APPID` | â€” |
| `SECURENOW_NO_UUID` | `1` to use exact app ID without UUID suffix | `0` |
| `SECURENOW_STRICT` | `1` to exit if APPID missing in PM2 cluster | `0` |

### OTLP Connection

| Variable | Description | Default |
|----------|-------------|---------|
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Standard OTel endpoint; overrides `SECURENOW_INSTANCE` | â€” |
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` | Override traces endpoint specifically | `{instance}/v1/traces` |
| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` | Override logs endpoint specifically | `{instance}/v1/logs` |
| `OTEL_EXPORTER_OTLP_HEADERS` | Comma-separated `key=value` headers for OTLP requests | â€” |

### Behavior

| Variable | Description | Default |
|----------|-------------|---------|
| `SECURENOW_LOGGING_ENABLED` | Enable OTLP log export | `1` |
| `SECURENOW_CAPTURE_BODY` | Capture HTTP request bodies | `1` |
| `SECURENOW_MAX_BODY_SIZE` | Max body size in bytes | `10240` |
| `SECURENOW_CAPTURE_MULTIPART` | Capture multipart/form-data (streaming, metadata only) | `1` |
| `SECURENOW_SENSITIVE_FIELDS` | Comma-separated extra fields to redact | â€” |
| `SECURENOW_DISABLE_INSTRUMENTATIONS` | Comma-separated packages to skip (e.g. `fs,dns`) | â€” |
| `SECURENOW_TEST_SPAN` | `1` to emit a test span on startup | `0` |
| `SECURENOW_HIDE_BANNER` | `1` to suppress free-trial upgrade banner | `0` |
| `OTEL_LOG_LEVEL` | SDK diagnostic override: `error`, `warn`, `info`, `debug`, or `none` | `error` |
| `NODE_ENV` | Sent as `deployment.environment` attribute | `production` |

### Firewall

| Variable | Description | Default |
|----------|-------------|---------|
| `SECURENOW_API_KEY` | Optional production/CI override API key (`snk_live_...`). Since v7.5.1, login writes the scoped firewall key to `.securenow/credentials.json`; env var only wins when it starts with `snk_live_`. | - |
| `SECURENOW_API_URL` | SecureNow API base URL | `https://api.securenow.ai` |
| `SECURENOW_FIREWALL_VERSION_INTERVAL` | Seconds between lightweight ETag checks | `10` |
| `SECURENOW_FIREWALL_SYNC_INTERVAL` | Safety-net full blocklist refresh interval in seconds | `3600` |
| `SECURENOW_FIREWALL_FAIL_MODE` | `open` (allow all when unavailable) or `closed` | `open` |
| `SECURENOW_FIREWALL_STATUS_CODE` | HTTP status for blocked requests | `403` |
| `SECURENOW_FIREWALL_LOG` | Log blocked requests | `1` |
| `SECURENOW_FIREWALL_TCP` | Enable Layer 2 TCP blocking | `0` |
| `SECURENOW_FIREWALL_IPTABLES` | Enable Layer 3 iptables/nftables | `0` |
| `SECURENOW_FIREWALL_CLOUD` | Cloud WAF: `cloudflare`, `aws`, or `gcp` | â€” |
| `SECURENOW_FIREWALL_CLOUD_DRY_RUN` | `1` to log cloud pushes without applying | `0` |
| `SECURENOW_TRUSTED_PROXIES` | Comma-separated trusted proxy IPs | â€” |

**Resilience:** The firewall SDK includes a circuit breaker (opens after 5 consecutive errors, 2-min cooldown), in-flight request guards (prevents overlapping requests), 429 Retry-After support, and exponential backoff on both lightweight ETag checks and initial sync retries.

### Cloud WAF Provider Variables

| Provider | Variables |
|----------|-----------|
| Cloudflare | `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID` |
| AWS WAF | `AWS_WAF_IP_SET_ID`, `AWS_WAF_IP_SET_NAME`, `AWS_WAF_SCOPE` |
| GCP | `GCP_PROJECT_ID`, `GCP_SECURITY_POLICY` |

### Priority Order

**Service name:** `OTEL_SERVICE_NAME` > `SECURENOW_APPID` > auto-generated

**Endpoint:** `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` > `OTEL_EXPORTER_OTLP_ENDPOINT` > `SECURENOW_INSTANCE` > `https://ingest.securenow.ai`

---

## Recipes for Agentic AI

### Add Observability to an Existing Express App

```bash
npm install securenow@latest
npx securenow login
```

Create `.env`:
```
SECURENOW_APPID=my-express-api
SECURENOW_INSTANCE=https://ingest.securenow.ai
SECURENOW_LOGGING_ENABLED=1
SECURENOW_CAPTURE_BODY=1
```

Update `package.json`:
```json
{ "scripts": { "start": "node -r securenow/register src/index.js" } }
```

No code changes to the application needed.

### Add Observability + Firewall to a Next.js App

```bash
npm install securenow@latest
npx securenow login              # pick/create app; firewall key is minted automatically
```

`securenow login` enables the selected app's firewall toggle and writes session, app, and firewall key to `.securenow/credentials.json` (gitignored via credential-file patterns, not a whole-directory `.securenow/` ignore). Traces, logs, request body capture, multipart metadata capture, and firewall enforcement are enabled by default. The `init` command still works for manual setup â€” it creates `instrumentation.ts` and suggests `next.config` changes. Most users only need this `.env.local`:

```
SECURENOW_APPID=my-nextjs-app
SECURENOW_INSTANCE=https://ingest.securenow.ai
# SECURENOW_API_KEY=snk_live_...   (otherwise lives in .securenow/credentials.json)
```

### Enable Firewall With Zero Tracing Overhead

For apps that only need IP blocking:

```bash
node -r securenow/firewall-only app.js
```

Make sure an API key is resolvable by running `npx securenow login` first. If you already have a key, `securenow api-key set snk_live_...` writes the creds file; `SECURENOW_API_KEY` is mainly for production/CI overrides.

### Production Hardened Configuration

```bash
SECURENOW_APPID=prod-api
SECURENOW_INSTANCE=https://collector.prod.internal:4318
SECURENOW_NO_UUID=1
SECURENOW_STRICT=1
SECURENOW_LOGGING_ENABLED=1
SECURENOW_CAPTURE_BODY=1
SECURENOW_CAPTURE_MULTIPART=1
SECURENOW_API_KEY=snk_live_abc123...
SECURENOW_FIREWALL_TCP=1
SECURENOW_FIREWALL_SYNC_INTERVAL=30
SECURENOW_FIREWALL_FAIL_MODE=open
OTEL_LOG_LEVEL=error
NODE_ENV=production
```

### Instrument a Docker Container

```dockerfile
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .

ENV SECURENOW_APPID=my-service
ENV SECURENOW_INSTANCE=http://otel-collector:4318
ENV SECURENOW_NO_UUID=1
ENV SECURENOW_API_KEY=snk_live_abc123...

CMD ["node", "-r", "securenow/register", "src/index.js"]
```

### Kubernetes with Separate Trace/Log Collectors

```bash
SECURENOW_APPID=k8s-service
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://tempo:4318/v1/traces
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://loki:4318/v1/logs
SECURENOW_LOGGING_ENABLED=1
SECURENOW_NO_UUID=1
```

---

## Verification

On startup, securenow logs its configuration:

```
[securenow] pid=12345 SECURENOW_APPID="my-app" â†’ service.name=my-app
[securenow] OTel SDK started â†’ https://collector:4318/v1/traces
[securenow] Logging: ENABLED â†’ https://collector:4318/v1/logs
[securenow] Request body capture: ENABLED (max: 10240 bytes)
[securenow] Firewall: ENABLED
[securenow] Firewall: synced 142 blocked IPs
```

Use `OTEL_LOG_LEVEL=debug` and `SECURENOW_TEST_SPAN=1` to troubleshoot connectivity issues.

**CLI equivalent** (works without booting the SDK â€” useful when the app won't start):

```bash
securenow env                     # show resolved service name, endpoints, env vars
securenow doctor                  # probe OTLP + API endpoints, exits 1 on failure
securenow test-span               # send a real span to the collector
```
