Civic-AI intrusion counter-measure for Node.js sites. Free to install. Connects to the WellSpr.ing collective blocklist.
DATABASE_URL is already set on every Replit project. Add the Resend integration in one click for email. Tables are created on first startup.Download one file. No package installation required.
GET https://netsentinel.org/sentinel.ts — TypeScript / ESM (Replit standard, tsx, Hono, Express)
GET https://netsentinel.org/sentinel.js — Node.js CommonJS (plain JS, require(), older Express)
Add these three lines to your server file before your application routes:
import { sentinelMiddleware, sentinelAdminRouter, sentinelPublicHandler }
from './sentinel.js'; // .js extension required in ESM
// Mount BEFORE your routes
app.use(sentinelMiddleware);
app.use('/api/admin/sentinel', sentinelAdminRouter);
app.get('/api/sentinel/public', sentinelPublicHandler);
DATABASE_URL=... # Replit: already set automatically SENTINEL_ADMIN_EMAIL=you@yoursite.com SENTINEL_DOMAIN=yoursite.com SENTINEL_NETWORK_KEY=... # Optional — from Step 4 below RESEND_API_KEY=... # Replit: install the Resend integration
A partner key joins the WellSpr.ing collective blocklist — your server blocks IPs caught by others and contributes its catches to the network.
POST https://netsentinel.org/api/sentinel/register
Content-Type: application/json
{ "name": "Your Name", "email": "you@example.com", "domain": "yoursite.com" }
Returns { "apiKey": "ns_..." }. Set SENTINEL_NETWORK_KEY=<apiKey> in your environment.
On first startup Sentinel creates two tables automatically:
bot_incidents — every probe event: IP, path, probe type, abuse report status sentinel_blocklist — auto-blocked IPs with first/last seen timestamps
If you prefer to create them manually:
psql $DATABASE_URL -c " CREATE TABLE IF NOT EXISTS bot_incidents ( id VARCHAR PRIMARY KEY DEFAULT gen_random_uuid(), ip VARCHAR NOT NULL, path VARCHAR, domain VARCHAR, user_agent TEXT, method VARCHAR DEFAULT 'GET', probe_type VARCHAR, ip_org VARCHAR, ip_country VARCHAR, ip_range VARCHAR, abuse_contacts TEXT, report_sent_at TIMESTAMPTZ, report_email VARCHAR, action_taken VARCHAR DEFAULT 'blocked', source VARCHAR, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE IF NOT EXISTS sentinel_blocklist ( id VARCHAR PRIMARY KEY DEFAULT gen_random_uuid(), ip VARCHAR UNIQUE NOT NULL, probe_count INT DEFAULT 1, ip_org VARCHAR, ip_country VARCHAR, block_reason VARCHAR, first_seen TIMESTAMPTZ, last_seen TIMESTAMPTZ, blocked_at TIMESTAMPTZ DEFAULT now(), unblocked_at TIMESTAMPTZ );"