2026-03-18 15:09:51 +01:00

86 lines
2.9 KiB
JavaScript

/**
* Anonymize payload (folder_context, question) by replacing PII with placeholders.
* Returns anonymized payload and a mapping for recontextualization.
* Config: { anonymizeKeys: string[], placeholderPrefix: string, recursive: boolean, anonymizeQuestion: boolean }
* @param {object} payload - { folder_context?: object, question?: string, ... }
* @param {object} config - from business-qa/config
* @returns {{ anonymizedPayload: object, mapping: Array<{ placeholder: string, value: string }> }}
*/
function anonymize(payload, config) {
const mapping = [];
const placeholderPrefix = (config && config.placeholderPrefix) || "PII";
const anonymizeKeys = (config && config.anonymizeKeys) || [];
const recursive = config && config.recursive !== false;
const anonymizeQuestion = config && config.anonymizeQuestion !== false;
function nextPlaceholder() {
return `${placeholderPrefix}_${mapping.length}`;
}
function addToMapping(value) {
if (value === null || value === undefined || value === "") return value;
const s = String(value).trim();
if (!s) return value;
const existing = mapping.find((m) => m.value === s);
if (existing) return existing.placeholder;
const placeholder = nextPlaceholder();
mapping.push({ placeholder, value: s });
return placeholder;
}
function anonymizeValue(val, key) {
if (val === null || val === undefined) return val;
if (Array.isArray(val)) {
return val.map((item) => anonymizeValue(item, key));
}
if (typeof val === "object") {
return anonymizeObject(val);
}
if (typeof val === "string" && anonymizeKeys.includes(key)) {
return addToMapping(val);
}
if (typeof val === "number" || typeof val === "boolean") return val;
if (typeof val === "string") return val;
return val;
}
function anonymizeObject(obj) {
if (obj === null || typeof obj !== "object") return obj;
const out = {};
for (const [k, v] of Object.entries(obj)) {
const key = k;
if (recursive && (Array.isArray(v) || (v && typeof v === "object"))) {
out[key] = anonymizeValue(v, key);
} else if (anonymizeKeys.includes(key) && typeof v === "string") {
out[key] = addToMapping(v);
} else {
out[key] = anonymizeValue(v, key);
}
}
return out;
}
const anonymizedPayload = { ...payload };
if (payload.folder_context && typeof payload.folder_context === "object") {
anonymizedPayload.folder_context = anonymizeObject(payload.folder_context);
}
if (anonymizeQuestion && payload.question && typeof payload.question === "string") {
let q = payload.question;
for (const m of mapping) {
const re = new RegExp(escapeRegex(m.value), "g");
q = q.replace(re, m.placeholder);
}
anonymizedPayload.question = q;
}
return { anonymizedPayload, mapping };
}
function escapeRegex(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
module.exports = { anonymize };