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.
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.
| 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 |
| 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 |
# 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 | 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) |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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 β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Pipeline is authoritative.
POST /api/v1/scansand/resumeroute through the 6-stageScanPipeline(ReconβDiscoverβVerifyβTriageβPatchβRetest), which now carries findings + patches all the way to DB persistence. The legacyScanneris retained only for the CI-friendly CLI commands (check,gate,sarif) where AI is not needed. Path-only scans (noproject_id) are supported βscans.project_idis 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) |
| 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 |
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 buildWhen 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.
| 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).
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 (manifestnotificationsentry, 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 sametrace_id(falling back toresource_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.
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 backgroundWiring:
- 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 + manifestpagesdeep links. API calls go through the@fusion/web-sdkpostMessage 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 passesX-Tenant-Idthrough (build-timeVITE_TENANT_ID, defaultdefault). - Standalone:
npm run devserves at:11476(registered inarchitecture/port-registry.yaml) with/apiproxied to:11454. Dev auth viaVITE_API_KEY+VITE_TENANT_IDinfrontend/.env.local(dev convenience only). - Gateway side (upstream, fusion-gateway repo): add
web_shell.modules.fusion-securitywithupstream: http://127.0.0.1:11454and a tenant-assigned fusion-security API key asapi_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 byget_principalwith the same fail-closed tenant checks asX-API-Key(JWT-shaped tokens still go to fusion-identity).
Pipeline scans can be interrupted and resumed from the last completed stage.
- Checkpoint β After each pipeline stage completes, state is saved to
.fusion_checkpoints/ - Resume β Pass
scan_idto resume from the last checkpoint, skipping completed stages - Circuit Breaker β If consecutive stages fail, the pipeline auto-stops (CLOSEDβOPEN)
- Retry β Failed stages retry with exponential backoff before tripping the breaker
CLOSED (normal) β OPEN (tripped, failure_threshold exceeded)
β recovery_timeout
HALF_OPEN (probing, 1 request allowed)
β success β failure
CLOSED OPEN
| 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"}'Send scan results to Feishu (Lark) or DingTalk with HMAC-SHA256 webhook signing.
| 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}}'Start the API server:
fusion-security serve
# or with options
fusion-security serve --host 127.0.0.1 --port 8080| 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 |
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/summaryAs 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-Idheader β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.
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
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
# 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| 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 |
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.
| 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.
fusion-security scan ~/my-project # writes to ~/.fusion-security/fusion_security.db# 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 11454Docker 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 upSQLite-only optimizations (WAL journal mode,
StaticPool,PRAGMA foreign_keys,PRAGMA table_infomigrations) are auto-applied only when the resolved URL is SQLite. On PostgreSQL/MySQL the engine uses standard pooling and portableinformation_schemamigrations, so no SQLite-specific SQL is ever sent to the shared DB.
# 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 loggingpip install -e ".[test]"
pytest tests/ -v930 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.
- 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
sha256hashes in theapi_keystable (never plaintext); the master admin key is stabilized viaFUSION_SECURITY_MASTER_KEYso 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_hashis never returned in API responses. No fallback key literal exists:FUSION_PROFILE=prodrefuses 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_idthreaded through scan/vulnerability/patch routes; cross-tenant record access returns404(no existence leak β closes tenant IDOR) - API Key Auth β
X-API-Keyheader; keys stored assha256hashes in DB, plaintext returned once at creation - Webhook Signing β HMAC-SHA256 for Feishu/DingTalk notifications; the
X-Fusion-Security-Signatureheader is sent when a webhook secret is configured - SSRF Redirect Guard β Outbound webhook/notification HTTP follows redirects but re-validates each
Locationhop 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,409on exceed)
| 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. |
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 |
MIT License. See LICENSE for details.
Fusion-Security β Local AI Code Security. Zero Upload, Maximum Privacy.
Built with β€οΈ and fusion-mlx