npm package: @goguard/node
Description: GoGuard SDK for Node.js — behavioral API security in 3 lines of code
Latest version: 0.2.1

# @goguard/node

Behavioral API security and abuse prevention for Node.js. Drop one middleware into your Express app and get protection against bots, credential stuffing, scrapers, disposable emails, phone fraud, and more.

[![npm version](https://img.shields.io/npm/v/@goguard/node.svg)](https://www.npmjs.com/package/@goguard/node)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

---

## Installation

```bash
npm install @goguard/node
```

---

## Quick start

```js
const express = require('express')
const { goguard } = require('@goguard/node')

const app = express()

app.use(goguard({ apiKey: process.env.GOGUARD_API_KEY }))

app.get('/api/data', (req, res) => {
  res.json({ message: 'Protected by GoGuard' })
})

app.listen(3000)
```

That's it. Every request is now checked against GoGuard's decision engine — bots, scrapers, rate-limit abusers, and known bad IPs are blocked before they reach your route handlers.

---

## How it works

```
Incoming request
  │
  ├── goguard() middleware
  │     ├── Extracts IP, User-Agent, headers, fingerprint
  │     ├── POST /v1/decide → GoGuard decision service
  │     │     ├── IP blocklist
  │     │     ├── Rate limits (per-IP + per-fingerprint)
  │     │     ├── Velocity / spread detection
  │     │     ├── Threat intel feeds
  │     │     ├── Credential stuffing (on login paths)
  │     │     ├── Scraper / bot detection
  │     │     ├── ML anomaly scoring
  │     │     └── Custom business rules
  │     │
  │     ├── verdict = block  → 403 returned immediately
  │     ├── verdict = challenge → X-Shield-Challenge header added
  │     └── verdict = allow  → request passes through
  │
  └── Your route handler
```

The fingerprint is **IP-independent** — it survives VPN/proxy rotation. If GoGuard is unreachable the SDK **fails open** by default — your app is never broken by an upstream outage.

---

## Middleware configuration

```js
app.use(goguard({
  apiKey:       process.env.GOGUARD_API_KEY,  // required

  mode:         'block',     // 'block' | 'monitor' | 'challenge' (default: 'block')
                             // monitor = score everything but never block
  
  excludePaths: ['/health'], // paths to skip entirely

  timeout:      1500,        // decision API timeout in ms (default: 1500)

  failOpen:     true,        // allow requests if GoGuard is unreachable (default: true)

  onBlock: (req, reason) => {
    console.log(`Blocked ${req.ip}: ${reason}`)
  },

  onError: (err) => {
    console.error('GoGuard error:', err.message)
  },

  cacheSize: 10000,          // local verdict LRU cache entries (default: 10000)
  cacheTtl:  300,            // cache TTL in seconds (default: 300)
}))
```

---

## Request metadata

After the middleware runs, GoGuard attaches data to the request object:

```js
app.post('/api/login', (req, res) => {
  req.shieldRequestId   // unique request ID for tracing
  req.shieldFingerprint // device fingerprint (IP-independent)
  req.shieldVerdict     // { action, reason, confidence }
})
```

---

## ML detection methods

For checks that need data from the **request body** (email, phone number), use `GoGuardClient` directly. These calls go to the GoGuard ML service — all fail open if the service is unreachable.

```js
const { GoGuardClient } = require('@goguard/node')

const client = new GoGuardClient({ apiKey: process.env.GOGUARD_API_KEY })
```

### Email intelligence

```js
// Detect disposable emails, plus-addressing (+tag), Gmail dot tricks
const analysis = await client.analyzeEmail(email)

// analysis.is_disposable       → mailinator.com, tempmail.com, etc.
// analysis.is_plus_addressed   → user+tag@gmail.com
// analysis.is_dot_tricked      → u.s.e.r@gmail.com
// analysis.normalized          → canonical form for duplicate checks
// analysis.risk_score          → 0.0 (safe) → 1.0 (definitely abuse)
// analysis.risk_reasons        → ['plus_addressing_detected', ...]

if (analysis?.is_disposable || analysis?.is_plus_addressed || analysis?.is_dot_tricked) {
  return res.status(400).json({ error: 'Email not allowed', reasons: analysis.risk_reasons })
}

// Always use analysis.normalized for duplicate checks
const existing = await db.users.findOne({ email: analysis.normalized })
```

```js
// Detect one person creating multiple accounts from the same device
const abuse = await client.detectEmailAbuse(['user@gmail.com', 'user+1@gmail.com', 'u.s.e.r@gmail.com'])

// abuse.is_abuse              → true
// abuse.total_fake_accounts   → 2
// abuse.clusters              → grouped by canonical email
// abuse.confidence            → 0.0 → 1.0
```

### Phone intelligence

```js
// Detect VoIP/virtual numbers, suspicious country codes, geo mismatches
const phone = await client.analyzePhone('+15551234567', {
  ip_country:       'US',   // country from IP geolocation
  accept_language:  'en-US',
  timezone_offset:  -300,   // JS: new Date().getTimezoneOffset()
})

// phone.is_virtual              → VoIP / SIM-box / virtual number
// phone.is_suspicious_country   → high-risk country code
// phone.cross_signal_mismatch   → phone country ≠ IP/language country
// phone.risk_score              → 0.0 → 1.0
```

```js
// Detect a user cycling through multiple phone numbers (SIM farming)
const cycling = await client.detectPhoneCycling([
  { phone: '+15551234567', timestamp: 1700000000000 },
  { phone: '+15557654321', timestamp: 1700003600000 },
])

// cycling.is_cycling     → true/false
// cycling.unique_phones  → number of distinct numbers
// cycling.confidence     → 0.0 → 1.0
```

### Credential stuffing signal

```js
// Report a login attempt — lets GoGuard track stuffing across accounts
app.post('/login', async (req, res) => {
  const { email, password } = req.body

  const user = await db.users.findOne({ email })
  const success = user && await bcrypt.compare(password, user.passwordHash)

  // Report outcome so GoGuard can update its success-rate model
  await client.detectStuffing(email, req.ip, Date.now(), success)

  if (!success) return res.status(401).json({ error: 'Invalid credentials' })
  res.json({ token: signJwt(user) })
})
```

### Scraper detection

```js
// Check if a fingerprint is behaving like a scraper
// Pass the last N request times, paths, and methods for that fingerprint
const scraper = await client.detectScraper(
  req.shieldFingerprint,
  recentTimestamps,   // number[]  — Unix ms
  recentPaths,        // string[]  — e.g. ['/products/1', '/products/2']
  recentMethods,      // string[]  — e.g. ['GET', 'GET', 'GET']
)

// scraper.is_scraper   → true/false
// scraper.confidence   → 0.0 → 1.0
// scraper.signals      → { timing_cv, path_uniqueness, get_ratio, sequential_paths }
```

---

## What gets detected automatically (middleware)

| Attack | How |
|---|---|
| Bot networks | IP-independent fingerprinting survives VPN/proxy rotation |
| Rate limit abuse | Sliding window per IP and per fingerprint |
| Credential stuffing | Multi-IP login attempts on auth paths |
| Scrapers | Timing regularity, path patterns, GET ratio |
| Known bad IPs | Threat intel feeds |
| ML anomalies | Isolation Forest trained per customer baseline |
| Custom rules | Configurable in the GoGuard dashboard |

## What you call explicitly (GoGuardClient)

| Attack | Method |
|---|---|
| Disposable email | `analyzeEmail()` |
| Email aliasing (+tag, dot trick) | `analyzeEmail()` |
| Multi-account signup abuse | `detectEmailAbuse()` |
| VoIP / virtual phone numbers | `analyzePhone()` |
| Phone cycling / SIM farming | `detectPhoneCycling()` |
| Stuffing outcome reporting | `detectStuffing()` |

---

## Response headers

| Header | Value |
|---|---|
| `X-Shield-Request-Id` | Unique ID for tracing this request |
| `X-Shield-Action` | `allow` / `block` / `challenge` |
| `X-Shield-Challenge` | Present when action is `challenge` |

---

## Requirements

- Node.js 20+
- Express 4+ (or any `(req, res, next)` compatible middleware stack)

## License

MIT