Розробка системи зберігання даних регулятора
Вимоги регулятора щодо зберігання даних — один із найбільш ігнорованих аспектів дотримання. Типові проблеми: дані зберігаються без шифрування, політика збереження не формалізована, неможливо швидко надати необхідні дані за запитом регулятора.
Вимоги до зберігання
FATF R11: зберігати документи KYC та записи транзакцій мінімум 5 років (деякі юрисдикції вимагають 7 або 10 років).
GDPR: дані не довше необхідного (конфлікт з FATF — вирішується через юридичну основу "юридичне зобов'язання" для AML даних).
MiCA / Ліцензії VASP: повна аудит-трасса всіх рішень, включаючи рішення про дотримання.
Архітектура сховища
interface RegulatorDataStore {
// Зберігання документів KYC
storeKYCDocument(params: {
userId: string;
documentType: "PASSPORT" | "DRIVING_LICENSE" | "UTILITY_BILL" | "SELFIE" | "OTHER";
fileContent: Buffer;
mimeType: string;
expiresAt?: Date; // період дійсності документа
retentionUntil: Date; // коли можна видалити
}): Promise<string>; // ID документа
// Зберігання рішення про дотримання
storeComplianceDecision(params: {
userId: string;
decisionType: "KYC_APPROVAL" | "KYC_REJECTION" | "RISK_UPGRADE" | "SAR_FILED" | "ACCOUNT_FROZEN";
decision: "APPROVED" | "REJECTED" | "ESCALATED";
rationale: string;
decidedBy: string; // співробітник або система
evidenceIds: string[]; // посилання на документи
}): Promise<string>;
// Експорт даних регулятора
generateRegulatoryExport(params: {
userId?: string;
dateRange?: { from: Date; to: Date };
dataTypes: string[];
}): Promise<RegulatorExport>;
}
Шифрування в стані спокою
Усі документи KYC зберігаються в зашифрованому вигляді. Управління ключами має вирішальне значення: якщо ключі втрачені, дані недоступні; якщо скомпрометовані — порушення.
class EncryptedDocumentStore {
private readonly KMS_KEY_ID = process.env.AWS_KMS_KEY_ID;
async store(userId: string, document: Buffer, metadata: DocumentMetadata): Promise<string> {
// Генеруємо ключ даних через AWS KMS
const { CiphertextBlob: encryptedDataKey, Plaintext: dataKey } =
await kms.generateDataKey({ KeyId: this.KMS_KEY_ID, KeySpec: "AES_256" }).promise();
// Шифруємо документ за допомогою ключа даних
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv("aes-256-gcm", dataKey, iv);
const encryptedDoc = Buffer.concat([cipher.update(document), cipher.final()]);
const authTag = cipher.getAuthTag();
// Зберігаємо зашифрований документ + зашифрований ключ даних
const docId = crypto.randomUUID();
await s3.putObject({
Bucket: process.env.KYC_BUCKET,
Key: `${userId}/${docId}`,
Body: encryptedDoc,
Metadata: {
"encrypted-data-key": encryptedDataKey.toString("base64"),
"iv": iv.toString("base64"),
"auth-tag": authTag.toString("base64"),
"user-id": userId,
"document-type": metadata.documentType,
"retention-until": metadata.retentionUntil.toISOString(),
},
}).promise();
// Зберігаємо зашифрований ключ даних у БД (не сам ключ даних!)
await db.saveDocumentRecord(docId, userId, metadata, encryptedDataKey.toString("base64"));
return docId;
}
async retrieve(docId: string): Promise<Buffer> {
const record = await db.getDocumentRecord(docId);
const s3Object = await s3.getObject({ Bucket: process.env.KYC_BUCKET, Key: `${record.userId}/${docId}` }).promise();
// Розшифровуємо ключ даних через KMS
const { Plaintext: dataKey } = await kms.decrypt({
CiphertextBlob: Buffer.from(record.encryptedDataKey, "base64"),
}).promise();
const iv = Buffer.from(s3Object.Metadata!["iv"], "base64");
const authTag = Buffer.from(s3Object.Metadata!["auth-tag"], "base64");
const decipher = crypto.createDecipheriv("aes-256-gcm", dataKey, iv);
decipher.setAuthTag(authTag);
await db.logAccess(docId, "READ"); // аудит-трасса
return Buffer.concat([decipher.update(s3Object.Body as Buffer), decipher.final()]);
}
}
Автоматизація політики збереження
// Щоденна перевірка даних, що закінчилися
@Cron("0 3 * * *")
async enforceRetentionPolicy() {
// Знаходимо документи з закінченим терміном збереження
const expiredDocs = await db.findExpiredDocuments();
for (const doc of expiredDocs) {
// Перевіряємо наявність активних юридичних затримань (регуляторне розслідування, судові позови)
const hasLegalHold = await db.checkLegalHold(doc.userId);
if (hasLegalHold) {
await db.extendRetention(doc.id, doc.userId, "LEGAL_HOLD");
continue;
}
// Безпечне видалення
await s3.deleteObject({ Bucket: process.env.KYC_BUCKET, Key: `${doc.userId}/${doc.id}` }).promise();
await db.markDocumentDeleted(doc.id, "RETENTION_EXPIRED");
this.logger.log(`Deleted expired document ${doc.id} for user ${doc.userId}`);
}
}
Запит регулятора (виробничий запит)
async function handleRegulatorRequest(request: RegulatorRequest): Promise<ExportPackage> {
const { userId, dateRange, requestedBy, legalBasis } = request;
// Логуємо запит (аудит-трасса)
await db.logRegulatorRequest(request);
const [kycDocs, transactions, amlDecisions, sars] = await Promise.all([
docStore.getKYCDocuments(userId),
db.getTransactions(userId, dateRange),
db.getComplianceDecisions(userId),
db.getSARs(userId),
]);
// Пакуємо у zip з маніфестом
const exportPackage = await createExportPackage({
userId,
requestedBy,
exportedAt: new Date(),
legalBasis,
contents: { kycDocs, transactions, amlDecisions, sars },
});
await db.logDataExport(userId, request.id, exportPackage.manifest);
return exportPackage;
}
Система зберігання з AES-256 шифруванням, управлінням ключів KMS, політикою збереження та експортом для регулятора — 2-3 тижні розробки.







