shieldx/integrations/n8n-shieldx-node.js
Rene Fichtmueller 1c4c034483 feat: ShieldX v0.3.0 — UnicodeScanner (L5), DNS Covert Channel rules, ATLAS v5.4 mappings
- Layer 4 EntropyScanner: Shannon entropy, Base32/Base64 detection, CVE-2025-55284
  ping/nslookup exfil, EchoLeak markdown pattern, DNS tunneling (iodine/dnscat)
- Layer 5 UnicodeScanner: ASCII Smuggling (U+E0000 Tags Block), Variant Selectors,
  Zero-Width steganography, CamoLeak image-ordering (CVE-2025-53773), homoglyphs,
  BiDi override, high-entropy URL params
- 30 DNS covert channel rules (dns-001 to dns-030)
- ATLASMapper: 29 techniques (ATLAS v5.4.0 Feb 2026), added AML.T0062 (Agent Tool
  Invocation), AML.TA0015 (C2 tactic), memory poisoning, multi-agent trust,
  CamoLeak, Unicode steganography mappings
- Rule count: 72 → 102
- Build: tsup 316ms, zero TypeScript errors
2026-03-31 16:32:16 +02:00

137 lines
4.2 KiB
JavaScript

/**
* ShieldX n8n Community Node
*
* Scans prompts through the ShieldX proxy before sending to LLMs.
* Add this as a custom node in n8n.
*
* Setup:
* 1. Copy to ~/.n8n/custom/nodes/ShieldX.node.js
* 2. Set SHIELDX_PROXY_URL in n8n environment (default: http://localhost:11435)
* 3. Add ShieldX node BEFORE any AI/LLM node in your workflow
*
* The node scans input text and either passes it through (clean)
* or blocks it (threat detected), based on the configured action.
*/
module.exports = {
description: {
displayName: 'ShieldX',
name: 'shieldX',
group: ['transform'],
version: 1,
description: 'Scan prompts for injection attacks before sending to LLMs',
defaults: { name: 'ShieldX' },
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Input Field',
name: 'inputField',
type: 'string',
default: 'text',
description: 'The field name containing the text to scan',
},
{
displayName: 'Proxy URL',
name: 'proxyUrl',
type: 'string',
default: 'http://localhost:11435',
description: 'ShieldX proxy URL',
},
{
displayName: 'On Threat',
name: 'onThreat',
type: 'options',
options: [
{ name: 'Block (stop workflow)', value: 'block' },
{ name: 'Warn (add flag, continue)', value: 'warn' },
{ name: 'Log Only', value: 'log' },
],
default: 'block',
description: 'Action when threat is detected',
},
{
displayName: 'Min Threat Level',
name: 'minThreatLevel',
type: 'options',
options: [
{ name: 'Low', value: 'low' },
{ name: 'Medium', value: 'medium' },
{ name: 'High', value: 'high' },
{ name: 'Critical', value: 'critical' },
],
default: 'medium',
description: 'Minimum threat level to trigger action',
},
],
},
async execute() {
const items = this.getInputData()
const inputField = this.getNodeParameter('inputField', 0)
const proxyUrl = this.getNodeParameter('proxyUrl', 0)
const onThreat = this.getNodeParameter('onThreat', 0)
const minLevel = this.getNodeParameter('minThreatLevel', 0)
const LEVEL_ORDER = ['none', 'low', 'medium', 'high', 'critical']
const minIdx = LEVEL_ORDER.indexOf(minLevel)
const results = []
for (let i = 0; i < items.length; i++) {
const item = items[i]
const input = item.json[inputField] || ''
if (!input) {
results.push(item)
continue
}
try {
const response = await this.helpers.httpRequest({
method: 'POST',
url: `${proxyUrl}/shieldx/scan`,
body: { input },
headers: { 'Content-Type': 'application/json' },
timeout: 5000,
})
const threatIdx = LEVEL_ORDER.indexOf(response.threatLevel || 'none')
const isAboveThreshold = threatIdx >= minIdx
// Add scan metadata to item
item.json.shieldx = {
scanned: true,
detected: response.detected || false,
threatLevel: response.threatLevel || 'none',
killChainPhase: response.killChainPhase || 'none',
action: response.action || 'allow',
matchedPatterns: response.matchedPatterns || [],
latencyMs: response.latencyMs || 0,
}
if (response.detected && isAboveThreshold) {
if (onThreat === 'block') {
throw new Error(
`ShieldX blocked: ${response.threatLevel} threat detected ` +
`(${response.killChainPhase}). Patterns: ${(response.matchedPatterns || []).join(', ')}`
)
}
// warn or log — continue with flag
item.json.shieldx.blocked = onThreat === 'block'
}
results.push(item)
} catch (err) {
if (err.message?.startsWith('ShieldX blocked')) throw err
// Proxy unavailable — fail open
console.warn('[ShieldX] Proxy unavailable, failing open:', err.message)
item.json.shieldx = { scanned: false, error: 'Proxy unavailable' }
results.push(item)
}
}
return [results]
},
}