Last updated: 2025-12-12 Pipeline Version: v2.1.0 Workflow Nodes: 24 (11 Code nodes)
This document provides a comprehensive technical overview of Vigil Guard's v2.1.0 architecture, featuring the 3-branch parallel detection system with Arbiter-based weighted aggregation.
v2.1.0 Changes:
- Arbiter weights: Heuristics 30%, Semantic 40% (+5%), LLM Guard 30% (-5%)
- Solo-PG exception: Reduces false positives on security education queries
- SEMANTIC_CORROBORATION boost: Solo-PG detections (PG≥70, H<15, S<15) reduced to score=45
- Overview
- Architecture Evolution
- Pipeline Flow
- 3-Branch Detection System
- Branch C: LLM Safety Engine Analysis
- Arbiter Engine
- PII Redaction (Post-Detection)
- Data Flow & Schema
- Service Topology
- Configuration
- Performance Characteristics
- Security Model
Vigil Guard v2.1.0 introduces a 3-branch parallel detection architecture that replaces the previous sequential 40-node pipeline. The new system achieves:
- Parallel Processing: All 3 branches execute simultaneously using Promise.all()
- Binary Decisions: ALLOW or BLOCK (sanitization reserved for PII only)
- Weighted Aggregation: Configurable branch weights with priority boost policies
- Fail-Secure Design: All branches degraded → automatic BLOCK
- Reduced Latency: <5s total processing (vs. 30s+ in v1.x)
- Improved Accuracy: Semantic + ML analysis complements heuristic detection
Key Architectural Shift:
v1.x: Input → 40 Sequential Nodes → Score → Decision → Output
v2.1: Input → 3 Parallel Branches → Arbiter → Decision → PII (if ALLOW) → Output
- Nodes: 40 total (16 Code nodes)
- Flow: PII → Normalize → Bloom → Allowlist → Pattern Matching → Decision → Sanitization
- Decisions: ALLOW (0-29), SANITIZE_LIGHT (30-64), SANITIZE_HEAVY (65-84), BLOCK (85-100)
- Latency: 15-30s typical (sequential processing)
- Nodes: 24 total (11 Code nodes)
- Flow: Input Validation → 3-Branch Executor → Arbiter → Decision Router → PII Redaction (ALLOW only)
- Decisions: ALLOW or BLOCK (binary)
- Latency: 1-5s typical (parallel execution with timeouts)
- PII: Applied after detection, only for ALLOW decisions
┌─────────────────────────────────────────────────────────────────────────┐
│ Vigil Guard v2.1.0 Pipeline │
└─────────────────────────────────────────────────────────────────────────┘
[Webhook v2] / [Chat Trigger]
│
▼
┌──────────────────┐
│ Extract Input │ ← Wrap chat_payload
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Load Config │ ← allowlist.schema.json, pii.conf, unified_config.json
│ (3 parallel) │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Config Loader v2 │ ← Parse & validate configuration
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Input Validator │ ← Schema validation, early rejection
└────────┬─────────┘
│
├─[Invalid]──→ ┌──────────────────┐
│ │ Early Block v2 │ → Output
│ └──────────────────┘
│
▼[Valid]
┌───────────────────────────────────────────────────────────────────────┐
│ 3-Branch Executor (PARALLEL) │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Branch A │ │ Branch B │ │ Branch C │ │
│ │ Heuristics │ │ Semantic │ │ LLM Safety Engine │ │
│ │ :5005 │ │ :5006 │ │ :8000 │ │
│ │ timeout: 1s │ │ timeout: 2s │ │ timeout: 3s │ │
│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │ │
│ └────────────────────┴────────────────────┘ │
│ │ │
└───────────────────────────────┼──────────────────────────────────────┘
│
▼
┌─────────────────┐
│ Arbiter v2 │ ← Weighted aggregation + boosts
│ Weights: │
│ A: 30% │
│ B: 35% │
│ C: 35% │
└────────┬────────┘
│
▼
┌─────────────────┐
│ Arbiter Decision│ ← Binary: ALLOW or BLOCK?
└────────┬────────┘
│
┌─────────────────┴─────────────────┐
│ │
[ALLOW] [BLOCK]
│ │
▼ ▼
┌───────────────────┐ ┌──────────────────┐
│ PII_Redactor_v2 │ │ Block Response │
│ (Presidio pl/en) │ │ v2 │
└─────────┬─────────┘ └────────┬─────────┘
│ │
└──────────┬───────────────────────┘
│
▼
┌──────────────────┐
│ Build NDJSON v2 │ ← Prepare events_v2 schema
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Log to ClickHouse│ ← events_v2 table
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Clean Output v2 │ ← Return to client
└──────────────────┘
| Node | Type | Responsibility |
|---|---|---|
Webhook v2 |
webhook | HTTP webhook trigger for API consumers |
When chat message received |
chatTrigger | Chat interface trigger |
Extract Input |
set | Wrap chat_payload object |
Load allowlist.schema.json |
readWriteFile | Load allowlist configuration |
Load pii.conf |
readWriteFile | Load PII detection rules |
Load unified_config.json |
readWriteFile | Load main configuration |
Extract allowlist |
extractFromFile | Parse allowlist JSON |
Extract pii.conf |
extractFromFile | Parse PII configuration |
Extract unified_config |
extractFromFile | Parse unified config JSON |
Merge Config |
merge | Combine all configuration streams |
Config Loader v2 |
code | Validate and prepare config for execution |
Input Validator v2 |
code | Schema validation, malformed input detection |
Validation Check |
if | Route based on validation result |
3-Branch Executor |
code | Execute all 3 branches in parallel |
Arbiter v2 |
code | Weighted aggregation + priority boosts |
Arbiter Decision |
if | Binary decision: ALLOW or BLOCK? |
PII_Redactor_v2 |
code | Dual-language PII detection (Polish + English) |
Block Response v2 |
code | Generate block message |
Early Block v2 |
code | Early rejection for invalid input |
Merge Final |
merge | Combine ALLOW and BLOCK paths |
Build NDJSON v2 |
code | Prepare events_v2 schema for logging |
Log to ClickHouse v2 |
httpRequest | Insert to events_v2 table |
Clean Output v2 |
set | Return minimal payload to client |
output to plugin |
code | Format response for browser extension |
The 3-Branch Executor sends the original text (not normalized) to all 3 services simultaneously using axios and Promise.all(). Each branch has independent timeouts and graceful degradation.
Unified Contract (v2.1):
All branches return:
{
branch_id: 'A' | 'B' | 'C',
name: string,
score: number, // 0-100
threat_level: 'LOW' | 'MEDIUM' | 'HIGH',
confidence: number, // 0.0-1.0
critical_signals: { // Boolean flags for boosts
[key: string]: boolean
},
features: object, // Branch-specific metadata
explanations: string[],
timing_ms: number,
degraded: boolean
}Endpoint: http://heuristics-service:5005/analyze
Timeout: 1000ms
Weight: 30% (default)
Detection Capabilities:
-
Obfuscation Detection (weight: 0.25)
- Zero-width characters (U+200B, U+200C, U+FEFF, etc.)
- Homoglyph substitution (Cyrillic/Greek lookalikes)
- Excessive unicode escapes
- Mixed scripts (Latin + Cyrillic/Arabic/Hebrew)
-
Structure Analysis (weight: 0.20)
- Unusual punctuation density
- Repeated special characters
- Bracket imbalance
- Excessive whitespace
-
Whisper Keywords (weight: 0.25)
- Instruction override phrases ("ignore previous", "disregard above")
- Role manipulation ("you are now", "act as")
- Jailbreak patterns
-
Entropy Analysis (weight: 0.15)
- Shannon entropy (thresholds: low 2.0, high 4.8)
- Language-aware bigram analysis (anomaly threshold: 0.25)
- Relative KL divergence (threshold: 0.4)
- Character class diversity (threshold: 4 classes)
-
Security Keyword Detection (weight: 0.15)
- SQL injection patterns (
UNION SELECT,OR 1=1) - XSS vectors (
<script>,onerror=) - Command injection (
; cat /etc/passwd,| nc) - Privilege escalation attempts
- SQL injection patterns (
Thresholds:
- LOW: score ≤ 30
- MEDIUM: 30 < score ≤ 65
- HIGH: score > 65
Critical Signals:
obfuscation_detected: TriggersHEURISTICS_CRITICALboost if score ≥ 75
Output Example:
{
"branch_id": "A",
"name": "heuristics",
"score": 78,
"threat_level": "HIGH",
"confidence": 0.82,
"critical_signals": {
"obfuscation_detected": true
},
"features": {
"obfuscation_score": 85,
"structure_score": 42,
"whisper_score": 90,
"entropy_score": 65,
"security_score": 70,
"entropy_details": {
"shannon": 4.2,
"bigram_anomaly": 0.31,
"relative_entropy": 0.52,
"char_class_diversity": 5
}
},
"explanations": ["High obfuscation detected", "Whisper keywords found"],
"timing_ms": 320,
"degraded": false
}Endpoint: http://semantic-service:5006/analyze
Timeout: 2000ms
Weight: 35% (default)
Detection Method:
- Embedding Similarity: Compares input text embeddings to known attack vectors
- Vector Database: Pre-indexed malicious prompt patterns
- Cosine Similarity: Threshold-based matching
Critical Signals:
high_similarity: TriggersSEMANTIC_HIGH_SIMILARITYboost (min score: 70)
Output Example:
{
"branch_id": "B",
"name": "semantic",
"score": 72,
"threat_level": "HIGH",
"confidence": 0.88,
"critical_signals": {
"high_similarity": true
},
"features": {
"max_similarity": 0.91,
"matched_categories": ["prompt_injection", "role_manipulation"],
"top_matches": 5
},
"explanations": ["High similarity to known prompt injection attacks"],
"timing_ms": 1580,
"degraded": false
}Endpoint: http://prompt-guard-api:8000/detect
Timeout: 3000ms
Weight: 35% (default)
Detection Method:
- Model: Llama Guard 2 (86M parameters)
- Classification: Binary attack detection with confidence scoring
Response Normalization:
The 3-Branch Executor normalizes the LLM Guard response to the unified contract:
if (is_attack === true) {
score = 85; // HIGH threat
} else {
score = Math.round((risk_score || 0.01) * 100);
}
threat_level = is_attack ? 'HIGH' : (score >= 40 ? 'MEDIUM' : 'LOW');Critical Signals:
llm_attack: Set totruewhenis_attack === true- Triggers
LLM_GUARD_HIGH_CONFIDENCEboost if confidence > 0.9 - Triggers
CONSERVATIVE_OVERRIDEboost if weighted score < 50
- Triggers
Output Example:
{
"branch_id": "C",
"name": "llm_guard",
"score": 85,
"threat_level": "HIGH",
"confidence": 0.93,
"critical_signals": {
"llm_attack": true
},
"features": {
"is_attack": true,
"risk_score": 0.85,
"verdict": "Attack detected"
},
"explanations": ["Attack detected"],
"timing_ms": 2340,
"degraded": false
}When a branch fails (timeout, service unavailable, error), a degraded result is returned:
{
"branch_id": "A",
"name": "heuristics",
"score": 0,
"threat_level": "LOW",
"confidence": 0,
"critical_signals": {},
"features": {
"degraded_reason": "Service unavailable"
},
"explanations": ["heuristics degraded: Service unavailable"],
"timing_ms": 1020,
"degraded": true
}Weight Adjustment:
Degraded branches have their weights multiplied by degradation.weight_multiplier (default: 0.1), then all weights are normalized to sum to 1.0.
Fail-Secure Policy:
If all 3 branches are degraded, the Arbiter returns:
{
"combined_score": 100,
"final_decision": "BLOCK",
"all_degraded": true,
"explanations": ["All branches degraded - fail-secure BLOCK"]
}The Arbiter v2 aggregates results from all 3 branches using weighted voting and applies priority boost policies to handle edge cases where weighted scores may be misleading.
{
"arbiter_config": {
"weights": {
"heuristics": 0.30,
"semantic": 0.40,
"llm_guard": 0.30
},
"thresholds": {
"block_min": 50
},
"boosts": {
"llm_high_confidence_enabled": true,
"llm_high_confidence_threshold": 0.9,
"llm_high_confidence_min_score": 85,
"semantic_high_similarity_enabled": true,
"semantic_high_similarity_min_score": 70,
"heuristics_critical_enabled": true,
"heuristics_critical_score_threshold": 75,
"heuristics_critical_min_score": 70,
"unanimous_high_enabled": true,
"unanimous_high_min_score": 90,
"conservative_override_enabled": true,
"conservative_override_confidence": 0.95,
"conservative_override_score": 50,
"conservative_override_min_score": 65
},
"degradation": {
"weight_multiplier": 0.1,
"all_degraded_action": "BLOCK"
}
}
}Step 1: Adjust weights for degraded branches (v2.1.0 defaults)
const weights = { A: 0.30, B: 0.40, C: 0.30 }; // v2.1.0: S increased to 0.40
if (branchA.degraded) weights.A *= 0.1; // 0.03
if (branchB.degraded) weights.B *= 0.1; // 0.04
// weights.C unchanged (0.30)
// Normalize to sum to 1.0
const totalWeight = 0.03 + 0.04 + 0.30 = 0.37;
weights.A = 0.03 / 0.37 = 0.081;
weights.B = 0.04 / 0.37 = 0.108;
weights.C = 0.30 / 0.37 = 0.811;Step 2: Calculate weighted score
combinedScore = (branchA.score * weights.A) +
(branchB.score * weights.B) +
(branchC.score * weights.C);Example (v2.1.0 weights):
- Branch A: score=65, degraded=false, weight=0.30
- Branch B: score=42, degraded=false, weight=0.40
- Branch C: score=78, degraded=false, weight=0.30
combinedScore = (65 * 0.30) + (42 * 0.40) + (78 * 0.30)
= 19.5 + 16.8 + 23.4
= 59.7
Boosts are applied after weighted score calculation to handle cases where the weighted score underestimates threat severity.
Boost Execution Order:
- CONSERVATIVE_OVERRIDE
- SEMANTIC_HIGH_SIMILARITY
- HEURISTICS_CRITICAL
- LLM_GUARD_HIGH_CONFIDENCE
- UNANIMOUS_HIGH
Trigger Conditions:
branchC.critical_signals.llm_attack === truebranchC.confidence > 0.95combinedScore < 50
Action:
combinedScore = Math.max(combinedScore, 65);Rationale:
If the LLM Safety Engine detects an attack with very high confidence, but the weighted score is low (possibly due to low heuristic/semantic scores), boost to at least 65 to ensure BLOCK decision.
Example (v2.1.0):
- Branch A: score=20 (benign-looking structure)
- Branch B: score=18 (no semantic match)
- Branch C: score=85, confidence=0.97, llm_attack=true
Weighted: (20*0.3) + (18*0.40) + (85*0.30) = 38.7
Boost applied: 38.7 → 65
Trigger Conditions:
branchB.threat_level === 'HIGH'branchB.critical_signals.high_similarity === true
Action:
combinedScore = Math.max(combinedScore, 70);Rationale:
High embedding similarity to known attacks indicates a strong match to the attack database.
Trigger Conditions:
branchA.score >= 75branchA.critical_signals.obfuscation_detected === true
Action:
combinedScore = Math.max(combinedScore, 70);Rationale:
Heavy obfuscation with high heuristic score is a strong indicator of evasion techniques.
Trigger Conditions:
branchC.threat_level === 'HIGH'branchC.critical_signals.llm_attack === truebranchC.confidence > 0.9
Action:
combinedScore = Math.max(combinedScore, 85);Rationale:
Very high confidence attack detection from the LLM Safety Engine.
Trigger Conditions:
branchA.threat_level === 'HIGH'branchB.threat_level === 'HIGH'branchC.threat_level === 'HIGH'
Action:
combinedScore = Math.max(combinedScore, 90);Rationale:
All 3 independent branches agree on HIGH threat → very strong consensus.
const finalScore = Math.round(combinedScore);
const finalDecision = finalScore >= 50 ? 'BLOCK' : 'ALLOW';Binary Decisions:
finalScore >= 50→ BLOCKfinalScore < 50→ ALLOW
{
"arbiter_result": {
"combined_score": 73,
"final_decision": "BLOCK",
"confidence": 0.817,
"all_degraded": false,
"branches": {
"A": {
"score": 65,
"threat_level": "MEDIUM",
"weight": 0.300,
"degraded": false,
"critical_signals": {}
},
"B": {
"score": 72,
"threat_level": "HIGH",
"weight": 0.400,
"degraded": false,
"critical_signals": { "high_similarity": true }
},
"C": {
"score": 85,
"threat_level": "HIGH",
"weight": 0.300,
"degraded": false,
"critical_signals": { "llm_attack": true }
}
},
"boosts_applied": ["SEMANTIC_HIGH_SIMILARITY"],
"explanations": [
"High obfuscation detected",
"High similarity to known prompt injection attacks",
"Attack detected"
]
},
"_isBlocked": true
}Critical Change in v2.1.0:
PII redaction is applied ONLY for ALLOW decisions, AFTER the Arbiter decision.
Flow:
- Arbiter returns
final_decision: "ALLOW" Arbiter Decisionroutes toPII_Redactor_v2- Dual-language Presidio analysis (Polish + English in parallel)
- Entity deduplication and redaction
- Output flagged with
_pii_sanitized: true
Services Used:
- Presidio PII API (
:5001) - Polish entities - Presidio PII API (
:5001) - English entities - Language Detector (
:5002) - Hybrid detection
Supported Entities:
EMAIL_ADDRESS,PHONE_NUMBER,PERSONPL_PESEL,PL_NIP,PL_REGON,PL_ID_CARDCREDIT_CARD,IBAN_CODEIP_ADDRESS,URL,DATE_TIME,LOCATION- 50+ total entity types
Redaction Tokens:
EMAIL: [EMAIL USUNIĘTY]
PHONE: [TELEFON USUNIĘTY]
PERSON: [NAZWISKO USUNIĘTE]
PL_PESEL: [PESEL USUNIĘTY]
CREDIT_CARD: [KARTA USUNIĘTA]
Audit Flags:
{
"_pii_sanitized": true,
"pii_classification": {
"types": ["EMAIL_ADDRESS", "PERSON"],
"count": 3,
"method": "presidio",
"detected_language": "pl"
}
}Final Status Values:
ALLOWED- No PII detected, arbiter decision = ALLOWSANITIZED- PII detected and redacted, arbiter decision = ALLOWBLOCKED- Arbiter decision = BLOCK
The Build NDJSON v2 node prepares data for the events_v2 table:
CREATE TABLE events_v2 (
-- Identifiers
sessionId String,
action String,
timestamp DateTime64(3),
-- Text data
original_input String,
chat_input String,
result String,
detected_language String,
-- Scores
threat_score UInt8,
branch_a_score UInt8,
branch_b_score UInt8,
branch_c_score UInt8,
-- Decision
final_status Enum8('ALLOWED' = 1, 'SANITIZED' = 2, 'BLOCKED' = 3),
final_decision Enum8('ALLOW' = 1, 'BLOCK' = 2, 'UNKNOWN' = 3),
boosts_applied Array(String),
confidence Float32,
-- PII
pii_sanitized UInt8,
pii_types_detected Array(String),
pii_entities_count UInt16,
-- Client metadata
client_id String,
browser_name String,
browser_version String,
os_name String,
-- Pipeline metadata
pipeline_version String,
config_version String,
-- JSON fields for detailed analysis
arbiter_json String,
branch_results_json String,
pii_classification_json String
) ENGINE = MergeTree()
ORDER BY (timestamp, sessionId)
PARTITION BY toYYYYMM(timestamp)
TTL timestamp + INTERVAL 90 DAY;Key Columns:
threat_score: Arbiter's combined score (0-100)branch_a_score,branch_b_score,branch_c_score: Individual branch scoresfinal_status: ALLOWED, SANITIZED, or BLOCKEDboosts_applied: Array of boost policy namespii_sanitized: 0 (no PII) or 1 (PII redacted)arbiter_json: Full Arbiter result for investigation
┌────────────────────────────────────────────────────────────────┐
│ vigil-network (bridge) │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Caddy │ │ n8n │ │ ClickHouse │ │
│ │ :80 │ │ :5678 │ │ :8123, :9000│ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌──────▼───────┐ ┌──────▼───────┐ ┌─────▼──────┐ │
│ │ Web UI │ │ Heuristics │ │ Grafana │ │
│ │ Frontend │ │ Service │ │ :3001 │ │
│ │ :5173→:80 │ │ :5005 │ └────────────┘ │
│ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Web UI │ │ Semantic │ │ Prompt Guard│ │
│ │ Backend │ │ Service │ │ API │ │
│ │ :8787 │ │ :5006 │ │ :8000 │ │
│ └──────────────┘ └──────────────┘ └─────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Presidio PII │ │ Language │ │
│ │ :5001 │ │ Detector │ │
│ └──────────────┘ │ :5002 │ │
│ └──────────────┘ │
└────────────────────────────────────────────────────────────────┘
| Service | Internal Port | External Port | Purpose |
|---|---|---|---|
| Caddy | 80 | 80 | Reverse proxy (main entry point) |
| n8n | 5678 | 5678 | Workflow engine |
| ClickHouse HTTP | 8123 | 8123 | Analytics database |
| ClickHouse Native | 9000 | - | Native protocol (internal only) |
| Grafana | 3000 | 3001 | Monitoring dashboards |
| Web UI Frontend | 80 | 5173 | React SPA (dev mode) |
| Web UI Backend | 8787 | 8787 | Express API |
| Heuristics Service | 5005 | 5005 | Branch A detection |
| Semantic Service | 5006 | 5006 | Branch B detection |
| Prompt Guard API | 8000 | 8000 | Branch C detection |
| Presidio PII | 5001 | 5001 | PII detection |
| Language Detector | 5002 | 5002 | Language identification |
n8n
├── Heuristics Service (:5005)
├── Semantic Service (:5006)
├── Prompt Guard API (:8000)
├── Presidio PII (:5001)
├── Language Detector (:5002)
└── ClickHouse (:8123)
Web UI Backend
├── ClickHouse (:8123)
└── Presidio PII (:5001)
Grafana
└── ClickHouse (:8123)
Location: services/workflow/config/unified_config.json
Key Sections:
{
"version": "2.1.0",
"arbiter_config": {
"weights": {
"heuristics": 0.30,
"semantic": 0.40,
"llm_guard": 0.30
},
"thresholds": {
"block_min": 50
},
"boosts": { ... },
"degradation": {
"weight_multiplier": 0.1,
"all_degraded_action": "BLOCK"
}
},
"endpoints": {
"heuristics": "http://heuristics-service:5005/analyze",
"semantic": "http://semantic-service:5006/analyze",
"llm_guard": "http://prompt-guard-api:8000/detect",
"presidio_pii": "http://presidio-pii-api:5001/analyze",
"language_detector": "http://language-detector:5002/detect"
},
"timeouts": {
"branch_a": 1000,
"branch_b": 2000,
"branch_c": 3000
}
}Location: services/workflow/config/pii.conf
Contains:
- Regex patterns for PII fallback detection (13 patterns)
- Entity type enable/disable flags
- Redaction token templates
Location: services/workflow/config/allowlist.schema.json
JSON Schema for allowlisted input patterns (early ALLOW bypass).
| Component | Target | Typical |
|---|---|---|
| 3-Branch Executor (parallel) | <3s | 1-2s |
| Branch A (Heuristics) | <1s | 300-500ms |
| Branch B (Semantic) | <2s | 1-1.5s |
| Branch C (LLM Safety Engine) | <3s | 2-2.5s |
| Arbiter | <100ms | 20-50ms |
| PII Redaction | <500ms | 200-350ms |
| Total Pipeline | <5s | 2-4s |
- Sequential Processing: 1 request/workflow instance
- Horizontal Scaling: Deploy multiple n8n workers
- Branch Services: Independently scalable
| Service | CPU | Memory |
|---|---|---|
| n8n | 0.5-1.0 core | 512MB-1GB |
| Heuristics Service | 0.2-0.5 core | 256MB |
| Semantic Service | 1-2 cores | 2-4GB (embedding models) |
| Prompt Guard API | 2-4 cores | 4-8GB (Llama Guard 2) |
| ClickHouse | 1-2 cores | 2-4GB |
| Total | 5-10 cores | 10-20GB |
-
Input Validation (Early Block)
- Schema validation
- Malformed JSON rejection
- Length limits
-
3-Branch Detection (Parallel)
- Heuristics: Obfuscation, structure, entropy
- Semantic: Embedding similarity
- LLM Safety Engine: ML-based classification
-
Arbiter Aggregation (Weighted + Boosts)
- Fail-secure degradation
- Priority boost policies
-
PII Redaction (ALLOW only)
- Dual-language detection
- 50+ entity types
- All branches degraded → BLOCK
- Service unavailable → Degraded result (low weight)
- Timeout exceeded → Degraded result
- Configuration error → Block by default
- Web UI: JWT tokens (24h expiration)
- n8n Webhooks: Optional API key authentication
- ClickHouse: Password-protected (auto-generated)
- Grafana: Admin password (auto-generated)
sequenceDiagram
participant Client
participant n8n
participant BranchA as Heuristics (A)
participant BranchB as Semantic (B)
participant BranchC as LLM Safety Engine (C)
participant Arbiter
participant Presidio
participant ClickHouse
Client->>n8n: POST webhook (text)
n8n->>n8n: Load config + validate input
par 3-Branch Parallel Execution
n8n->>BranchA: analyze (text)
BranchA-->>n8n: {score: 65, threat: MEDIUM}
n8n->>BranchB: analyze (text)
BranchB-->>n8n: {score: 72, threat: HIGH}
n8n->>BranchC: detect (text)
BranchC-->>n8n: {score: 48, threat: MEDIUM}
end
n8n->>Arbiter: Aggregate results
Arbiter-->>n8n: {score: 61, decision: BLOCK}
alt ALLOW Decision
n8n->>Presidio: Analyze PII (text)
Presidio-->>n8n: {entities: [...]}
n8n->>n8n: Redact PII
else BLOCK Decision
n8n->>n8n: Generate block message
end
n8n->>ClickHouse: Log to events_v2
ClickHouse-->>n8n: OK
n8n-->>Client: {result, status, score}
stateDiagram-v2
[*] --> InputValidation
InputValidation --> EarlyBlock : Invalid
InputValidation --> 3BranchExecutor : Valid
3BranchExecutor --> BranchA
3BranchExecutor --> BranchB
3BranchExecutor --> BranchC
BranchA --> Arbiter
BranchB --> Arbiter
BranchC --> Arbiter
Arbiter --> DecisionRouter
DecisionRouter --> PIIRedaction : ALLOW
DecisionRouter --> BlockResponse : BLOCK
PIIRedaction --> BuildNDJSON : PII detected
PIIRedaction --> BuildNDJSON : No PII
BlockResponse --> BuildNDJSON
EarlyBlock --> BuildNDJSON
BuildNDJSON --> ClickHouseLog
ClickHouseLog --> CleanOutput
CleanOutput --> [*]
- n8n Documentation: https://docs.n8n.io/
- Microsoft Presidio: https://microsoft.github.io/presidio/
- ClickHouse: https://clickhouse.com/docs/
- Llama Guard 2: https://huggingface.co/meta-llama/Llama-Guard-2-8B
- OWASP LLM Top 10: https://owasp.org/www-project-top-10-for-large-language-model-applications/
Document Version: 2.1.0 Last Reviewed: 2025-12-14 Maintainer: Vigil Guard Team