{
  "nodes": [
    {
      "id": "webhook-receive",
      "name": "Receive Document",
      "type": "n8n-nodes-base.webhook",
      "onError": "continueRegularOutput",
      "position": [
        -1200,
        400
      ],
      "parameters": {
        "path": "pii-proxy",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "regex-detect",
      "name": "Detect PII via Regex",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        -976,
        400
      ],
      "parameters": {
        "jsCode": "// PII Detection Engine using Regex patterns\nconst input = $input.first().json;\nconst text = input.body?.text || input.text || JSON.stringify(input);\n\nconst piiPatterns = [\n  { type: 'EMAIL', pattern: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g },\n  { type: 'PHONE', pattern: /(?:\\+?\\d{1,3}[-.\\s]?)?\\(?\\d{2,4}\\)?[-.\\s]?\\d{3,4}[-.\\s]?\\d{3,4}/g },\n  { type: 'SSN', pattern: /\\b\\d{3}[-]?\\d{2}[-]?\\d{4}\\b/g },\n  { type: 'CREDIT_CARD', pattern: /\\b(?:\\d{4}[-.\\s]?){3}\\d{4}\\b/g },\n  { type: 'CNP', pattern: /\\b[1-8]\\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\\d|3[01])\\d{6}\\b/g },\n  { type: 'IBAN', pattern: /\\b[A-Z]{2}\\d{2}[A-Z0-9]{4,30}\\b/g },\n  { type: 'IP_ADDRESS', pattern: /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g },\n  { type: 'DATE_OF_BIRTH', pattern: /\\b(?:\\d{1,2}[/.-]\\d{1,2}[/.-]\\d{2,4})\\b/g }\n];\n\nconst detectedPII = [];\nlet processedText = text;\n\nfor (const { type, pattern } of piiPatterns) {\n  const matches = text.match(pattern) || [];\n  for (const match of matches) {\n    const tokenId = `<<PII_${type}_${Math.random().toString(36).substring(2, 8).toUpperCase()}>>`;\n    detectedPII.push({\n      token: tokenId,\n      originalValue: match,\n      type: type,\n      detectedAt: new Date().toISOString()\n    });\n    processedText = processedText.replace(match, tokenId);\n  }\n}\n\nreturn {\n  json: {\n    originalText: text,\n    sanitizedText: processedText,\n    piiEntities: detectedPII,\n    piiCount: detectedPII.length,\n    sessionId: `SESSION_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,\n    timestamp: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "tokenize-pii",
      "name": "Tokenize and Replace PII",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        -752,
        400
      ],
      "parameters": {
        "jsCode": "// Enhanced tokenization with context-aware NER simulation\nconst input = $input.first().json;\nlet text = input.sanitizedText;\nconst existingPII = input.piiEntities || [];\n\n// Context-aware name detection (common name patterns)\nconst namePatterns = [\n  /(?:Mr\\.?|Mrs\\.?|Ms\\.?|Dr\\.?|Prof\\.?)\\s+[A-Z][a-z]+(?:\\s+[A-Z][a-z]+)*/g,\n  /(?:Dear|Hi|Hello|Attention:|To:|From:)\\s+[A-Z][a-z]+(?:\\s+[A-Z][a-z]+)*/g\n];\n\n// Address detection\nconst addressPattern = /\\d{1,5}\\s+[A-Z][a-zA-Z]+(?:\\s+[A-Z]?[a-zA-Z]+)*,?\\s+(?:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Drive|Dr|Lane|Ln|Way|Court|Ct)/gi;\n\nconst additionalPII = [];\n\nfor (const pattern of namePatterns) {\n  const matches = text.match(pattern) || [];\n  for (const match of matches) {\n    const cleanName = match.replace(/^(?:Mr\\.?|Mrs\\.?|Ms\\.?|Dr\\.?|Prof\\.?|Dear|Hi|Hello|Attention:|To:|From:)\\s+/i, '');\n    if (cleanName.length > 2) {\n      const tokenId = `<<PII_NAME_${Math.random().toString(36).substring(2, 8).toUpperCase()}>>`;\n      additionalPII.push({ token: tokenId, originalValue: cleanName, type: 'PERSON_NAME', detectedAt: new Date().toISOString() });\n      text = text.replace(cleanName, tokenId);\n    }\n  }\n}\n\nconst addressMatches = text.match(addressPattern) || [];\nfor (const addr of addressMatches) {\n  const tokenId = `<<PII_ADDR_${Math.random().toString(36).substring(2, 8).toUpperCase()}>>`;\n  additionalPII.push({ token: tokenId, originalValue: addr, type: 'ADDRESS', detectedAt: new Date().toISOString() });\n  text = text.replace(addr, tokenId);\n}\n\nconst allPII = [...existingPII, ...additionalPII];\n\nreturn {\n  json: {\n    sanitizedText: text,\n    piiVault: allPII,\n    totalPIIDetected: allPII.length,\n    sessionId: input.sessionId,\n    timestamp: input.timestamp\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "store-vault",
      "name": "Store Token Vault",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        -528,
        400
      ],
      "parameters": {
        "columns": {
          "values": [
            {
              "value": "={{ $json.sessionId }}",
              "column": "Session ID"
            },
            {
              "value": "={{ $json.piiVault.map(p => p.token).join(' | ') }}",
              "column": "Token"
            },
            {
              "value": "={{ $json.piiVault.map(p => p.type).join(' | ') }}",
              "column": "Type"
            },
            {
              "value": "={{ $json.timestamp }}",
              "column": "Timestamp"
            },
            {
              "value": "={{ $json.totalPIIDetected }}",
              "column": "PII Count"
            }
          ],
          "mappingMode": "defineBelow"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "PII Vault",
          "cachedResultName": "PII Vault"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "",
          "cachedResultName": "Configure Google Sheet ID"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "openai-analyze",
      "name": "OpenAI Analyze Sanitized Text",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "onError": "continueRegularOutput",
      "position": [
        -304,
        400
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5.5",
          "cachedResultName": "GPT-5.5"
        },
        "options": {
          "maxTokens": 800,
          "truncation": true,
          "temperature": 0.2
        },
        "responses": {
          "values": [
            {
              "role": "system",
              "content": "You are a data analysis agent. You receive sanitized text where PII has been replaced with tokens. Analyze sentiment, key topics, and intent. Return concise JSON with sentiment, topics array, and summary. Do not reveal or infer masked PII."
            },
            {
              "content": "={{ $json.sanitizedText }}"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "restore-pii",
      "name": "Restore Original PII",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        48,
        400
      ],
      "parameters": {
        "jsCode": "// Re-inject original PII values from the vault\nconst input = $input.first().json;\nconst piiVault = $('Tokenize and Replace PII').first().json.piiVault;\n\n// Get the AI analysis text\nlet analysisText = input.message?.content || input.output || input.output_text || input.text || JSON.stringify(input);\n\n// Replace each token with its original value\nfor (const entity of piiVault) {\n  analysisText = analysisText.split(entity.token).join(entity.originalValue);\n}\n\nreturn {\n  json: {\n    analysis: analysisText,\n    sessionId: $('Tokenize and Replace PII').first().json.sessionId,\n    piiCount: piiVault.length,\n    piiTypesDetected: [...new Set(piiVault.map(p => p.type))],\n    timestamp: new Date().toISOString(),\n    gdprCompliant: true\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "audit-log",
      "name": "Log Audit Trail",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        272,
        400
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "values": [
            {
              "value": "={{ $json.sessionId }}",
              "column": "Session ID"
            },
            {
              "value": "={{ $json.timestamp }}",
              "column": "Timestamp"
            },
            {
              "value": "={{ $json.piiCount }}",
              "column": "PII Count"
            },
            {
              "value": "={{ $json.piiTypesDetected.join(', ') }}",
              "column": "PII Types"
            },
            {
              "value": "={{ $json.gdprCompliant }}",
              "column": "GDPR Compliant"
            },
            {
              "value": "Completed",
              "column": "Status"
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Audit Log",
          "cachedResultName": "Audit Log"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "",
          "cachedResultName": "Configure Google Sheet ID"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "respond-webhook",
      "name": "Return Processed Document",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        496,
        400
      ],
      "parameters": {
        "options": {
          "responseCode": 200
        },
        "respondWith": "json"
      },
      "typeVersion": 1.5
    },
    {
      "id": "sticky-pii-overview",
      "name": "PII Proxy Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -896,
        608
      ],
      "parameters": {
        "color": 4,
        "width": 1176,
        "height": 400,
        "content": "## Who's it for\nCompliance teams, support teams, HR operations, legal teams, and builders who want to use AI analysis while reducing exposure of personal data.\n\n## How it works\nThis workflow receives text through a webhook, detects structured PII with regex patterns, adds lightweight context-based matching for names and addresses, replaces sensitive values with reversible tokens, and stores audit metadata in Google Sheets. The sanitized text is then sent to GPT-5.5 for analysis. After the model returns a response, the workflow restores original values from the in-execution token vault, logs an audit row, and returns the processed JSON response.\n\n## How to set up\nCreate a Google Sheet with `PII Vault` and `Audit Log` tabs, configure Google Sheets credentials, connect your OpenAI credential with GPT-5.5 access, and send requests to the `/pii-proxy` webhook with a `text` field.\n\n## Requirements\nOpenAI, Google Sheets, and n8n webhook access.\n\n## How to customize\nAdd jurisdiction-specific PII patterns, replace Google Sheets with encrypted database storage, or route high-risk documents to manual review before AI processing."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-pii-detection",
      "name": "Detection Phase",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1216,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 520,
        "height": 220,
        "content": "## PII detection and tokenization\n\n**Detect PII via Regex:** Scans the input text for emails, phone numbers, SSNs, card-like numbers, CNP, IBAN, IP addresses, and dates.\n\n**Tokenize and Replace PII:** Adds simple context patterns for names and street addresses, then replaces detected values with tokens like `<<PII_EMAIL_7F3A>>`.\n\nFor official template use, review these patterns for your country, industry, and retention policy before processing regulated data."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-pii-vault",
      "name": "Vault and Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -608,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 220,
        "content": "## Vault metadata and GPT-5.5 analysis\n\n**Store Token Vault:** Appends session ID, token list, detected types, timestamp, and count to Google Sheets.\n\n**OpenAI Analyze Sanitized Text:** Sends only tokenized text to GPT-5.5 with a prompt that asks for sentiment, topics, intent, and summary without inferring masked values.\n\nThis template stores audit metadata, not a durable encrypted secrets vault. Use encrypted storage for stricter compliance requirements."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-pii-restore",
      "name": "Restore and Audit",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 596,
        "height": 220,
        "content": "## Restoration, audit, and response\n\n**Restore Original PII:** Uses the in-memory token vault from the current execution to replace tokens in the model output.\n\n**Log Audit Trail:** Records session ID, timestamp, PII count, detected types, and completion status.\n\n**Return Processed Document:** Sends a JSON response through Respond to Webhook.\n\nCustomize this step if different user roles should receive masked vs restored output."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Log Audit Trail": {
      "main": [
        [
          {
            "node": "Return Processed Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive Document": {
      "main": [
        [
          {
            "node": "Detect PII via Regex",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Token Vault": {
      "main": [
        [
          {
            "node": "OpenAI Analyze Sanitized Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect PII via Regex": {
      "main": [
        [
          {
            "node": "Tokenize and Replace PII",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Restore Original PII": {
      "main": [
        [
          {
            "node": "Log Audit Trail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tokenize and Replace PII": {
      "main": [
        [
          {
            "node": "Store Token Vault",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Analyze Sanitized Text": {
      "main": [
        [
          {
            "node": "Restore Original PII",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}