Skip to content

About

πŸ”’ Fusion-Security β€” Local AI-powered code security audit tool. 100% offline, zero code upload, powered by fusion-mlx. Domestic alternative to Claude Security.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

πŸ”’ Fusion-Security

Local AI-powered code security audit tool for macOS Apple Silicon

100% offline, zero code upload, powered by fusion-mlx. The domestic alternative to Claude Security.

Python macOS License MLX Offline Beta Tests React Helm

δΈ­ζ–‡ζ–‡ζ‘£


πŸ“‹ Overview

Fusion-Security is a local AI-powered code security audit tool, designed as a domestic alternative to Claude Security. Built on fusion-mlx, it provides enterprise-grade vulnerability scanning, AI-powered semantic analysis, and automatic fix generation β€” all 100% offline with zero code uploaded.

Key Differentiators vs Claude Security

Feature Fusion-Security Claude Security
Data residency βœ… 100% local, no upload ❌ Code uploaded to Anthropic servers
Offline capable βœ… Fully offline ❌ Requires internet
China accessible βœ… Yes ❌ Blocked
AI model fusion-mlx (Apple Silicon) Claude Opus 4.7 (cloud)
Vulnerability scan βœ… Cross-file data flow βœ… Cross-file + AST (9 langs)
AI semantic analysis βœ… Logic flaw detection βœ… Logic flaw detection
Auto fix generation βœ… Template + AI enhanced βœ… AI-generated patches
Low false positives βœ… Adversarial verification βœ… Adversarial validation
Audit reports βœ… Markdown/JSON/HTML βœ… Enterprise dashboard
CI/CD integration βœ… CLI + GitHub Actions + GitLab CI βœ… Webhook + API
Web Dashboard βœ… React + Ant Design βœ… Enterprise dashboard
Notifications βœ… Feishu + DingTalk ❌
Checkpoint/Resume βœ… Pipeline resume + circuit breaker ❌
K8s Deployment βœ… Helm Chart ❌
Web API βœ… FastAPI REST API βœ…
License MIT (free) Enterprise subscription

Security Hardening

Control What it does
SSRF guard Webhook / notification outbound URLs validated against private/loopback/link-local/metadata IPs; DNS-rebinding safe. Redirects are followed but every Location hop is re-validated and DNS is pinned per request β€” a 302 cannot rebind to an internal IP (engine/ci/_url_guard.py)
Secret redaction Log filter scrubs password/api_key/token/Bearer/Authorization values before they reach stdout or log files (utils/logger.py)
Offline-first SCA Dependency scan defaults to local known-vuln DB; OSV.dev cloud lookup is opt-in via --osv and warns when enabled
AI patch review AI-generated fixes are validated and flagged needs_review=True; failure markers and no-op output are rejected
Hashed key store API keys stored as sha256 hashes in DB (ApiKeyORM); plaintext returned only once at creation, never persisted or logged
DB file perms SQLite database file (holds key hashes + code snippets) is created 0600 via umask 0o077 on init
CORS hardening allow_credentials=True with explicit method/header lists; FUSION_CORS_ORIGINS=* is rejected at startup (invalid + dangerous with credentials)
SSRF defense-in-depth Jira base_url validated via the SSRF guard on both API config and client init; issue_key sanitized against path/query injection
Error redaction Scan failure summaries, /system/model/config, and AI fix-failure markers return generic text β€” exception internals stay in server logs only
Tenant path safety Audit log filenames sanitize tenant_id to a safe slug (path-traversal safe)
Fail-closed verifiers Retest marks a patch failed (not verified) when its rule regex throws; Verify keeps verified=False when AI verification aborts β€” security gates never fail-open
Orphan-scan recovery On API startup, scans left running by a prior crash are marked failed, and queued scans are re-enqueued β€” no scan hangs forever after a restart
AI backpressure MLX calls are gated by a concurrency semaphore (default 4) so concurrent scans cannot OOM the single inference instance
Batched cache writes ProjectScanCache flushes once per stage instead of committing per file, eliminating N fsync storms on large repos
API rate limiting Per-(client_ip, api_key) sliding-window limiter (FUSION_RATE_LIMIT_PER_MINUTE, default 120/min β†’ 429 + Retry-After) protects every /api/v1/ route except the public health probe
Per-tenant scan quota Concurrent active scans (running/queued/pending) per tenant are capped (FUSION_MAX_CONCURRENT_SCANS, default 4 β†’ 409), preventing one tenant from exhausting the worker pool
Per-endpoint RBAC Every route enforces a specific permission (scan:run, vuln:manage, system:manage, …) via require_permission, not just "any valid key" β€” least-privilege by default
Tenant IDOR closure Scan/vulnerability/patch/project/webhook/schedule lookups scope by tenant_id fail-closed (no if tenant_id: skip); a cross-tenant request returns 404, leaking no record existence. Empty-tenant API keys return 401 (no all-data leak). Tenant identity is owned by fusion-identity
Webhook HMAC at fire time Webhook secrets are Fernet-encrypted (key derived from FUSION_SECURITY_MASTER_KEY) so the X-Fusion-Security-Signature HMAC can be recomputed when an event fires, without ever storing the plaintext secret

