Docs

Architecture, config, and the CLI

Everything you need to install, configure, and operate logSguarDian — plus the pipeline that decides every verdict.

Quick start

Three steps to a running middleware

npm install logsguardian
npx logsguardian config init

This writes logsguardian.config.js in the current directory. Mount the middleware after your body parsers — express.urlencoded, express.json, and any multer instance if your app accepts file uploads.

const express = require('express');
const { logsguardian } = require('logsguardian');
const config = require('./logsguardian.config.js');

const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(logsguardian(config));

Every request past that point is inspected. Attacks get an HTTP 403; everything else is forwarded unchanged. Requires Node.js ≥ 20 and Express 4 or 5.

Docker note: onnxruntime-node requires glibc. Alpine images (musl) silently fail to load the native binary, and the worker-spawn try/catch swallows the error — meaning 100% silent fail-open with no visible warning. Use node:20-slim, not -alpine.

Architecture

Request in, verdict out

Feature extraction happens once, inside worker threads — not on the main thread — and produces a 73-feature vector. RF uses 67 of them; IF uses 61 (6 fewer: features confirmed zero/near-zero variance on benign traffic). 6 features are excluded from both models entirely: status_code (unknown at intercept time) and five temporal features that need cross-request state the extractor doesn't keep.

HTTP request Feature extractor 73 canonical features RF worker 67 features · blocks IF worker pool ×2 61 features · logs only Decision policy threshold = 0.35 block · 403 pass pass_anomaly timeout → pass (fail-open) never blocks RF timeout / crash

Decision policy, exactly as shipped

rf_classes   = ['benign', 'cmdi', 'path_traversal', 'sqli', 'xss']
RF_THRESHOLD = 0.35
IF_THRESHOLD = 0.002486040118540811   // if_v9

predicted_class = rf_classes[argmax(rf_probs)]
confidence      = max(rf_probs)
is_attack       = predicted_class != 'benign'
is_anomaly      = if_score < IF_THRESHOLD

if is_attack AND confidence >= RF_THRESHOLD:  verdict = 'block'
else if is_anomaly:                            verdict = 'pass_anomaly'
else:                                           verdict = 'pass'

Bodies and query strings are serialized with URLSearchParams, not JSON.stringify — chosen specifically to avoid introducing structural characters ({ } : ") that caused false positives on plain form POSTs. A JSON-encoded login body once scored xss @ 0.40; the same body URL-encoded scored benign @ 0.90.

Configuration

Every option in logsguardian.config.js

module.exports = {
  mode: 'block',        // 'block' (403 on attacks) or 'monitor' (log only, never blocks)
  threshold: 0.35,       // RF confidence above which a request is blocked
  model: 'hybrid',        // 'rf' (blocking only), 'if' (anomaly logging only), or 'hybrid'
  timeoutMs: 50,           // fail-open timeout — if RF doesn't answer in time, request passes
  dbPath: './logsguardian.db',   // SQLite event log + webhook registry
  webhookUrl: undefined,          // optional static webhook, called on block/anomaly
};
FieldTypeDefaultNotes
mode'block' | 'monitor''block'monitor is a dark-launch mode — logs what would have blocked, never blocks
thresholdnumber 0–10.35lower = more attacks caught, more false positives
model'rf' | 'if' | 'hybrid''hybrid'which model(s) run
timeoutMsnumber50RF only — IF is never on the blocking path
dbPathstring./logsguardian.dbuse :memory: in tests
webhookUrlstringunsetsingle static webhook; HTTPS not enforced here (unlike CLI-registered ones)

Start in mode: 'monitor' to see what logSguarDian would have blocked before turning on enforcement.

CLI

logsguardian --help

14 subcommands across four groups. All except config init require a logsguardian.config.js in the current directory.

GroupCommands
configinit, show [--format table|json], set <key> <value>, validate
attackslist, summary [--from] [--to] [--endpoint], inspect <type> — sqli, xss, path_traversal, or cmdi
endpointstop [--limit], profile <route> [--method], report [--format json|csv] [--output]
webhooksadd <url> (HTTPS required), remove <id>, list, test <id> — no restart needed

Block response shape

{ "error": "Forbidden", "class": "sqli" }

Detection event schema (SQLite)

timestamp, method, path, query_string, user_agent, client_ip, verdict, predicted_class, confidence, if_score, is_anomaly, webhook_sent, elapsed_ms

Webhooks fire a fire-and-forget POST of the detection event JSON, 3s timeout, and never affect the response on failure. A verdict can flip retroactively: a pass becomes pass_anomaly once IF's async result lands, firing a webhook at that point if one is configured.