This commit is contained in:
subhajit 2026-08-06 20:05:55 +05:30
parent 7404e3a3da
commit 7662689e00
5 changed files with 102 additions and 44 deletions

View File

@ -771,10 +771,19 @@ public function uploadTabScreenshot(Request $request, $id)
$screenshots = $interview->tab_switch_screenshots ?? [];
$screenshots[] = $screenshotEntry;
$aiDetails = $aiVerdict['summary'] . ' (' . round(($aiVerdict['confidence'] ?? 0.85) * 100) . '% Confidence - ' . ($aiVerdict['engine'] ?? 'AI Engine') . ')';
$hasAiTool = !empty($aiVerdict['is_cheating']) && !empty($aiVerdict['ai_tool_detected']);
$logType = ($reason === 'call_start') ? 'call_start_screenshot' : (($aiVerdict['is_cheating'] && str_contains(strtolower($aiVerdict['ai_tool_detected'] ?? ''), 'parakeet')) ? 'external_ai_detected' : 'tab_switch');
$logMsg = ($reason === 'call_start') ? '📸 CALL STARTED SYSTEM SNAPSHOT: System screen snapshot captured automatically when call started.' : ('🤖 AI INSPECTOR VERDICT: ' . $aiDetails);
if ($reason === 'call_start') {
$logType = 'call_start_screenshot';
$logMsg = '📸 CALL STARTED SYSTEM SNAPSHOT: System screen snapshot captured automatically when call started.';
} elseif ($hasAiTool) {
$logType = 'external_ai_detected';
$toolName = $aiVerdict['ai_tool_detected'];
$logMsg = "🤖 candidate might use external ai ({$toolName}): " . $aiVerdict['summary'];
} else {
$logType = 'tab_switch';
$logMsg = '📸 Candidate switched browser tab or window: System screen screenshot captured for interviewer & admin review.';
}
$logs = $interview->proctor_logs ?? [];
$logs[] = [

View File

@ -41,7 +41,7 @@ public function analyzeScreenshot(string $imagePathOrBase64): array
[
'parts' => [
[
'text' => "You are an AI Anti-Cheat Proctoring Security Inspector for live technical interviews. Analyze this candidate desktop screen capture. Look specifically for external AI assistant overlays (Parakeet AI, Final Round AI, Otter.ai, ChatGPT, Claude, GitHub Copilot, WhatsApp, cheat sheets, or unauthorized secondary coding windows). Respond ONLY with valid JSON in this exact structure: {\"is_cheating\": boolean, \"confidence\": number (0.0 to 1.0), \"ai_tool_detected\": string or null, \"summary\": string}"
'text' => "You are an AI Anti-Cheat Proctoring Security Inspector for live technical interviews. Analyze this candidate desktop screen capture. Look specifically for external AI assistant overlays (Parakeet AI, Final Round AI, Otter.ai, ChatGPT, Claude, GitHub Copilot, WhatsApp, cheat sheets, or unauthorized secondary coding windows). If an external AI assistant or cheat tool is open/visible, set is_cheating to true, specify ai_tool_detected with the name of the tool, and set summary to 'candidate might use external ai: ' followed by details. If no external AI tool is open, set is_cheating to false, ai_tool_detected to null, and summary to 'Candidate desktop screenshot captured. No external AI detected.' Respond ONLY with valid JSON in this exact structure: {\"is_cheating\": boolean, \"confidence\": number (0.0 to 1.0), \"ai_tool_detected\": string or null, \"summary\": string}"
],
[
'inline_data' => [
@ -64,11 +64,13 @@ public function analyzeScreenshot(string $imagePathOrBase64): array
$data = json_decode($text, true);
if (is_array($data)) {
$isCheating = (bool) ($data['is_cheating'] ?? false);
$aiTool = $data['ai_tool_detected'] ?? null;
return [
'is_cheating' => (bool) ($data['is_cheating'] ?? false),
'is_cheating' => $isCheating,
'confidence' => (float) ($data['confidence'] ?? 0.85),
'ai_tool_detected' => $data['ai_tool_detected'] ?? null,
'summary' => $data['summary'] ?? 'AI Screen Inspection completed.',
'ai_tool_detected' => $aiTool,
'summary' => $data['summary'] ?? ($isCheating ? 'candidate might use external ai' : 'AI Screen Inspection completed.'),
'engine' => 'Gemini Vision AI'
];
}
@ -78,13 +80,13 @@ public function analyzeScreenshot(string $imagePathOrBase64): array
}
}
// Fallback Heuristic Inspection Engine (Local Rule-based AI Classifier)
// Fallback Inspection Engine (Do not falsely claim AI cheating unless explicitly detected)
return [
'is_cheating' => true,
'confidence' => 0.92,
'ai_tool_detected' => 'External Desktop / App Switch Detected',
'summary' => 'Candidate switched away from assessment window. Desktop screen snapshot captured for interviewer review.',
'engine' => 'SingleLogin Anti-Cheat Heuristic AI'
'is_cheating' => false,
'confidence' => 0.85,
'ai_tool_detected' => null,
'summary' => 'Candidate desktop screenshot captured for interviewer & admin review. No external AI tool detected.',
'engine' => 'SingleLogin Anti-Cheat Inspection'
];
}

View File

@ -459,8 +459,8 @@
const code = e.browserEvent ? (e.browserEvent.code || '') : '';
if (isAlt || isMeta || (isCtrl && isShift) || code === 'F12') {
logViolation('external_ai_detected', `Parakeet AI / External AI Hotkey Intercepted in Editor (${code || e.keyCode}): Candidate activated external AI shortcut`);
captureAndUploadTabScreenshot();
logViolation('external_ai_detected', `candidate might use external ai: Intercepted AI hotkey shortcut in editor (${code || e.keyCode})`);
captureAndUploadTabScreenshot('external_ai_detected');
}
});
@ -757,25 +757,25 @@ function uploadTabScreenshotBlob(imageData, reason = 'tab_switch') {
}
});
// 2. Window Focus Loss / External AI Overlay Application Switch Detection
// 2. Window Focus Loss / Assessment Window Blur Detection
window.addEventListener('blur', function () {
if (isFocusSuppressed()) return;
const now = Date.now();
if (now - lastTabSwitchTime > 1000) {
lastTabSwitchTime = now;
logViolation('external_ai_detected', 'PARAKEET AI / EXTERNAL APP OVERLAY DETECTED: Assessment window lost focus (Candidate interacting with external application overlay)');
captureAndUploadTabScreenshot();
logViolation('tab_switch', 'Candidate assessment window lost focus');
captureAndUploadTabScreenshot('tab_switch');
}
});
// Mouse Viewport Departure (Candidate moving cursor outside window to interact with Parakeet AI or secondary app overlay)
// Mouse Viewport Departure (Candidate moving cursor outside window)
document.addEventListener('mouseleave', function () {
if (isFocusSuppressed()) return;
const now = Date.now();
if (now - lastTabSwitchTime > 2500) {
lastTabSwitchTime = now;
logViolation('external_ai_detected', 'Candidate cursor departed assessment window viewport (suspected external Parakeet AI overlay interaction)');
captureAndUploadTabScreenshot();
logViolation('tab_switch', 'Candidate cursor departed assessment window viewport');
captureAndUploadTabScreenshot('tab_switch');
}
});
@ -810,8 +810,8 @@ function uploadTabScreenshotBlob(imageData, reason = 'tab_switch') {
(key === 'f12' || code === 'f12' || key === 'f11' || code === 'f11');
if (isAiHotkey) {
logViolation('external_ai_detected', `Parakeet AI / External AI Hotkey Intercepted (${e.code || e.key}): Candidate activated external AI assistant overlay shortcut`);
captureAndUploadTabScreenshot();
logViolation('external_ai_detected', `candidate might use external ai: Intercepted AI assistant shortcut hotkey (${e.code || e.key})`);
captureAndUploadTabScreenshot('external_ai_detected');
}
}, true);
@ -823,8 +823,8 @@ function uploadTabScreenshotBlob(imageData, reason = 'tab_switch') {
if (node.nodeType === 1) { // Element node
const nodeStr = ((node.id || '') + ' ' + (node.className || '') + ' ' + (node.getAttribute('src') || '')).toLowerCase();
if (nodeStr.includes('parakeet') || nodeStr.includes('finalround') || nodeStr.includes('copilot') || nodeStr.includes('ai-assistant') || nodeStr.includes('otter')) {
logViolation('external_ai_detected', 'Parakeet AI / Extension Overlay Element Injected into DOM: ' + node.tagName);
captureAndUploadTabScreenshot();
logViolation('external_ai_detected', 'candidate might use external ai: Injected AI extension overlay element detected in DOM (' + node.tagName + ')');
captureAndUploadTabScreenshot('external_ai_detected');
}
}
}
@ -836,7 +836,7 @@ function uploadTabScreenshotBlob(imageData, reason = 'tab_switch') {
// 6. Picture-in-Picture / Floating Overlay Window Detector
setInterval(function() {
if (document.pictureInPictureElement) {
logViolation('external_ai_detected', 'External Picture-in-Picture Floating Window Active: Suspected AI assistant overlay window');
logViolation('external_ai_detected', 'candidate might use external ai: External Picture-in-Picture floating AI window active');
}
}, 6000);
@ -1623,13 +1623,6 @@ function startCandidateCameraStream() {
// Initialize candidate camera immediately on room load
startCandidateCameraStream();
// Prompt candidate to activate Entire Screen Proctoring Shield upon joining
setTimeout(() => {
if (!screenShareStream) {
promptScreenShareRequired();
}
}, 1500);
function candidateLogoutAction() {
if (typeof Swal !== 'undefined') {
@ -1803,8 +1796,8 @@ function initLiveOverlayDetector() {
const now = Date.now();
if (now - lastTabSwitchTime > 3000) {
lastTabSwitchTime = now;
logViolation('external_ai_detected', 'PARAKEET AI OVERLAY DETECTED: External desktop overlay active on screen!');
captureAndUploadTabScreenshot();
logViolation('tab_switch', 'Candidate window lost focus during active screen share');
captureAndUploadTabScreenshot('tab_switch');
}
}
} catch(e) {}
@ -1930,8 +1923,7 @@ function startScreenShare() {
btn.innerText = '🖥️ Share Screen with Interviewer';
btn.style.background = 'linear-gradient(135deg, #6366f1 0%, #0078d4 100%)';
}
logViolation('tab_switch', 'Candidate stopped screen sharing (proctoring screen share disabled)');
promptScreenShareRequired('Screen sharing was stopped. You must share your Entire Screen to continue.');
logViolation('tab_switch', 'Candidate stopped screen sharing');
};
initLiveOverlayDetector();
@ -1940,8 +1932,11 @@ function startScreenShare() {
endNativePrompt();
console.log('Screen sharing cancelled/denied:', err);
screenShareStream = null;
logViolation('tab_switch', 'Candidate denied or cancelled screen sharing permission prompt');
promptScreenShareRequired('Screen sharing permission was denied or cancelled.');
const btn = document.getElementById('share-screen-btn');
if (btn) {
btn.innerText = '🖥️ Share Screen with Interviewer';
btn.style.background = 'linear-gradient(135deg, #6366f1 0%, #0078d4 100%)';
}
});
}

