/** * integrity-guard.ts * * Extension: Integrity Guard * Triggers: on_input, before_agent_start * * Monitors user input for academic integrity risk patterns. * Skill routing (safe vs. graded) is driven by the active RubricSpec's * integrityProfile — set via setIntegrityProfile() when a domain package loads * its rubric. Falls back to Core defaults when no rubric is active: * safeSkills: [], gradedSkills: ["attempt"] * * Also reinforces graded-mode constraints on every agent turn when a session * has been flagged as graded (see graded-session.ts). */ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { PiAdapter } from "../pi-adapter"; import { getWorkspaceState } from "../workspace-detector"; import { isGradedModeActive, setGradedMode, incrementJailbreakCount, JAILBREAK_ESCALATION_THRESHOLD, } from "../graded-session"; import { loadConfig } from "./lib/config"; import { getActiveAgeGroup } from "./workspace-gate"; import { getAgeGroupProfile } from "./lib/age-group"; // ─── Types ───────────────────────────────────────────────────────────────── export type IntegrityRisk = "none" | "low" | "medium" | "high"; export interface IntegrityCheckResult { risk: IntegrityRisk; reason?: string; warning?: string; shouldAskUser?: boolean; isJailbreak?: boolean; } export interface PromptInjectionResult { detected: boolean; warning?: string; } // ─── Profile State ───────────────────────────────────────────────────────── // Skills that may always produce complete solutions (e.g. interview-prep, scaffolding). // Populated by domain packages via setIntegrityProfile() after loading their RubricSpec. let activeSafeSkills: string[] = []; // Skills whose entire output is treated as graded-assignment evaluation. let activeGradedSkills: string[] = ["attempt"]; export function setIntegrityProfile(profile: { safeSkills: string[]; gradedSkills: string[]; }): void { activeSafeSkills = profile.safeSkills; activeGradedSkills = profile.gradedSkills; } export function resetIntegrityProfile(): void { activeSafeSkills = []; activeGradedSkills = ["attempt"]; } // ─── Risk Signals ────────────────────────────────────────────────────────── // Jailbreak: attempts to override persona, system instructions, or identity. // These are checked before ALL other logic — no skill or safe-phrase bypass applies. const JAILBREAK_PATTERNS: RegExp[] = [ // "pretend you are/you're [something else]" — handle both full form and contraction /\bpretend\s+you(?:'?re|\s+are)\s+(not\s+\w+|a\s+(different|regular|normal|other|new|unrestricted)\s+|without\s+(rules?|restrictions?|guidelines?))/i, // "act as [something unrestricted/different]" — "a/an" article handled /\bact\s+as\s+(if\s+you\s+(are|were)\s+|(?:an?\s+)?(?:different|another|unrestricted|unfiltered))/i, // "you are now [some redefined identity]" — "helpful", "free", "different", etc. /\byou\s+are\s+now\s+(?:a\s+)?(?:different|another|unrestricted|unfiltered|free|helpful|new)\b/i, // Instruction overrides — specific objects (instructions/rules/etc.) /\bignore\s+(?:(?:your|all|previous|prior)\s+)+(?:instructions?|rules?|constraints?|guidelines?|restrictions?|system\s+prompt)/i, /\bforget\s+(?:(?:all|your)\s+)+(?:instructions?|training|rules?|guidelines?|constraints?)/i, /\boverride\s+(?:(?:your|all)\s+)+(?:instructions?|rules?|restrictions?|guidelines?)/i, /\bdisregard\s+(?:(?:your|all|previous|prior)\s+)+(?:instructions?|rules?|constraints?|guidelines?)/i, // Catch-all override phrases — "ignore everything", "forget all that", etc. // These skip the specific-object requirement and catch sweep-style overrides. /\bignore\s+(?:everything|all\s+(?:of\s+)?(?:that|this|the\s+above)|what\s+(?:I\s+said|you\s+know))\b/i, /\bforget\s+(?:everything|all\s+(?:of\s+)?(?:that|this)|what\s+(?:I\s+said|you\s+know))\b/i, /\bdisregard\s+(?:everything|all\s+(?:of\s+)?(?:that|this|the\s+above))\b/i, // Meta-jailbreak vocabulary /\bnew\s+(system\s+prompt|instructions?\s+are|rules?\s+are)\b/i, /\byour\s+(new\s+)?(instructions?|rules?|persona)\s+(is|are)\b/i, /\bdeveloper\s+mode\b/i, /\bdan\s+mode\b/i, /\bjailbreak\b/i, /\bdisable\s+(your\s+)?(safety|integrity|restrictions?|guidelines?)\b/i, ]; // Solution laundering: indirect requests that result in complete submission-ready code. // Checked after safe-skill bypass — laundering in a safe skill (e.g. interview-prep) is fine. const LAUNDERING_PATTERNS: RegExp[] = [ /\btranslate\s+(this\s+)?(pseudo[-\s]?code|outline|approach|algorithm|steps?)\s+(to|into)\s+(code|python|javascript|java|c\+\+|typescript|rust|go|kotlin)/i, /\bconvert\s+(this\s+)?(pseudo[-\s]?code|outline|approach|algorithm)\s+(to|into)\s+(actual\s+)?code/i, /\bimplement\s+(what\s+you\s+(just\s+)?(described|explained|suggested|outlined|gave\s+me)|the\s+(approach|algorithm|solution)\s+(we|you)\s+(discussed|described|outlined))/i, /\bnow\s+(just\s+)?(write|code|turn|convert)\s+(it|that|this)\s+(into|to)\s+code/i, /\b(turn|convert)\s+(the\s+)?(hints?|pseudocode|outline|approach|algorithm)\s+(you\s+(gave|provided|described)\s+me?\s+)?(to|into)\s+code/i, /\bjust\s+(code|implement|write)\s+(what|the\s+(solution|approach))\s+(we|you)\s+(talked\s+about|discussed|described)/i, /\bcode\s+(up\s+)?(the\s+)?(solution|approach|algorithm)\s+(we|you)\s+(discussed|described|outlined)/i, ]; // Patterns that indicate a code-generation request inside a graded skill session. // Distinct from general high-risk patterns because graded context is always // active — even innocuous-sounding "fix" requests cross the line. const GRADED_CODE_GEN_PATTERNS: RegExp[] = [ /\bfix\s+(this|it|my|the)\b/i, /\bcorrect\s+(this|it|my|the)\b/i, /\brewrite\b/i, /\bimprove\s+(this|my|the)\s+(code|solution|implementation)\b/i, /\bgive\s+me\s+(the|a)\s+(solution|answer|fix|correct\s+version)\b/i, /\bhow\s+(do|should)\s+I\s+(implement|write|solve|fix)\b/i, /\bcomplete\s+(this|my)\b/i, // "write the code", "write the full code", "write the complete solution", etc. // Allow optional adjectives (full/complete/entire/whole) between article and noun. /\bwrite\s+(?:(?:the|a|my)\s+)?(?:full|complete|entire|whole|actual|working|final)?\s*(?:solution|implementation|code|program)\b/i, /\b(?:produce|generate|create|type\s+out|output)\s+(?:(?:the|a|my)\s+)?(?:full|complete|entire|whole|actual|working|final)?\s*(?:solution|implementation|code|program)\b/i, /\bcan\s+you\s+(fix|rewrite|complete|solve|write|code|implement)\b/i, /\b(?:please\s+)?(?:just\s+)?(?:write|code|implement|finish)\s+(?:it|this|the\s+\w+)\s+for\s+me\b/i, ]; // High-risk: user is likely asking for submission-ready work const HIGH_RISK_PATTERNS: RegExp[] = [ /write.*(my|the)\s+(entire|whole|complete|full)\s+(assignment|homework|lab|project)/i, /do\s+my\s+(homework|assignment|lab|project)/i, /complete\s+(my|the)\s+(assignment|homework|lab|task)/i, /submit.*(this|it)\s+as\s+my\s+own/i, /just\s+give\s+me\s+the\s+(code|answer|solution)\s+(for|to)\s+my/i, /finish\s+my\s+(assignment|homework|lab)\s+for\s+me/i, ]; // Medium-risk: could be academic work, should clarify const MEDIUM_RISK_PATTERNS: RegExp[] = [ /\bassignment\b/i, /\bhomework\b/i, /\bdue\s+(tomorrow|tonight|today|friday|monday)\b/i, /\blab\s+\d+\b/i, /\bproject\s+\d+\b/i, /\bgraded\b/i, /\bsubmit\b/i, /\bprofessor\s+(wants|requires|said)\b/i, ]; // Safe patterns that override medium-risk (clearly practice/learning) const SAFE_OVERRIDE_PATTERNS: RegExp[] = [ /\bpractic/i, /\blearn\b/i, /\bunderstand\b/i, /\bjust\s+curious\b/i, /\bnot\s+for\s+grade/i, /\bpersonal\s+project\b/i, /\bside\s+project\b/i, /\bleetcode\b/i, ]; // Prompt injection patterns for content submitted via /attempt. // These are intentionally tighter than the chat-input jailbreak patterns because // submitted work is real student content. Broad patterns like /\bact\s+as\b/i // trigger on "the buffer acts as a cache" and /\byou\s+are\s+now\b/i triggers on // "you are now familiar with recursion" — both legitimate in essays and code. // The patterns below require an AI-identity context to keep false-positive rates low. const SUBMISSION_INJECTION_PATTERNS: RegExp[] = [ // Explicit instruction/rule overrides — specific enough to keep broad /\bignore\s+(?:previous|prior|all|your)\s+instructions?\b/i, /\bforget\s+(?:your|all)\s+(?:training|instructions?|rules?)\b/i, /\boverride\s+(?:your\s+)?(?:instructions?|rules?|safety)\b/i, /\bdisregard\s+(?:previous|your|all)\b/i, // Meta-block injection markers /\[SYSTEM\]/, /(?:^|\n)\s*new\s+instructions?\s*:/im, // "you are now [AI identity]" — identity noun required after optional article + modifier. // Catches: "you are now DAN", "you are now an unrestricted AI", "you are now a different assistant" // Misses: "you are now in the loop", "you are now familiar with recursion", "you are now ready" /\byou\s+are\s+now\s+(?:an?\s+)?(?:(?:different|new|another|unrestricted|unfiltered|uncensored|helpful|evil|free|jailbroken?)\s+)?(?:AI|bot|agent|chatbot|model|assistant|system|persona|mode|entity|DAN)\b/i, // "pretend you are [AI identity]" — identity noun required /\bpretend\s+you\s+are\s+(?:an?\s+)?(?:(?:different|another|unrestricted|unfiltered|uncensored)\s+)?(?:AI|bot|agent|model|assistant|system|persona|entity|DAN)\b/i, // "act as [AI identity]" — identity noun required; avoids "act as a responsible engineer", // "to act as a proxy", "the component acts as a cache" (third-person "acts" also excluded by \bact\b) /\bact\s+as\s+(?:an?\s+)?(?:(?:different|another|unrestricted|unfiltered|uncensored)\s+)?(?:AI|bot|agent|model|assistant|chatbot|persona|entity|DAN)\b/i, // Pisces internal eval block — embedding this in a submission attempts to forge a grading result. // The model may echo the submitted content including the block, causing a fake score write-back. /