Remediate security vulnerability with n8n and Port AI
Security teams often struggle to quickly understand the full context of vulnerabilities when they're detected. Raw vulnerability data from scanners like Snyk lacks critical operational context: which services are affected, who owns them, what environments are impacted, and what dependencies exist.
This guide demonstrates how to use Port as a context lake to enrich n8n workflows with comprehensive service metadata. When a vulnerability is detected, n8n queries Port's AI agent to retrieve contextual information about affected services, ownership, environments, and dependencies. This enriched context enables powerful workflows:
- Severity-based Jira ticket creation — Automatically create prioritized Jira tickets with full context.
- Targeted Slack notifications — Send vulnerability alerts directly to owning teams with full context.
- AI-powered remediation — Route critical vulnerabilities to coding agents with complete service context for automated fixes.
Common use cases
- Contextual vulnerability alerts — Enrich security notifications with service ownership, dependencies, and environment information.
- Automated ticket creation — Create Jira tickets with appropriate priority based on vulnerability severity.
- Automated remediation routing — Route vulnerabilities to appropriate teams or AI agents based on service context.
- Incident management — Provide operational context for security incidents, outages, or deployment events.
Prerequisites
Before you begin, ensure you have:
- Completed the onboarding process
- A Port account with AI agents feature enabled
- Security vulnerabilities synced into Port (e.g., via Snyk, Wiz, or SonarQube)
- You have completed the setup in the Set up Port's n8n custom node guide
- You have completed the setup in the Trigger Claude Code from Port guide (Optional - for auto-fix capabilities)
- Jira Cloud account with API access (for ticket creation)
- Slack workspace and developer token (for the notification workflow)
- OpenAI API key (for AI remediation workflow)
Set up the Port context retriever agent
The context retriever agent is an AI agent that uses Port's catalog as a knowledge base to provide contextual enrichment for any entity or event. It analyzes inputs (like vulnerability details) and returns structured information about affected services, owners, environments, scorecards, dependencies and more.
Create the context retriever agent
-
Go to the AI Agents page of your portal.
-
Click on
+ AI Agent. -
Toggle
Json modeon. -
Copy and paste the following JSON schema:
Context Retriever Agent (Click to expand)
{
"identifier": "context_retriever_agent",
"title": "Context Retriever Agent",
"icon": "Details",
"team": [],
"properties": {
"description": "AI agent that provides contextual enrichment for any entity or event using the Port catalog as a knowledge base",
"status": "active",
"prompt": "You are an assistant that provides **contextual enrichment** for any entity or event using the **Port catalog** as a knowledge base. Your goal is to retrieve relevant context that helps downstream systems, agents, or security teams understand how a given entity fits within the broader ecosystem.\n\nYou will receive an input describing an **entity or event**.\nAnalyze the input and use the Port catalog to return structured contextual information.\n\n**Example inputs include:**\n\n* A detected security vulnerability\n* An active incident or outage\n* A new deployment or release event\n* A system health anomaly\n\n---\n\n### **For each input, provide the following:**\n\n1. Affected service identifier\n2. Ownership or responsible team(s)\n3. Communication channels such as Slack channel name\n4. Related environments or deployment targets\n5. Dependencies or linked entities in the Port catalog (e.g., repositories, cloud resources, deployments, last X pull requests, or issues)\n6. Relevant scorecards the define and track engineering standards, KPIs, and quality metrics\n7. Any additional metadata or relationships that provide useful operational context\n\n---\n\n### **Output Format**\n\nReturn the result as **structured JSON** with the following keys:\n\n`identifier`, `title`, `related_services`, `owners`, `slack_channel`, `environments`, `dependencies`, `scorecards` and `notes`.\n\n**Example output:**\n\n```json\n{\n \"identifier\": \"INC-1023\",\n \"title\": \"Database connection timeout in payments service\",\n \"affected_service\": \"port-labs/payment-service\",\n \"owners\": [\"payments-team\"],\n \"slack_channel\": \"tech-general\",\n \"environments\": [\"production\"],\n \"dependencies\": [\"postgres-cluster-prod\", \"redis-cache\"],\n \"scorecards\": [\"production-readiness not met\", \"code maturity passed\"],\n \"notes\": \"The issue affects all payment operations in production. Linked to previous incident INC-1009.\"\n}\n```\n\nIf a field cannot be determined, return it as an empty string or an empty list.",
"execution_mode": "Automatic",
"tools": [
"^(list|search|track|describe)_.*"
]
},
"relations": {}
} -
Click
Createto save the agent.
Import the n8n workflow template
-
Open your n8n instance.
-
Copy the workflow JSON from the template below and paste it into a json file. You can name the file
remediate-vulnerability-with-n8n.json.n8n Workflow Template (Click to expand)
{
"name": "Remediate security vulnerabilities with Port",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "security/vulnerability",
"options": {}
},
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"position": [
-1696,
544
],
"typeVersion": 1,
"webhookDescription": "Triggered when a new vulnerability is detected"
},
{
"parameters": {
"operation": "invokeAgent",
"agentIdentifier": "context_retriever_agent",
"prompt": "=You are an assistant that provides contextual enrichment for vulnerabilities using the Port catalog as a knowledge base. A new security vulnerability has been detected.\nVulnerability Details:\n- Identifier: {{ $('Webhook Trigger').item.json.body.vulnerability_id }}\n- Description: {{ $('Webhook Trigger').item.json.body.description }}\n- Severity: {{ $('Webhook Trigger').item.json.body.severity }}\n- CVE: {{ $('Webhook Trigger').item.json.body.cve || 'N/A' }}\n- Package: {{ $('Webhook Trigger').item.json.body.package || 'N/A' }}\n\nUsing the Port catalog, return relevant contextual information. Specifically, retrieve:\n1. The affected service or repository\n2. Service owners and responsible team\n3. Environment (production/staging/dev)\n4. Service tier and SLA requirements\n5. Team's Slack channel\n6. Related dependencies\n7. Jira project key for security tickets\n\nYour response must be a single valid JSON object only, without any code block formatting, markdown, explanations, or extra text.\nDo not include ```json or ``` or any surrounding characters — just raw JSON.\n\nRequired output schema:\n{\n \"service_name\": \"\",\n \"repository\": \"\",\n \"service_tier\": \"production|staging|development\",\n \"sla_hours\": 0,\n \"owners\": [],\n \"slack_channel\": \"\",\n \"environment\": \"\",\n \"dependencies\": [],\n \"jira_project_key\": \"\"\n}"
},
"name": "Get Context From Port",
"type": "CUSTOM.portApiAi",
"position": [
-1456,
544
],
"typeVersion": 1,
"credentials": {
"portApi": {
"id": "",
"name": ""
}
}
},
{
"parameters": {
"resource": "chat",
"model": "gpt-4o-mini",
"prompt": {
"messages": [
{
"content": "=You are a security engineer. Analyze this vulnerability and create a remediation plan.\n\nVulnerability:\n- ID: {{ $('Webhook Trigger').item.json.body.vulnerability_id }}\n- Description: {{ $('Webhook Trigger').item.json.body.description }}\n- Severity: {{ $('Webhook Trigger').item.json.body.severity }}\n- CVE: {{ $('Webhook Trigger').item.json.body.cve || 'N/A' }}\n- Package: {{ $('Webhook Trigger').item.json.body.package || 'N/A' }}\n\nService Context from Port:\n{{ $('Get Context From Port').item.json.executionMessage }}\n\nYour response must be a single valid JSON object only, without any code block formatting, markdown, explanations, or extra text.\nDo not include ```json or ``` or any surrounding characters — just raw JSON.\n\nRequired output schema:\n{\n \"summary\": \"Brief description of the vulnerability\",\n \"impact\": \"Business impact assessment\",\n \"remediation_steps\": [\"Step 1\", \"Step 2\"],\n \"is_auto_fixable\": true or false,\n \"fix_prompt\": \"Detailed prompt for a coding agent to fix this vulnerability\",\n \"estimated_effort\": \"low|medium|high\"\n}"
}
]
},
"options": {},
"requestOptions": {}
},
"name": "OpenAI Remediation Plan",
"type": "n8n-nodes-base.openAi",
"position": [
-1040,
544
],
"typeVersion": 1,
"credentials": {
"openAiApi": {
"id": "",
"name": ""
}
}
},
{
"parameters": {
"dataType": "string",
"value1": "={{ $('Webhook Trigger').item.json.body.severity.toLowerCase() }}",
"rules": {
"rules": [
{
"value2": "critical"
},
{
"value2": "high"
},
{
"operation": "regex",
"value2": ".*"
}
]
},
"fallbackOutput": 2
},
"name": "Check Severity Level",
"type": "n8n-nodes-base.switch",
"position": [
-736,
544
],
"typeVersion": 2
},
{
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"cachedResultName": "<YOUR_JIRA_PROJECT>"
},
"issueType": {
"__rl": true,
"mode": "list",
"cachedResultName": "Task"
},
"summary": "=[CRITICAL] {{ $('Webhook Trigger').item.json.body.vulnerability_id }}: {{ $('Webhook Trigger').item.json.body.description.substring(0, 100) }}",
"additionalFields": {
"description": "=*Security Vulnerability - CRITICAL*\n\n*Vulnerability Details*\n- ID: {{ $('Webhook Trigger').item.json.body.vulnerability_id }}\n- CVE: {{ $('Webhook Trigger').item.json.body.cve || 'N/A' }}\n- Severity: CRITICAL\n- Package: {{ $('Webhook Trigger').item.json.body.package || 'N/A' }}\n\n*Description*\n{{ $('Webhook Trigger').item.json.body.description }}\n\n*Affected Service*\n{{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return '- Service: ' + (parsed?.service_name || 'Unknown') + '\\n- Repository: ' + (parsed?.repository || 'Unknown') + '\\n- Environment: ' + (parsed?.environment || 'Unknown') + '\\n- Owners: ' + (parsed?.owners?.join(', ') || 'Unknown'); } catch(e) { return 'Context unavailable'; } })() }}\n\n*AI Remediation Plan*\n{{ (() => { try { let content = $('OpenAI Remediation Plan').item.json.message?.content || '{}'; if (content.includes('```json')) { content = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, ''); } const ai = JSON.parse(content); return ai.summary || 'See remediation steps'; } catch(e) { return 'Analysis pending'; } })() }}\n\n---\n_Auto-generated by n8n + Port security workflow_",
"labels": [
"security"
],
"priority": {
"__rl": true,
"mode": "list",
"cachedResultName": "Highest"
}
}
},
"name": "Create Critical Jira Ticket",
"type": "n8n-nodes-base.jira",
"position": [
-464,
496
],
"typeVersion": 1,
"credentials": {
"jiraSoftwareCloudApi": {
"id": "",
"name": ""
}
}
},
{
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"cachedResultName": "<YOUR_JIRA_PROJECT>"
},
"issueType": {
"__rl": true,
"mode": "list",
"cachedResultName": "Task"
},
"summary": "=[HIGH] {{ $('Webhook Trigger').item.json.body.vulnerability_id }}: {{ $('Webhook Trigger').item.json.body.description.substring(0, 100) }}",
"additionalFields": {
"description": "=*Security Vulnerability - HIGH*\n\n*Vulnerability Details*\n- ID: {{ $('Webhook Trigger').item.json.body.vulnerability_id }}\n- CVE: {{ $('Webhook Trigger').item.json.body.cve || 'N/A' }}\n- Severity: HIGH\n- Package: {{ $('Webhook Trigger').item.json.body.package || 'N/A' }}\n\n*Description*\n{{ $('Webhook Trigger').item.json.body.description }}\n\n*Affected Service*\n{{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return '- Service: ' + (parsed?.service_name || 'Unknown') + '\\n- Repository: ' + (parsed?.repository || 'Unknown') + '\\n- Environment: ' + (parsed?.environment || 'Unknown') + '\\n- Owners: ' + (parsed?.owners?.join(', ') || 'Unknown'); } catch(e) { return 'Context unavailable'; } })() }}\n\n*AI Remediation Plan*\n{{ (() => { try { let content = $('OpenAI Remediation Plan').item.json.message?.content || '{}'; if (content.includes('```json')) { content = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, ''); } const ai = JSON.parse(content); return ai.summary || 'See remediation steps'; } catch(e) { return 'Analysis pending'; } })() }}\n\n---\n_Auto-generated by n8n + Port security workflow_",
"labels": [
"security"
],
"priority": {
"__rl": true,
"mode": "list",
"cachedResultName": "High"
}
}
},
"name": "Create High Jira Ticket",
"type": "n8n-nodes-base.jira",
"position": [
-464,
640
],
"typeVersion": 1,
"credentials": {
"jiraSoftwareCloudApi": {
"id": "",
"name": ""
}
}
},
{
"parameters": {
"project": {
"__rl": true,
"mode": "list",
"cachedResultName": "<YOUR_JIRA_PROJECT>"
},
"issueType": {
"__rl": true,
"mode": "list",
"cachedResultName": "Task"
},
"summary": "=[{{ $('Webhook Trigger').item.json.body.severity.toUpperCase() }}] {{ $('Webhook Trigger').item.json.body.vulnerability_id }}: {{ $('Webhook Trigger').item.json.body.description.substring(0, 100) }}",
"additionalFields": {
"description": "=*Security Vulnerability*\n\n*Vulnerability Details*\n- ID: {{ $('Webhook Trigger').item.json.body.vulnerability_id }}\n- CVE: {{ $('Webhook Trigger').item.json.body.cve || 'N/A' }}\n- Severity: {{ $('Webhook Trigger').item.json.body.severity }}\n- Package: {{ $('Webhook Trigger').item.json.body.package || 'N/A' }}\n\n*Description*\n{{ $('Webhook Trigger').item.json.body.description }}\n\n*Affected Service*\n{{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return '- Service: ' + (parsed?.service_name || 'Unknown') + '\\n- Repository: ' + (parsed?.repository || 'Unknown'); } catch(e) { return 'Context unavailable'; } })() }}\n\n---\n_Auto-generated by n8n + Port security workflow_",
"labels": [
"security"
],
"priority": {
"__rl": true,
"mode": "list",
"cachedResultName": "Low"
}
}
},
"name": "Create Medium/Low Jira Ticket",
"type": "n8n-nodes-base.jira",
"position": [
-464,
784
],
"typeVersion": 1,
"credentials": {
"jiraSoftwareCloudApi": {
"id": "",
"name": ""
}
}
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ (() => { try { let content = $('OpenAI Remediation Plan').item.json.message?.content || '{}'; if (content.includes('```json')) { content = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, ''); } const ai = JSON.parse(content); return ai.is_auto_fixable === true || (ai.fix_prompt && ai.fix_prompt.length > 10); } catch(e) { return false; } })() }}",
"value2": "={{ true }}"
}
]
}
},
"name": "Is Auto-Fixable?",
"type": "n8n-nodes-base.if",
"position": [
-160,
480
],
"typeVersion": 1
},
{
"parameters": {
"method": "POST",
"url": "https://api.getport.io/v1/actions/run_claude_code/runs",
"authentication": "genericCredentialType",
"genericAuthType": "httpBearerAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ properties: { service: (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return parsed?.repository || '<OWNER/REPO>'; } catch(e) { return '<OWNER/REPO>'; } })(), prompt: (() => { try { let content = $('OpenAI Remediation Plan').item.json.message?.content || '{}'; if (content.includes('```json')) { content = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, ''); } const ai = JSON.parse(content); const vulnId = $('Webhook Trigger').item.json.body.vulnerability_id; const jiraKey = $('Create Critical Jira Ticket').item.json?.key || 'N/A'; return 'Fix vulnerability ' + vulnId + ': ' + (ai.fix_prompt || ai.summary || 'security vulnerability') + '. Reference Jira ticket: ' + jiraKey + '. Create a PR with the fix.'; } catch(e) { return 'Fix the security vulnerability and create a PR.'; } })() } }) }}",
"options": {}
},
"name": "Trigger Fix via Port AI Agent",
"type": "n8n-nodes-base.httpRequest",
"position": [
160,
352
],
"typeVersion": 4.3,
"credentials": {
"httpBearerAuth": {
"id": "",
"name": ""
}
}
},
{
"parameters": {
"channel": "={{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return parsed?.slack_channel || '#security-alerts'; } catch(e) { return '#security-alerts'; } })() }}",
"text": "=🚨 *CRITICAL SECURITY VULNERABILITY DETECTED*\n\n*Vulnerability:* {{ $('Webhook Trigger').item.json.body.vulnerability_id }}\n*CVE:* {{ $('Webhook Trigger').item.json.body.cve || 'N/A' }}\n*Severity:* CRITICAL 🔴\n\n*Affected Service:* {{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return parsed?.service_name || 'Unknown'; } catch(e) { return 'Unknown'; } })() }}\n*Owners:* {{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return parsed?.owners?.join(', ') || 'Not specified'; } catch(e) { return 'Not specified'; } })() }}\n\n*Summary:*\n{{ (() => { try { let content = $('OpenAI Remediation Plan').item.json.message?.content || '{}'; if (content.includes('```json')) { content = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, ''); } const ai = JSON.parse(content); return ai.summary || 'See Jira ticket'; } catch(e) { return 'See Jira ticket'; } })() }}\n\n*Jira Ticket:* {{ $('Create Critical Jira Ticket').item.json?.key || 'Creating...' }}\n\n🤖 *Auto-Fix Status:* Triggered",
"otherOptions": {},
"attachments": []
},
"name": "Alert Critical to Slack",
"type": "n8n-nodes-base.slack",
"position": [
400,
384
],
"typeVersion": 1,
"credentials": {
"slackApi": {
"id": "",
"name": ""
}
}
},
{
"parameters": {
"channel": "={{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return parsed?.slack_channel || '#security-alerts'; } catch(e) { return '#security-alerts'; } })() }}",
"text": "=🚨 *CRITICAL SECURITY VULNERABILITY - MANUAL FIX REQUIRED*\n\n*Vulnerability:* {{ $('Webhook Trigger').item.json.body.vulnerability_id }}\n*CVE:* {{ $('Webhook Trigger').item.json.body.cve || 'N/A' }}\n*Severity:* CRITICAL 🔴\n\n*Affected Service:* {{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return parsed?.service_name || 'Unknown'; } catch(e) { return 'Unknown'; } })() }}\n*Owners:* {{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return parsed?.owners?.join(', ') || 'Not specified'; } catch(e) { return 'Not specified'; } })() }}\n\n*Jira Ticket:* {{ $('Create Critical Jira Ticket').item.json?.key || 'Creating...' }}\n\n⚠️ *This vulnerability requires manual remediation*",
"otherOptions": {},
"attachments": []
},
"name": "Alert Critical (Manual Fix)",
"type": "n8n-nodes-base.slack",
"position": [
176,
560
],
"typeVersion": 1,
"credentials": {
"slackApi": {
"id": "",
"name": ""
}
}
},
{
"parameters": {
"channel": "={{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return parsed?.slack_channel || '#security-alerts'; } catch(e) { return '#security-alerts'; } })() }}",
"text": "=⚠️ *HIGH SEVERITY VULNERABILITY DETECTED*\n\n*Vulnerability:* {{ $('Webhook Trigger').item.json.body.vulnerability_id }}\n*CVE:* {{ $('Webhook Trigger').item.json.body.cve || 'N/A' }}\n*Severity:* HIGH 🟠\n\n*Affected Service:* {{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return parsed?.service_name || 'Unknown'; } catch(e) { return 'Unknown'; } })() }}\n*Owners:* {{ (() => { try { const ctx = $('Get Context From Port').item.json.executionMessage; const parsed = typeof ctx === 'string' ? JSON.parse(ctx) : ctx; return parsed?.owners?.join(', ') || 'Not specified'; } catch(e) { return 'Not specified'; } })() }}\n\n*Summary:*\n{{ (() => { try { let content = $('OpenAI Remediation Plan').item.json.message?.content || '{}'; if (content.includes('```json')) { content = content.replace(/```json\\n?/g, '').replace(/```\\n?/g, ''); } const ai = JSON.parse(content); return ai.summary || 'See Jira ticket'; } catch(e) { return 'See Jira ticket'; } })() }}\n\n*Jira Ticket:* {{ $('Create High Jira Ticket').item.json?.key || 'Creating...' }}",
"otherOptions": {},
"attachments": []
},
"name": "Alert High to Slack",
"type": "n8n-nodes-base.slack",
"position": [
-160,
672
],
"typeVersion": 1,
"credentials": {
"slackApi": {
"id": "",
"name": ""
}
}
},
{
"parameters": {
"content": "## Security Vulnerability Lifecycle with Port\n\nComplete security workflow from vulnerability detection to automated remediation, with full organizational context from Port's catalog.\n\n### How it works\n1. **Detect**: Webhook receives vulnerability from scanner (Snyk, Wiz, SonarQube)\n2. **Enrich**: Query Port catalog for service context, owners, SLA, and dependencies\n3. **Assess**: AI generates remediation plan and determines if auto-fixable\n4. **Route by Severity**:\n - Critical → Jira + Auto-fix via Claude Code + Slack alert\n - High → Jira + Slack notification\n - Medium/Low → Jira ticket only\n5. **Remediate**: Claude Code creates PR with fix for critical issues\n6. **Track**: Update team via Slack\n\n### Prerequisites\n- Port account with services and team ownership configured\n- Port AI agent (`context_retriever_agent`)\n- Claude Code action configured in Port\n- Jira project for security tickets\n- Slack workspace connected\n- OpenAI API key",
"height": 720,
"width": 520
},
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2272,
208
],
"typeVersion": 1
},
{
"parameters": {
"content": "## Port Context Enrichment\n\nEnrich vulnerabilities with:\n- Service metadata\n- Repository info\n- Team ownership\n- Environment context\n- SLA requirements\n- Dependencies\n- Jira project key",
"height": 440,
"width": 320,
"color": 6
},
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1536,
256
],
"typeVersion": 1
},
{
"parameters": {
"content": "## Severity-Based Routing\n\n**Critical** 🔴\n- Immediate Jira ticket\n- Auto-fix via Claude Code\n- Urgent Slack alert\n\n**High** 🟠\n- Jira ticket\n- Team notification\n\n**Medium/Low**\n- Jira ticket only",
"height": 440,
"width": 320,
"color": 4
},
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-864,
224
],
"typeVersion": 1
},
{
"parameters": {
"content": "## Claude Code Remediation\n\nFor auto-fixable critical vulnerabilities:\n1. Triggers Port's Claude Code action\n2. Claude analyzes the vulnerability\n3. Creates PR with security patch\n4. Links PR to Jira ticket\n5. Notifies team via Slack",
"height": 280,
"width": 560,
"color": 3
},
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
64,
96
],
"typeVersion": 1
}
],
"pinData": {},
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Get Context From Port",
"type": "main",
"index": 0
}
]
]
},
"Get Context From Port": {
"main": [
[
{
"node": "OpenAI Remediation Plan",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Remediation Plan": {
"main": [
[
{
"node": "Check Severity Level",
"type": "main",
"index": 0
}
]
]
},
"Check Severity Level": {
"main": [
[
{
"node": "Create Critical Jira Ticket",
"type": "main",
"index": 0
}
],
[
{
"node": "Create High Jira Ticket",
"type": "main",
"index": 0
}
],
[
{
"node": "Create Medium/Low Jira Ticket",
"type": "main",
"index": 0
}
]
]
},
"Create Critical Jira Ticket": {
"main": [
[
{
"node": "Is Auto-Fixable?",
"type": "main",
"index": 0
}
]
]
},
"Create High Jira Ticket": {
"main": [
[
{
"node": "Alert High to Slack",
"type": "main",
"index": 0
}
]
]
},
"Is Auto-Fixable?": {
"main": [
[
{
"node": "Trigger Fix via Port AI Agent",
"type": "main",
"index": 0
}
],
[
{
"node": "Alert Critical (Manual Fix)",
"type": "main",
"index": 0
}
]
]
},
"Trigger Fix via Port AI Agent": {
"main": [
[
{
"node": "Alert Critical to Slack",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
}
} -
Click on Workflows → Import from File.
-
After importing, configure the following credentials in n8n:
- Port.io credentials — Use the Port.io credential you created during node installation (contains your Client ID and Client Secret)
- Jira credentials — Add your Jira Cloud API credentials (email and API token)
- Slack credentials — Add your Slack API token
- OpenAI credentials — Add your OpenAI API key
- Bearer Auth credentials — Add your Port API token (for Claude Code integration)
After importing the workflow, you'll need to configure the following in each Jira node:
- Project: Select your Jira project from the dropdown (the workflow uses
<YOUR_JIRA_PROJECT>as a placeholder) - Issue Type: Select an appropriate issue type (e.g., Task, Bug, or a custom Security type)
- Priority: Verify the priority mappings match your Jira configuration
Replace <OWNER/REPO> with your default infrastructure repository for the Claude Code auto-fix feature.
Configure the workflow
The workflow consists of several key nodes:
-
Webhook Trigger — Receives vulnerability alerts from Snyk or other security scanners.
-
Get Context From Port — Uses Port's custom node to invoke the context retriever agent and retrieve service metadata, ownership, and Jira project information.
-
OpenAI Remediation Plan — Generates a remediation plan using the enriched context from Port.
-
Check Severity Level — Routes vulnerabilities by severity (Critical, High, Medium/Low).
-
Create Jira Ticket — Creates prioritized Jira tickets based on severity:
- Critical: Highest priority with full context and SLA information.
- High: High priority with remediation steps.
- Medium/Low: Standard priority for tracking.
-
Is Auto-Fixable? — Determines if the vulnerability can be automatically fixed based on AI analysis.
-
Trigger Fix via Port AI Agent — (Critical only) Triggers Claude Code to create a PR with the fix.
-
Slack Alerts — Sends targeted notifications to the appropriate team channels.
Workflow patterns
The n8n workflow demonstrates three primary patterns for using Port as a context lake:
Pattern 1: Severity-based Jira ticket creation
When a vulnerability is detected, the workflow:
-
Receives the vulnerability alert via webhook.
-
Queries Port's context retriever agent to get service metadata and Jira project information.
-
Routes to the appropriate Jira ticket creation based on severity.
-
Creates a ticket with:
- Appropriate priority (Highest/High/Medium/Low).
- Full vulnerability details.
- Service context from Port.
- AI-generated remediation plan.
- Security labels for tracking.
This ensures all vulnerabilities are tracked with consistent metadata and priority.
Pattern 2: Targeted Slack notifications
For high and critical vulnerabilities, the workflow:
-
Creates the Jira ticket first.
-
Queries Port's context retriever agent to get:
- Affected services
- Owning teams
- Slack channel
- Related environments
-
Sends a targeted Slack notification with the Jira ticket reference.
This ensures security alerts reach the right people with all the information they need to prioritize and respond.
Pattern 3: AI-powered auto-remediation
For critical auto-fixable vulnerabilities, the workflow:
-
Creates a critical Jira ticket.
-
Checks if the vulnerability is auto-fixable (based on AI analysis).
-
Triggers Claude Code via Port action with:
- The affected repository from Port context
- The AI-generated fix prompt
- Reference to the Jira ticket
-
Claude Code creates a PR with the fix.
-
Notifies the team via Slack with auto-fix status.
The Port context ensures the AI agent understands the service architecture, dependencies, and best practices before generating code.
Test the workflow
Test with a sample vulnerability
-
Trigger the webhook with a sample payload:
{
"vulnerability_id": "CVE-2025-10023",
"description": "Buffer overflow in crypto library 2.1.4",
"severity": "critical",
"cve": "CVE-2025-10023",
"package": "crypto-lib"
} -
Verify Port context retrieval — Check that the "Get Context From Port" node successfully retrieves:
- Related services
- Ownership information
- Environment details
- Dependencies
- Jira project key
-
Check Jira ticket creation — Verify the Jira ticket includes:
- Correct severity prefix in summary (e.g.,
[High]) - Appropriate priority
- Full vulnerability details
- Service context from Port
- AI remediation plan
- Security label
- Correct severity prefix in summary (e.g.,
-
Check Slack notification — For HIGH and CRITICAL, verify the notification includes:
-
Vulnerability details
-
Service context from Port
-
Jira ticket reference
-
-
Verify AI remediation (Critical only) — Confirm that auto-fixable critical vulnerabilities trigger the Claude Code action with proper context and Jira ticket reference
Related resources
- Remediate vulnerabilities with AI — Use Port AI agents to automatically enrich and fix vulnerabilities
- Trigger Claude Code from Port — Set up Claude Code integration with Port
- Set up the Task Manager AI agent — Create an AI agent to manage and prioritize development tasks