Skip to main content 首页 创作者 proffesor-for-testing agentic-qe n8n-security-testing
n8n-security-testing Credential exposure detection, OAuth flow validation, API key management testing, and data sanitization verification for n8n workflows. Use when validating n8n workflow security.
跳到安装 Skills Marketplace 发现并探索由社区构建的 Agent Skills
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/proffesor-for-testing/agentic-qe --skill n8n-security-testing命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
下载 Zip 下载中... Ruflo is a multi-agent orchestration platform for AI coding agents (Claude Code, Cursor, Codex, Copilot, Gemini, Amp, +12 more). Use this skill when the user wants to (1) install/init ruflo in a project, (2) run multi-agent swarms with hierarchical coordination, (3) use ruflo's 314+ MCP tools for memory, routing, hooks, sub-agents, or workflows, (4) check ruflo status/version/doctor health, or (5) discover which of ruflo's 30+ plugins fits their task.
name n8n-security-testing description Credential exposure detection, OAuth flow validation, API key management testing, and data sanitization verification for n8n workflows. Use when validating n8n workflow security. category n8n-testing priority critical tokenEstimate 1100 agents ["n8n-integration-test"] implementation_status production optimization_version 1 last_optimized "2025-12-15T00:00:00.000Z" dependencies [] quick_reference_card true tags ["n8n","security","credentials","oauth","api-keys","encryption","testing"] trust_tier 3 validation {"schema_path":"schemas/output.json","validator_path":"scripts/validate-config.json","eval_path":"evals/n8n-security-testing.yaml"}
n8n Security Testing
<default_to_action>
When testing n8n security:
SCAN for credential exposure in workflows
VERIFY encryption of sensitive data
TEST OAuth token handling
CHECK for insecure data transmission
VALIDATE input sanitization
Quick Security Checklist:
No credentials in workflow JSON
No credentials in execution logs
OAuth tokens properly encrypted
API keys not in version control
Webhook authentication enabled
Input data sanitized
Critical Success Factors:
Scan all workflow exports
Test credential rotation
Verify encryption at rest
Check audit logging
</default_to_action>
Quick Reference Card
Security Risk Areas
Area Risk Level Testing Focus Credential Storage Critical Encryption, exposure Webhook Security High Authentication, validation Expression Injection High Input sanitization Data Leakage Medium Logging, error messages OAuth Flows Medium Token handling, refresh
Credential Types
Type Exposure Risk Rotation API Keys High if exposed Manual OAuth Tokens Medium (short-lived) Automatic Passwords Critical Manual Webhooks Medium Generate new
Credential Security Testing
Scan for Exposed Credentials
async ( ): < > {
workflow = (workflowId);
workflowJson = . (workflow, , );
sensitivePatterns = [
{ : , : },
{ : , : },
{ : , : },
{ : , : },
{ : , : },
{ : , : },
{ : , : },
{ : , : },
{ : , : },
{ : , : }
];
: [] = [];
( pattern sensitivePatterns) {
matches = workflowJson. (pattern. );
(matches) {
( match matches) {
findings. ({
: pattern. ,
: (workflow, match),
: ,
:
});
}
}
}
{
workflowId,
: ,
: findings. ,
findings,
: findings. ===
};
}
function
scanForExposedCredentials
workflowId : string
Promise
CredentialScanResult
const
await
getWorkflow
const
JSON
stringify
null
2
const
name
'Generic API Key'
pattern
/api[_-]?key["\s:=]+["']?([a-zA-Z0-9_-]{20,})["']?/gi
name
'AWS Access Key'
pattern
/AKIA[0-9A-Z]{16}/g
name
'AWS Secret Key'
pattern
/[a-zA-Z0-9/+=]{40}/g
name
'Bearer Token'
pattern
/bearer\s+[a-zA-Z0-9_-]{20,}/gi
name
'JWT Token'
pattern
/eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g
name
'Slack Token'
pattern
/xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}/g
name
'Password Field'
pattern
/"password":\s*"[^"]+"/gi
name
'Secret Field'
pattern
/"secret":\s*"[^"]+"/gi
name
'Client Secret'
pattern
/client[_-]?secret["\s:=]+["']?([a-zA-Z0-9_-]{20,})["']?/gi
name
'Refresh Token'
pattern
/refresh[_-]?token["\s:=]+["']?([a-zA-Z0-9_-]{20,})["']?/gi
const
findings
CredentialFinding
for
const
of
const
match
pattern
if
for
const
of
push
type
name
location
findLocationInWorkflow
severity
'CRITICAL'
recommendation
`Remove ${pattern.name} from workflow. Use n8n credentials instead.`
return
scanned
true
findingsCount
length
secure
length
0
Verify Credential Encryption
async function verifyCredentialEncryption (credentialId : string ): Promise <EncryptionResult > {
const credential = await getCredentialMetadata (credentialId);
const encryptionChecks = {
isEncrypted : !isPlainText (credential.data ),
algorithm : credential.encryptionAlgorithm || 'unknown' ,
keyDerivation : credential.keyDerivation || 'unknown' ,
instanceEncryption : credential.useInstanceKey || false
};
return {
credentialId,
credentialName : credential.name ,
credentialType : credential.type ,
encryption : encryptionChecks,
secure : encryptionChecks.isEncrypted && encryptionChecks.algorithm !== 'unknown' ,
recommendations : generateEncryptionRecommendations (encryptionChecks)
};
}
function isPlainText (data : string ): boolean {
const plainTextPatterns = [
/^[a-zA-Z0-9_-]+$/ ,
/^sk-[a-zA-Z0-9]+$/ ,
/^Bearer\s/ ,
];
return plainTextPatterns.some (p => p.test (data));
}
Test Credential Rotation
async function testCredentialRotation (credentialId : string ): Promise <RotationTestResult > {
const credential = await getCredentialMetadata (credentialId);
const rotationTests = {
hasRotationSchedule : !!credential.rotationSchedule ,
lastRotated : credential.lastRotatedAt ,
rotationDue : isRotationDue (credential),
oauthRefresh : credential.type .includes ('oauth' )
? await testOAuthRefresh (credentialId)
: null ,
credentialAge : calculateAge (credential.createdAt ),
isStale : calculateAge (credential.createdAt ) > 90
};
return {
credentialId,
rotationTests,
recommendations : generateRotationRecommendations (rotationTests)
};
}
async function testOAuthRefresh (credentialId : string ): Promise <OAuthRefreshResult > {
try {
const refreshed = await refreshCredential (credentialId);
return {
success : true ,
newExpiry : refreshed.expiresAt ,
refreshedAt : new Date ()
};
} catch (error) {
return {
success : false ,
error : error.message ,
recommendation : 'Re-authorize OAuth connection'
};
}
}
Webhook Security Testing
Authentication Testing
async function testWebhookAuthentication (webhookUrl : string ): Promise <WebhookAuthResult > {
const authTests = [
{
name : 'No Auth' ,
headers : {},
expectedStatus : 401
},
{
name : 'Invalid Basic Auth' ,
headers : { 'Authorization' : 'Basic aW52YWxpZDppbnZhbGlk' },
expectedStatus : 401
},
{
name : 'Invalid Bearer' ,
headers : { 'Authorization' : 'Bearer invalid-token-12345' },
expectedStatus : 401
},
{
name : 'Invalid Header Auth' ,
headers : { 'X-API-Key' : 'invalid-key' },
expectedStatus : 401
}
];
const results : AuthTestResult [] = [];
for (const test of authTests) {
const response = await fetch (webhookUrl, {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
...test.headers
},
body : '{}'
});
results.push ({
test : test.name ,
status : response.status ,
passed : response.status === test.expectedStatus ,
actualStatus : response.status ,
expectedStatus : test.expectedStatus
});
}
const noAuthResponse = results.find (r => r.test === 'No Auth' );
const webhookHasAuth = noAuthResponse?.status === 401 ;
return {
webhookUrl,
hasAuthentication : webhookHasAuth,
testResults : results,
allTestsPassed : results.every (r => r.passed ),
recommendation : !webhookHasAuth
? 'CRITICAL: Enable authentication on webhook'
: null
};
}
Input Validation Testing
async function testWebhookInputValidation (webhookUrl : string ): Promise <InputValidationResult > {
const maliciousPayloads = [
{
name : 'XSS Script Tag' ,
payload : { text : '<script>alert("xss")</script>' },
check : 'sanitized'
},
{
name : 'XSS Event Handler' ,
payload : { text : '<img onerror="alert(1)" src="x">' },
check : 'sanitized'
},
{
name : 'SQL Injection' ,
payload : { id : "1; DROP TABLE users; --" },
check : 'escaped'
},
{
name : 'Command Injection' ,
payload : { filename : '; rm -rf /' },
check : 'rejected'
},
{
name : 'Path Traversal' ,
payload : { path : '../../../etc/passwd' },
check : 'rejected'
},
{
name : 'JSON Injection' ,
payload : { data : '{"admin": true}' },
check : 'escaped'
},
{
name : 'Oversized Payload' ,
payload : { data : 'x' .repeat (10000000 ) },
check : 'rejected'
}
];
const results : ValidationTestResult [] = [];
for (const test of maliciousPayloads) {
try {
const response = await fetch (webhookUrl, {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' },
body : JSON .stringify (test.payload )
});
const responseBody = await response.text ();
results.push ({
test : test.name ,
status : response.status ,
handled : response.status !== 500 ,
sanitized : !responseBody.includes (test.payload .text || test.payload .data ),
recommendation : response.status === 500
? `Input not handled safely: ${test.name} `
: null
});
} catch (error) {
results.push ({
test : test.name ,
handled : false ,
error : error.message
});
}
}
return {
webhookUrl,
testsRun : maliciousPayloads.length ,
passed : results.filter (r => r.handled ).length ,
failed : results.filter (r => !r.handled ).length ,
results,
secure : results.every (r => r.handled )
};
}
Expression Security Testing
Detect Dangerous Expressions
async function scanExpressionsForSecurity (workflowId : string ): Promise <ExpressionSecurityResult > {
const workflow = await getWorkflow (workflowId);
const expressions = extractExpressions (workflow);
const dangerousPatterns = [
{ name : 'eval()' , pattern : /eval\s*\(/g , severity : 'CRITICAL' },
{ name : 'Function()' , pattern : /new\s+Function\s*\(/g , severity : 'CRITICAL' },
{ name : 'setTimeout string' , pattern : /setTimeout\s*\(\s*["'`]/g , severity : 'HIGH' },
{ name : 'setInterval string' , pattern : /setInterval\s*\(\s*["'`]/g , severity : 'HIGH' },
{ name : 'require()' , pattern : /require\s*\(/g , severity : 'HIGH' },
{ name : 'import()' , pattern : /import\s*\(/g , severity : 'HIGH' },
{ name : 'fs access' , pattern : /\bfs\./g , severity : 'HIGH' },
{ name : 'child_process' , pattern : /child_process/g , severity : 'CRITICAL' },
{ name : 'process.' , pattern : /process\./g , severity : 'MEDIUM' },
{ name : 'exec()' , pattern : /exec\s*\(/g , severity : 'CRITICAL' },
{ name : 'spawn()' , pattern : /spawn\s*\(/g , severity : 'CRITICAL' },
{ name : 'fetch()' , pattern : /fetch\s*\(/g , severity : 'MEDIUM' },
{ name : 'XMLHttpRequest' , pattern : /XMLHttpRequest/g , severity : 'MEDIUM' },
{ name : '__proto__' , pattern : /__proto__/g , severity : 'HIGH' },
{ name : 'constructor.prototype' , pattern : /constructor\.prototype/g , severity : 'HIGH' }
];
const findings : SecurityFinding [] = [];
for (const expr of expressions) {
for (const pattern of dangerousPatterns) {
if (pattern.pattern .test (expr.expression )) {
findings.push ({
node : expr.nodeName ,
parameter : expr.parameter ,
expression : expr.expression ,
pattern : pattern.name ,
severity : pattern.severity ,
recommendation : `Remove ${pattern.name} from expression. Use safer alternatives.`
});
}
}
}
return {
workflowId,
expressionsScanned : expressions.length ,
findings,
secure : findings.length === 0 ,
criticalIssues : findings.filter (f => f.severity === 'CRITICAL' ).length ,
highIssues : findings.filter (f => f.severity === 'HIGH' ).length
};
}
Data Leakage Testing
Scan Execution Logs
async function scanExecutionLogs (workflowId : string , executionCount : number = 10 ): Promise <LogScanResult > {
const executions = await getRecentExecutions (workflowId, executionCount);
const findings : LogFinding [] = [];
const sensitivePatterns = [
{ name : 'Password' , pattern : /password["\s:=]+["']?[^"'\s]+["']?/gi },
{ name : 'API Key' , pattern : /api[_-]?key["\s:=]+["']?[^"'\s]{20,}["']?/gi },
{ name : 'Token' , pattern : /token["\s:=]+["']?[a-zA-Z0-9_-]{20,}["']?/gi },
{ name : 'Secret' , pattern : /secret["\s:=]+["']?[^"'\s]+["']?/gi },
{ name : 'Authorization Header' , pattern : /authorization["\s:]+["']?(bearer|basic)\s+[^"'\s]+["']?/gi }
];
for (const execution of executions) {
const logString = JSON .stringify (execution.data , null , 2 );
for (const pattern of sensitivePatterns) {
const matches = logString.match (pattern.pattern );
if (matches) {
findings.push ({
executionId : execution.id ,
type : pattern.name ,
matchCount : matches.length ,
severity : 'HIGH' ,
recommendation : `Mask ${pattern.name} in logs`
});
}
}
}
return {
workflowId,
executionsScanned : executions.length ,
findings,
secure : findings.length === 0 ,
recommendation : findings.length > 0
? 'Enable credential masking in n8n settings'
: null
};
}
Check Error Message Exposure
async function checkErrorMessageSecurity (workflowId : string ): Promise <ErrorMessageResult > {
const errorScenarios = [
{ name : 'Invalid credentials' , inject : { credentials : null } },
{ name : 'Invalid endpoint' , inject : { url : 'https://invalid' } },
{ name : 'Database error' , inject : { query : 'INVALID SQL' } }
];
const findings : ErrorFinding [] = [];
for (const scenario of errorScenarios) {
try {
await executeWithError (workflowId, scenario.inject );
} catch (error) {
const errorMessage = error.message ;
const sensitiveData = [
{ name : 'Connection string' , pattern : /mongodb:\/\/[^@]+@/i },
{ name : 'Password in URL' , pattern : /:\/\/[^:]+:[^@]+@/i },
{ name : 'Full file path' , pattern : /\/(?:home|Users|var)\/[^\s]+/i },
{ name : 'Stack trace' , pattern : /at\s+\w+\s+\([^)]+\)/i },
{ name : 'Internal IP' , pattern : /\b(?:10|172\.(?:1[6-9]|2[0-9]|3[01])|192\.168)\.\d+\.\d+\b/i }
];
for (const check of sensitiveData) {
if (check.pattern .test (errorMessage)) {
findings.push ({
scenario : scenario.name ,
exposedData : check.name ,
severity : 'MEDIUM' ,
recommendation : `Sanitize ${check.name} from error messages`
});
}
}
}
}
return {
workflowId,
scenariosTested : errorScenarios.length ,
findings,
secure : findings.length === 0
};
}
Security Report Template # n8n Security Audit Report
## Summary
| Category | Status | Findings |
|----------|--------|----------|
| Credential Security | PASS/FAIL | X issues |
| Webhook Security | PASS/FAIL | X issues |
| Expression Security | PASS/FAIL | X issues |
| Data Leakage | PASS/FAIL | X issues |
## Critical Findings
### CRIT-001: API Key Exposed in Workflow
- **Location:** HTTP Request node, URL parameter
- **Impact:** Credential theft, unauthorized access
- **Fix:** Move to n8n credentials store
### CRIT-002: eval() in Expression
- **Location:** Set node, custom field
- **Impact:** Remote code execution
- **Fix:** Remove eval, use explicit logic
## Recommendations
1. **Enable webhook authentication** - All public webhooks
2. **Rotate exposed credentials** - Immediately
3. **Enable log masking** - For all credentials
4. **Regular security scans** - Weekly automated scans
## Compliance Status
- OWASP Top 10: X/10 addressed
- SOC 2: Partially compliant
- GDPR: Review data handling
Related Skills
Remember n8n handles sensitive credentials for 400+ integrations. Security testing requires:
Credential exposure scanning
Encryption verification
Webhook authentication testing
Expression security analysis
Data leakage detection
Critical practices: Never expose credentials in workflow JSON. Enable webhook authentication. Mask sensitive data in logs. Rotate credentials regularly. Scan expressions for dangerous functions.