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 커뮤니티가 만든 AI 스킬을 발견하고 탐색하세요.
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.