Journal d'Audit
Le callback onAudit se déclenche après chaque appel à protect() — succès ou échec — avec une entrée de journal structurée complète. Utilisez-le pour l'analyse, la surveillance de la sécurité et les registres de conformité.
import pino from 'pino';
const logger = pino();
const guard = new Guardian({
onAudit: (entry) => {
logger.info({
type: 'ai_guard_audit',
requestId: entry.requestId,
timestamp: entry.timestamp,
duration: entry.durationMs,
passed: entry.passed,
blocked: entry.blockedBy,
pii: entry.meta.piiRedacted?.length ?? 0,
tokens: entry.meta.budget?.totalTokens,
cost: entry.meta.budget?.estimatedCostUSD,
});
},
});Structure de l'Entrée d'Audit
interface AuditEntry {
requestId: string; // ID de requête unique (UUIDv4)
timestamp: number; // Timestamp Unix (ms)
durationMs: number; // Temps d'exécution total du Guardian
passed: boolean; // La requête a-t-elle passé tous les contrôles ?
blockedBy: string | null; // Quel contrôle l'a bloquée (le cas échéant)
prompt: string | null; // Prompt d'origine (opt-in, voir ci-dessous)
response: string | null; // Réponse de l'IA (opt-in)
meta: {
piiRedacted?: PiiMatch[];
schemaRepairLevel?: 0 | 1 | 2 | 3;
injectionScore?: number;
canaryLeaked?: boolean;
contentPolicy?: ContentPolicyResult;
hallucination?: HallucinationResult;
budget?: BudgetUsage;
rateLimit?: RateLimitResult;
};
error?: {
code: string;
message: string;
};
}Contrôles de Confidentialité
Par défaut, les prompts et les réponses ne sont pas journalisés. Il faut les activer explicitement :
const guard = new Guardian({
onAudit: (entry) => logger.info(entry),
audit: {
logPrompt: false, // Par défaut — ne stocke pas les prompts bruts
logResponse: false, // Par défaut — ne stocke pas les réponses brutes
logPromptHash: true, // Stocke un hachage SHA-256 à la place (pour la déduplication)
},
});Enregistrer dans la Base de Données
const guard = new Guardian({
onAudit: async (entry) => {
await db.insert('ai_audit_log').values({
request_id: entry.requestId,
timestamp: new Date(entry.timestamp),
passed: entry.passed,
blocked_by: entry.blockedBy,
total_tokens: entry.meta.budget?.totalTokens ?? 0,
cost_usd: entry.meta.budget?.estimatedCostUSD ?? 0,
pii_count: entry.meta.piiRedacted?.length ?? 0,
});
},
});Envoyer vers un Service Externe
const guard = new Guardian({
onAudit: (entry) => {
// Datadog, Segment, PostHog, etc.
analytics.track('ai_request', {
blocked: !entry.passed,
duration_ms: entry.durationMs,
...entry.meta,
});
},
});