πŸš€ Quick Start

# Clone
git clone https://github.com/dahai80/fusion-security.git
cd fusion-security

# Install
pip install -e .

# Scan a project
fusion-security scan /path/to/project

# Scan with AI analysis (requires fusion-mlx running)
fusion-security scan /path/to/project --model qwen3.5-9b

# Quick check (CI-friendly JSON output)
fusion-security check /path/to/project

# List detection rules
fusion-security rules

# Start Web API server
fusion-security serve --host 127.0.0.1 --port 8080

# Start frontend dashboard
cd frontend && npm install && npm run dev

πŸ“– Command Reference

Command Description
scan <path> Full vulnerability scan
scan --severity high Only report high/critical
scan --output ./reports Save reports to directory
scan --format html Generate HTML report
scan --no-ai Disable AI analysis
scan --pipeline 5-stage pipeline scan (Recon→Discover→Verify→Triage→Patch)
scan --sca Enable SCA dependency vulnerability scan (local known-vuln DB, offline)
scan --sca --osv Enable SCA + OSV.dev cloud lookup (sends dep list to osv.dev, breaks offline)
check <path> Quick check (JSON output, CI-friendly)
gate <path> Security quality gate (CI/CD pass/fail)
gate --policy strict Gate policy: strict/standard/permissive
sarif <path> Export SARIF format results
rules List all detection rules
serve Start Web API server (FastAPI)
serve --host 127.0.0.1 Bind to all interfaces
serve --port 8080 Custom port (default 11454)

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Access Layer                               β”‚
β”‚    CLI β”‚ Web Dashboard β”‚ Web API β”‚ IDE Plugin β”‚ CI/CD β”‚ REST API β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                        Service Layer                              β”‚
β”‚  ScanService β”‚ VerifyService β”‚ PatchService β”‚ ReportService       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                        Engine Layer                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ Semantic     β”‚  β”‚ Taint        β”‚  β”‚ Adversarial             β”‚ β”‚
β”‚  β”‚ (RuleEngine) β”‚  β”‚ (TaintTracker)β”‚  β”‚ (AdversarialVerifier)  β”‚ β”‚
│  │ + AST Parser │  │ Source→Sink  │  │ Attack + Defense agents │ │
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ Rules Engine (37 rules) β”‚ AI Analyzer β”‚ SCA Scanner β”‚ CVSS  β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ 5-Stage Pipeline β”‚ Security Gate β”‚ Feedback β”‚ Custom Rules   β”‚ β”‚
β”‚  β”‚ Checkpoint/Resume β”‚ Circuit Breaker β”‚ Retry Policy           β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                      Integration Layer                            β”‚
β”‚  Feishu Notifier β”‚ DingTalk Notifier β”‚ GitHub Actions β”‚ GitLab CIβ”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                        Infra Layer                                β”‚
β”‚  Pluggable DB (SQLite default / PostgreSQL shared) β”‚ Local AI     β”‚
β”‚  Multi-tenant β”‚ Audit β”‚ Kubernetes (Helm) β”‚ PVC β”‚ HPA Autoscaling β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Engine Components

Pipeline is authoritative. POST /api/v1/scans and /resume route through the 6-stage ScanPipeline (Recon→Discover→Verify→Triage→Patch→Retest), which now carries findings + patches all the way to DB persistence. The legacy Scanner is retained only for the CI-friendly CLI commands (check, gate, sarif) where AI is not needed. Path-only scans (no project_id) are supported — scans.project_id is nullable with no FK constraint.

Component Description
5-Stage Pipeline Recon→Discover→Verify→Triage→Patch orchestration
Checkpoint/Resume Save pipeline state, resume from last completed stage
Circuit Breaker Auto-stop pipeline on consecutive failures (CLOSED→OPEN→HALF_OPEN)
Retry Policy Exponential backoff with configurable max retries
AST Parser Tree-sitter multi-language AST (Python/JS/TS/Java/Go/C/C++/Rust/Ruby/PHP) with subprocess isolation
Taint Tracker Source β†’ Propagation β†’ Sink data flow analysis
Adversarial Verifier Dual-agent verification (attack + defense)
Rule Engine 37 built-in regex + AST-based detection rules (15 regex + 8 AI semantic + 3 SCA + 11 legacy)
Custom Rule Engine User-defined rules with CRUD and gray-release
SCA Scanner Dependency vulnerability scan (OSV.dev API + fallback) + deprecated packages (FUS-SCA-002) + license risk (FUS-SCA-003) + stale version (FUS-SCA-004)
AI Analyzer Semantic scan via fusion-mlx (100% local) + 8 AI semantic rules (ACL, AUTH, CONF, LOGIC)
CVSS 3.1 Scorer Full CVSS base score calculator
Security Gate CI/CD quality gate (strict/standard/permissive)
Feedback Loop False-positive learning and suppression
Fix Generator Template + AI-enhanced patch generation with verification
Jira Integration Create Jira issues from vulnerabilities (REST API v2, basic auth)
Scan Cache LRU cache with TTL + project-level persistent cache (file hash β†’ results)
HTML Report Jinja2 template engine with XSS auto-escaping
Compliance Mapper ISO 27001 / PCI DSS mapping
SARIF Export SARIF 2.1.0 format for IDE/CI integration
RBAC + API Key Role-based access (admin/operator/viewer)
Multi-Tenant Tenant isolation with per-tenant data dirs
Audit Logging Full operation audit trail (JSONL)
Dashboard React + Ant Design frontend (scan metrics, trends, management)
Scheduler Cron-like periodic scan (hourly/daily/weekly/monthly)
Feishu Notifier Interactive card message + HMAC-SHA256 signing
DingTalk Notifier Markdown message + HMAC-SHA256 signing
GitHub Actions Composite action with severity gate + artifact upload
GitLab CI Include template with full/incremental scan jobs
Helm Chart Kubernetes deployment (fusion-security + fusion-mlx dual)

