Conformité CyberPulse – Pipeline de traitement par lots v2
Ceci est uncontenant 18 nœuds.Utilise principalement des nœuds comme Set, Code, Merge, GoogleSheets, ManualTrigger. Notation automatisée des contrôles de conformité, en utilisant CyberPulse, GPT-4o et Google Sheets
- •Informations d'identification Google Sheets API
- •Clé API OpenAI
Nœuds utilisés (18)
Catégorie
{
"id": "6yJ0KXSSGqSb9FY6",
"meta": {
"instanceId": "6feff41aadeb8409737e26476f9d0a45f95eec6a9c16afff8ef87a662455b6df",
"templateCredsSetupCompleted": true
},
"name": "CyberPulse Compliance – v2 batch pipeline",
"tags": [],
"nodes": [
{
"id": "1ae2a107-6515-4dbe-a3b8-4c3fd5d64cce",
"name": "Déclencheur manuel",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-1008,
-608
],
"parameters": {},
"typeVersion": 1
},
{
"id": "2360ade2-1686-4554-beb3-76ba59e16408",
"name": "Ajouter une ligne dans la feuille",
"type": "n8n-nodes-base.googleSheets",
"position": [
2208,
-336
],
"parameters": {
"columns": {
"value": {
"score": "={{ $json.score }}",
"status": "={{ $json.status }}",
"rationale": "={{ $json.rationale }}",
"timestamp": "={{ $json.timestamp }}",
"ai_summary": "={{ $json.ai_summary }}",
"categories": "={{ $json.categories }}",
"confidence": "={{ $json.confidence }}",
"control_id": "={{ $json.control_id }}",
"evaluation": "={{ $json.evaluation }}",
"ai_findings": "={{ $json.ai_findings }}",
"mapped_count": "={{ $json.mapped_count }}",
"mapping_flat": "={{ $json.mapping_flat }}",
"response_text": "={{ $json.response_text }}",
"engine_version": "={{ $json.engine_version }}",
"evidence_count": "={{ $json.evidence_count }}",
"evidence_url_1": "={{ $json.evidence_url_1 }}",
"evidence_url_2": "={{ $json.evidence_url_2 }}",
"evidence_url_3": "={{ $json.evidence_url_3 }}",
"evidence_url_4": "={{ $json.evidence_url_4 }}",
"ai_recommendations": "={{ $json.ai_recommendations }}",
"control_description": "={{ $json.control_description }}",
"frameworks_selected": "={{ $json.frameworks_selected }}",
"implementation_notes": "={{ $json.implementation_notes }}"
},
"schema": [
{
"id": "timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "control_id",
"type": "string",
"display": true,
"required": false,
"displayName": "control_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "control_description",
"type": "string",
"display": true,
"required": false,
"displayName": "control_description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "response_text",
"type": "string",
"display": true,
"required": false,
"displayName": "response_text",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "implementation_notes",
"type": "string",
"display": true,
"required": false,
"displayName": "implementation_notes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "evidence_url_1",
"type": "string",
"display": true,
"required": false,
"displayName": "evidence_url_1",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "evidence_url_2",
"type": "string",
"display": true,
"required": false,
"displayName": "evidence_url_2",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "evidence_url_3",
"type": "string",
"display": true,
"required": false,
"displayName": "evidence_url_3",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "evidence_url_4",
"type": "string",
"display": true,
"required": false,
"displayName": "evidence_url_4",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "evaluation",
"type": "string",
"display": true,
"required": false,
"displayName": "evaluation",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "score",
"type": "string",
"display": true,
"required": false,
"displayName": "score",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "confidence",
"type": "string",
"display": true,
"required": false,
"displayName": "confidence",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "rationale",
"type": "string",
"display": true,
"required": false,
"displayName": "rationale",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "categories",
"type": "string",
"display": true,
"required": false,
"displayName": "categories",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "evidence_count",
"type": "string",
"display": true,
"required": false,
"displayName": "evidence_count",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "mapped_count",
"type": "string",
"display": true,
"required": false,
"displayName": "mapped_count",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "mapping_flat",
"type": "string",
"display": true,
"required": false,
"displayName": "mapping_flat",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "frameworks_selected",
"type": "string",
"display": true,
"required": false,
"displayName": "frameworks_selected",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "engine_version",
"type": "string",
"display": true,
"required": false,
"displayName": "engine_version",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ai_summary",
"type": "string",
"display": true,
"required": false,
"displayName": "ai_summary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ai_findings",
"type": "string",
"display": true,
"required": false,
"displayName": "ai_findings",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ai_recommendations",
"type": "string",
"display": true,
"required": false,
"displayName": "ai_recommendations",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "priority",
"type": "string",
"display": true,
"required": false,
"displayName": "priority",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "owner",
"type": "string",
"display": true,
"required": false,
"displayName": "owner",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "due_date",
"type": "string",
"display": true,
"required": false,
"displayName": "due_date",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ticket_id",
"type": "string",
"display": true,
"required": false,
"displayName": "ticket_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "next_action",
"type": "string",
"display": true,
"required": false,
"displayName": "next_action",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "evidence_gap_flag",
"type": "string",
"display": true,
"required": false,
"displayName": "evidence_gap_flag",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "policy_gap_flag",
"type": "string",
"display": true,
"required": false,
"displayName": "policy_gap_flag",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "last_run_id",
"type": "string",
"display": true,
"required": false,
"displayName": "last_run_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "source_sheet_row",
"type": "string",
"display": true,
"required": false,
"displayName": "source_sheet_row",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1117838353,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1owd3qVXCO34EhvBO5Vr3d2Pn2_QQXt0rNl_kroSmf0I/edit#gid=1117838353",
"cachedResultName": "controls_results_template.csv"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1owd3qVXCO34EhvBO5Vr3d2Pn2_QQXt0rNl_kroSmf0I",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1owd3qVXCO34EhvBO5Vr3d2Pn2_QQXt0rNl_kroSmf0I/edit?usp=drivesdk",
"cachedResultName": "controls_results_template"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "SHFVDGHEaA6jzLc4",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "46fe1437-f8b0-4ede-9cc6-23958351755a",
"name": "Obtenir une/des ligne(s) dans la feuille",
"type": "n8n-nodes-base.googleSheets",
"position": [
-992,
-368
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1182991363,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Gcak2OCpo2vnDkB2W49xz4TD762DGCY3ZA6Pkfv82nM/edit#gid=1182991363",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1Gcak2OCpo2vnDkB2W49xz4TD762DGCY3ZA6Pkfv82nM",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Gcak2OCpo2vnDkB2W49xz4TD762DGCY3ZA6Pkfv82nM/edit?usp=drivesdk",
"cachedResultName": "controls_input_mock_100_rows"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "SHFVDGHEaA6jzLc4",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "2f4910f6-3fdf-4133-b714-b6752d5bdb94",
"name": "Expliquer et Recommander",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
640,
-288
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {},
"messages": {
"values": [
{
"role": "system",
"content": "=Use only the provided fields. Do not invent evidence, numbers, or frameworks.\nReturn a JSON object with keys exactly:\n- ai_summary (string)\n- ai_findings (array of 3 short strings), no bullets, no dashes, no numbering, no checkboxes.\n- ai_recommendations (array of 3 short, actionable strings), no bullets, no dashes, no numbering, no checkboxes.\nNo other keys.\n"
},
{
"content": "={\n \"timestamp\": \"{{ $('Edit Fields').item.json.timestamp }}\",\n \"control_id\": \"{{ $('Edit Fields').item.json.control_id }}\",\n \"control_description\": \"{{ $('Edit Fields').item.json.control_description }}\",\n \"response_text\": \"{{ $('Edit Fields').item.json.response_text }}\",\n \"implementation_notes\": \"{{$json.implementation_notes || ''}}\",\n\n \"status\": \"{{$json.status}}\",\n \"evaluation\": \"{{$json.evaluation}}\",\n \"score\": {{ +$json.score }},\n \"confidence\": {{ +$json.confidence }},\n \"rationale\": \"{{$json.rationale}}\",\n\n \"evidence_count\": {{\n Array.isArray($json.evidence)\n ? $json.evidence.filter(u => u && String(u).trim()).length\n : ([\n $json.evidence_url_1,\n $json.evidence_url_2,\n $json.evidence_url_3,\n $json.evidence_url_4\n ].filter(u => u && String(u).trim()).length || ($json.evidence_count ?? 0))\n}},\n\n \"mapped_count\": {{ Array.isArray($json.mapped_requirements) ? $json.mapped_requirements.length : ($json.mapped_count ?? 0) }},\n \"mapping_flat\": \"{{ Array.isArray($json.mapped_requirements) ? $json.mapped_requirements.map(m => [m.framework, m.clause, m.title].filter(Boolean).join(': ')).join(' | ') : ($json.mapping_flat || '') }}\",\n \"categories\": \"{{ Array.isArray($json.categories) ? $json.categories.join(', ') : ($json.categories || '') }}\",\n \"frameworks_selected\": \"{{ Array.isArray($json.mapped_requirements) ? [...new Set($json.mapped_requirements.map(m => m.framework).filter(Boolean))].join(', ') : ($json.frameworks_selected || '') }}\",\n \"engine_version\": \"{{$json.engine_version || ''}}\",\n\n \"format_instructions\": \"Return ai_summary exactly as: 'Status: {status}. Evaluation: {evaluation}. Score: {score}. Confidence: {confidence}. Evidence items: {evidence_count}. Categories: {categories}. Mappings: {mapping_flat}'. Then return ai_findings as a single string with 3 short bullets (prefix each with '• ') grounded in rationale/categories. Then return ai_recommendations as a single string with 3 short, actionable bullets (prefix each with '• ') tied to mapping_flat and evidence_count. Also compute priority, next_action, evidence_gap_flag, policy_gap_flag using the rules below.\\n\\nRules:\\n- priority:\\n - P1 if (status == 'Non-Compliant' && mapped_count >= 3) OR (score < 50 && evidence_count == 0)\\n - P2 if (status in ['Non-Compliant','Partial'] && (score >= 50 && score < 75)) OR (evidence_count <= 1)\\n - P3 if (status == 'Compliant' && (confidence < 60 || evidence_count < 2))\\n - else P4\\n- next_action:\\n - 'implement' if status == 'Non-Compliant'\\n - 'remediate' if status == 'Partial'\\n - 'monitor' if status == 'Compliant' and (confidence < 60 || evidence_count < 2)\\n - else 'review'\\n- evidence_gap_flag: 'yes' if evidence_count == 0 OR evidence_count == 1, else 'no'\\n- policy_gap_flag: 'yes' if ('policy' appears in (response_text or rationale) case-insensitive) OR categories contains 'policy', else 'no'\"\n}\n"
}
]
},
"jsonOutput": true
},
"credentials": {
"openAiApi": {
"id": "gcqJ07oLkr9oSi82",
"name": "OpenAi account"
}
},
"typeVersion": 1.8
},
{
"id": "1920b78c-97ac-4303-a055-e2541cf12f29",
"name": "Analyser + attacher à chaque élément",
"type": "n8n-nodes-base.code",
"position": [
1696,
-336
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// Helpers\nfunction isNonEmpty(x){ if(x===undefined||x===null) return false; if(typeof x==='number') return true; return String(x).trim()!==''; }\nfunction prefer(...vals){ for(const v of vals){ if(isNonEmpty(v)) return v; } return ''; }\nfunction safeNum(x, def=''){ const n = Number(x); return Number.isFinite(n) ? n : def; }\n\n// Strip any leading bullets / dashes / numbers / checkboxes before we add our own bullets\nfunction stripLeadingMarkers(s){\n return String(s).replace(/^\\s*(?:[-*•\\u2022]+|\\d+[.)]|[✓✔✗✘xX\\[\\]\\(\\)])\\s*/u, '');\n}\nfunction asArray(x){\n if(!x) return [];\n if(Array.isArray(x)) return x.filter(Boolean).map(v => stripLeadingMarkers(v));\n return String(x).split(/\\r?\\n+/).map(v => stripLeadingMarkers(v)).filter(Boolean);\n}\nfunction bullets(x){\n const a = asArray(x);\n return a.length ? a.map(s => `• ${s}`).join('\\n') : '';\n}\n\n// Current item from Merge\nconst i = $json;\n// Originals straight from \"Edit Fields\" (for fields the merge might not include)\nconst src = $(\"Edit Fields\").item?.json ?? {};\n\n// ---- Ingest (preserve originals)\nconst timestamp = prefer(i.timestamp, src.timestamp, new Date().toISOString());\nconst control_id = prefer(i.control_id, src.control_id);\nconst control_description = prefer(i.control_description, src.control_description);\nconst response_text = prefer(i.response_text, src.response_text);\nconst implementation_notes= prefer(i.implementation_notes, src.implementation_notes);\nconst evidence_url_1 = prefer(i.evidence_url_1, src.evidence_url_1);\nconst evidence_url_2 = prefer(i.evidence_url_2, src.evidence_url_2);\nconst evidence_url_3 = prefer(i.evidence_url_3, src.evidence_url_3);\nconst evidence_url_4 = prefer(i.evidence_url_4, src.evidence_url_4);\n\n// ---- Scoring & mapping\nconst status = prefer(i.status, 'Unknown');\nconst evaluation = prefer(i.evaluation, status);\nconst score = safeNum(i.score);\nconst confidence = safeNum(i.confidence);\nconst rationaleIn = prefer(i.rationale, i.reason);\n\n// categories may be array or string\nconst categoriesStr = Array.isArray(i.categories) ? i.categories.join(', ') : prefer(i.categories);\n\n// evidence: prefer i.evidence (array), else derive from URL fields, then filter empties\nlet evidenceArr = [];\nif (Array.isArray(i.evidence)) {\n evidenceArr = i.evidence.filter(u => u && String(u).trim());\n} else {\n evidenceArr = [evidence_url_1, evidence_url_2, evidence_url_3, evidence_url_4]\n .filter(u => u && String(u).trim());\n}\nconst evidence_count = isNonEmpty(i.evidence_count) ? safeNum(i.evidence_count, evidenceArr.length) : evidenceArr.length;\n\n// mapped requirements\nconst mappedReqs = Array.isArray(i.mapped_requirements) ? i.mapped_requirements : [];\nconst mapped_count = isNonEmpty(i.mapped_count) ? safeNum(i.mapped_count, mappedReqs.length) : mappedReqs.length;\nconst mapping_flat = isNonEmpty(i.mapping_flat)\n ? String(i.mapping_flat)\n : mappedReqs.map(m => [m.framework, m.clause, m.title].filter(Boolean).join(': ')).join(' | ');\n\n// frameworks selected (pretty commas)\nconst frameworks_selected = (isNonEmpty(i.frameworks_selected)\n ? String(i.frameworks_selected)\n : [...new Set(mappedReqs.map(m => m.framework).filter(Boolean))].join(', ')\n).replace(/,\\s*/g, ', ');\n\nconst engine_version = prefer(i.engine_version, i.version);\n\n// ---- AI outputs (handle message.content object OR JSON string; also accept top-level)\nlet ai_summary = i.ai_summary;\nlet ai_findings_any = i.ai_findings;\nlet ai_recommendations_any = i.ai_recommendations;\n\nif ((!ai_summary || (!ai_findings_any && !ai_recommendations_any)) && i.message?.content) {\n if (typeof i.message.content === 'object') {\n const c = i.message.content;\n ai_summary = ai_summary ?? c.ai_summary;\n ai_findings_any = ai_findings_any ?? c.ai_findings;\n ai_recommendations_any = ai_recommendations_any ?? c.ai_recommendations;\n } else {\n try {\n const parsed = JSON.parse(i.message.content);\n ai_summary = ai_summary ?? parsed.ai_summary;\n ai_findings_any = ai_findings_any ?? parsed.ai_findings;\n ai_recommendations_any = ai_recommendations_any ?? parsed.ai_recommendations;\n } catch {}\n }\n}\n\n// Normalize to single strings for the sheet (no double bullets)\nconst ai_findings = bullets(ai_findings_any);\nconst ai_recommendations = bullets(ai_recommendations_any);\n\n// Sync the item count inside rationale (e.g., replace \"(4 items)\" with \"(3 items)\")\nlet rationale = rationaleIn;\nif (isNonEmpty(rationale)) {\n rationale = rationale.replace(/\\(\\s*\\d+\\s*items?\\s*\\)/i, `(${evidence_count} items)`);\n}\n\n// Synthesize summary if missing (deterministic)\nif (!isNonEmpty(ai_summary)) {\n ai_summary = `Status: ${status}. Evaluation: ${evaluation}. Score: ${score}. `\n + `Confidence: ${confidence}. Evidence items: ${evidence_count}. `\n + `Categories: ${categoriesStr}. Mappings: ${mapping_flat}`;\n}\n\n// Return final row payload\nreturn {\n json: {\n // Ingest\n timestamp,\n control_id,\n control_description,\n response_text,\n implementation_notes,\n evidence_url_1,\n evidence_url_2,\n evidence_url_3,\n evidence_url_4,\n\n // Scoring & mapping\n status,\n evaluation,\n score,\n confidence,\n rationale,\n categories: categoriesStr,\n evidence_count,\n mapped_count,\n mapping_flat,\n frameworks_selected,\n engine_version,\n\n // AI (strings)\n ai_summary,\n ai_findings,\n ai_recommendations\n }\n};"
},
"typeVersion": 2
},
{
"id": "23954e15-29e1-49d3-9614-772e33562fc4",
"name": "Boucler sur les éléments",
"type": "n8n-nodes-base.splitInBatches",
"position": [
160,
-368
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "1f2dda49-fde7-44f8-8efd-e6ffd73e4e96",
"name": "Modifier les champs",
"type": "n8n-nodes-base.set",
"position": [
-672,
-368
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "87318d44-cd2c-47d8-850a-c0b152d6b455",
"name": "row_number",
"type": "string",
"value": "={{ $json.row_number }}"
},
{
"id": "9eab7a02-e58e-46a8-85cb-d2a41693759c",
"name": "timestamp",
"type": "string",
"value": "={{ $json.timestamp }}"
},
{
"id": "6d3fa2f3-5e8b-45c6-80f6-e9b713fd82e4",
"name": "control_description",
"type": "string",
"value": "={{ $json.control_description }}"
},
{
"id": "e0b7a487-bc52-40d9-80a6-5bc2a13c19a0",
"name": "response_text",
"type": "string",
"value": "={{ $json.response_text }}"
},
{
"id": "b6347812-76af-454b-8869-81c47f4c90a8",
"name": "evidence_url_1",
"type": "string",
"value": "={{ $json.evidence_url_1 }}"
},
{
"id": "f8784a98-ead8-4af2-a1ca-42aa2fcc6032",
"name": "evidence_url_2",
"type": "string",
"value": "={{ $json.evidence_url_2 }}"
},
{
"id": "d91dceb0-e1a7-4322-89a9-041a34e00f52",
"name": "evidence_url_3",
"type": "string",
"value": "={{ $json.evidence_url_3 }}"
},
{
"id": "14d6374b-5191-4f4f-85b0-d443a9e404ee",
"name": "evidence_url_4",
"type": "string",
"value": "={{ $json.evidence_url_4 }}"
},
{
"id": "b8bd82a7-c19c-458b-a0cb-5cf2941efc0c",
"name": "implementation_notes",
"type": "string",
"value": "={{ $json.implementation_notes }}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "af2a5b70-b8e7-4ab4-b9cb-29da669d1474",
"name": "Fusion1",
"type": "n8n-nodes-base.merge",
"position": [
1216,
-336
],
"parameters": {
"mode": "combine",
"options": {
"includeUnpaired": true
},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "d2232908-5186-4e0a-b771-7230028b8b43",
"name": "CyberPulse Compliance (Dev)",
"type": "n8n-nodes-cyberpulse-compliance-dev.cyberPulseCompliance",
"position": [
-288,
-368
],
"parameters": {
"controlText": "=={{ $json.response_text + ($json.implementation_notes ? (' ' + $json.implementation_notes) : '') }}",
"crosswalkUrl": "https://gist.githubusercontent.com/gitadta/c6b7b69ae2a00f2a67e3bbac4b6648d4/raw/238ce80b0252702d4e6e9c19015bf958d0a0bad6/crosswalk.json",
"evidenceUrls": [
"={{ $json.evidence_url_1 }}",
"={{ $json.evidence_url_2 }}",
"={{ $json.evidence_url_3 }}",
"={{ $json.evidence_url_4 }}"
]
},
"credentials": {
"cyberPulseHttpHeaderAuthApi": {
"id": "s8WtV23qQ1z0bVTT",
"name": "CyberPulse HTTP Header Auth account"
}
},
"typeVersion": 6
},
{
"id": "a7a4f9e1-df82-4776-827b-e89177d9c935",
"name": "Note adhésive",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1328,
-704
],
"parameters": {
"color": 4,
"width": 528,
"height": 512,
"content": "\n\n🟢 Manual Trigger — Start/diagnostics\n\nReceives POST and starts the run; echoes runId for tracing\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n🟩 Get row(s) in sheet — Read inputs\n\nLoads model/routing settings from\n the config sheet."
},
"typeVersion": 1
},
{
"id": "95cc8cf7-0b2b-4e33-974c-bcb23d07c265",
"name": "Note adhésive1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-784,
-576
],
"parameters": {
"color": 5,
"width": 400,
"height": 384,
"content": "\n\n\n\n\n🟦 Edit Fields — Normalize columns\n\nMaps incoming keys, trims text, and sets safe defaults."
},
"typeVersion": 1
},
{
"id": "f98d67c1-31e3-4274-a0b0-41812290b18a",
"name": "Note adhésive2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-368,
-576
],
"parameters": {
"width": 432,
"height": 384,
"content": "\n\n🟨 CyberPulse Compliance (Dev) — Score + map\n\nScores and maps each control using control text + implementation notes + up to 4 evidence URLs and selected frameworks.\nOutputs score (0–100), status, confidence, binary evaluation, categories, crosswalk mappings, and adds gaps/actions when evidence is weak or missing."
},
"typeVersion": 1
},
{
"id": "0777506c-4b63-45ae-a27e-2c4fa519947c",
"name": "Note adhésive3",
"type": "n8n-nodes-base.stickyNote",
"position": [
80,
-576
],
"parameters": {
"color": 6,
"width": 432,
"height": 384,
"content": "\n\n🟪 Loop Over Items — Iterate per control\n\nIterates each control independently to run LLM → parse → append, preserving per-row context and emitting one result per input."
},
"typeVersion": 1
},
{
"id": "f5161050-7010-43d6-aadc-eec7daae85d0",
"name": "Note adhésive4",
"type": "n8n-nodes-base.stickyNote",
"position": [
528,
-576
],
"parameters": {
"color": 5,
"width": 512,
"height": 384,
"content": "\n\n🟦 Explain & Recommend (Message Model) — Exec summary\n\nGenerates a strict-JSON executive summary—ai_summary, three ai_findings, and three ai_recommendations—from the control’s status, score, confidence, categories, evidence, and framework mappings."
},
"typeVersion": 1
},
{
"id": "ed04125f-e8d8-4a05-afe0-3f8844f07f5e",
"name": "Note adhésive5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1056,
-576
],
"parameters": {
"color": 3,
"width": 464,
"height": 384,
"content": "\n\n🟧 Merge1 — Combine model + original\n\nCombines CyberPulse scoring/mapping with the LLM summary by position, producing one merged item per row."
},
"typeVersion": 1
},
{
"id": "16c54ee5-0dc1-4374-b2f9-ca831de1c1a4",
"name": "Note adhésive6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1536,
-576
],
"parameters": {
"width": 464,
"height": 384,
"content": "\n\n🟨 Parse + attach to each item — Final shaping\n\nMerges CyberPulse scores/mappings with the LLM output by index into a single unified row."
},
"typeVersion": 1
},
{
"id": "b4e8b1ee-95ad-4a8b-bd3b-2c07ecab42b3",
"name": "Note adhésive7",
"type": "n8n-nodes-base.stickyNote",
"position": [
2016,
-576
],
"parameters": {
"color": 4,
"width": 496,
"height": 384,
"content": "\n\n🟩 Append row in sheet — Write results\n\nAppends one result row per item to the results sheet with core, scoring, mapping, and AI fields, leaving future columns blank."
},
"typeVersion": 1
},
{
"id": "297e3ea4-35bd-4bc5-a851-ecf0f8dda5bc",
"name": "Note adhésive8",
"type": "n8n-nodes-base.stickyNote",
"position": [
192,
-912
],
"parameters": {
"color": 7,
"width": 784,
"height": 288,
"content": "\n\nWhat is CyberPulse Agent workflow ?\n\nAutomates evidence-aware control scoring (0–100) with deterministic gates and confidence from evidence/text quality.\nReads controls from Google Sheets (text, up to 4 evidence URLs, notes) and classifies, scores, and maps via the CyberPulse node.\nGenerates board-ready AI outputs per control: one-paragraph summary, 3 findings, and 3 actionable recommendations.\nWrites normalized, analytics-ready rows back to a results sheet with flattened framework mappings and detected categories.\nCovers ISO 27001, NIST CSF, SOC 2, PCI DSS, Essential Eight, GDPR; secure, scalable in n8n with tunable weights/thresholds."
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "8a012f28-964c-4087-9b74-7c24a4d8f76e",
"connections": {
"af2a5b70-b8e7-4ab4-b9cb-29da669d1474": {
"main": [
[
{
"node": "1920b78c-97ac-4303-a055-e2541cf12f29",
"type": "main",
"index": 0
}
]
]
},
"1f2dda49-fde7-44f8-8efd-e6ffd73e4e96": {
"main": [
[
{
"node": "d2232908-5186-4e0a-b771-7230028b8b43",
"type": "main",
"index": 0
}
]
]
},
"1ae2a107-6515-4dbe-a3b8-4c3fd5d64cce": {
"main": [
[
{
"node": "46fe1437-f8b0-4ede-9cc6-23958351755a",
"type": "main",
"index": 0
}
]
]
},
"23954e15-29e1-49d3-9614-772e33562fc4": {
"main": [
[],
[
{
"node": "2f4910f6-3fdf-4133-b714-b6752d5bdb94",
"type": "main",
"index": 0
},
{
"node": "af2a5b70-b8e7-4ab4-b9cb-29da669d1474",
"type": "main",
"index": 0
}
]
]
},
"2360ade2-1686-4554-beb3-76ba59e16408": {
"main": [
[
{
"node": "23954e15-29e1-49d3-9614-772e33562fc4",
"type": "main",
"index": 0
}
]
]
},
"2f4910f6-3fdf-4133-b714-b6752d5bdb94": {
"main": [
[
{
"node": "af2a5b70-b8e7-4ab4-b9cb-29da669d1474",
"type": "main",
"index": 1
}
]
]
},
"46fe1437-f8b0-4ede-9cc6-23958351755a": {
"main": [
[
{
"node": "1f2dda49-fde7-44f8-8efd-e6ffd73e4e96",
"type": "main",
"index": 0
}
]
]
},
"d2232908-5186-4e0a-b771-7230028b8b43": {
"main": [
[
{
"node": "23954e15-29e1-49d3-9614-772e33562fc4",
"type": "main",
"index": 0
}
]
]
},
"1920b78c-97ac-4303-a055-e2541cf12f29": {
"main": [
[
{
"node": "2360ade2-1686-4554-beb3-76ba59e16408",
"type": "main",
"index": 0
}
]
]
}
}
}Comment utiliser ce workflow ?
Copiez le code de configuration JSON ci-dessus, créez un nouveau workflow dans votre instance n8n et sélectionnez "Importer depuis le JSON", collez la configuration et modifiez les paramètres d'authentification selon vos besoins.
Dans quelles scénarios ce workflow est-il adapté ?
Avancé
Est-ce payant ?
Ce workflow est entièrement gratuit et peut être utilisé directement. Veuillez noter que les services tiers utilisés dans le workflow (comme l'API OpenAI) peuvent nécessiter un paiement de votre part.
Workflows recommandés
Adnan Tariq
@adnantariqFounder of CYBERPULSE AI — helping security teams and SMEs eliminate repetitive tasks through modular n8n automations. I build workflows for vulnerability triage, compliance reporting, threat intel, and Red/Blue/GRC ops. Book a session if you'd like custom automation for your use case. https://linkedin.com/in/adnan-tariq-4b2a1a47
Partager ce workflow