10-layer defense pipeline with kill chain mapping, self-healing, self-learning, and compliance reporting. Local-first, zero cloud deps. - 72 detection rules across 7 kill chain phases - 294 unit tests, 500+ attack corpus samples - Management dashboard (Next.js 15, 10 pages) - Automated resistance testing (2x daily, 31 probes) - MITRE ATLAS, OWASP LLM Top 10, EU AI Act compliance - Integrations: Next.js middleware, Ollama, n8n - PostgreSQL 17 + pgvector for persistent learning
137 lines
4.2 KiB
JavaScript
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]
|
|
},
|
|
}
|