A multi-tenant security testing platform, built solo in 8 weeks.
Continuous attack-surface discovery, distributed vulnerability scanning, and client-ready reporting — on a security-grade microservice backend. Here is the architecture and the decisions behind it, not just the feature list.
5 services + isolated task workers behind Nginx
Tenant isolation & scan orchestration that actually hold
Real scans, real reports, a real deploy — solo
Skylance's own product — built to prove one claim.
That one AI-native engineer can ship a real, security-grade, multi-tenant SaaS — the kind of system that usually takes a funded team.
It continuously discovers an organization's internet-facing assets, scans them for vulnerabilities, and turns the findings into client-ready security reports — automatically. Attack-surface management is a domain where "roughly right" isn't good enough: multi-tenant isolation, auth hardening, and correct scan pipelines have to actually work.
Constraint that shaped everything: solo, on a single modest server. That budget is exactly why the interesting decisions below are about isolation, throughput, and cost — not about throwing hardware at the problem.
Eleven containers, five services, two isolated worker pools.
The topology is microservice-style on one host: five FastAPI services handle auth, business API, policies, registration, and AI translation; two Celery worker pools (one for execution, one exclusively for monitoring) sit behind Redis queues; PostgreSQL and Redis anchor the data and coordination layers. The front-end is a React SPA on Cloudflare Pages; Nginx reverse-proxies everything.
Five services, two worker pools, one scheduler, Postgres and Redis — eleven containers total.
Four decisions, with the reasoning and the cost made explicit.
Isolate monitoring from execution at the queue level
Nessus status polling runs every 60 seconds and must stay responsive. Asset and vulnerability scans run for 30–45 minutes. On one shared queue, a burst of long scans starves the polling that's supposed to be watching them.
One `default` queue for everything (simple, but head-of-line blocking) · A dedicated `monitoring` queue with its own worker
A separate `monitoring` queue on a single-concurrency worker, with task priorities layered on top. Long scans and fast polling never compete.
A second worker process (~300 MB RAM) and hand-maintained routing for every task type — eleven task families total.
policytask/celery_app.pyMake scans fast without missing services
Scanning a fixed 1–10,000 port range tripped Nessus's "Max Ports Exceeded" limit on WAF/CDN-fronted targets, and those scans dragged past 45 minutes. Scanning only the asset's known ports would be fast but blind to anything newly exposed.
Fixed wide range (slow, error-prone) · Known-ports-only (fast, blind) · Computed union with adaptive grouping
Scan `known ports ∪ a 46-port baseline`. When the union exceeds 128 ports, split with greedy bin-packing and run groups concurrently.
Multi-scan-ID bookkeeping and ~200 lines of grouping logic. Coverage now depends on asset-data freshness.
On protected targets: ~45 min → 12–18 min (≈60% faster). Tasks hitting "Max Ports Exceeded": ~30% → <5%.
扫描加速实施计划.md · nessus_executor.pyPut tenant isolation in the query, on purpose
Every asset, vulnerability, and report row belongs to a tenant. One missed filter leaks another tenant's attack surface — the worst possible bug for a security product.
PostgreSQL Row-Level Security (RLS) · Schema-per-tenant · Explicit WHERE uid/tid in application layer
Explicit application-layer filtering — every query carries uid/tid derived from JWT, with contract tests that probe for cross-tenant leakage.
~140 query sites that each must remember the filter — a real review burden, mitigated by the contract test.
api/tenant/service.py · api/asset/service.pyLocal Postgres over managed, as a deliberate cost trade
The database could be a managed service (Supabase) or a local Postgres container on the existing host.
| Dimension | Local PG | Managed (Supabase) |
|---|---|---|
| Monthly cost | $16–27 | $34–47 |
| Latency | <1 ms | 1–3 ms |
| Failure RPO | ≤24 hours | 0 (PITR) |
| Long transactions | unlimited | 5-minute pooler cap |
Local Postgres 16, bound to 127.0.0.1, with self-built pg_dump → Cloudflare R2 daily backup. Saves ~$220/year and keeps sub-ms latency. The cost: I own backups and the RPO is ≤24h instead of zero — an explicit, documented trade.
生产环境架构设计.mdThe engineering, not just the story.
class ApiResponse(BaseModel):
code: int = 0
message: str = "success"
data: Optional[Any] = None
success: bool = True
def error_response(code: int, message: str, data=None):
return ApiResponse(code=code, message=message,
data=data, success=False)49 error codes, enforced by a contract test — so the front-end has exactly one way to handle success and failure.
Argon2 password hashing (memory-hard), a prepared-login handshake that puts Turnstile and rate-limiting before any password check, exponential lockout (60s → 300s → 3,600s → 86,400s → 1 year), max 5 sessions per user, and revocable JWTs with jti.
- · 132 findings on a reference scan — 18 Critical / 57 High / 44 Medium / 13 Low
- · Scan acceleration: 45 min → 12–18 min (60% faster), error rate 30% → <5%
- · 5 services · 2 worker pools · 11 containers · 78 API endpoints · 49 error codes
- · 10+ concurrent tenants
- · Per-tenant capacity: 2,000 monitored assets, 20,000 asset-scans and 1,000 vulnerability-scans per month
This is what "AI-native, production-grade" actually means.
A funded team's worth of security infrastructure — architecture, async processing, hardened auth, real deployment — shipped solo, and transparent enough that you can check the reasoning, not just the result.
If you need a real SaaS or security platform built, not a prototype, this is the bar.