Vulnerability Coverage (37 rules)

Category Rules CWE
SQL Injection SQL001, SQL002 CWE-89
Cross-Site Scripting XSS001, XSS002 CWE-79
Command Injection CMD001, CMD002 CWE-78
Path Traversal PATH001 CWE-22
Hardcoded Secrets SEC001, SEC002 CWE-798
Weak Cryptography CRYPTO001 CWE-327
Hardcoded Encryption Key CRYPTO002 CWE-321
Missing Authentication AUTH001 CWE-862
XML External Entity XXE001 CWE-611
Open Redirect REDIR001 CWE-601
Log Injection LOG001 CWE-117
Insecure Data Transmission INSECURETRANS001 CWE-319
Directory Listing Exposure DIRTRAVERS001 CWE-548
AI Semantic Rules
Horizontal Privilege Escalation AI_ACL001 CWE-639
Vertical Privilege Escalation AI_ACL002 CWE-639
Missing MFA AI_AUTH006 CWE-308
Verbose Error Leakage AI_CONF002 CWE-209
Payment Amount Tampering AI_LOGIC001 CWE-94
Race Condition AI_LOGIC002 CWE-362
Business Flow Bypass AI_LOGIC003 CWE-285
Data Integrity Check Missing AI_LOGIC004 CWE-354
SCA Rules
Known Vulnerabilities OSV + local DB Multiple
Deprecated Components FUS-SCA-002 CWE-1104
License Risk FUS-SCA-003 CWE-1104
Stale Dependencies FUS-SCA-004 CWE-1104

πŸ–₯️ Frontend Dashboard

