samurai
Design system and architecture documentation for the Samurai cybersecurity platform. Use when building or modifying any UI component, backend endpoint, or feature module in Samurai.
Samurai Design System & Architecture
Samurai is a cybersecurity analysis platform built with an Angular 21 frontend and FastAPI backend. The design follows the Samurai Design System philosophy: monochromatic, typographically driven, information-dense without clutter.
1. DESIGN PHILOSOPHY
- Subtract, don't add. Every element must earn its pixel. Default to removal.
- Structure is ornament. Expose the grid, the data, the hierarchy itself.
- Monochrome is the canvas. Color is an event, not a default -- except when encoding data status.
- Type does the heavy lifting. Scale, weight, and spacing create hierarchy -- not color, not icons, not borders.
- Both modes are first-class. Dark mode: OLED black. Light mode: warm off-white.
- Industrial warmth. Technical and precise, but never cold. A human hand should be felt.
2. FONT DISCIPLINE
Per screen, use maximum:
- 2 font families (Space Grotesk + Space Mono. Doto only for hero moments.)
- 3 font sizes (one large, one medium, one small)
- 2 font weights (Regular + one other -- usually Light or Medium, rarely Bold)
Font Assignment (Fixed Rules):
| Context | Font | Size | Weight | Letter-Spacing | |---------|------|------|--------|----------------| | Hero numbers, display | Doto | 48px–72px | Variable | -0.03em | | Headings | Space Grotesk | 24px | Regular | -0.01em | | Body text | Space Grotesk | 16px | Light/Regular | 0 | | UI Labels | Space Mono | 11px | Regular | 0.08em ALL CAPS | | Data/Numbers | Space Mono | 14–16px | Regular | 0 | | Terminal/logs | Space Mono | 13px | Regular | 0 |
3. FEATURE-DRIVEN ARCHITECTURE
See references/architecture-overview.md for the full system architecture.
The frontend follows a strict feature-driven structure:
frontend/src/app/
├── app.component.ts # Root shell: sidebar nav + router-outlet
├── app.routes.ts # Lazy-loaded routes (4 features)
├── app.config.ts # HttpClient + Router providers
├── services/
│ └── theme.service.ts # Dark/Light mode with Angular Signals
└── features/
├── scanner/ # Nmap port scanning
├── recon/ # Web reconnaissance
├── vulnerabilities/ # DAST vulnerability crawling
└── history/ # Scan history & archive
Each feature is self-contained with its own components, models, and services.
4. COMPONENT PATTERNS
See references/component-patterns.md for detailed component specifications.
Key patterns to follow:
- All components are standalone (Angular 21, no NgModules).
- Input/Output only -- no service injection in reusable components. Use
@Input()for data,@Output()EventEmitter for actions. - State lives in parent -- feature components hold state, child components are pure rendering.
- ChangeDetectorRef.detectChanges() for WebSocket-driven updates.
- No global state library -- state is managed locally per feature.
5. BACKEND PATTERNS
See references/backend-patterns.md for API and service architecture.
- WebSocket-first: All long-running operations (scanning, crawling, recon) use WebSockets for real-time streaming.
- REST for CRUD: Short-lived operations (list, get, delete) use standard REST.
- DB Dependency Injection: All endpoints use
db: Session = Depends(get_db). - Subprocess Integration: External tools (Nmap, SQLMap, Nuclei) run as OS subprocesses via
asyncio.create_subprocess_exec. - Module Orchestration: Recon modules are pluggable async functions conforming to a
ModuleRunnercallable type.
6. DATABASE SCHEMA
See references/database-schema.md for complete schema documentation.
Three tables:
- scans -- Scan records (target, status, type, timestamp)
- discovered_links -- URLs discovered during crawling (FK → scans)
- findings -- Security findings/vulnerabilities (FK → scans, FK → links)
With cascade delete relationships: deleting a scan removes all its links and findings.
7. EXPORT SYSTEM
See references/export-patterns.md for full export architecture.
Exports are implemented client-side using:
- CSV: Manual string building with CSV escaping
- JSON:
JSON.stringify(payload, null, 2) - PDF: jsPDF + jspdf-autotable, landscape A4, dark-themed
- Binary: pako gzip compression of JSON payload
Each feature has its own export-actions component with 4 buttons: CSV, JSON, PDF, BIN.
8. ANTI-PATTERNS -- WHAT TO NEVER DO
- No gradients in UI chrome
- No shadows. No blur. Flat surfaces, border separation.
- No skeleton loading screens. Use
[LOADING...]text. - No toast popups. Use inline status text:
[SAVED],[ERROR: ...] - No sad-face illustrations, cute mascots, or multi-paragraph empty states
- No zebra striping in tables
- No filled icons, multi-color icons, or emoji as UI
- No parallax, scroll-jacking, or gratuitous animation
- No spring/bounce easing. Use subtle ease-out only.
- No border-radius > 16px on cards. Buttons are pill (999px) or technical (4-8px).
9. REFERENCE FILES
For detailed specifications:
references/architecture-overview.md-- System architecture, Docker services, API endpoints, tech stackreferences/design-tokens.md-- CSS custom properties, type scale, color system (dark + light), spacing, motionreferences/component-patterns.md-- Component structure, button variants, inputs, tables, navigation, export actionsreferences/backend-patterns.md-- API routes, WebSocket endpoints, service layer, database interactionreferences/database-schema.md-- ORM models, relationships, cascade rules, query patternsreferences/export-patterns.md-- Client-side export architecture, format implementations, encryption patterns
References (6)
architecture-overview
Samurai Design System -- Architecture Overview
This documents the complete system architecture of the Samurai cybersecurity platform.
1. SYSTEM DIAGRAM
┌─────────────────────────────────────────────────────────────────┐
│ DOCKER COMPOSE │
│ ┌───────────────┐ ┌───────────────┐ ┌───────┐ ┌─────────┐ │
│ │ FRONTEND │ │ BACKEND │ │ REDIS │ │ DB │ │
│ │ Angular 21 │ │ FastAPI │ │alpine │ │ PG 15 │ │
│ │ :4200 │──│ :8000 │ │:6379 │ │ :5432 │ │
│ │ ng serve │ │ uvicorn │──│ │──│ │ │
│ │ (HMR) │ │ (reload) │ │ │ │ │ │
│ └───────────────┘ └───────────────┘ └───────┘ └─────────┘ │
│ │ │ │
│ └──── WebSocket ────┘ (3 WS endpoints) │
│ + REST API (5 HTTP endpoints) │
└─────────────────────────────────────────────────────────────────┘
2. TECH STACK
Frontend
| Technology | Version | Purpose | |---|---|---| | Angular (standalone) | 21.2.8 | SPA framework | | TypeScript | 5.9.3 | Language | | SCSS | - | Styling | | RxJS | 7.8.1 | Reactive streams | | jsPDF + jspdf-autotable | 4.2.1 / 5.0.7 | PDF export generation | | pako | 2.1.0 | gzip for binary exports |
Backend
| Technology | Version | Purpose | |---|---|---| | FastAPI | 0.105.0 | REST + WebSocket API framework | | Uvicorn | 0.24.0 | ASGI server | | SQLAlchemy | 2.0.23 | ORM + session management | | PostgreSQL | 15-alpine | Primary database | | cryptography | 42.0.2 | AES encryption (available) | | Playwright | 1.52.0 | Headless browser analysis | | httpx | 0.25.2 | Async HTTP client | | requests | 2.31.0 | Sync HTTP for probes | | BeautifulSoup4 | 4.12.2 | HTML parsing | | dnspython | 2.5.0 | DNS resolution |
Infrastructure
| Component | Technology | |---|---| | Containerization | Docker Compose (4 services) | | Dev mode | ng serve + HMR, uvicorn --reload | | External tools | Nmap, SQLMap, Nuclei (in container) |
3. API ENDPOINTS
REST Endpoints
| Method | Path | Description |
|---|---|---|
| GET | / | Health check |
| GET | /api/scans | List all scans (ordered by ID desc) |
| GET | /api/scans/{scan_id} | Get scan with findings + links (eager loaded) |
| DELETE | /api/scans/{scan_id} | Delete scan + cascade |
| POST | /api/scan/cancel/{scan_id} | Cancel running scan |
| GET | /api/database/export/raw | Download full database as JSON |
| POST | /api/database/export/encrypted | Download encrypted database dump |
WebSocket Endpoints
| Path | Parameters | Engine |
|---|---|---|
| /api/scan/live | target, profile, timeout, web_scan, collect_contacts, scan_unsanitized, max_pages | scanner.py - Nmap + web surface scan |
| /api/vuln/live | target, modules, auth_mode, auth_bearer, auth_user, auth_pass, auth_cookie | crawler.py - DAST vulnerability crawler |
| /api/recon/live | target, recon_types, timeout | recon/orchestrator.py - Web recon modules |
4. FRONTEND ROUTES
| Path | Component | Description |
|---|---|---|
| /scanner | ScannerComponent | Nmap port scanning dashboard |
| /recon | ReconComponent | Web reconnaissance dashboard |
| /vulnerabilities | VulnerabilitiesComponent | DAST vulnerability crawler |
| /history | HistoryComponent | Scan history archive |
| /export | ExportDatabaseComponent | Full database export |
| / | Redirect → /scanner | Default route |
All routes use lazy loading via loadComponent.
5. DIRECTORY STRUCTURE
samurai/
├── docs/ # Documentation
│ ├── manual.md # Dev & production setup
│ ├── python-libraries.md # Backend dependency inventory
│ ├── ui-architecture.md # UI architecture philosophy
│ ├── uses/ # Use case docs
│ │ └── dast.md
│ └── skills/ # AI-consumable design docs (THIS)
│ ├── SKILL.md
│ └── references/
│ ├── design-tokens.md
│ ├── component-patterns.md
│ ├── backend-patterns.md
│ ├── database-schema.md
│ ├── export-patterns.md
│ └── architecture-overview.md
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ └── app/
│ ├── main.py # FastAPI app + all routes
│ ├── database.py # SQLAlchemy engine + session
│ ├── models.py # ORM models
│ ├── scanner.py # Nmap + web surface scan engine
│ ├── crawler.py # DAST vulnerability crawler
│ ├── db_exporter.py # Database export engine (NEW)
│ └── recon/ # Web recon subsystem
│ ├── orchestrator.py
│ ├── target.py
│ ├── logger.py
│ ├── types.py
│ └── modules/
├── frontend/
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── angular.json
│ ├── package.json
│ └── src/
│ ├── main.ts
│ ├── index.html
│ ├── styles.scss # Samurai Design System tokens
│ └── app/
│ ├── app.component.ts/html/scss
│ ├── app.config.ts
│ ├── app.routes.ts
│ ├── services/
│ │ └── theme.service.ts
│ └── features/
│ ├── scanner/
│ ├── recon/
│ ├── vulnerabilities/
│ ├── history/
│ └── export-database/ # (NEW)
└── docker-compose.yml
6. DATA FLOW
Scanning Flow (WebSocket)
- User enters target + config → clicks "Start Scan"
- Frontend opens WebSocket to
/api/{type}/live?target=... - Backend creates
Scanrecord → sends[SCAN_META] scan_id={id} - Backend runs tool/analysis → streams stdout + findings over WebSocket
- Findings stored as
Findingrecords linked to theScan - On completion: scan status = COMPLETED,
[done]message sent
History/Replay Flow
- User navigates to
/history - Frontend GETs
/api/scans→ lists all scans - Clicking a scan navigates to appropriate feature with
scanIdquery param - Feature page detects
scanId→ GETs/api/scans/{id}→ rehydrates UI from DB
Export Flow (Current)
- Feature component collects all scan data into a payload object
- User clicks export button → feature calls format function
- JS creates Blob → creates download URL → triggers browser download
- All export logic is client-side, no backend involved
Database Export Flow (NEW)
- User navigates to
/export - Chooses direct export → backend queries all scans+findings+links → returns JSON file
- OR chooses encrypted export → enters password → backend encrypts with AES → returns .bin file
backend-patterns
Samurai Design System -- Backend Patterns
This documents the FastAPI backend architecture, conventions, and patterns used in Samurai.
1. API ENTRY POINT
File: backend/app/main.py
from fastapi import FastAPI, Depends, WebSocket, WebSocketDisconnect, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
app = FastAPI(title="Samurai API", version="2.5.0")
@app.on_event("startup")
def init_database():
database.wait_for_db()
models.Base.metadata.create_all(bind=database.engine)
app.add_middleware(CORSMiddleware, allow_origins=["*"], ...)
Conventions
- Version string:
"2.5.0"(also inapp.component.htmlfooter andROADMAP.md) - CORS: wide open for development
- DB tables auto-created on startup (after waiting for DB availability)
- All routes are defined directly in
main.py
2. DATABASE CONNECTION
File: backend/app/database.py
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker, declarative_base
DB_USER = os.getenv("DB_USER", "postgres")
DB_PASS = os.getenv("DB_PASS", "postgres")
DB_HOST = os.getenv("DB_HOST", "db")
DB_NAME = os.getenv("DB_NAME", "samurai")
DATABASE_URL = f"postgresql://{DB_USER}:{DB_PASS}@{DB_HOST}/{DB_NAME}"
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
def wait_for_db(max_retries=30, retry_delay=1.5):
# Retries connection until DB is available
Key Points
get_db()is the FastAPI dependency for session injectionwait_for_db()handles Docker startup ordering (DB may start after backend)pool_pre_ping=Trueensures stale connections are detected
3. ORM MODELS
File: backend/app/models.py
See database-schema.md for complete schema. Key patterns:
Scanis the root entity with cascade-delete relationshipsBaseis imported fromdatabase.py(not directly from SQLAlchemy)- Default
statusis"RUNNING" created_atusesdatetime.utcnowpoc_payloadis unlimited-length String (formerly had a limit that was removed)
4. REST ENDPOINT PATTERNS
Standard CRUD endpoint:
@app.get("/api/resource")
def list_resource(db: Session = Depends(database.get_db)):
items = db.query(models.Model).order_by(models.Model.id.desc()).all()
return items
@app.get("/api/resource/{id}")
def get_resource(id: int, db: Session = Depends(database.get_db)):
item = db.query(models.Model).filter(models.Model.id == id).first()
if not item:
raise HTTPException(status_code=404, detail="Not found")
return item
@app.delete("/api/resource/{id}")
def delete_resource(id: int, db: Session = Depends(database.get_db)):
item = db.query(models.Model).filter(models.Model.id == id).first()
if not item:
raise HTTPException(status_code=404, detail="Not found")
db.delete(item)
db.commit()
return {"status": "deleted", "id": id}
Eager loading for relationships:
scan = db.query(models.Scan)\
.options(
joinedload(models.Scan.findings),
joinedload(models.Scan.discovered_links)
.joinedload(models.DiscoveredLink.findings)
)\
.filter(models.Scan.id == scan_id)\
.first()
5. WEBSOCKET ENDPOINT PATTERNS
Standard WebSocket endpoint:
@app.websocket("/api/endpoint/live")
async def websocket_endpoint(
websocket: WebSocket,
target: str,
param1: str = "default",
db: Session = Depends(database.get_db)
):
await websocket.accept()
try:
# Create scan record
scan_record = models.Scan(
domain_target=target,
status="RUNNING",
scan_type="type_identifier"
)
db.add(scan_record)
db.commit()
db.refresh(scan_record)
await websocket.send_text(f"[SCAN_META] scan_id={scan_record.id}")
# Perform the actual work
await perform_operation(target, websocket, db, ...)
# Mark complete
scan_record.status = "COMPLETED"
db.commit()
await websocket.send_text("[done] scan completed and saved to history")
except WebSocketDisconnect:
if scan_record:
scan_record.status = "CANCELLED"
db.commit()
except Exception as e:
if scan_record:
scan_record.status = "ERROR"
db.commit()
await websocket.send_text(f"[!] CRITICAL ERROR: {str(e)}")
await websocket.close()
WebSocket Message Convention
[SCAN_META] scan_id={id}-- First message, identifies the scan record[...]-- Bracket-prefixed log/status messages[!] ...-- Error messages[done] ...-- Completion message
6. ASYNCHRONOUS SERVICE LAYER
Long-running operations are async functions running in the WebSocket handler:
Subprocess Execution Pattern
process = await asyncio.create_subprocess_exec(
"tool-name", *args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT
)
async for line in process.stdout:
decoded = line.decode("utf-8", errors="replace").rstrip()
await websocket.send_text(decoded)
Sync HTTP in Async Context
Synchronous requests calls are wrapped with asyncio.to_thread():
response = await asyncio.to_thread(requests.get, url, timeout=10)
Module Orchestration
The recon system uses a pluggable module pattern:
# Each module is a callable:
async def run_module(target: str, results: dict, logger: ReconStreamLogger):
...
# Orchestrator runs them sequentially:
for module_name in requested_modules:
await run_module(target, all_results, logger)
7. ADDING A NEW ENDPOINT
- If it's a long operation with streaming output → use WebSocket
- If it's a quick CRUD operation → use REST
- Add route in
main.pyfollowing existing conventions - Use
db: Session = Depends(database.get_db)for DB access - Follow error handling patterns (HTTPException for REST, try/except for WS)
- Use the
[SCAN_META],[done],[!]message conventions
8. NEW FILE EXPORT (Database Dump)
The database export is a new POST endpoint pattern:
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64, os, json
@app.get("/api/database/export/raw")
def export_db_raw(db: Session = Depends(database.get_db)):
# Query all data
# Return StreamingResponse or FileResponse
@app.post("/api/database/export/encrypted")
def export_db_encrypted(payload: dict, db: Session = Depends(database.get_db)):
password = payload.get("password", "")
# Derive key from password
# Encrypt database dump
# Return encrypted file
9. ENVIRONMENT VARIABLES (Docker Compose)
environment:
REDIS_URL: redis://redis:6379/0
DB_HOST: db
DB_NAME: samurai
DB_USER: postgres
DB_PASS: postgres
Access in Python with:
os.getenv("DB_HOST", "db")
component-patterns
Samurai Design System -- Component Patterns
This documents the actual component patterns used in the Samurai frontend, based on the Samurai design philosophy.
1. COMPONENT ARCHITECTURE RULES
1.1 Standalone Components (Mandatory)
All components are Angular standalone. No NgModules. Import what you need:
@Component({
selector: 'app-my-component',
standalone: true,
imports: [CommonModule],
templateUrl: './my.component.html',
styleUrls: ['./my.component.scss']
})
1.2 Communication Pattern
- Parent → Child:
@Input()bindings - Child → Parent:
@Output()EventEmitter - No global state: State lives in feature-level components
- No service injection in reusable/dumb components
1.3 Template Rendering
Use *ngIf for conditional rendering. Use *ngFor with trackBy where possible.
1.4 Change Detection for WebSockets
For components receiving WebSocket data streams, inject ChangeDetectorRef and call this.cdr.detectChanges() after updating properties:
constructor(private cdr: ChangeDetectorRef) {}
// After setting data:
this.cdr.detectChanges();
2. SIDEBAR NAVIGATION
HTML Structure (app.component.html)
<nav class="sidebar">
<div class="brand">
<h1 class="t-heading">SAMURAI</h1>
<span class="t-label">XWA - MODULE</span>
</div>
<ul class="nav-links">
<li><a routerLink="/scanner" routerLinkActive="active" class="t-label">01 // SCANNER</a></li>
<!-- ... -->
</ul>
<div class="sidebar-footer">
<!-- Theme toggle, social links, version, dot-grid -->
</div>
</nav>
SCSS Rules
- Width: 250px fixed, border-right:
1px solid var(--border-visible) - Background:
var(--surface) - Nav links:
padding: var(--space-lg) var(--space-xl), border-bottom divider - Active link:
background-color: var(--black),border-left: 2px solid var(--interactive) - Labels: Space Mono, ALL CAPS
Adding a New Nav Item
- Add
<li>with<a routerLink="/newroute" routerLinkActive="active" class="t-label">05 // NEW</a>toapp.component.html - Add route to
app.routes.tswithloadComponent - Create feature folder under
features/
3. SECTION HEADERS
Every feature page starts with a standard header:
<header class="section-header">
<div>
<h1 class="t-heading">PAGE_TITLE</h1>
<span class="t-label">DESCRIPTIVE_SUBTITLE</span>
</div>
</header>
SCSS:
.section-header {
border-bottom: 1px solid var(--border);
padding-bottom: var(--space-md);
margin-bottom: var(--space-xl);
}
4. BUTTONS
Export Action Buttons (Most Common Pattern)
<button class="btn-reset export-btn" type="button" (click)="action()" aria-label="Description">
<svg class="export-icon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
<!-- SVG path data -->
</svg>
<span>LABEL</span>
</button>
SCSS:
.export-btn {
display: inline-flex;
align-items: center;
gap: 6px;
border: 1px solid var(--border-visible);
background-color: transparent;
color: var(--text-secondary);
padding: var(--space-sm) var(--space-md);
font-family: var(--font-data);
font-size: var(--label);
min-height: 40px;
cursor: pointer;
transition: all 0.2s ease;
.export-icon {
width: 16px;
height: 16px;
fill: currentColor;
transition: color 0.2s ease;
flex-shrink: 0;
}
&:hover {
color: #FFD700;
border-color: #FFD700;
}
&:disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
Destructive/Delete Buttons
<button type="button" class="btn-sm destructive" (click)="deleteAction()">DELETE</button>
SCSS:
.btn-sm {
background-color: transparent;
color: var(--text-primary);
border: 1px solid var(--border-visible);
font-family: var(--font-data);
font-size: var(--body-sm);
cursor: pointer;
&.destructive:hover {
background-color: var(--accent);
color: var(--black);
border-color: var(--accent);
}
}
Theme Toggle Button
<button type="button" class="theme-toggle-btn" (click)="toggleTheme()">
<svg><!-- moon/sun icon --></svg>
<span>{{ label }}</span>
</button>
SCSS:
.theme-toggle-btn {
display: inline-flex;
align-items: center;
gap: var(--space-sm);
border: 1px solid var(--border-visible);
background: var(--surface-raised);
color: var(--text-primary);
padding: 0.45rem 0.7rem;
font-family: var(--font-data);
font-size: var(--caption);
letter-spacing: 0.08em;
text-transform: uppercase;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
opacity: 1;
color: var(--success);
border-color: var(--success);
}
}
5. PANELS / CARDS
Standard container for feature content sections:
<section class="panel">
<!-- content -->
</section>
SCSS:
.panel {
padding: var(--space-lg);
border: 1px solid var(--border-visible);
background-color: var(--surface);
display: flex;
flex-direction: column;
gap: var(--space-md);
}
6. INPUTS
Text Input (Underline Style)
<label class="t-label" for="input-id">LABEL</label>
<input type="text" id="input-id" [(ngModel)]="value" class="input-field">
SCSS:
.input-field {
background: transparent;
border: none;
border-bottom: 1px solid var(--border-visible);
color: var(--text-primary);
font-family: var(--font-data);
font-size: var(--body);
padding: var(--space-sm) 0;
&:focus {
outline: none;
border-bottom-color: var(--text-primary);
}
}
Select/Dropdown
<select [(ngModel)]="selected" class="input-field">
<option *ngFor="let opt of options" [value]="opt.value">{{opt.label}}</option>
</select>
7. STATUS INDICATORS
Status Text
<span class="t-label badge" [ngClass]="status === 'COMPLETED' ? 'text-success' : 'text-warning'">[STATUS]</span>
Severity Dots
.severity-dot {
width: 10px;
height: 10px;
border-radius: 50%;
&.high { background-color: var(--accent); }
&.medium { background-color: var(--warning); }
&.low { background-color: var(--success); }
&.info { background-color: var(--interactive); }
&.critical { background-color: var(--accent); }
}
8. LOADING STATE
Never use skeleton screens. Use text-based loading:
<div *ngIf="isLoading" class="t-label">Loading database...</div>
<div *ngIf="!isLoading && items.length === 0" class="empty-state t-label">
[ NO DATA AVAILABLE ]
</div>
9. LAYOUT GRIDS
Dashboard Grid (2-column with sidebar)
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-xl);
&.archive-grid {
grid-template-columns: 350px 1fr; // For list+detail layouts
}
}
Export Actions Row
.export-actions {
display: flex;
gap: var(--space-sm);
justify-content: flex-end;
flex-wrap: wrap;
@media (max-width: 900px) {
justify-content: center;
}
}
10. ACCORDION (Details/Summary)
<details class="nothing-accordion">
<summary>
<span class="t-label">HEADER</span>
</summary>
<div class="accordion-content">
<!-- Content -->
</div>
</details>
.nothing-accordion {
border: 1px solid var(--border-visible);
background-color: var(--black);
margin-bottom: var(--space-sm);
summary {
padding: var(--space-md);
cursor: pointer;
display: flex;
align-items: center;
gap: var(--space-md);
list-style: none;
&:hover { background-color: var(--surface-raised); }
&::-webkit-details-marker { display: none; }
}
.accordion-content {
border-top: 1px solid var(--border-visible);
padding: var(--space-md);
}
}
11. API URL PATTERN
All HTTP requests use the current hostname with port 8000:
this.http.get<Type[]>(`http://${window.location.hostname}:8000/api/path`).subscribe({...});
WebSocket connections use the same pattern:
new WebSocket(`ws://${window.location.hostname}:8000/api/endpoint/live?param=value`);
database-schema
Samurai Design System -- Database Schema
This documents the PostgreSQL database schema used by Samurai.
1. ENTITY-RELATIONSHIP DIAGRAM
┌──────────┐ ┌──────────────────┐ ┌──────────┐
│ scans │ 1───────*│ discovered_links │ 1───────*│ findings │
│ │ │ │ │ │
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ target │ │ scan_id (FK) │ │ scan_id │
│ status │ │ url │ │ link_id │
│ type │ │ status_code │ │ severity │
│ created │ │ content_type │ │ type │
└──────────┘ └──────────────────┘ │ desc │
│ │ poc │
│ 1───────* (direct findings, link_id=NULL) │ cvss │
└─────────────────────────────────────────────│ │
└──────────┘
A Scan can have:
- Direct
findings(wherelink_idis NULL) -- e.g., open ports, global recon results discovered_linkswhich each can have their ownfindings-- e.g., SQLi on a specific page
2. TABLE DEFINITIONS
2.1 scans
class Scan(Base):
__tablename__ = "scans"
id = Column(Integer, primary_key=True, index=True)
domain_target = Column(String, index=True)
status = Column(String, default="RUNNING")
created_at = Column(DateTime, default=datetime.utcnow)
scan_type = Column(String, default="port_scan")
| Column | Type | Constraints | Notes |
|--------|------|------------|-------|
| id | Integer | PK, auto-increment, indexed | Scan identifier |
| domain_target | String(255) | Indexed | Target hostname/IP |
| status | String | Default: RUNNING | One of: RUNNING, COMPLETED, ERROR, CANCELLED |
| created_at | DateTime | Default: utcnow | Scan creation timestamp |
| scan_type | String | Default: port_scan | Type identifier: port_scan:{profile}, crawler, web_recon |
2.2 discovered_links
class DiscoveredLink(Base):
__tablename__ = "discovered_links"
id = Column(Integer, primary_key=True, index=True)
scan_id = Column(Integer, ForeignKey("scans.id", ondelete="CASCADE"))
url = Column(String)
status_code = Column(Integer, nullable=True)
content_type = Column(String, nullable=True)
| Column | Type | Constraints | Notes |
|--------|------|------------|-------|
| id | Integer | PK, indexed | Link identifier |
| scan_id | Integer | FK → scans.id, CASCADE | Parent scan |
| url | String | Required | Full URL discovered |
| status_code | Integer | Nullable | HTTP response code |
| content_type | String | Nullable | HTTP Content-Type header |
2.3 findings
class Finding(Base):
__tablename__ = "findings"
id = Column(Integer, primary_key=True, index=True)
scan_id = Column(Integer, ForeignKey("scans.id", ondelete="CASCADE"))
link_id = Column(Integer, ForeignKey("discovered_links.id", ondelete="CASCADE"), nullable=True)
severity = Column(String)
finding_type = Column(String)
description = Column(String)
poc_payload = Column(String, nullable=True) # Unlimited text (JSON/proof)
cvss_score = Column(String, nullable=True)
| Column | Type | Constraints | Notes |
|--------|------|------------|-------|
| id | Integer | PK, indexed | Finding identifier |
| scan_id | Integer | FK → scans.id, CASCADE | Parent scan |
| link_id | Integer | FK → discovered_links.id, CASCADE, Nullable | NULL = global/direct finding |
| severity | String | Required | info, low, medium, high, critical |
| finding_type | String | Required | Type code: OPEN_PORT, SQL_INJECTION, REFLECTED_XSS, etc. |
| description | String | Required | Human-readable description |
| poc_payload | String | Nullable, unlimited length | Proof-of-concept or serialized JSON |
| cvss_score | String | Nullable | CVSS score string |
3. RELATIONSHIPS & CASCADE RULES
# Scan has many Findings (direct)
findings = relationship("Finding", back_populates="scan",
cascade="all, delete-orphan")
# Scan has many DiscoveredLinks
discovered_links = relationship("DiscoveredLink", back_populates="scan",
cascade="all, delete-orphan")
# DiscoveredLink has many Findings
findings = relationship("Finding", back_populates="link",
cascade="all, delete-orphan")
Cascade Behavior
- Deleting a
Scan→ deletes all itsdiscovered_linksandfindings - Deleting a
DiscoveredLink→ deletes all its childfindings - These cascades are enforced at both ORM level (
cascade="all, delete-orphan") and DB level (ondelete="CASCADE")
4. QUERY PATTERNS
List all scans (latest first):
scans = db.query(Scan).order_by(Scan.id.desc()).all()
Get scan with all relationships (eager loaded):
scan = db.query(Scan)\
.options(
joinedload(Scan.findings),
joinedload(Scan.discovered_links)
.joinedload(DiscoveredLink.findings)
)\
.filter(Scan.id == scan_id)\
.first()
Export all data (full dump):
all_scans = db.query(Scan)\
.options(
joinedload(Scan.findings),
joinedload(Scan.discovered_links)
.joinedload(DiscoveredLink.findings)
)\
.order_by(Scan.id.desc())\
.all()
Create new scan with findings:
scan = Scan(domain_target="example.com", status="RUNNING", scan_type="crawler")
db.add(scan)
db.commit()
db.refresh(scan)
finding = Finding(
scan_id=scan.id,
severity="high",
finding_type="SQL_INJECTION",
description="SQL injection in login form",
cvss_score="8.5"
)
db.add(finding)
db.commit()
5. FINDING TYPES REFERENCE
Common finding_type values used across engines:
| finding_type | Engine | Description |
|---|---|---|
| OPEN_PORT | scanner | Nmap-discovered open port |
| CONTACT_INFO_DISCLOSURE | scanner | Email/phone found on web surface |
| UNSANITIZED_INPUT_CANDIDATE | scanner | Form input without sanitization |
| REFLECTED_INPUT_ECHO | scanner | Probe value reflected in response |
| web_recon_results | recon | Full recon results as JSON blob in poc_payload |
| SQL_INJECTION | crawler | SQLMap or manual payload confirmation |
| REFLECTED_XSS | crawler | XSS payload reflected in response |
| MISSING_SECURITY_HEADER | crawler | Missing HSTS/CSP/etc. |
| INSECURE_COOKIE | crawler | Missing Secure/HttpOnly flags |
| EXPOSED_CONFIG | crawler | .env/.git/config accessible |
| OPEN_CORS | crawler | Overly permissive CORS |
| LFI_VULNERABILITY | crawler | Directory traversal |
| INSECURE_LOGIN_FORM | crawler | Password over HTTP |
| JS_SECRET_EXPOSURE | crawler | API keys/tokens in JS |
| API_DISCOVERY | crawler | API docs/schema publicly exposed |
| RISKY_HTTP_METHOD | crawler | PUT/DELETE allowed publicly |
| SSL_TLS_ISSUE | crawler | SSL/TLS protocol vulnerabilities |
design-tokens
Samurai Design System -- Design Tokens
This documents the exact CSS custom properties used in /frontend/src/styles.scss.
1. TYPOGRAPHY
Font Stack
| Role | Font | Fallback | CSS |
|------|------|----------|-----|
| Display | "Doto" | "Space Mono", monospace | --font-display |
| Body / UI | "Space Grotesk" | "DM Sans", system-ui, sans-serif | --font-body |
| Data / Labels | "Space Mono" | "JetBrains Mono", "SF Mono", monospace | --font-data |
Type Scale
| Token | Size | Line Height | Letter Spacing | Use |
|-------|------|-------------|----------------|-----|
| --display-xl | 72px | 1.0 | -0.03em | Hero numbers |
| --display-lg | 48px | 1.05 | -0.02em | Section heroes |
| --display-md | 36px | 1.1 | -0.02em | Page titles |
| --heading | 24px | 1.2 | -0.01em | Section headings |
| --subheading | 18px | 1.3 | 0 | Subsections |
| --body | 16px | 1.5 | 0 | Body text |
| --body-sm | 14px | 1.5 | 0.01em | Secondary body |
| --caption | 12px | 1.4 | 0.04em | Timestamps, footnotes |
| --label | 11px | 1.2 | 0.08em | ALL CAPS monospace labels |
Typography Utility Classes
| Class | Font | Size | Weight |
|-------|------|------|--------|
| .t-display-xl | Doto | 72px | Variable, tight tracking |
| .t-display-lg | Doto | 48px | Variable |
| .t-display-md | Doto | 36px | Variable |
| .t-heading | Space Grotesk | 24px | Regular |
| .t-label | Space Mono | 11px | ALL CAPS |
| .t-data | Space Mono | inherit | Regular |
Typographic Rules (Hard Constraints)
- Doto: 36px+ only, tight tracking, never for body text
- Labels: Always Space Mono, ALL CAPS, 0.08em spacing, 11px
- Data/Numbers: Always Space Mono
- Hierarchy: display (Doto) > heading (Space Grotesk) > label (Space Mono caps) > body (Space Grotesk)
2. COLOR SYSTEM
Primary Palette (Dark Mode -- Default)
| Token | Hex | Role |
|-------|-----|------|
| --black | #000000 | Primary background (OLED) |
| --surface | #111111 | Elevated surfaces, cards |
| --surface-raised | #1A1A1A | Secondary elevation |
| --border | #222222 | Subtle dividers (decorative only) |
| --border-visible | #333333 | Intentional borders, wireframe lines |
| --text-disabled | #666666 | Disabled text |
| --text-secondary | #999999 | Labels, captions, metadata |
| --text-primary | #E8E8E8 | Body text |
| --text-display | #FFFFFF | Headlines, hero numbers |
Light Mode Overrides
| Token | Dark | Light |
|-------|------|-------|
| --black | #000000 | #F5F5F5 |
| --surface | #111111 | #FFFFFF |
| --surface-raised | #1A1A1A | #F0F0F0 |
| --border | #222222 | #E8E8E8 |
| --border-visible | #333333 | #CCCCCC |
| --text-disabled | #666666 | #999999 |
| --text-secondary | #999999 | #666666 |
| --text-primary | #E8E8E8 | #1A1A1A |
| --text-display | #FFFFFF | #000000 |
| --interactive | #5B9BF6 | #007AFF |
Light mode is activated by adding class .theme-light to <body>.
Accent & Status Colors (Identical Across Modes)
| Token | Hex | Usage |
|-------|-----|-------|
| --accent | #D71921 | Destructive, urgent, active states |
| --accent-subtle | rgba(215,25,33,0.15) | Accent tint backgrounds |
| --success | #4A9E5C | Completed, connected |
| --warning | #D4A843 | Caution, pending |
| --error | #D71921 | Shares accent red |
| --interactive | #5B9BF6 (dark) / #007AFF (light) | Links, picker values |
Utility Classes for Colors
.text-accent→color: var(--accent).text-success→color: var(--success).text-warning→color: var(--warning).bg-surface→background-color: var(--surface).border-divider→border: 1px solid var(--border).border-visible→border: 1px solid var(--border-visible)
3. SPACING
Spacing Scale (8px base)
| Token | Value | Use |
|-------|-------|-----|
| --space-2xs | 2px | Optical adjustments only |
| --space-xs | 4px | Icon-to-label gaps, tight padding |
| --space-sm | 8px | Component internal spacing |
| --space-md | 16px | Standard padding, element gaps |
| --space-lg | 24px | Group separation |
| --space-xl | 32px | Section margins |
| --space-2xl | 48px | Major section breaks |
| --space-3xl | 64px | Page-level vertical rhythm |
| --space-4xl | 96px | Hero breathing room |
Spacing as Meaning
Tight (4-8px) = "These belong together"
Medium (16px) = "Same group, different items"
Wide (32-48px) = "New group starts here"
Vast (64-96px) = "This is a new context"
4. MOTION & INTERACTION
- Duration: 150-250ms micro, 300-400ms transitions
- Easing:
cubic-bezier(0.25, 0.1, 0.25, 1)-- subtle ease-out. No spring/bounce. - Hover: border/text brightens. No scale, no shadows.
- Theme transition:
cubic-bezier(0.16, 1, 0.3, 1)over 260ms on color properties - Route animation:
fadeInSlideUp400ms, opacity + translateY(10px → 0)
5. DOT-MATRIX MOTIF
Two utility classes for dot-grid backgrounds:
.dot-grid {
background-image: radial-gradient(circle, var(--border-visible) 1px, transparent 1px);
background-size: 16px 16px;
}
.dot-grid-subtle {
background-image: radial-gradient(circle, var(--border) 0.5px, transparent 0.5px);
background-size: 12px 12px;
}
dot-grid-subtleis used on the main content area backgrounddot-gridis used for decorative surface treatments- Never use dot-grid as container border or button style
6. EXPORT BUTTON GOLD
Export buttons use a specific hover color across the entire application:
- Hover text/border:
#FFD700(gold) - This is a project-level convention for export/download actions
7. RESPONSIVE BREAKPOINTS
| Breakpoint | Target | |-----------|--------| | 980px | Sidebar collapses to top bar, nav becomes 2-column grid | | 900px | Export actions center-align | | 640px | Nav becomes single column, main padding reduces |
export-patterns
Samurai Design System -- Export Patterns
This documents the export system architecture, both existing client-side exports and the new database export feature.
1. CLIENT-SIDE EXPORT (Existing)
Architecture
All existing exports are client-side only -- no backend involved. The browser generates the file and triggers a download via Blob + URL.createObjectURL.
Export Component Pattern
Each feature has its own export-actions component:
features/scanner/components/export-actions/ → scanner-export-actions.component.*
features/recon/components/export-actions/ → recon-export-actions.component.*
features/vulnerabilities/.../findings-export-actions/ → findings-export-actions.component.*
Each follows the same template:
- 4 buttons: CSV, JSON, PDF, BIN
- Component receives conditions via
@Input()(e.g.,hasExports,findingsCount) - Emits events via
@Output()(e.g.,exportCsv,exportJson,exportPdf,exportBinary) - Parent feature component handles the actual export logic
Button HTML
<button class="btn-reset export-btn" type="button" (click)="exportJson.emit()" aria-label="Export as JSON">
<svg class="export-icon" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true">
<!-- JSON icon path -->
</svg>
<span>EXPORT JSON</span>
</button>
Note: Each format has a distinct SVG icon (different paths).
Button SCSS (Identical across all export-actions)
.export-btn {
display: inline-flex;
align-items: center;
gap: 6px;
border: 1px solid var(--border-visible);
background-color: transparent;
color: var(--text-secondary);
padding: var(--space-sm) var(--space-md);
font-family: var(--font-data);
font-size: var(--label);
min-height: 40px;
cursor: pointer;
transition: all 0.2s ease;
.export-icon {
width: 16px;
height: 16px;
fill: currentColor;
flex-shrink: 0;
}
&:hover { color: #FFD700; border-color: #FFD700; }
&:disabled { opacity: 0.4; cursor: not-allowed; }
}
Parent Feature Export Implementation
JSON Export (simplest):
exportAsJson(payload: any, filename: string): void {
const json = JSON.stringify(payload, null, 2);
const blob = new Blob([json], { type: 'application/json' });
this.downloadBlob(blob, `${filename}.json`);
}
CSV Export:
exportAsCsv(rows: any[], headers: string[], filename: string): void {
const escape = (v: string) => `"${String(v).replace(/"/g, '""')}"`;
const csv = [headers.join(','), ...rows.map(r => headers.map(h => escape(r[h])).join(','))].join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
this.downloadBlob(blob, `${filename}.csv`);
}
PDF Export (uses jsPDF + jspdf-autotable):
import { jsPDF } from 'jspdf';
import autoTable from 'jspdf-autotable';
exportAsPdf(rows: any[], headers: string[], title: string, filename: string): void {
const doc = new jsPDF('landscape', 'mm', 'a4');
doc.setFillColor(0, 0, 0);
doc.rect(0, 0, 297, 210, 'F');
doc.setTextColor(255, 255, 255);
// Title, metadata, table...
doc.save(`${filename}.pdf`);
}
Binary Export (uses pako for gzip):
import * as pako from 'pako';
exportAsBinary(payload: any, filename: string): void {
const json = JSON.stringify(payload, null, 2);
const compressed = pako.gzip(json);
const blob = new Blob([compressed], { type: 'application/octet-stream' });
this.downloadBlob(blob, `${filename}.bin`);
}
Universal Download Helper:
private downloadBlob(blob: Blob, filename: string): void {
const url = URL.createObjectURL(blob);
const anchor = document.createElement('a');
anchor.href = url;
anchor.download = filename;
anchor.click();
URL.revokeObjectURL(url);
}
2. DATABASE EXPORT (New Feature)
Backend Endpoints
GET /api/database/export/raw -- Returns full database dump as downloadable JSON:
{
"export_metadata": {
"exported_at": "2026-05-30T12:00:00Z",
"samurai_version": "2.5.0",
"scan_count": 15,
"finding_count": 142,
"link_count": 89
},
"scans": [
{
"id": 1,
"domain_target": "example.com",
"status": "COMPLETED",
"scan_type": "crawler",
"created_at": "2026-05-29T10:00:00Z",
"findings": [...],
"discovered_links": [...]
}
]
}
POST /api/database/export/encrypted -- Returns AES-encrypted JSON:
- Request body:
{ "password": "user-entered-password" } - Response: Binary file with AES-encrypted content
- Encryption: AES-256-GCM via PBKDF2 key derivation
Frontend Feature
New page at /export accessible from sidebar 05 // EXPORT DB.
Two modes:
- Direct export: Single click downloads
samurai-database-export-{timestamp}.json - Encrypted export: Enter password → submit → downloads
samurai-database-export-{timestamp}.bin.enc
UI Layout:
- Left panel: Export mode selection + description
- Right panel: Action area (download button or password form)
- Export buttons follow the gold hover pattern
3. FILE NAMING CONVENTION
| Export Type | Filename Pattern |
|---|---|
| Scanner JSON | samurai-scanner-{scanId}.json |
| Recon JSON | samurai-recon-{target}.json |
| Findings JSON | samurai-findings-scan-{scanId}.json |
| DB Raw Export | samurai-database-export-{YYYY-MM-DD}.json |
| DB Encrypted | samurai-database-export-{YYYY-MM-DD}.bin.enc |