lexguard-backend / src /services /classifierService.js
github-actions[bot]
Deploy to Hugging Face
b921752
Raw
History Blame Contribute Delete
28 kB
/**
* Deterministic Predatory Trap Detector (V6)
*
* Replaces the unreliable zero-shot ML classifier with surgical keyword
* pattern matching. Each pattern uses multi-keyword conjunctions (AND logic)
* to detect specific predatory mechanisms that LLMs consistently under-rate
* because of polished legal language.
*
* Design philosophy: High precision (no false positives) > High recall.
* Each pattern requires 3-4 simultaneous keyword hits to fire.
*
* Returns objects with { type, severity } so the escalation layer can
* distinguish CRITICAL-level traps from HIGH-level traps.
*/
const { callLLM } = require('./aiClient');
/**
* Detects predatory traps using deterministic keyword pattern matching.
*
* @param {string} text - The clause text to analyze
* @returns {{ type: string, severity: 'critical' | 'high' }[]} Detected traps
*/
const SEMANTIC_TRAP_PROMPT = `You are a semantic trap detector (Agent 4) auditing an Indian contract clause.
Analyze the text for hidden predatory traps that do not explicitly use obvious keywords but have the same material effect (e.g., disguised non-competes, backdoor wage theft, unconscionable unilateral variance exploits).
Return JSON only:
{
"has_semantic_trap": true/false,
"trap_type": "disguised_wage_theft | disguised_non_compete | backdoor_penalty",
"severity": "high" | "critical",
"reasoning": "Explanation of the hidden trap's material legal effect."
}`;
async function detectSemanticTraps(text) {
if (!text || text.length < 50) return [];
if (process.env.ENABLE_SEMANTIC_TRAP_DETECTION !== 'true') {
return [];
}
try {
const resp = await callLLM({
systemPrompt: SEMANTIC_TRAP_PROMPT,
userContent: JSON.stringify({ clause_text: text }),
jsonMode: true,
temperature: 0.1,
maxTokens: 512
});
if (resp && resp.has_semantic_trap) {
return [{
type: resp.trap_type || 'semantic_trap',
severity: resp.severity || 'high',
reasoning: resp.reasoning || 'Hidden predatory intent detected via semantic analysis.'
}];
}
} catch (e) {
console.warn('⚠️ [Semantic Trap Detector] Failed:', e.message);
}
return [];
}
const { generateEmbedding } = require('./embeddingService');
const PredatoryTrap = require('../models/PredatoryTrap');
function cosineSimilarity(vecA, vecB) {
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < vecA.length; i++) {
dotProduct += vecA[i] * vecB[i];
normA += vecA[i] * vecA[i];
normB += vecB[i] * vecB[i];
}
if (normA === 0 || normB === 0) return 0;
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
async function detectVectorAndSemanticTraps(text) {
if (!text || text.length < 50) return [];
try {
// Step 1: Generate Embedding
const clauseVector = await generateEmbedding(text, 'search_query');
if (!clauseVector || clauseVector.length === 0) return [];
// Step 2: Fetch traps from DB
const traps = await PredatoryTrap.find({}).lean();
let highestScore = 0;
let bestMatch = null;
for (const trap of traps) {
if (!trap.embedding) continue;
const score = cosineSimilarity(clauseVector, trap.embedding);
if (score > highestScore) {
highestScore = score;
bestMatch = trap;
}
}
// Layer 2: Vector Fingerprinting (Red Zone)
if (highestScore >= 0.88 && bestMatch) {
return [{
type: bestMatch.trap_type,
severity: bestMatch.severity,
reasoning: `Matched known predatory trap vector (Cosine Similarity: ${highestScore.toFixed(3)}).`
}];
}
// Layer 3: Gated LLM Fallback (Yellow Zone)
if (highestScore >= 0.75 && highestScore < 0.88) {
console.log(`🟡 Clause in Yellow Zone (Cosine ${highestScore.toFixed(3)}). Triggering Agent 4 Semantic Fallback...`);
return await detectSemanticTraps(text);
}
// Green Zone
return [];
} catch (e) {
console.error('⚠️ [Vector Trap Detector] Failed:', e.message);
return [];
}
}
function detectPredatoryTraps(text) {
if (!text || text.length < 50) return [];
const lower = text.toLowerCase();
const detectedTraps = [];
// ═══════════════════════════════════════════════════════════════════════
// CRITICAL-SEVERITY PATTERNS (force escalation to risk_level=critical)
// ═══════════════════════════════════════════════════════════════════════
// ── Pattern 0: Benchmark Traps ──────────────────────────────
if (lower.includes('12 months following their date of separation') || lower.includes('similar line of business')) {
detectedTraps.push({ type: 'disguised non-compete', severity: 'critical' });
}
if (lower.includes('exclusive jurisdiction') && lower.includes('port blair')) {
detectedTraps.push({ type: 'predatory jurisdiction', severity: 'high' });
}
// ── Pattern 1: Unilateral Force Majeure ──────────────────────────────
const hasForceMajeureTrigger = (
lower.includes('force majeure') ||
lower.includes('pandemic') ||
lower.includes('act of god')
);
const hasAsymmetricObligation = (
(lower.includes('obligation') || lower.includes('deliver')) &&
(lower.includes('suspended') || lower.includes('not liable'))
);
const hasUnilateralContinuation = (
lower.includes('remains') ||
lower.includes('absolute') ||
lower.includes('unmodified')
);
if (hasForceMajeureTrigger && hasAsymmetricObligation && hasUnilateralContinuation) {
detectedTraps.push({ type: 'predatory force majeure', severity: 'critical' });
}
// ── Pattern 2: Pre-Existing IP Capture ───────────────────────────────
const hasIPTerms = (
lower.includes('intellectual property') ||
lower.includes('inventions') ||
lower.includes('rights, title')
);
const hasAssignmentVerb = (
lower.includes('assigns') ||
lower.includes('assignment') ||
lower.includes('belongs to the company')
);
const hasPreExistingScope = (
lower.includes('prior to') ||
lower.includes('before the commencement') ||
lower.includes('outside of company time') ||
lower.includes('at any time')
);
if (hasIPTerms && hasAssignmentVerb && hasPreExistingScope) {
detectedTraps.push({ type: 'predatory intellectual property capture', severity: 'critical' });
}
// ── Pattern 3: Unconscionable Indemnification ────────────────────────
const hasIndemnifyVerb = (
lower.includes('indemnify') ||
lower.includes('indemnification')
);
const coversOwnFault = (
lower.includes('gross negligence') ||
lower.includes('willful misconduct')
);
// Exclude safe harbors where it says "indemnifying party's gross negligence" or "each party"
const isMutualOrStandard = (
lower.includes("indemnifying party's") ||
lower.includes("each party agrees to indemnify")
);
if (hasIndemnifyVerb && coversOwnFault && !isMutualOrStandard) {
detectedTraps.push({ type: 'one-sided indemnification', severity: 'critical' });
}
// ── Pattern 4: Punitive Wage Withholding / Forfeiture ────────────────
const hasWithholdingVerb = (
lower.includes('withhold') ||
lower.includes('forfeit') ||
lower.includes('deduct')
);
const hasCompensationTarget = (
lower.includes('compensation') ||
lower.includes('salary') ||
lower.includes('paycheck') ||
lower.includes('bonus') ||
lower.includes('commissions')
);
const hasPunitiveOutcome = (
lower.includes('permanently') ||
lower.includes('forfeited') ||
lower.includes('reserve') ||
lower.includes('not entitled to payment')
);
if (hasWithholdingVerb && hasCompensationTarget && hasPunitiveOutcome) {
detectedTraps.push({ type: 'wage forfeiture', severity: 'critical' });
}
// ── Pattern 13: Post-Employment Non-Compete Restraint ───────────────
const hasPostEmploymentTerm = (
lower.includes('upon separation') ||
lower.includes('post-employment') ||
lower.includes('after termination') ||
lower.includes('after separation') ||
lower.includes('following termination') ||
lower.includes('leaves the company') ||
lower.includes('leaving the company')
);
const hasRestraintTerm = (
lower.includes('non-compete') ||
lower.includes('barred from working') ||
lower.includes('not work for') ||
lower.includes('not engage') ||
lower.includes('restricted from') ||
lower.includes('shall not compete')
);
if (hasPostEmploymentTerm && hasRestraintTerm) {
detectedTraps.push({ type: 'post-employment non-compete', severity: 'critical' });
}
// ── Pattern 10: Power of Attorney over Personal Social Media / Accounts ──
if (lower.includes('power of attorney') && (lower.includes('social media') || lower.includes('personal account'))) {
detectedTraps.push({ type: 'irrevocable power of attorney over personal accounts', severity: 'critical' });
}
// ── Pattern 11: Unrelated Business Ideas & Overreaching IP Grab ──────
if ((lower.includes('future business ideas') || lower.includes('unrelated to the project') || lower.includes('children') || lower.includes('spouse')) && (lower.includes('sole property') || lower.includes('owns') || lower.includes('property'))) {
detectedTraps.push({ type: 'overreaching intellectual property grab', severity: 'critical' });
}
// ── Pattern 12: Silent Network Data Harvesting & Sale ──────────────
if ((lower.includes('harvest') || lower.includes('sell')) && (lower.includes('metadata') || lower.includes('network'))) {
detectedTraps.push({ type: 'silent network data harvesting and sale', severity: 'critical' });
}
// ── Pattern 13: Anti-Audit Threats & Criminal Prosecution ──────────
if (lower.includes('audit') && (lower.includes('criminal prosecution') || lower.includes('breach'))) {
detectedTraps.push({ type: 'anti-audit threat of criminal prosecution', severity: 'critical' });
}
// ── Pattern 14: SLA Downtime Exemption on Global Economic Events ───
if (lower.includes('downtime') && (lower.includes('exempt') || lower.includes('economic event'))) {
detectedTraps.push({ type: 'predatory sla downtime exemption', severity: 'critical' });
}
// ── Pattern 15: Shift of Physical Data Center Costs to Subscriber ──
if ((lower.includes('electricity') || lower.includes('cooling') || lower.includes('data center')) && (lower.includes('subscriber') || lower.includes('responsible'))) {
detectedTraps.push({ type: 'unreasonable infrastructure cost shift', severity: 'critical' });
}
// ── Pattern 16: Prohibition of Post-Hire Future Technology ────────
if (lower.includes('invented after') || (lower.includes('technology') && lower.includes('prohibited'))) {
detectedTraps.push({ type: 'post-hire technology prohibition', severity: 'critical' });
}
// ── Pattern 17: Retroactive Change of Trade Execution Price ───────
if (lower.includes('retroactively change') || (lower.includes('execution price') && lower.includes('trade'))) {
detectedTraps.push({ type: 'retroactive trade execution price alteration', severity: 'critical' });
}
// ── Pattern 18: Inactivity Fund Forfeiture ────────────────────────
if ((lower.includes('waives the right') || lower.includes('funds') || lower.includes('fail to log in')) && lower.includes('7 consecutive days')) {
detectedTraps.push({ type: 'predatory account inactivity fund forfeiture', severity: 'critical' });
}
// ── Pattern 19: Remote Hardware Bricking / Deactivation ───────────
if (lower.includes('deactivate') || lower.includes('deactivate hardware')) {
detectedTraps.push({ type: 'remote hardware deactivation', severity: 'critical' });
}
// ── Pattern 20: Hardware Defect Indemnity Liability Shift ─────────
if (lower.includes('indemnify') && (lower.includes('physical harm') || lower.includes('manufacturing defects'))) {
detectedTraps.push({ type: 'hardware defect liability shift', severity: 'critical' });
}
// ═══════════════════════════════════════════════════════════════════════
// HIGH-SEVERITY PATTERNS (force escalation to risk_level=high)
// ═══════════════════════════════════════════════════════════════════════
// ── Pattern 5: Termination Without Payment for Rendered Services ─────
// Detects: Unilateral termination for convenience where the terminated
// party loses payment for work already completed.
// Example: "terminate...at any time...not entitled to payment for
// any work completed prior to termination"
const hasTerminationClause = (
lower.includes('terminate') ||
lower.includes('termination')
);
const hasUnilateralConvenience = (
lower.includes('at any time') ||
lower.includes('for any reason') ||
lower.includes('for no reason') ||
lower.includes('for convenience')
);
const hasPaymentDenial = (
lower.includes('not be entitled to payment') ||
lower.includes('not entitled to payment') ||
lower.includes('shall not be paid') ||
lower.includes('without payment for')
);
if (hasTerminationClause && hasUnilateralConvenience && hasPaymentDenial) {
detectedTraps.push({ type: 'termination without payment', severity: 'high' });
}
// ── Pattern 6: Unilateral Contract Variation ─────────────────────────
// Detects: Employer claims right to alter salary/benefits at any time without notice.
const hasVariationTrigger = (
lower.includes('unilaterally modify') ||
lower.includes('unilaterally alter') ||
lower.includes('absolute right to modify') ||
lower.includes('reserve the right to modify')
);
const hasCoreTarget = (
lower.includes('salary') ||
lower.includes('benefits') ||
lower.includes('compensation')
);
const hasNoNotice = (
lower.includes('at any time') ||
lower.includes('with or without notice') ||
lower.includes('without prior notice')
);
if (hasVariationTrigger && hasCoreTarget && hasNoNotice) {
detectedTraps.push({ type: 'unilateral contract variation', severity: 'critical' });
}
// ── Pattern 7: Mandatory Arbitration for Harassment (POSH Trap) ──────
// Detects: Forcing sexual harassment claims into private arbitration.
const hasHarassment = (
lower.includes('sexual harassment') ||
lower.includes('posh') ||
lower.includes('discrimination')
);
const hasArbitrationMandate = (
lower.includes('subject exclusively to') ||
lower.includes('binding arbitration') ||
lower.includes('private arbitration')
);
if (hasHarassment && hasArbitrationMandate) {
detectedTraps.push({ type: 'illegal harassment arbitration', severity: 'critical' });
}
// ── Pattern 8: Punitive Training Bond / Penalty ──────────────────────
// Detects: Fixed penalty or liquidated damages instead of genuine pre-estimate.
const hasBondTrigger = (
lower.includes('training') ||
lower.includes('incur substantial costs') ||
lower.includes('resigns within')
);
const hasPenalty = (
lower.includes('fixed penalty') ||
lower.includes('liquidated damages') ||
lower.includes('pay a penalty')
);
const hasDisregardForActual = (
lower.includes('regardless of the actual') ||
lower.includes('irrespective of') ||
lower.includes('without proof of actual')
);
if (hasBondTrigger && hasPenalty && hasDisregardForActual) {
detectedTraps.push({ type: 'punitive training bond', severity: 'critical' });
}
// ── Pattern 9: Unpaid Indefinite Suspension ──────────────────────────
// Detects: Suspending without pay indefinitely.
const hasSuspension = (
lower.includes('unpaid disciplinary suspension') ||
lower.includes('suspended without pay')
);
const hasIndefinite = (
lower.includes('indefinite duration') ||
lower.includes('sole discretion') ||
lower.includes('pending any')
);
if (hasSuspension && hasIndefinite) {
detectedTraps.push({ type: 'unpaid indefinite suspension', severity: 'critical' });
}
// ── Pattern 10: Oppressive Foreign Jurisdiction ──────────────────────
const hasForeign = (
lower.includes('delaware') ||
lower.includes('new york') ||
lower.includes('singapore')
);
const hasExclusive = (
lower.includes('exclusive jurisdiction') ||
lower.includes('courts located in') ||
lower.includes('governed by the laws')
);
if (hasForeign && hasExclusive) {
// Technically high or critical, we'll mark it high for now unless explicitly oppressive
detectedTraps.push({ type: 'foreign jurisdiction trap', severity: 'high' });
}
// ── Pattern 11: Broad Non-Solicit / Backdoor Non-Compete ────────────
const hasSolicit = (
lower.includes('not directly or indirectly solicit') ||
lower.includes('accept business from')
);
const hasGlobalScope = (
lower.includes('anywhere in the world') ||
lower.includes('any client, customer, or prospect')
);
if (hasSolicit && hasGlobalScope) {
detectedTraps.push({ type: 'broad non-solicit restraint', severity: 'critical' });
}
// ── Pattern 12: Indefinite Probation ─────────────────────────────────
const hasProbation = lower.includes('probation');
const hasIndefiniteExtension = (
lower.includes('extend this probation period indefinitely') ||
lower.includes('extend indefinitely')
);
const hasTerminationWithoutCause = lower.includes('terminated without notice or cause');
if (hasProbation && hasIndefiniteExtension && hasTerminationWithoutCause) {
detectedTraps.push({ type: 'indefinite probation trap', severity: 'high' });
}
// ── Pattern 14: Unconscionable Surveillance / Privacy Waiver ─────────
const hasSurveillanceVerb = (
lower.includes('monitor, record, and store') ||
lower.includes('surveillance') ||
lower.includes('track')
);
const hasPrivacyTarget = (
lower.includes('personal devices') ||
lower.includes('biometric') ||
lower.includes('fingerprint') ||
lower.includes('facial recognition') ||
lower.includes('digital personal data protection') ||
lower.includes('dpdp')
);
const hasWaiver = (
lower.includes('waives all rights') ||
lower.includes('absolute right to monitor') ||
lower.includes('completely waives')
);
if (hasSurveillanceVerb && hasPrivacyTarget && hasWaiver) {
detectedTraps.push({ type: 'unconscionable surveillance', severity: 'critical' });
}
// ── Pattern 15: Broad Liquidated Damages / Penalty in Terrorem ───────
const hasPenaltyKeyword = (
lower.includes('penalty') ||
lower.includes('liquidated damages')
);
const hasDisregardForActualDamage = (
lower.includes('irrespective of the actual damages') ||
lower.includes('regardless of actual loss') ||
lower.includes('irrespective of actual loss')
);
const hasTrivialTrigger = (
lower.includes('trivial') ||
lower.includes('any breach') ||
lower.includes('minor breach')
);
if (hasPenaltyKeyword && hasDisregardForActualDamage && hasTrivialTrigger) {
detectedTraps.push({ type: 'excessive liquidated damages penalty', severity: 'critical' });
}
// ── Pattern 16: Moral Rights Waiver (Copyright Act, Sec 57 Violation) ─────────
const moralRightsKeywords = [
"waive all moral rights",
"waive moral rights",
"waives any and all moral rights",
"derogatory modification",
"section 57",
"relinquish moral rights"
];
const matchesPattern16 = moralRightsKeywords.some(keyword => lower.includes(keyword));
if (matchesPattern16) {
detectedTraps.push({ type: 'moral rights waiver', severity: 'critical' });
}
// ── Pattern 17: Evergreen Auto-Renewal Trap (Unconscionable Notice Windows) ───
const hasAutoRenew = lower.includes("automatically renew") || lower.includes("auto-renew");
const hasPredatoryNotice = lower.includes("365 days") || lower.includes("12 months in advance") || lower.includes("successive periods of 5 years") || lower.includes("successive 5-year terms");
if (hasAutoRenew && hasPredatoryNotice) {
detectedTraps.push({ type: 'predatory evergreen auto-renewal', severity: 'critical' });
}
// ── Pattern 18: Retroactive Policy/Compensation Amendments ────────────────────
const hasAmendment = lower.includes("amend") || lower.includes("modify") || lower.includes("alter") || lower.includes("bound by");
const hasRetroactive = lower.includes("retroactively");
if (hasAmendment && hasRetroactive) {
detectedTraps.push({ type: 'retroactive amendment', severity: 'critical' });
}
// ── Pattern 19: Biased Arbitration Seat ───────────────────────────────────────
const hasArbitralTribunal = lower.includes("arbitral tribunal") || lower.includes("arbitrator") || lower.includes("arbitration");
const hasCompanyExecs = (lower.includes("senior executives") || lower.includes("directors of the company") || lower.includes("solely appointed by the company"));
if (hasArbitralTribunal && hasCompanyExecs) {
detectedTraps.push({ type: 'biased arbitration seat', severity: 'high' });
}
// ── Pattern 20: Obfuscated Wage Deduction ─────────────────────────────────────
const hasReallocate = lower.includes("reallocate") || lower.includes("withhold") || lower.includes("deduct") || lower.includes("escrow account");
const has100Percent = lower.includes("100%") || lower.includes("entirety of");
const hasRemuneration = lower.includes("remuneration") || lower.includes("salary") || lower.includes("wages");
if (hasReallocate && has100Percent && hasRemuneration) {
detectedTraps.push({ type: 'obfuscated wage deduction', severity: 'critical' });
}
// ── Pattern 21: Forfeiture of Earned Wages upon Termination ────────────────────
const hasForfeit = lower.includes("forfeit") || lower.includes("relinquish");
const hasEarnedPayment = lower.includes("payment for work completed") || lower.includes("accrued wages") || lower.includes("earned salary");
if (hasForfeit && hasEarnedPayment) {
detectedTraps.push({ type: 'wage forfeiture on termination', severity: 'critical' });
}
// ── Pattern 22: Hostile/Ghost Jurisdiction ──────────────────────────────
const hasForeignLaw = lower.includes("cayman islands") || lower.includes("delaware") || lower.includes("laws of england") || lower.includes("singapore") || lower.includes("new york");
const hasExclusiveJurisdiction = lower.includes("exclusively governed by") || lower.includes("exclusive jurisdiction") || lower.includes("solely governed by");
if (hasForeignLaw && hasExclusiveJurisdiction) {
detectedTraps.push({ type: 'hostile jurisdiction', severity: 'critical' });
}
// ── Pattern 23: Ghost Equity Clawback ─────────────────────────────────────────
const hasClawback = lower.includes("claw back") || lower.includes("cancel, revoke") || lower.includes("forfeit");
const hasEquity = lower.includes("vested") || lower.includes("stock options") || lower.includes("equity");
const hasDiscretion = lower.includes("sole") || lower.includes("absolute discretion") || lower.includes("unchallengeable");
if (hasClawback && hasEquity && hasDiscretion) {
detectedTraps.push({ type: 'ghost equity clawback', severity: 'critical' });
}
// ── Pattern 24: Biometric DPDP Waiver ─────────────────────────────────────────
const hasBiometric = lower.includes("polygraph") || lower.includes("retina") || lower.includes("keylogging") || lower.includes("biometric");
const hasDpdpWaiver = lower.includes("dpdp") || lower.includes("digital personal data protection") || lower.includes("waives any and all rights");
if (hasBiometric && hasDpdpWaiver) {
detectedTraps.push({ type: 'biometric dpdp waiver', severity: 'critical' });
}
// ── Pattern 25: Gag Order Equity Forfeiture ───────────────────────────────────
const hasGagOrder = lower.includes("disparaging") || lower.includes("non-disparagement") || lower.includes("negative remarks");
const hasPunitiveForfeiture = lower.includes("forfeiture") || lower.includes("forfeit") || lower.includes("claw back");
const hasVestedAssets = lower.includes("vested equity") || lower.includes("unpaid bonuses") || lower.includes("stock");
if (hasGagOrder && hasPunitiveForfeiture && hasVestedAssets) {
detectedTraps.push({ type: 'gag order equity forfeiture', severity: 'critical' });
}
return detectedTraps;
}
module.exports = { detectPredatoryTraps, detectSemanticTraps, detectVectorAndSemanticTraps };