BLA
BLA는 오픈소스 Blue Defense MCP와 CLI로, 소프트웨어 자산을 자동 AI 리버스 엔지니어링으로부터 보호합니다
GuardRail는 두 웹 앱을 실시간 감시해 무차별 공격·자격 증명 도용·위조 웹훅·스캔을 MITRE ATT&CK에 매핑한다
GuardRail watches two live web apps for brute force, credential stuffing, forged webhooks and scanning, mapped to MITRE ATT&CK.
보안TypeScript · PowerShellGitHub @GollapalliRasmitha온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기GuardRail: a mini SIEM security monitoring dashboard (Next.js, TypeScript, Postgres) for HomeHaat and Oppora
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A mini SIEM (security monitoring dashboard) for my two live websites, HomeHaat and Oppora. They send HMAC-signed security events; GuardRail runs six detection rules mapped to MITRE ATT&CK, raises alerts, emails me about the serious ones and shows everything live.
Live: https://guardrail-rasmitha.vercel.app · Try the read-only demo: https://guardrail-rasmitha.vercel.app/demo
The dashboard itself has exactly one account (mine, with 2FA). The demo shows the same pages with made-up data, clearly labelled, and can't change anything.
▶ Watch on YouTube (or download the MP4)
A one-minute explainer: the attacks live sites face, how signed events become alerts, the six MITRE ATT&CK rules, the
two real bugs GuardRail found, privacy, and what it's built with. Drawn on a canvas and encoded in Chrome
(docs/demo/maker/), with original background music.
All screenshots use the demo's made-up data (IP addresses from the ranges reserved for documentation, masked
example.com accounts).
| Overview: events per hour (stacked by site), busiest IPs, live feed, open alerts | Incident: what to do, the evidence, MITRE ATT&CK link, timeline with notes |
| Alerts: one alert per attack, severity, status | Log upload: an nginx/Apache or sshd log checked with the same rules, never stored |
| Home page with the 3D globe | What it catches: each card turns over to show when the rule fires |
Collects signed events from HomeHaat (Django on Render) and Oppora (Django on Render): failed and successful
sign-ins, admin sign-ins, rate-limit hits, scanner paths (/.env, /wp-admin…), forged payment webhooks, refunds,
403s. Each site signs every batch with its own secret; forged or replayed requests are refused.
Detects attacks with six rules, each a MITRE ATT&CK technique:
| Rule | MITRE | Fires when |
|---|---|---|
| Brute force | T1110 | 10+ wrong passwords for one account in 10 minutes |
| Credential stuffing | T1110.004 | one IP fails on 5+ different accounts in 10 minutes |
| Forged payment webhook | T1565 | any payment notification with a wrong signature |
| Scanning | T1595 | requests for scanner paths (medium at 5+ in 5 minutes) |
| Request flood | T1499 | one IP hits the rate limit 50 times in a minute |
| Sign-in from a new IP | T1078 | a sign-in from an IP the account hasn't used in 30 days (high for admins) |
Raises one alert per attack (not one per event), with evidence, plain advice, a status flow (open → acknowledged → resolved), notes and an incident timeline. High alerts send one email (Resend).
Shows it live: Server-Sent Events push new events to the dashboard within ~2 seconds, on free serverless hosting.
Analyses uploaded logs (nginx/Apache access logs, Linux auth.log) with the same rules, entirely in memory.
A public home page with a 3D globe of attack origins (countries only) and a public read-only demo.
components/views/) renders
both the real dashboard and the demo.Last-Event-ID, so nothing is missed or doubled. One
connection per tab, a polling fallback, and it pauses while the tab is hidden.
(app/api/stream/route.ts, components/live/LiveProvider.tsx)202 first; the rules run in Next.js
after(), so the sites' 2-second timeout never includes detection or email.
(lib/detection/engine.ts)components/landing/)components/charts/EventsChart.tsx)apiFetch is the
only place the browser calls the API; Drizzle ORM gives typed SQL.timestamp:nonce:raw body with a per-site secret, compared in
constant time; a ±5-minute window; each nonce is a primary key, so a captured request works exactly once, even
under concurrency. Every failure gives the same 401. (lib/ingest/signature.ts)UNIQUE (source, uid) stores it once. Found by testing: a slow first answer made HomeHaat retry a saved batch.UNIQUE (group_key) WHERE status <> 'resolved') plus ON CONFLICT DO NOTHING and SELECT … FOR UPDATE. (lib/alerts/store.ts)'strict-dynamic'), an Origin check on every POST (CSRF),
httpOnly __Host- cookies, HSTS, no framing.meta allow-list, raw IPs wiped after 30 days by a daily cron while a
keyed hash keeps the statistics working, alert emails without IPs, uploaded logs never stored, and the public
globe sees only country totals with at least 3 events per country.localhost (checked by parsing the URL, so
[email protected] is refused) and uses documentation-only IP ranges; tests that delete data refuse any database
except the local throwaway one.A race condition in Oppora's rate limiter. My attack simulator sent 75 sign-in requests to a local Oppora, 8 at
a time, against a limit of 20 a minute. 70 got through. Django REST Framework's throttle reads the request
history from Redis and writes it back in two separate steps, so requests arriving together all read the same old
history (check-then-act). The fix is an atomic counter (Redis ADD + INCR): now exactly 20 pass and 55 are
blocked, with a regression test that fires the requests in parallel.
Every Oppora visitor looked like Cloudflare. After going live, an alert showed the IP 172.69.131.168, a
Cloudflare address. On Render each request passes through Cloudflare and Render's relay (and Vercel, from the
website), and Oppora counted the wrong number of hops back in X-Forwarded-For. So visitors behind the same
Cloudflare data centre shared one sign-in limit. I measured the real chain with fake documentation addresses in
the header, then fixed it: the website passes the visitor's IP with a shared secret (Django trusts it only with the
secret), and requests straight to Render count back to the address Cloudflare itself wrote, which can't be faked.
HomeHaat got the same fix for requests that skip its website.
flowchart LR
subgraph Sites["The two sites (Render, Singapore)"]
HH["HomeHaat API<br/>Django"]
OP["Oppora API<br/>Django"]
end
HH -- "signed batch<br/>HMAC-SHA256, 2 s timeout<br/>(outbox + retry if down)" --> ING
OP -- "signed batch" --> ING
subgraph GR["GuardRail (Vercel, Singapore)"]
ING["POST /api/ingest<br/>signature → nonce → validate"]
DET["after(): 6 MITRE rules<br/>→ one alert per attack"]
SSE["GET /api/stream<br/>Server-Sent Events"]
UI["Dashboard (sign-in + 2FA)<br/>Demo · Home page globe"]
CRON["Daily cron<br/>wipe IPs after 30 days"]
end
ING --> DB[("Neon Postgres<br/>Singapore")]
ING --> DET --> DB
DET -- "high alerts" --> MAIL["Resend email"]
DB --> SSE --> UI
CRON --> DB
| Part | Choice |
|---|---|
| App | Next.js 16 (App Router, proxy.ts, after()), React 19, TypeScript strict |
| Styling | Tailwind CSS v4 with design tokens; Chakra Petch, IBM Plex Sans / Mono |
| 3D | Three.js + React Three Fiber |
| Database | PostgreSQL 17 (Neon in production, Docker locally), Drizzle ORM + migrations |
| Validation | Zod (requests, events, settings) |
| Auth | Argon2id (@node-rs/argon2), own TOTP (RFC 6238), AES-256-GCM, qrcode |
| Resend HTTP API (no SDK) | |
| Tests | Vitest (147 unit tests), Playwright (35 end-to-end tests) |
| Hosting | Vercel Hobby (functions in Singapore, daily cron), Neon free plan |
| The senders | Python in each Django site: a background-thread client with an outbox table |
You need Docker Desktop and Node.js 22+ on Windows.
Start GuardRail.bat (or run .\start-guardrail.ps1). It starts PostgreSQL in Docker, creates
.env with random local keys, installs packages, runs migrations and opens the app on http://localhost:3100.npm run admin:create to make your account, then sign in and set up 2FA.npm run connect:homehaat (or connect:oppora) once, then
npm run simulate (attacks your local copy only) and watch /dashboard.Tests: npm run check (lint + types + unit tests) and npm run test:e2e (Playwright on a separate throwaway
database). Putting it online for free: docs/deploy.md.
The plain-English explanation of every key piece ("How it works", with interview notes) is in CLAUDE.md.
I built GuardRail with Claude Code as my AI pair-programmer: I set the brief, the design direction and the rules (free plans only, real data only in the real dashboard, never paste secrets into chat, tests for every key piece of logic, never slow down or break the live sites), made the decisions at each stage, did the dashboard setup myself, and reviewed and tested the results.
<dialog>aria-live