View File

@ -1550,8 +1550,18 @@ function pollReviewData() {
if (totalViolations > acknowledgedViolationCount && !sosAutoTriggerDisabled) {
sosDismissed = false;
sosBtn.style.animation = 'sosPulse 1.2s infinite alternate';
sosBtn.innerText = '🚨 candidate might cheating';
const hasExternalAi = currentViolations.some(v => v.type === 'external_ai_detected');
const hasTabSwitch = currentViolations.some(v => v.type === 'tab_switch');
if (hasExternalAi) {
sosBtn.innerText = '🚨 candidate might use external ai';
sosBtn.style.background = 'linear-gradient(135deg, #ef4444 0%, #b91c1c 100%)';
} else if (hasTabSwitch) {
sosBtn.innerText = '📸 Candidate Switched Browser Tab';
sosBtn.style.background = 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)';
} else {
sosBtn.innerText = '🚨 Candidate Proctoring Violation Alert';
sosBtn.style.background = 'linear-gradient(135deg, #ef4444 0%, #b91c1c 100%)';
}
openSosModal();
}
} else {
@ -1565,9 +1575,15 @@ function openSosModal() {
let html = '';
if (activeViolations.length > 0) {
activeViolations.forEach(v => {
html += `<div style="margin-bottom: 8px;">
<strong>🚨 ${v.type.toUpperCase()}:</strong> ${v.details || 'Suspicious candidate behavior detected.'}
<div style="font-size: 0.65rem; color: #94a3b8;">Timestamp: ${new Date(v.timestamp).toLocaleTimeString()}</div>
let typeTitle = v.type.toUpperCase();
if (v.type === 'external_ai_detected') {
typeTitle = 'candidate might use external ai';
} else if (v.type === 'tab_switch') {
typeTitle = 'Candidate Switched Browser Tab';
}
html += `<div style="margin-bottom: 10px; padding: 8px; background: rgba(255,255,255,0.04); border-radius: 6px; border-left: 3px solid ${v.type === 'external_ai_detected' ? '#ef4444' : '#f59e0b'};">
<strong style="color: ${v.type === 'external_ai_detected' ? '#fca5a5' : '#fde047'};">🚨 ${typeTitle}:</strong> ${v.details || 'Suspicious candidate activity detected.'}
<div style="font-size: 0.65rem; color: #94a3b8; margin-top: 3px;">Timestamp: ${new Date(v.timestamp).toLocaleTimeString()}</div>
</div>`;
});
} else {

View File

@ -361,4 +361,40 @@ public function test_silent_proctoring_violations_and_multi_party_active_call_st
$activeCallsResp->assertStatus(200)
->assertJsonFragment(['submission_unique_id' => 'proctor-test-candidate_9555544444_1752000006']);
}
public function test_tab_switch_logging_and_external_ai_detection_behavior(): void
{
$interview = Interview::create([
'candidate_name' => 'Tab Switch Candidate',
'candidate_email' => 'tabswitch@gmail.com',
'candidate_phone' => '9998887776',
'temp_password' => 'Pass-999888',
'expires_at' => now()->addHours(2),
'language' => 'python',
'status' => 'in_progress',
'submission_unique_id' => 'tab-switch-candidate_9998887776_1752000099',
]);
// 1. Tab switch log violation should log type 'tab_switch'
$resp = $this->post(route('interview.violation', $interview->id), [
'type' => 'tab_switch',
'details' => 'Candidate switched browser tab or window',
]);
$resp->assertStatus(200);
$poll = $this->get(route('interview.poll', $interview->id));
$poll->assertStatus(200)
->assertJsonFragment(['type' => 'tab_switch', 'details' => 'Candidate switched browser tab or window']);
// 2. External AI hotkey violation should log type 'external_ai_detected' with meaningful message
$aiResp = $this->post(route('interview.violation', $interview->id), [
'type' => 'external_ai_detected',
'details' => 'candidate might use external ai: Intercepted AI assistant shortcut hotkey (KeyA)',
]);
$aiResp->assertStatus(200);
$poll2 = $this->get(route('interview.poll', $interview->id));
$poll2->assertStatus(200)
->assertJsonFragment(['type' => 'external_ai_detected']);
}
}