React 18 + Ant Design 5 + Vite 5 + TypeScript web dashboard. Dual-mode: standalone dev server, or embedded as a union-web (fusion-web-shell) remote module served by the gateway at /shell/fusion-security/ (Issue #35).

cd frontend
npm install
npm run dev        # Standalone dev server (http://localhost:11476, /api proxied to :11454)
npm run build      # Production build (base=/shell/fusion-security/)
npm run preview    # Preview production build

Production SPA serving (Issue #40)

When frontend/dist/ exists, the API server serves the built dashboard itself β€” at / and at /shell/fusion-security/ (client-side routes fall back to index.html; mounts are registered after all API routes so they never shadow them). SPA paths are exempt from the tenant middleware; the /api/* namespace keeps full fail-closed tenant auth. Without a built dist/ the server skips SPA serving silently.

When self-registering with the gateway, set FUSION_SECURITY_SHELL_API_KEY so the registration body carries api_key β€” the gateway proxy then injects that key as the upstream Bearer credential instead of issuing an fm- key that this service would reject.

Pages

Page Description
Admin Console (/console, default landing) Four-zone aggregation: members (fusion-identity), audit activity, key inventory, security posture checklist + latest high-risk events
Audit Workbench (/audit) Full-text search + time/risk/event-class filters over the auth audit trail, saved queries (exact replay), streaming CSV export, alert subscriptions
Keys & Policies (/keys) Local service keys (expiry, enable/disable) + fusion-identity module key ledger (rotate with grace window) + SSO state
Dashboard Stats cards (scans, vulns, high severity, fixed) + system health
Scans Scan list, create new scan, delete, status tracking
Vulnerabilities Vuln table with filters, detail modal, mark false positive, generate patch
Projects Project CRUD management

All pages follow the union-web frontend spec (architecture/fusion-web-ui-spec-1015.md): copy is bilingual and follows the shell language live (@fusion/web-sdk i18n subscription β€” a language switch re-renders mounted pages without reload, covering menus, page headers, buttons and the four data states; AntD's built-in texts β€” pagination, modals, date pickers β€” follow via ConfigProvider locale); theme follows the shell dark/light mode with status colors taken from AntD theme tokens (no hardcoded hex β€” severity levels map to theme-adaptive presets: critical=red, high=volcano, medium=gold, low=green, info=blue).

Admin Console (Issues #60 / #61 / #62)

The console surfaces are the admin aggregation layer of the module β€” fusion-security renders, fusion-identity is the data backbone (members, SSO, module keys):

  • Console home (/console, CS-1): four-zone summary (members / audit / keys / posture) with tenant selector, high-risk event strip, and deep links into the workbench and key console. Zones whose upstream data is not readable degrade honestly β€” the card shows the reason, never fabricated numbers.
  • Audit workbench (/audit, CS-2): search/filter over the append-only audit trail (~/.fusion_security/audit/audit_<tenant>.jsonl, every auth denial and key operation is recorded with risk level + trace id), saved queries that replay the identical result set, streaming CSV export (BOM, Excel-friendly), and alert subscriptions β€” matched events surface both on the console home and in the union-web shell notification center (manifest notifications entry, shell polls /api/v1/console/notifications). Triage extras (Issue #65): mark-for-review persists per tenant in a dedicated table (audit_review_marks; the jsonl stays append-only) with a "marked only" filter, and a one-click related events action re-filters the list by the same trace_id (falling back to resource_id) while highlighting the source row; both mark and unmark are audit-logged.
  • Key console (/keys, CS-3): local service key lifecycle (expiry dates, enable/disable) plus the fusion-identity module key ledger with rotation (old version gets a grace window; the new secret is displayed exactly once, never logged). SSO state is read-only from identity.

Identity aggregation depends on upstream fusion-identity#35 (service-token-readable member/IdP/app-secret inventory + secret revoke endpoints). Until that ships, member summaries, the full key inventory, SSO state and secret revocation degrade gracefully with an explicit reason β€” the module never fakes data. Key rotation and tenant listing work against the existing identity endpoints.

Environment: FUSION_IDENTITY_URL (default http://127.0.0.1:11470), FUSION_IDENTITY_SERVICE_TOKEN (unset = identity aggregation degraded, API-key-only mode), FUSION_AUDIT_RETENTION_DAYS (posture checklist hint). Console read endpoints require the system:manage role; key rotation/revocation/local-key edits require api_key:manage.

The dashboard connects to the FastAPI backend at http://localhost:11454/api/v1.

Union-web remote module (Issue #35)

The dashboard ships frontend/manifest.json (category domain, devices desktop + desktop-only β€” security controls are desktop-only per PRD Β§8.1) and self-registers with the gateway shell registry:

# Environment-gated β€” set both to enable registration + heartbeat (15s):
export FUSION_SHELL_GATEWAY_URL="http://127.0.0.1:11432"
export FUSION_ADMIN_KEY="<gateway key allowed on /api/v1/shell/manifests>"
./start.sh start     # API lifespan registers frontend/manifest.json and the background

Wiring:

  • Embedded: the shell loads the module in a same-origin iframe at /shell/fusion-security/?embedded=1 (iframe sandbox per PRD Β§7.2 β€” suitable for the security module's sensitivity). Own sidebar is hidden; navigation comes from the shell menu + manifest pages deep links. API calls go through the @fusion/web-sdk postMessage bridge (host attaches the gateway session token; the gateway /shell/ proxy then injects the module API key upstream β€” the UI bundle carries no secrets). The module passes X-Tenant-Id through (build-time VITE_TENANT_ID, default default).
  • Standalone: npm run dev serves at :11476 (registered in architecture/port-registry.yaml) with /api proxied to :11454. Dev auth via VITE_API_KEY + VITE_TENANT_ID in frontend/.env.local (dev convenience only).
  • Gateway side (upstream, fusion-gateway repo): add web_shell.modules.fusion-security with upstream: http://127.0.0.1:11454 and a tenant-assigned fusion-security API key as api_key. The key must have a tenant assigned β€” empty-tenant keys are rejected fail-closed.
  • Auth note: the gateway module proxy sends Authorization: Bearer <module key>; non-JWT-shaped bearer tokens are validated as API keys by get_principal with the same fail-closed tenant checks as X-API-Key (JWT-shaped tokens still go to fusion-identity).

πŸ”„ Checkpoint & Resume

Pipeline scans can be interrupted and resumed from the last completed stage.

How it works

  1. Checkpoint β€” After each pipeline stage completes, state is saved to .fusion_checkpoints/
  2. Resume β€” Pass scan_id to resume from the last checkpoint, skipping completed stages
  3. Circuit Breaker — If consecutive stages fail, the pipeline auto-stops (CLOSED→OPEN)
  4. Retry β€” Failed stages retry with exponential backoff before tripping the breaker

Circuit Breaker States

CLOSED (normal) β†’ OPEN (tripped, failure_threshold exceeded)
                      ↓ recovery_timeout
                 HALF_OPEN (probing, 1 request allowed)
                      ↓ success           ↓ failure
                 CLOSED                OPEN

API Endpoints

Method Endpoint Description
GET /api/v1/scans/checkpoints List all saved checkpoints
POST /api/v1/scans/resume Resume a scan from checkpoint
# Resume a scan
curl -X POST http://localhost:11454/api/v1/scans/resume \
  -H "Content-Type: application/json" \
  -d '{"scan_id": "abc-123", "path": "/path/to/project"}'

πŸ“’ Notifications

Send scan results to Feishu (Lark) or DingTalk with HMAC-SHA256 webhook signing.

API Endpoints

Method Endpoint Description
POST /api/v1/integrations/notify/feishu Configure Feishu webhook
POST /api/v1/integrations/notify/dingtalk Configure DingTalk webhook
POST /api/v1/integrations/notify/send Send notification to all configured channels
# Configure Feishu
curl -X POST http://localhost:11454/api/v1/integrations/notify/feishu \
  -H "Content-Type: application/json" \
  -d '{"webhook_url": "https://open.feishu.cn/open-apis/bot/v2/hook/xxx", "secret": "xxx"}'

# Configure DingTalk
curl -X POST http://localhost:11454/api/v1/integrations/notify/dingtalk \
  -H "Content-Type: application/json" \
  -d '{"webhook_url": "https://oapi.dingtalk.com/robot/send?access_token=xxx", "secret": "xxx"}'

# Send notification
curl -X POST http://localhost:11454/api/v1/integrations/notify/send \
  -H "Content-Type: application/json" \
  -d '{"event": "scan_completed", "data": {"project": "my-app", "vulns": 5, "high": 2}}'

🌐 Web API (FastAPI)

Start the API server:

fusion-security serve
# or with options
fusion-security serve --host 127.0.0.1 --port 8080

API Endpoints

Method Endpoint Description
Projects
POST /api/v1/projects Create project
GET /api/v1/projects List projects
GET /api/v1/projects/{id} Get project details
PUT /api/v1/projects/{id} Update project
DELETE /api/v1/projects/{id} Delete project
GET /api/v1/projects/{id}/scan-summary Project scan summary (scans, vulns, cache stats)
Scans
POST /api/v1/scans Start scan (async background)
GET /api/v1/scans List scans (filter by project/status)
GET /api/v1/scans/{id} Get scan details
DELETE /api/v1/scans/{id} Delete scan
GET /api/v1/scans/checkpoints List saved checkpoints
POST /api/v1/scans/resume Resume scan from checkpoint
Vulnerabilities
GET /api/v1/vulnerabilities List vulns (filter/severity/status)
GET /api/v1/vulnerabilities/{id} Get vulnerability details
PATCH /api/v1/vulnerabilities/{id} Update vulnerability status
PUT /api/v1/vulnerabilities/{id}/status Update status (with validation)
GET /api/v1/vulnerabilities/export Export vulns (JSON/CSV)
GET /api/v1/vulnerabilities/stats/summary Aggregated statistics
Patches
GET /api/v1/patches List patches
GET /api/v1/patches/{id} Get patch details
POST /api/v1/patches/{id}/verify Verify patch (test result)
Reports
GET /api/v1/reports/scans/{id}/sarif Export SARIF 2.1.0 report
Integrations
POST /api/v1/integrations/gate Security quality gate evaluation
POST /api/v1/integrations/cvss CVSS 3.1 score calculation
POST /api/v1/integrations/compliance Compliance mapping (ISO/PCI)
POST /api/v1/integrations/feedback Submit false-positive feedback
GET /api/v1/integrations/feedback/stats Feedback statistics
POST /api/v1/integrations/rules Create custom rule
GET /api/v1/integrations/rules List custom rules
DELETE /api/v1/integrations/rules/{id} Delete custom rule
GET /api/v1/integrations/dashboard Dashboard statistics
POST /api/v1/integrations/notify/feishu Configure Feishu notification
POST /api/v1/integrations/notify/dingtalk Configure DingTalk notification
POST /api/v1/integrations/notify/send Send notification
POST /api/v1/integrations/jira/config Configure Jira integration
POST /api/v1/integrations/jira/sync Sync vulnerabilities to Jira
GET /api/v1/integrations/jira/issue/{key} Get Jira issue details
Auth
POST /api/v1/keys Create API key
GET /api/v1/keys List API keys
Console
GET /api/v1/console/summary Four-zone console home summary
GET /api/v1/console/tenants Tenant selector (via fusion-identity)
GET /api/v1/console/audit/events Audit trail search/filter (search, action, risk, time window)
GET /api/v1/console/audit/export Streaming CSV export of the filtered audit trail
GET/POST/DELETE /api/v1/console/audit/saved-queries Saved query CRUD (replay = same filters)
POST /api/v1/console/audit/marks Mark / unmark an event for review (audit-logged)
GET/POST/DELETE /api/v1/console/alerts/subscriptions Alert subscription CRUD
GET /api/v1/console/notifications Shell notification poll: matched alerts + key-expiry warnings
GET /api/v1/console/keys Local key inventory + identity module key ledger
POST /api/v1/console/keys/rotate Rotate a module key via fusion-identity (grace window)
POST /api/v1/console/keys/revoke Revoke a module key version (needs fusion-identity#35)
PATCH /api/v1/console/keys/local/{key_id} Local key expiry / enable toggle
GET /api/v1/console/sso SSO state for the tenant (read-only)
System
GET /api/v1/system/health Health check
GET /api/v1/system/info Version & platform info
GET /api/v1/system/rules List built-in rules
PUT /api/v1/system/models Update default model config

API Example

Every data route requires the X-Tenant-Id header (enforced by the fusion-identity tenant middleware) plus either an X-API-Key or an Authorization: Bearer <jwt> credential. See the next section for details.

# Create project
curl -X POST http://localhost:11454/api/v1/projects \
  -H "X-API-Key: $KEY" -H "X-Tenant-Id: t1" \
  -H "Content-Type: application/json" \
  -d '{"name": "my-project", "local_path": "/path/to/project"}'

# Start scan
curl -X POST http://localhost:11454/api/v1/scans \
  -H "X-API-Key: $KEY" -H "X-Tenant-Id: t1" \
  -H "Content-Type: application/json" \
  -d '{"project_id": "<id>", "scan_type": "full", "use_ai": true}'

# List vulnerabilities
curl -H "X-API-Key: $KEY" -H "X-Tenant-Id: t1" \
  http://localhost:11454/api/v1/vulnerabilities?severity=critical

# Get stats
curl -H "X-API-Key: $KEY" -H "X-Tenant-Id: t1" \
  http://localhost:11454/api/v1/vulnerabilities/stats/summary

Authentication & Multi-Tenancy (fusion-identity)

As of v0.2.0, fusion-security delegates JWT issuance and tenant registry to fusion-identity β€” the ecosystem's single identity authority. The local tenant registry (TenantManager, fs_tenant_* keys, tenants.json) is retired; instantiating TenantManager now raises RuntimeError.

Dual-mode auth β€” both credentials are accepted on every data route:

Mode Credential Tenant source Role source
API key X-API-Key: <fs_...> key's tenant_id (cross-checked against X-Tenant-Id) key's roles
JWT Authorization: Bearer <jwt> JWT tid claim (cross-checked against X-Tenant-Id) JWT role claim

The X-Tenant-Id header is mandatory. The TenantMiddleware (from fusion_core.tenant) enforces header presence and JWT↔header match; the principal resolver (get_principal) then applies fail-closed tenant checks:

  • API key with empty tenant_id β†’ 401 (no all-data leak)
  • API key tenant_id β‰  X-Tenant-Id header β†’ 401
  • Cross-tenant scan/vuln/patch/project/webhook/schedule lookup β†’ 404 (existence not leaked)
  • Missing X-Tenant-Id β†’ 401
  • Bearer JWT whose tid β‰  X-Tenant-Id β†’ 401
  • Revoked JWT / identity unreachable (bearer path) β†’ 401 (fail-closed; API-key requests proceed since identity is not consulted)

Per-tenant concurrent-scan quota is sourced from the fusion-identity quota.max_concurrent_scans returned by /verify (JWT path), falling back to FUSION_MAX_CONCURRENT_SCANS (default 4) for API-key-only requests. Scan completion reports usage back to fusion-identity best-effort.

Environment variables:

Variable Default Purpose
FUSION_IDENTITY_URL http://127.0.0.1:11470 fusion-identity base URL
FUSION_IDENTITY_SERVICE_TOKEN (empty) service token bearer for /verify; when unset, JWT verification is unavailable (API-key mode still works)
FUSION_SHELL_GATEWAY_URL (empty) union-web gateway base URL; enables shell manifest registration + heartbeat when set with FUSION_ADMIN_KEY
FUSION_ADMIN_KEY (empty) gateway key authorized for POST /api/v1/shell/manifests
FUSION_SHELL_MANIFEST frontend/manifest.json shell manifest path override
FUSION_SECURITY_SHELL_API_KEY (empty) module API key sent as api_key in the manifest registration body so the gateway proxy injects a valid upstream credential; without it the gateway issues an fm- key and upstream calls get 401. Issue #53: when set, the key is also provisioned into the key store as an admin key at startup (idempotent upsert), so dashboard calls through the shell proxy pass the system:manage check
FUSION_SECURITY_SHELL_TENANT default tenant assigned to the provisioned shell module key (Issue #53); must match the frontend VITE_TENANT_ID (default default)

Public endpoints exempt from tenant middleware: /api/v1/system/health, /docs, /openapi.json, /redoc, plus SPA static paths (/, dashboard routes, /assets/*, /shell/fusion-security/*) when frontend/dist/ is built.


πŸ”— CI/CD Integration

GitHub Actions

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dahai80/fusion-security/ci/github@main
        with:
          path: '.'
          severity: 'high'
          fail-on-severity: 'critical'
          server-url: 'http://fusion-security:11454'

Inputs: path, severity, no-ai, format, output, fail-on-severity, server-url, incremental

GitLab CI

include:
  - remote: 'https://raw.githubusercontent.com/dahai80/fusion-security/main/ci/gitlab/.gitlab-ci.yml'
    inputs:
      severity: 'high'
      fail_on: 'critical'

Jobs: fusion-security-full (scheduled), fusion-security-incremental (MR events)

CI variables: FUSION_SECURITY_SEVERITY, FAIL_ON, NO_AI, FORMAT, SERVER


☸️ Kubernetes Deployment (Helm)

# Install
helm install fusion-security deploy/helm/fusion-security \
  --set image.tag=latest \
  --set fusionMLX.enabled=true \
  --set persistence.enabled=true

# With ingress
helm install fusion-security deploy/helm/fusion-security \
  --set ingress.enabled=true \
  --set ingress.host=fusion-security.example.com

Key Values

Parameter Default Description
image.repository fusion-security Image repository
image.tag latest Image tag
service.port 11454 Service port
ingress.enabled false Enable ingress
persistence.enabled true Enable PVC
persistence.size 5Gi PVC size
fusionMLX.enabled true Deploy fusion-mlx sidecar
fusionMLX.image fusion-mlx MLX image
autoscaling.enabled false Enable HPA
autoscaling.maxReplicas 5 Max replicas

πŸ—„οΈ Database (SQLite default / PostgreSQL shared)

The storage layer is pluggable via SQLAlchemy. Single-node deployments stay on SQLite (zero-config, 100% offline) β€” no extra setup. Multi-node clusters point all nodes at one shared PostgreSQL (or any SQLAlchemy-supported DB) so scan results, projects, and findings are consistent across nodes instead of forking per-node.

Configuration

Env Default Description
FUSION_SECURITY_DB_URL empty Full SQLAlchemy URL for a shared DB. When set, overrides the SQLite default. Example: postgresql+asyncpg://user:pass@host:5432/fusion
FUSION_DB_PATH ~/.fusion-security/fusion_security.db SQLite file path (single-node). Ignored if FUSION_SECURITY_DB_URL is set.

Resolution priority: explicit db_url arg > explicit db_path arg > FUSION_SECURITY_DB_URL > FUSION_DB_PATH > default SQLite file.

Single-node (default β€” nothing to do)

fusion-security scan ~/my-project   # writes to ~/.fusion-security/fusion_security.db

Multi-node cluster (shared PostgreSQL)

# 1. Install the optional DB drivers
pip install -e ".[postgres]"     # asyncpg (async) + psycopg2-binary (sync)

# 2. Point every node at the same shared DB
export POSTGRES_PASSWORD="your-strong-password"
export FUSION_SECURITY_DB_URL="postgresql+asyncpg://fusion:${POSTGRES_PASSWORD}@fusion-postgres:5432/fusion"
fusion-security serve --port 11454

Docker Compose multi-node: the docker-compose.postgres.yml override brings up a postgres:16 service and wires FUSION_SECURITY_DB_URL to it. The PostgreSQL password is not hardcoded β€” it is read from the POSTGRES_PASSWORD environment variable and compose fails fast if it is unset.

export POSTGRES_PASSWORD="your-strong-password"   # required
docker compose -f docker-compose.yml -f docker-compose.postgres.yml up

SQLite-only optimizations (WAL journal mode, StaticPool, PRAGMA foreign_keys, PRAGMA table_info migrations) are auto-applied only when the resolved URL is SQLite. On PostgreSQL/MySQL the engine uses standard pooling and portable information_schema migrations, so no SQLite-specific SQL is ever sent to the shared DB.


πŸ”§ Example

# Scan a project
fusion-security scan ~/my-project

# Output:
# πŸ”’ Fusion-Security Code Security Audit
# ================================================
#   Target: /Users/me/my-project
#   AI Analysis:  βœ… Enabled
#
#   πŸ“Š 3 vulnerabilities found | high: 2 | medium: 1
#   Files scanned: 42
#   Duration: 1250ms
#
#   Vulnerabilities:
#   1. 🟠 [HIGH] Hardcoded Secret
#      /Users/me/my-project/config.py:15
#      πŸ’‘ Use environment variables or key management service
#   2. 🟠 [HIGH] Command Injection
#      /Users/me/my-project/utils.py:42
#      πŸ’‘ Use subprocess.run with argument list instead of string
#   3. 🟑 [MEDIUM] Log Injection
#      /Users/me/my-project/app.py:88
#      πŸ’‘ Filter newline characters in user input before logging

πŸ§ͺ Running Tests

pip install -e ".[test]"
pytest tests/ -v

930 tests covering all modules: rule engine (37 rules), scanner, AI analyzer (8 semantic rules), SCA scanner (deprecated/license/stale), Jira integration, pipeline, checkpoint/resume, circuit breaker, notifications, fix generator, reports, API routes, CLI. 90% coverage (CI gate --cov-fail-under=90).

The test count is verified against reality in CI (scripts/check_test_count.py, Β±10% drift gate). To refresh the count after adding/removing tests: pytest --collect-only -q | tail -1.


πŸ”’ Security & Compliance

  • 100% Local Offline β€” Zero code upload, zero data leakage
  • No Telemetry β€” No analytics, no phoning home
  • Data Sovereignty β€” All scanning and processing on local machine
  • Regulation Compliant β€” No cross-border data transfer
  • Compliance Mapping β€” ISO 27001 / PCI DSS
  • CVSS 3.1 Scoring β€” Standardized vulnerability severity assessment
  • RBAC β€” admin/operator/viewer role-based access control; every API endpoint enforces a specific permission via require_permission(...) (e.g. scan:run, vuln:manage, system:manage), not just "authenticated"
  • DB-Backed Hashed Keys β€” API keys are persisted as sha256 hashes in the api_keys table (never plaintext); the master admin key is stabilized via FUSION_SECURITY_MASTER_KEY so it survives restarts, and the raw key is shown once in the create response β€” never in logs
  • Webhook Secret Safety β€” Webhook secrets are encrypted at rest with a Fernet token derived from FUSION_SECURITY_MASTER_KEY (PBKDF2-HMAC-SHA256), so the plaintext can be re-derived at fire time for HMAC signing without ever persisting it; secret_hash is never returned in API responses. No fallback key literal exists: FUSION_PROFILE=prod refuses to start webhook crypto without the env var set (RuntimeError); dev falls back to a process-random key with a loud warning
  • Audit Trail β€” Complete operation audit logs (JSONL)
  • Multi-Tenant Isolation β€” Per-tenant data dirs + tenant_id threaded through scan/vulnerability/patch routes; cross-tenant record access returns 404 (no existence leak β€” closes tenant IDOR)
  • API Key Auth β€” X-API-Key header; keys stored as sha256 hashes in DB, plaintext returned once at creation
  • Webhook Signing β€” HMAC-SHA256 for Feishu/DingTalk notifications; the X-Fusion-Security-Signature header is sent when a webhook secret is configured
  • SSRF Redirect Guard β€” Outbound webhook/notification HTTP follows redirects but re-validates each Location hop through the SSRF guard (no-redirect-to-internal bypass); DNS is pinned per request so a 302 cannot rebind to a private IP
  • Rate Limiting β€” Per-(client_ip, api_key) sliding-window limiter (FUSION_RATE_LIMIT_PER_MINUTE, default 120/min, 429 + Retry-After) plus a per-tenant concurrent-scan quota (FUSION_MAX_CONCURRENT_SCANS, default 4, 409 on exceed)

πŸ”§ Configuration (Environment Variables)

Env Default Description
FUSION_SECURITY_PORT 11454 API server port
FUSION_SECURITY_HOST 127.0.0.1 API server bind host
FUSION_SECURITY_MASTER_KEY generated Stable master admin API key. Set it explicitly in production so the admin key survives restarts and is not regenerated each boot. The plaintext value is never logged.
MLX_BASE_URL http://localhost:11432/v1 fusion-mlx OpenAI-compatible API URL. Falls back to FUSION_AI_URL then FUSION_MLX_URL for backward compatibility with existing Docker/Helm configs. Set this to the running fusion-mlx port (the monorepo default is 11434).
FUSION_CLIENT_KEY empty Bearer key the analyzer sends to fusion-mlx (Issue #52; resolved via fusion_core.config.client_key). Legacy aliases FUSION_MLX_API_KEY / MLX_API_KEY still resolve with a deprecation warning; empty in dev = anonymous MLX access, missing in prod = startup failure.
FUSION_MODEL auto Model name override (e.g. qwen3.5-9b). If unset, the analyzer auto-detects the first loaded model from /models.
FUSION_SECURITY_DB_URL empty Full SQLAlchemy URL for a shared DB (see Database section)
FUSION_DB_PATH ~/.fusion-security/fusion_security.db SQLite file path (single-node)
FUSION_CORS_ORIGINS localhost:3000,8080 Comma-separated allowed CORS origins (* rejected β€” invalid with credentials)
FUSION_RATE_LIMIT 1 Set to 0 to disable the per-IP/per-key API rate limiter (e.g. tests / single-node offline). On by default in production.
FUSION_RATE_LIMIT_PER_MINUTE 120 Max API requests per 60s sliding window, keyed by (client_ip, sha256(api_key)). Exceeding returns 429 with Retry-After.
FUSION_MAX_CONCURRENT_SCANS 4 Per-tenant concurrent active-scan quota (running/queued/pending). Creating a scan beyond this returns 409.

✨ Wired Features (v0.2.0rc1)

Five features previously declared in code but never connected are now fully wired into production paths and persisted to DB (survive restart):

Feature Status
Custom Rules CustomRuleStore injected into RuleEngine; CRUD via /api/v1/integrations/rules
Feedback Loop FeedbackStore.filter_vulnerabilities() runs in pipeline _stage_triage; false-positive feedback persisted to feedbacks table and suppresses repeat findings
Multi-Tenant Retired local TenantManager; tenant_id from fusion-identity (dual-mode JWT/API-key, fail-closed) and threaded through scan/persist paths (Issue #32)
Scan Scheduler ScanScheduler.start() runs on app startup; schedules persisted to scheduled_scans table; cron/periodic scans dispatched automatically
Webhook Webhooks persisted to webhooks table (replaces in-memory dict); scan.completed events fire WebhookNotifier on both scan completion paths

πŸ“„ License

MIT License. See LICENSE for details.


Fusion-Security β€” Local AI Code Security. Zero Upload, Maximum Privacy.

Built with ❀️ and fusion-mlx

About

πŸ”’ Fusion-Security β€” Local AI-powered code security audit tool. 100% offline, zero code upload, powered by fusion-mlx. Domestic alternative to Claude Security.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages