Implement CyberSim-OS Phase 1 MVP simulation environment

This commit is contained in:
2026-08-23 21:53:39 -07:00
parent f077c83ec7
commit 9028325dfc
27 changed files with 5008 additions and 221 deletions
+123
View File
@@ -0,0 +1,123 @@
/**
* CyberSim OS - Document & Spreadsheet Viewer
*/
export class DocViewerApp {
constructor({ windowManager, eventBus }) {
this.wm = windowManager;
this.eventBus = eventBus;
}
getIconSvg() {
return `<svg viewBox="0 0 24 24" fill="none" stroke="#16a34a" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>`;
}
openDocument(file) {
const win = this.wm.createWindow({
id: `doc_${file.id}`,
title: `${file.name} - Document Viewer`,
iconSvg: this.getIconSvg(),
width: 820,
height: 600,
bodyContent: this.renderDocument(file)
});
this.eventBus.emit('DOC_VIEWED', {
target: file.name,
details: { fileId: file.id, type: file.type }
});
}
renderDocument(file) {
if (file.type === 'spreadsheet' && file.content) {
return `
<div class="doc-viewer-container">
<div class="doc-toolbar">
<div class="doc-title-info">
<span>📊 ${file.name}</span>
<span class="cs-badge cs-badge-success">Read-Only</span>
</div>
<div style="font-size:11px; color:#94a3b8;">Format: XLSX Tabular</div>
</div>
<div class="doc-canvas">
<div class="doc-page">
<h1>${file.content.title}</h1>
<p style="font-size:12px; color:#64748b; margin-bottom:14px;">Confidential — Internal Use Only — Prepared for NexaCore Leadership</p>
<table class="doc-table">
<thead>
<tr>
${file.content.headers.map(h => `<th>${h}</th>`).join('')}
</tr>
</thead>
<tbody>
${file.content.rows.map(row => `
<tr>
${row.map((cell, idx) => `<td style="${idx > 0 ? 'text-align:right;' : 'font-weight:500;'}">${cell}</td>`).join('')}
</tr>
`).join('')}
</tbody>
</table>
<div style="margin-top:20px; font-size:11px; color:#64748b;">
<strong>Notes:</strong> Operating margin buffer is currently aligned with Q2 audit targets. Prepared by Finance Ops.
</div>
</div>
</div>
</div>
`;
}
if (file.type === 'pdf' && file.content) {
return `
<div class="doc-viewer-container">
<div class="doc-toolbar">
<div class="doc-title-info">
<span>📑 ${file.name}</span>
<span class="cs-badge cs-badge-info">NexaCore Document</span>
</div>
<div style="font-size:11px; color:#94a3b8;">Format: PDF Document</div>
</div>
<div class="doc-canvas">
<div class="doc-page">
<h1>${file.content.title}</h1>
${file.content.sections ? file.content.sections.map(s => `
<h2>${s.heading}</h2>
<p>${s.text}</p>
`).join('') : ''}
${file.content.table ? `
<table class="doc-table" style="margin-top:16px;">
<thead>
<tr>
${file.content.table.headers.map(h => `<th>${h}</th>`).join('')}
</tr>
</thead>
<tbody>
${file.content.table.rows.map(r => `
<tr>
${r.map(c => `<td>${c}</td>`).join('')}
</tr>
`).join('')}
</tbody>
</table>
` : ''}
</div>
</div>
</div>
`;
}
return `
<div class="doc-viewer-container">
<div class="doc-toolbar">
<div class="doc-title-info">${file.name}</div>
</div>
<div class="doc-canvas">
<div class="doc-page">
<h2>Archive Preview</h2>
<p>Archive file "${file.name}" contents cannot be executed directly within simulated document viewer.</p>
</div>
</div>
</div>
`;
}
}
+124
View File
@@ -0,0 +1,124 @@
/**
* CyberSim OS - Files Virtual File Explorer
*/
export class FilesApp {
constructor({ windowManager, eventBus, notifications, scenario, onOpenFile }) {
this.wm = windowManager;
this.eventBus = eventBus;
this.notifications = notifications;
this.scenario = scenario;
this.onOpenFile = onOpenFile;
this.currentFolder = 'Documents';
this.files = JSON.parse(JSON.stringify(scenario.files));
}
getIconSvg() {
return `<svg viewBox="0 0 24 24" fill="none" stroke="#eab308" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>`;
}
launch(initialFolder = 'Documents') {
this.currentFolder = initialFolder;
const win = this.wm.createWindow({
id: 'files',
title: 'Files - Corporate Storage',
iconSvg: this.getIconSvg(),
width: 780,
height: 500,
bodyContent: this.renderShell()
});
this.bindEvents(win.bodyElement);
this.renderFolderContents();
this.eventBus.emit('APP_OPENED', { target: 'files' });
}
renderShell() {
return `
<div class="files-container">
<div class="files-sidebar">
<div class="files-folder-btn active" data-folder="Documents">
<span>[Docs] Documents</span>
</div>
<div class="files-folder-btn" data-folder="Downloads">
<span>[Down] Downloads</span>
</div>
<div class="files-folder-btn" data-folder="Company Shared">
<span>[Share] Company Shared</span>
</div>
</div>
<div class="files-content">
<div class="files-address-bar" id="files-current-path">Location: /Documents</div>
<div class="files-grid" id="files-grid"></div>
</div>
</div>
`;
}
bindEvents(container) {
container.querySelectorAll('.files-folder-btn').forEach(btn => {
btn.addEventListener('click', () => {
container.querySelectorAll('.files-folder-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
this.currentFolder = btn.dataset.folder;
this.renderFolderContents();
});
});
}
addFile(fileObj) {
this.files.push(fileObj);
this.renderFolderContents();
}
renderFolderContents() {
const pathEl = document.getElementById('files-current-path');
const gridEl = document.getElementById('files-grid');
if (pathEl) pathEl.innerText = `Location: /${this.currentFolder}`;
if (!gridEl) return;
const filtered = this.files.filter(f => f.folder === this.currentFolder);
if (filtered.length === 0) {
gridEl.innerHTML = `<div style="grid-column:1/-1; padding:40px; text-align:center; color:#94a3b8;">This folder is empty.</div>`;
return;
}
gridEl.innerHTML = filtered.map(f => {
let icon = 'FILE';
if (f.type === 'spreadsheet') icon = 'XLSX';
if (f.type === 'pdf') icon = 'PDF';
if (f.type === 'archive') icon = 'ZIP';
if (f.type === 'executable') icon = 'EXE';
return `
<div class="file-item" data-file-id="${f.id}" title="${f.name} (${f.size})">
<div style="font-weight:700; font-size:18px; padding:8px 0; color:#2563eb;">[${icon}]</div>
<div class="file-name">${f.name}</div>
<div class="file-size">${f.size}</div>
</div>
`;
}).join('');
gridEl.querySelectorAll('.file-item').forEach(item => {
item.addEventListener('click', () => {
gridEl.querySelectorAll('.file-item').forEach(i => i.classList.remove('selected'));
item.classList.add('selected');
});
item.addEventListener('dblclick', () => {
const fileId = item.dataset.fileId;
const fileObj = this.files.find(f => f.id === fileId);
if (fileObj) {
this.eventBus.emit('FILE_OPENED', {
target: fileObj.name,
details: { fileId: fileObj.id, folder: fileObj.folder, type: fileObj.type }
});
if (this.onOpenFile) {
this.onOpenFile(fileObj);
}
}
});
});
}
}
+417
View File
@@ -0,0 +1,417 @@
/**
* CyberSim OS - Inlook Email Application
*/
export class InlookApp {
constructor({ windowManager, eventBus, notifications, scenario, onNavigateUrl, onOpenDoc, onFileDownloaded }) {
this.wm = windowManager;
this.eventBus = eventBus;
this.notifications = notifications;
this.scenario = scenario;
this.onNavigateUrl = onNavigateUrl;
this.onOpenDoc = onOpenDoc;
this.onFileDownloaded = onFileDownloaded;
this.currentFolder = 'inbox';
this.activeEmailId = null;
this.emails = JSON.parse(JSON.stringify(scenario.emails));
}
getIconSvg() {
return `<svg viewBox="0 0 24 24" fill="none" stroke="#2563eb" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>`;
}
launch() {
const win = this.wm.createWindow({
id: 'inlook',
title: 'Inlook Mail - NexaCore Workplace',
iconSvg: this.getIconSvg(),
width: 860,
height: 560,
bodyContent: this.renderShell()
});
this.bindEvents(win.bodyElement);
this.selectFirstEmail();
this.eventBus.emit('APP_OPENED', { target: 'inlook' });
}
renderShell() {
return `
<div class="inlook-container">
<div class="inlook-sidebar">
<button class="cs-btn cs-btn-primary cs-btn-sm" style="margin-bottom:8px;" id="inlook-compose-btn">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14M5 12h14"/></svg>
New Message
</button>
<div class="inlook-folder-item active" data-folder="inbox">
<span>📥 Inbox</span>
<span class="cs-badge cs-badge-info" id="inlook-unread-count">0</span>
</div>
<div class="inlook-folder-item" data-folder="sent">
<span>📤 Sent</span>
</div>
<div class="inlook-folder-item" data-folder="trash">
<span>🗑️ Trash</span>
</div>
</div>
<div class="inlook-list-pane" id="inlook-email-list"></div>
<div class="inlook-reading-pane" id="inlook-reading-pane">
<div style="padding:40px; text-align:center; color:#94a3b8; margin:auto;">
Select an email message to view its contents.
</div>
</div>
</div>
`;
}
bindEvents(container) {
container.querySelectorAll('.inlook-folder-item').forEach(el => {
el.addEventListener('click', () => {
container.querySelectorAll('.inlook-folder-item').forEach(f => f.classList.remove('active'));
el.classList.add('active');
this.currentFolder = el.dataset.folder;
this.renderEmailList();
});
});
const composeBtn = container.querySelector('#inlook-compose-btn');
if (composeBtn) {
composeBtn.addEventListener('click', () => {
this.notifications.show({
title: 'Inlook Mail',
body: 'Corporate policy restricts unassigned outgoing mail during initial shift orientation.',
type: 'info'
});
});
}
this.renderEmailList();
}
renderEmailList() {
const listEl = document.getElementById('inlook-email-list');
if (!listEl) return;
const filtered = this.emails.filter(e => e.folder === this.currentFolder);
const unread = this.emails.filter(e => e.folder === 'inbox' && e.unread).length;
const badge = document.getElementById('inlook-unread-count');
if (badge) badge.innerText = unread;
if (filtered.length === 0) {
listEl.innerHTML = `<div style="padding:20px; text-align:center; color:#94a3b8; font-size:12px;">No messages in ${this.currentFolder}</div>`;
return;
}
listEl.innerHTML = filtered.map(email => `
<div class="inlook-list-item ${email.unread ? 'unread' : ''} ${email.id === this.activeEmailId ? 'active' : ''}" data-email-id="${email.id}">
<div class="inlook-item-header">
<span class="inlook-item-sender">${email.sender.split('(')[0]}</span>
<span class="inlook-item-date">${email.date}</span>
</div>
<div class="inlook-item-subject">${email.subject}</div>
<div class="inlook-item-snippet">${email.body.replace(/<[^>]*>?/gm, '').substring(0, 48)}...</div>
</div>
`).join('');
listEl.querySelectorAll('.inlook-list-item').forEach(item => {
item.addEventListener('click', () => {
const id = item.dataset.emailId;
this.openEmail(id);
});
});
}
selectFirstEmail() {
const inboxEmails = this.emails.filter(e => e.folder === 'inbox');
if (inboxEmails.length > 0) {
this.openEmail(inboxEmails[0].id);
}
}
openEmail(id) {
const email = this.emails.find(e => e.id === id);
if (!email) return;
this.activeEmailId = id;
email.unread = false;
this.renderEmailList();
this.eventBus.emit('EMAIL_OPENED', {
target: id,
details: { subject: email.subject, rfcSender: email.rfcSender, isThreat: !!email.isThreat }
});
const readingPane = document.getElementById('inlook-reading-pane');
if (!readingPane) return;
readingPane.innerHTML = `
<div class="inlook-toolbar">
<button class="cs-btn cs-btn-sm" id="btn-inlook-reply">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 17 4 12 9 7"/><path d="M20 18v-2a4 4 0 0 0-4-4H4"/></svg> Reply
</button>
<button class="cs-btn cs-btn-danger cs-btn-sm" id="btn-inlook-report">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4"/><path d="M12 17h.01"/><path d="M3.6 9h16.8L12 21 3.6 9z"/></svg> Report Suspicious
</button>
<button class="cs-btn cs-btn-sm" id="btn-inlook-delete">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg> Delete
</button>
</div>
<div class="inlook-msg-header">
<div class="inlook-msg-subject">${email.subject}</div>
<div class="inlook-sender-row">
<div class="inlook-sender-box">
<div class="inlook-sender-avatar">${email.sender.charAt(0)}</div>
<div class="inlook-sender-details">
<div class="sender-name">${email.sender}</div>
<div class="sender-email">${email.rfcSender}</div>
</div>
<button class="inlook-inspect-btn" id="btn-inspect-header">Inspect Header</button>
</div>
<div style="font-size:11px; color:#94a3b8;">${email.date}</div>
</div>
${email.attachments && email.attachments.length > 0 ? `
<div class="inlook-attachments">
<span style="font-size:11px; font-weight:600; color:#64748b;">Attachments (${email.attachments.length}):</span>
${email.attachments.map(att => `
<div class="inlook-attachment-chip" data-att-name="${att.name}" data-att-size="${att.size}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg>
<span>${att.name}</span>
<span style="color:#94a3b8; font-size:10px;">(${att.size})</span>
</div>
`).join('')}
</div>
` : ''}
</div>
<div class="inlook-msg-body">
${email.body.replace(/\n/g, '<br>')}
</div>
`;
this.bindEmailActions(email);
}
bindEmailActions(email) {
const inspectBtn = document.getElementById('btn-inspect-header');
if (inspectBtn) {
inspectBtn.addEventListener('click', () => {
this.eventBus.emit('EMAIL_INSPECTED_SENDER', {
target: email.id,
details: { displaySender: email.sender, rfcSender: email.rfcSender }
});
const isDomainMismatch = email.rfcSender.includes('nexac0re-portal.com') || email.rfcSender.includes('apex-global-supplies.net');
alert(`--- Inlook RFC Header Inspector ---\n\nDisplay Name: ${email.sender}\nEnvelope RFC From: <${email.rfcSender}>\nAuthentication-Results: spf=pass (domain: ${email.rfcSender.split('@')[1]})\nReturn-Path: <bounce@${email.rfcSender.split('@')[1]}>\n\n${isDomainMismatch ? '⚠️ Notice: Envelope domain differs from standard corporate (@nexacore.internal).' : '✓ Envelope domain matches internal enterprise domain.'}`);
});
}
const links = document.querySelectorAll('.inlook-msg-link');
links.forEach(link => {
const actualUrl = link.dataset.url;
const displayText = link.dataset.display || link.innerText;
link.title = `Simulated Target: ${actualUrl}`;
link.addEventListener('mouseenter', () => {
this.eventBus.emit('EMAIL_LINK_HOVERED', {
target: email.id,
details: { displayText, actualUrl }
});
});
link.addEventListener('click', (e) => {
e.preventDefault();
this.eventBus.emit('EMAIL_LINK_CLICKED', {
target: email.id,
details: { displayText, actualUrl, isPhishing: actualUrl.includes('nexac0re-portal.com') }
});
if (this.onNavigateUrl) {
this.onNavigateUrl(actualUrl);
}
});
});
const attChips = document.querySelectorAll('.inlook-attachment-chip');
attChips.forEach(chip => {
chip.addEventListener('click', () => {
const attName = chip.dataset.attName;
const attSize = chip.dataset.attSize;
this.eventBus.emit('EMAIL_ATTACHMENT_OPENED', {
target: email.id,
details: { attachmentName: attName, attachmentSize: attSize }
});
if (this.onFileDownloaded) {
this.onFileDownloaded({
id: `down_${Date.now()}`,
name: attName,
type: 'archive',
folder: 'Downloads',
size: attSize,
date: new Date().toISOString().split('T')[0]
});
}
this.notifications.show({
title: 'Download Complete',
body: `File "${attName}" saved to Downloads folder.`,
type: 'warning'
});
});
});
const reportBtn = document.getElementById('btn-inlook-report');
if (reportBtn) {
reportBtn.addEventListener('click', () => {
this.openReportDialog(email);
});
}
const replyBtn = document.getElementById('btn-inlook-reply');
if (replyBtn) {
replyBtn.addEventListener('click', () => {
this.openReplyDialog(email);
});
}
const deleteBtn = document.getElementById('btn-inlook-delete');
if (deleteBtn) {
deleteBtn.addEventListener('click', () => {
email.folder = 'trash';
this.renderEmailList();
this.eventBus.emit('EMAIL_DELETED', {
target: email.id,
details: { subject: email.subject, wasThreat: !!email.isThreat }
});
this.selectFirstEmail();
this.notifications.show({
title: 'Inlook Mail',
body: 'Message moved to Trash.',
type: 'info'
});
});
}
}
openReportDialog(email) {
const modalOverlay = document.createElement('div');
modalOverlay.className = 'cs-modal-overlay';
modalOverlay.innerHTML = `
<div class="cs-modal">
<div class="cs-modal-header">
<div class="cs-modal-title">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 9v4"/><path d="M12 17h.01"/><path d="M3.6 9h16.8L12 21 3.6 9z"/></svg>
Report Suspicious Message to SOC
</div>
<button class="cs-btn-win close" id="btn-close-modal">&times;</button>
</div>
<div class="cs-modal-body">
<p style="margin-bottom:12px;">You are about to report the following email to the NexaCore Security Operations Center:</p>
<div style="background:#f1f5f9; padding:10px; border-radius:4px; font-size:12px; margin-bottom:14px;">
<strong>Subject:</strong> ${email.subject}<br>
<strong>Sender:</strong> ${email.rfcSender}
</div>
<div class="cs-form-group">
<label class="cs-form-label">Select Primary Threat Reason:</label>
<select class="cs-select" id="report-reason">
<option value="spoofed_sender">Spoofed or lookalike sender domain</option>
<option value="deceptive_link">Deceptive / mismatched link destination</option>
<option value="suspicious_attachment">Unsolicited or executable attachment</option>
<option value="credential_harvest">Credential theft / artificial urgency</option>
<option value="general_spam">General unsolicited spam</option>
</select>
</div>
<div class="cs-form-group">
<label class="cs-form-label">Investigative Notes (Optional):</label>
<textarea class="cs-textarea" id="report-notes" rows="2" placeholder="e.g. Lookalike domain nexac0re-portal.com detected"></textarea>
</div>
</div>
<div class="cs-modal-footer">
<button class="cs-btn" id="btn-cancel-report">Cancel</button>
<button class="cs-btn cs-btn-danger" id="btn-submit-report">Submit Incident Report</button>
</div>
</div>
`;
document.body.appendChild(modalOverlay);
const close = () => modalOverlay.remove();
modalOverlay.querySelector('#btn-close-modal').addEventListener('click', close);
modalOverlay.querySelector('#btn-cancel-report').addEventListener('click', close);
modalOverlay.querySelector('#btn-submit-report').addEventListener('click', () => {
const reason = modalOverlay.querySelector('#report-reason').value;
const notes = modalOverlay.querySelector('#report-notes').value;
this.eventBus.emit('EMAIL_REPORTED', {
target: email.id,
details: { reason, notes, isThreat: !!email.isThreat, threatId: email.threatId || null }
});
email.folder = 'trash';
this.renderEmailList();
this.selectFirstEmail();
close();
this.notifications.show({
title: 'Security Center Report Acknowledged',
body: `Incident report for "${email.subject.substring(0, 30)}..." received by SOC.`,
type: 'success'
});
});
}
openReplyDialog(email) {
const modalOverlay = document.createElement('div');
modalOverlay.className = 'cs-modal-overlay';
modalOverlay.innerHTML = `
<div class="cs-modal">
<div class="cs-modal-header">
<div class="cs-modal-title">Reply to: ${email.sender}</div>
<button class="cs-btn-win close" id="btn-close-reply">&times;</button>
</div>
<div class="cs-modal-body">
<div class="cs-form-group">
<label class="cs-form-label">To:</label>
<input type="text" class="cs-input" value="${email.rfcSender}" readonly>
</div>
<div class="cs-form-group">
<label class="cs-form-label">Subject:</label>
<input type="text" class="cs-input" value="Re: ${email.subject}" readonly>
</div>
<div class="cs-form-group">
<label class="cs-form-label">Response Message:</label>
<textarea class="cs-textarea" id="reply-text" rows="4" placeholder="Type your workplace response here..."></textarea>
</div>
</div>
<div class="cs-modal-footer">
<button class="cs-btn" id="btn-cancel-reply">Cancel</button>
<button class="cs-btn cs-btn-primary" id="btn-send-reply">Send Reply</button>
</div>
</div>
`;
document.body.appendChild(modalOverlay);
const close = () => modalOverlay.remove();
modalOverlay.querySelector('#btn-close-reply').addEventListener('click', close);
modalOverlay.querySelector('#btn-cancel-reply').addEventListener('click', close);
modalOverlay.querySelector('#btn-send-reply').addEventListener('click', () => {
const text = modalOverlay.querySelector('#reply-text').value;
this.eventBus.emit('EMAIL_REPLIED', {
target: email.id,
details: { responseText: text, recipient: email.rfcSender }
});
close();
this.notifications.show({
title: 'Inlook Mail',
body: `Reply sent to ${email.sender.split('(')[0]}.`,
type: 'success'
});
});
}
}
+185
View File
@@ -0,0 +1,185 @@
/**
* CyberSim OS - Navigator Simulated Web Browser
*/
export class NavigatorApp {
constructor({ windowManager, eventBus, notifications, scenario }) {
this.wm = windowManager;
this.eventBus = eventBus;
this.notifications = notifications;
this.scenario = scenario;
this.currentUrl = 'http://intranet.nexacore.internal';
this.history = [this.currentUrl];
this.historyIndex = 0;
}
getIconSvg() {
return `<svg viewBox="0 0 24 24" fill="none" stroke="#0284c7" stroke-width="2"><circle cx="12" cy="12" r="10"/><polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76"/></svg>`;
}
launch(initialUrl = null) {
const url = initialUrl || this.currentUrl;
const win = this.wm.createWindow({
id: 'navigator',
title: 'Navigator Web Browser',
iconSvg: this.getIconSvg(),
width: 880,
height: 580,
bodyContent: this.renderShell()
});
this.bindEvents(win.bodyElement);
this.navigateTo(url);
this.eventBus.emit('APP_OPENED', { target: 'navigator' });
}
renderShell() {
return `
<div class="nav-container">
<div class="nav-toolbar">
<div class="nav-buttons">
<button class="nav-btn-icon" id="nav-btn-back" title="Back">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
</button>
<button class="nav-btn-icon" id="nav-btn-forward" title="Forward">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>
</button>
<button class="nav-btn-icon" id="nav-btn-reload" title="Reload">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>
</button>
</div>
<div class="nav-address-bar secure" id="nav-addr-bar">
<div class="nav-lock-icon" id="nav-lock-icon">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
</div>
<input type="text" class="nav-url-input" id="nav-url-input" value="${this.currentUrl}">
</div>
</div>
<div class="nav-viewport" id="nav-viewport"></div>
</div>
`;
}
bindEvents(container) {
const urlInput = container.querySelector('#nav-url-input');
const backBtn = container.querySelector('#nav-btn-back');
const forwardBtn = container.querySelector('#nav-btn-forward');
const reloadBtn = container.querySelector('#nav-btn-reload');
urlInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
this.navigateTo(urlInput.value.trim());
}
});
backBtn.addEventListener('click', () => {
if (this.historyIndex > 0) {
this.historyIndex--;
this.navigateTo(this.history[this.historyIndex], false);
}
});
forwardBtn.addEventListener('click', () => {
if (this.historyIndex < this.history.length - 1) {
this.historyIndex++;
this.navigateTo(this.history[this.historyIndex], false);
}
});
reloadBtn.addEventListener('click', () => {
this.navigateTo(this.currentUrl, false);
});
}
navigateTo(url, recordHistory = true) {
this.currentUrl = url;
if (recordHistory) {
this.history = this.history.slice(0, this.historyIndex + 1);
this.history.push(url);
this.historyIndex = this.history.length - 1;
}
const urlInput = document.getElementById('nav-url-input');
const addrBar = document.getElementById('nav-addr-bar');
const lockIcon = document.getElementById('nav-lock-icon');
const viewport = document.getElementById('nav-viewport');
if (urlInput) urlInput.value = url;
const isSecure = url.startsWith('https://') || url.includes('.nexacore.internal');
const isPhish = url.includes('nexac0re-portal.com');
if (addrBar) {
addrBar.className = `nav-address-bar ${isSecure ? 'secure' : 'insecure'}`;
}
if (lockIcon) {
lockIcon.style.color = isSecure ? '#10b981' : '#f59e0b';
}
this.eventBus.emit('NAV_VISITED', {
target: url,
details: { isSecure, isPhishing: isPhish }
});
const page = this.scenario.pages.find(p => p.url === url) || this.scenario.pages.find(p => url.startsWith(p.url));
if (viewport) {
if (page) {
viewport.innerHTML = page.content;
this.bindPageLinks(viewport);
} else {
viewport.innerHTML = `
<div style="padding:60px 20px; text-align:center;">
<h2 style="color:#64748b; font-size:18px; margin-bottom:8px;">404 Page Not Found</h2>
<p style="color:#94a3b8; font-size:13px;">The simulated URL <code>${url}</code> was not found on this simulation network.</p>
<button class="cs-btn cs-btn-primary" style="margin-top:16px;" onclick="window.CyberSimOS.navigator.navigateTo('http://intranet.nexacore.internal')">Go to Intranet Home</button>
</div>
`;
}
}
}
bindPageLinks(viewport) {
viewport.querySelectorAll('a').forEach(a => {
a.addEventListener('click', (e) => {
const href = a.getAttribute('href');
if (href && (href.startsWith('http://') || href.startsWith('https://'))) {
e.preventDefault();
this.navigateTo(href);
}
});
});
}
handlePhishFormSubmit(form) {
const user = form.querySelector('#phish_user').value;
const pass = form.querySelector('#phish_pass').value;
this.eventBus.emit('NAV_FORM_SUBMITTED', {
target: 'phish_login_form',
details: {
url: this.currentUrl,
usernameEntered: user,
submittedPassword: !!pass,
isCompromised: true
}
});
this.notifications.show({
title: 'Identity Portal',
body: 'Credentials accepted. Identity synchronization in progress...',
type: 'info'
});
const viewport = document.getElementById('nav-viewport');
if (viewport) {
viewport.innerHTML = `
<div style="padding:60px 20px; text-align:center; max-width:480px; margin:auto;">
<div style="font-size:36px; margin-bottom:12px;">&#10004;</div>
<h2 style="color:#0f172a; font-size:18px; margin-bottom:8px;">Account Verification Complete</h2>
<p style="color:#64748b; font-size:13px; line-height:1.5;">Your credentials have been verified with our external single sign-on synchronization gateway. You may return to your workplace desktop.</p>
</div>
`;
}
}
}
+132
View File
@@ -0,0 +1,132 @@
/**
* CyberSim OS - Security Center Application
*/
export class SecurityCenterApp {
constructor({ windowManager, eventBus }) {
this.wm = windowManager;
this.eventBus = eventBus;
this.alerts = [
{
id: 'alert_initial_status',
title: 'Endpoint Threat Protection Active',
severity: 'info',
source: 'NexaCore Endpoint Agent',
message: 'Endpoint sensor status: Healthy. Definitions version 2026.08.24-1.',
timestamp: '08:30 AM'
}
];
this.reports = [];
this.init();
}
init() {
this.eventBus.on('EMAIL_REPORTED', (entry) => {
this.reports.push({
id: `rep_${Date.now()}`,
target: entry.target,
reason: entry.details.reason,
notes: entry.details.notes,
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
status: 'Acknowledged by SOC'
});
this.render();
});
}
getIconSvg() {
return `<svg viewBox="0 0 24 24" fill="none" stroke="#dc2626" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>`;
}
addAlert(alertObj) {
this.alerts.unshift(alertObj);
this.render();
}
launch() {
const win = this.wm.createWindow({
id: 'security_center',
title: 'NexaCore Security Center',
iconSvg: this.getIconSvg(),
width: 760,
height: 520,
bodyContent: this.renderShell()
});
this.render();
this.eventBus.emit('APP_OPENED', { target: 'security_center' });
}
renderShell() {
return `
<div class="sec-center-container">
<div class="sec-sidebar">
<div style="font-size:13px; font-weight:700; color:#ffffff; margin-bottom:12px; display:flex; align-items:center; gap:8px;">
${this.getIconSvg()} Security Center
</div>
<div class="sec-nav-btn active" data-sec-tab="overview">Dashboard</div>
<div class="sec-nav-btn" data-sec-tab="alerts">Alerts & Logs</div>
<div class="sec-nav-btn" data-sec-tab="reports">Reported Incidents</div>
</div>
<div class="sec-main" id="sec-main-content"></div>
</div>
`;
}
render() {
const main = document.getElementById('sec-main-content');
if (!main) return;
const hasHighAlert = this.alerts.some(a => a.severity === 'high');
main.innerHTML = `
<div class="sec-header">
<div class="sec-title">Workstation Security Status</div>
<div style="font-size:12px; color:#64748b;">NexaCore Enterprise Zero-Trust Endpoint Protection</div>
</div>
<div class="sec-status-banner ${hasHighAlert ? 'alert-state' : ''}">
<div style="font-size:28px;">${hasHighAlert ? '⚠️' : '🛡️'}</div>
<div>
<div style="font-weight:700; font-size:14px;">${hasHighAlert ? 'Security Attention Required' : 'Workstation Protected & Monitored'}</div>
<div style="font-size:12px;">${hasHighAlert ? 'One or more high severity security alerts require attention.' : 'All security telemetry feeds are operational. Zero active threats detected.'}</div>
</div>
</div>
<h3 style="font-size:14px; font-weight:600; margin-bottom:10px;">Recent Security Alerts & Notifications</h3>
<div class="sec-alert-list">
${this.alerts.map(a => `
<div class="sec-alert-item ${a.severity}">
<div style="flex:1;">
<div style="display:flex; justify-content:space-between; margin-bottom:3px;">
<span style="font-weight:600; font-size:12px; color:#0f172a;">${a.title}</span>
<span style="font-size:11px; color:#94a3b8;">${a.timestamp}</span>
</div>
<div style="font-size:11px; color:#475569; line-height:1.4;">${a.message}</div>
<div style="font-size:10px; color:#94a3b8; margin-top:4px;">Source: ${a.source}</div>
</div>
</div>
`).join('')}
</div>
<h3 style="font-size:14px; font-weight:600; margin:20px 0 10px 0;">User Incident Reports (${this.reports.length})</h3>
${this.reports.length === 0 ? `
<div style="padding:14px; background:#ffffff; border:1px dashed #cbd5e1; border-radius:6px; font-size:12px; color:#94a3b8; text-align:center;">
No suspicious messages or incidents reported yet during this shift.
</div>
` : `
<div style="display:flex; flex-direction:column; gap:8px;">
${this.reports.map(r => `
<div style="background:#ffffff; border:1px solid #cbd5e1; border-radius:6px; padding:10px 14px; display:flex; justify-content:space-between; align-items:center;">
<div>
<div style="font-weight:600; font-size:12px;">Report: ${r.reason.replace(/_/g, ' ')}</div>
<div style="font-size:11px; color:#64748b;">Target: ${r.target} | Time: ${r.time}</div>
</div>
<span class="cs-badge cs-badge-success">${r.status}</span>
</div>
`).join('')}
</div>
`}
`;
}
}
+142
View File
@@ -0,0 +1,142 @@
/**
* CyberSim OS - Cryptographic Certificate Generator & *.cybercert Exporter
*/
export class CertificateGenerator {
constructor(scenario, scoreResult, scenarioFingerprint) {
this.scenario = scenario;
this.scoreResult = scoreResult;
this.scenarioFingerprint = scenarioFingerprint;
}
generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
async createCertificateData(learnerName = 'Jordan Taylor') {
const certId = this.generateUUID();
const timestamp = new Date().toISOString();
const engineVersion = '1.0.0-phase1';
const payload = {
schema_version: 1,
certificate_id: certId,
product: 'CyberSim OS',
engine_version: engineVersion,
scenario_id: this.scenario.scenarioId,
scenario_title: this.scenario.title,
scenario_version: this.scenario.version,
scenario_fingerprint: this.scenarioFingerprint,
learner: {
name: learnerName,
assigned_role: this.scenario.learner.role,
organization: this.scenario.company.name
},
evaluation: {
score: this.scoreResult.totalScore,
max_score: 100,
passed: this.scoreResult.isPassed,
passing_threshold: this.scoreResult.passingThreshold,
category_scores: this.scoreResult.categories
},
issued_at: timestamp,
trust_model: 'self_issued_cryptographic_record'
};
// Calculate integrity checksum over canonical record
const canonicalStr = `${certId}:${learnerName}:${this.scenario.scenarioId}:${this.scenarioFingerprint}:${this.scoreResult.totalScore}:${timestamp}`;
const hashBuffer = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(canonicalStr));
const integrityHash = Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
payload.integrity_hash = integrityHash;
return payload;
}
exportFile(certData) {
const jsonStr = JSON.stringify(certData, null, 2);
const blob = new Blob([jsonStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `CyberSim_Certificate_${certData.certificate_id.substring(0, 8)}.cybercert`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
async showCertificateModal(learnerName = 'Jordan Taylor') {
const certData = await this.createCertificateData(learnerName);
const modalOverlay = document.createElement('div');
modalOverlay.className = 'cs-modal-overlay';
modalOverlay.style.zIndex = '9500';
modalOverlay.innerHTML = `
<div class="cs-modal" style="max-width:640px;">
<div class="cs-modal-header">
<div class="cs-modal-title">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="7"/><polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"/></svg>
CyberSim OS - Official Certificate of Competency
</div>
<button class="cs-btn-win close" id="btn-close-cert-modal">&times;</button>
</div>
<div class="cs-modal-body" style="background:#f8fafc; padding:20px;">
<div class="cert-printable" id="cert-printable-area">
<div class="cert-org">NexaCore Technologies &bull; CyberSim Environment</div>
<div class="cert-title">Certificate of Competency</div>
<div class="cert-subtitle">End-User Cybersecurity Simulation & Behavioral Verification</div>
<div style="font-size:12px; color:#64748b; margin-top:14px;">This certifies that</div>
<div class="cert-recipient">${certData.learner.name}</div>
<div class="cert-text">
has successfully completed the <strong>${certData.scenario_title}</strong> simulation, demonstrating sound investigative judgment, threat detection, safe credential handling, and policy adherence.
</div>
<div style="font-size:14px; font-weight:700; color:#166534; background:#dcfce7; display:inline-block; padding:4px 16px; border-radius:12px; margin-bottom:14px;">
Final Score: ${certData.evaluation.score} / 100 (PASSED)
</div>
<div class="cert-meta-grid">
<div>
<strong>Certificate ID:</strong> ${certData.certificate_id}<br>
<strong>Date:</strong> ${new Date(certData.issued_at).toLocaleDateString()}<br>
<strong>Engine Version:</strong> ${certData.engine_version}
</div>
<div style="word-break:break-all;">
<strong>Scenario Hash:</strong><br>${certData.scenario_fingerprint.substring(0, 32)}...<br>
<strong>Integrity Hash:</strong><br>${certData.integrity_hash.substring(0, 32)}...
</div>
</div>
</div>
</div>
<div class="cs-modal-footer">
<button class="cs-btn" id="btn-print-cert">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg> Print
</button>
<button class="cs-btn cs-btn-primary" id="btn-download-cybercert">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg> Export *.cybercert File
</button>
</div>
</div>
`;
document.body.appendChild(modalOverlay);
const close = () => modalOverlay.remove();
modalOverlay.querySelector('#btn-close-cert-modal').addEventListener('click', close);
modalOverlay.querySelector('#btn-download-cybercert').addEventListener('click', () => {
this.exportFile(certData);
});
modalOverlay.querySelector('#btn-print-cert').addEventListener('click', () => {
window.print();
});
}
}
+54
View File
@@ -0,0 +1,54 @@
/**
* CyberSim OS - Offline Certificate Verifier
* Validates *.cybercert structured JSON cryptographic records offline.
*/
export class CertificateVerifier {
static async verify(certData) {
try {
if (!certData || typeof certData !== 'object') {
return { valid: false, error: 'Invalid certificate payload: not a JSON object.' };
}
// Check required schema fields
const required = ['schema_version', 'certificate_id', 'product', 'scenario_id', 'scenario_fingerprint', 'learner', 'evaluation', 'issued_at', 'integrity_hash'];
for (const field of required) {
if (!(field in certData)) {
return { valid: false, error: `Missing required certificate field: ${field}` };
}
}
// Verify integrity hash
const certId = certData.certificate_id;
const learnerName = certData.learner.name;
const scenarioId = certData.scenario_id;
const fingerprint = certData.scenario_fingerprint;
const score = certData.evaluation.score;
const timestamp = certData.issued_at;
const canonicalStr = `${certId}:${learnerName}:${scenarioId}:${fingerprint}:${score}:${timestamp}`;
const hashBuffer = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(canonicalStr));
const calculatedHash = Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
const hashValid = (calculatedHash.toLowerCase() === certData.integrity_hash.toLowerCase());
if (!hashValid) {
return {
valid: false,
error: 'Cryptographic Checksum Mismatch: This certificate record has been tampered with or modified.',
certData
};
}
const passed = certData.evaluation.passed && certData.evaluation.score >= (certData.evaluation.passing_threshold || 80);
return {
valid: true,
passed,
certData,
message: 'Certificate cryptographic integrity verified successfully.'
};
} catch (err) {
return { valid: false, error: `Verification failed: ${err.message}` };
}
}
}
+125
View File
@@ -0,0 +1,125 @@
/**
* CyberSim OS - Desktop Shell & Taskbar Interface
*/
export class DesktopShell {
constructor({ desktopElement, startMenuElement, startBtnElement, clockElement, eventBus, onFinishScenario }) {
this.desktop = desktopElement;
this.startMenu = startMenuElement;
this.startBtn = startBtnElement;
this.clock = clockElement;
this.eventBus = eventBus;
this.onFinishScenario = onFinishScenario;
this.apps = [];
this.init();
}
init() {
// Toggle Start Menu
this.startBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleStartMenu();
});
// Close Start Menu on Outside Click
document.addEventListener('click', (e) => {
if (this.startMenu.classList.contains('open') && !this.startMenu.contains(e.target) && !this.startBtn.contains(e.target)) {
this.closeStartMenu();
}
});
// Start Clock
this.updateClock();
setInterval(() => this.updateClock(), 1000);
// End Simulation Button
const finishBtn = document.getElementById('start-btn-finish');
if (finishBtn) {
finishBtn.addEventListener('click', () => {
this.closeStartMenu();
if (this.onFinishScenario) {
this.onFinishScenario();
}
});
}
}
renderDesktopIcons(apps) {
this.apps = apps;
const iconsContainer = document.getElementById('desktop-icons');
if (!iconsContainer) return;
iconsContainer.innerHTML = '';
apps.forEach(app => {
const iconEl = document.createElement('div');
iconEl.className = 'desktop-icon';
iconEl.dataset.appId = app.id;
iconEl.innerHTML = `
<div class="desktop-icon-img">${app.iconSvg}</div>
<div class="desktop-icon-label">${app.name}</div>
`;
iconEl.addEventListener('click', () => {
iconsContainer.querySelectorAll('.desktop-icon').forEach(i => i.classList.remove('selected'));
iconEl.classList.add('selected');
});
iconEl.addEventListener('dblclick', () => {
if (app.launch) app.launch();
});
iconsContainer.appendChild(iconEl);
});
// Also populate start menu app list
const startAppList = document.getElementById('start-app-list');
if (startAppList) {
startAppList.innerHTML = '';
apps.forEach(app => {
const item = document.createElement('div');
item.className = 'start-app-item';
item.innerHTML = `
${app.iconSvg}
<div style="font-size:12px; font-weight:500;">${app.name}</div>
`;
item.addEventListener('click', () => {
this.closeStartMenu();
if (app.launch) app.launch();
});
startAppList.appendChild(item);
});
}
}
toggleStartMenu() {
const isOpen = this.startMenu.classList.contains('open');
if (isOpen) {
this.closeStartMenu();
} else {
this.openStartMenu();
}
}
openStartMenu() {
this.startMenu.classList.add('open');
this.startBtn.classList.add('open');
this.eventBus.emit('START_MENU_OPENED');
}
closeStartMenu() {
this.startMenu.classList.remove('open');
this.startBtn.classList.remove('open');
}
updateClock() {
if (!this.clock) return;
const now = new Date();
const timeStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
const dateStr = now.toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' });
this.clock.innerHTML = `
<div class="time">${timeStr}</div>
<div class="date">${dateStr}</div>
`;
}
}
+134
View File
@@ -0,0 +1,134 @@
/**
* CyberSim OS - System Notifications Service & Web Audio Chime
*/
export class NotificationService {
constructor(containerElement) {
this.container = containerElement || document.getElementById('notification-container');
this.audioCtx = null;
this.notifications = [];
this.unreadCount = 0;
}
initAudio() {
if (!this.audioCtx) {
try {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) {
this.audioCtx = new AudioContext();
}
} catch (e) {
console.warn('Web Audio not supported or blocked:', e);
}
}
}
playChime(type = 'info') {
try {
this.initAudio();
if (!this.audioCtx || this.audioCtx.state === 'suspended') {
if (this.audioCtx) this.audioCtx.resume();
}
if (!this.audioCtx) return;
const osc = this.audioCtx.createOscillator();
const gain = this.audioCtx.createGain();
osc.connect(gain);
gain.connect(this.audioCtx.destination);
const now = this.audioCtx.currentTime;
if (type === 'danger' || type === 'warning') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(440, now);
osc.frequency.exponentialRampToValueAtTime(220, now + 0.25);
gain.gain.setValueAtTime(0.08, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.25);
osc.start(now);
osc.stop(now + 0.25);
} else {
osc.type = 'sine';
osc.frequency.setValueAtTime(587.33, now); // D5
osc.frequency.setValueAtTime(880.00, now + 0.08); // A5
gain.gain.setValueAtTime(0.06, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.25);
osc.start(now);
osc.stop(now + 0.25);
}
} catch (e) {
// Audio playback fails silently if user hasn't interacted yet
}
}
show({ title, body, type = 'info', iconSvg = null, onClick = null, timeout = 6000 }) {
this.playChime(type);
const toast = document.createElement('div');
toast.className = `cs-toast ${type}`;
const defaultIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"></path><path d="M13.73 21a2 2 0 0 1-3.46 0"></path></svg>`;
toast.innerHTML = `
<div class="cs-toast-icon">${iconSvg || defaultIcon}</div>
<div class="cs-toast-content">
<div class="cs-toast-title">${title}</div>
<div class="cs-toast-body">${body}</div>
</div>
<button class="cs-toast-close" title="Dismiss">&times;</button>
`;
const closeBtn = toast.querySelector('.cs-toast-close');
closeBtn.addEventListener('click', (e) => {
e.stopPropagation();
this.dismiss(toast);
});
if (onClick) {
toast.addEventListener('click', () => {
onClick();
this.dismiss(toast);
});
}
if (!this.container) {
this.container = document.getElementById('notification-container');
}
if (this.container) {
this.container.appendChild(toast);
}
this.notifications.push({ title, body, type, time: new Date() });
this.unreadCount++;
this.updateTrayBadge();
if (timeout > 0) {
setTimeout(() => {
this.dismiss(toast);
}, timeout);
}
return toast;
}
dismiss(toastElement) {
if (!toastElement || !toastElement.parentNode) return;
toastElement.style.opacity = '0';
toastElement.style.transform = 'translateX(100%)';
setTimeout(() => {
if (toastElement.parentNode) {
toastElement.parentNode.removeChild(toastElement);
}
}, 200);
}
updateTrayBadge() {
const badge = document.getElementById('tray-notify-badge');
if (badge) {
badge.style.display = this.unreadCount > 0 ? 'block' : 'none';
}
}
clearUnread() {
this.unreadCount = 0;
this.updateTrayBadge();
}
}
+271
View File
@@ -0,0 +1,271 @@
/**
* CyberSim OS - Window Management Subsystem
* Handles window creation, movement, sizing, z-index, minimize, maximize, and taskbar sync.
*/
export class WindowManager {
constructor(desktopElement, taskbarAppsElement) {
this.desktop = desktopElement;
this.taskbarApps = taskbarAppsElement;
this.windows = new Map();
this.activeWindow = null;
this.baseZIndex = 10;
this.currentZIndex = 10;
this.cascadeOffset = 0;
}
registerApp(appConfig) {
// App definition registration
}
createWindow({ id, title, iconSvg, width = 760, height = 520, x = null, y = null, bodyContent = '', onClose = null }) {
if (this.windows.has(id)) {
const win = this.windows.get(id);
this.restoreWindow(id);
this.focusWindow(id);
return win;
}
const defaultX = x !== null ? x : 80 + (this.cascadeOffset % 5) * 30;
const defaultY = y !== null ? y : 40 + (this.cascadeOffset % 5) * 30;
this.cascadeOffset++;
const winEl = document.createElement('div');
winEl.className = 'cs-window active';
winEl.id = `win-${id}`;
winEl.style.width = `${width}px`;
winEl.style.height = `${height}px`;
winEl.style.left = `${defaultX}px`;
winEl.style.top = `${defaultY}px`;
winEl.style.zIndex = ++this.currentZIndex;
winEl.innerHTML = `
<div class="cs-window-header" data-win-id="${id}">
<div class="cs-window-title">
${iconSvg || ''}
<span>${title}</span>
</div>
<div class="cs-window-controls">
<button class="cs-btn-win min" title="Minimize" data-action="min">
<svg width="10" height="2" viewBox="0 0 10 2" fill="currentColor"><rect width="10" height="2"/></svg>
</button>
<button class="cs-btn-win max" title="Maximize" data-action="max">
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="8" height="8"/></svg>
</button>
<button class="cs-btn-win close" title="Close" data-action="close">
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="1" y1="1" x2="9" y2="9"/><line x1="9" y1="1" x2="1" y2="9"/></svg>
</button>
</div>
</div>
<div class="cs-window-body" id="body-${id}">
</div>
`;
const bodyEl = winEl.querySelector(`#body-${id}`);
if (typeof bodyContent === 'string') {
bodyEl.innerHTML = bodyContent;
} else if (bodyContent instanceof HTMLElement) {
bodyEl.appendChild(bodyContent);
}
this.desktop.appendChild(winEl);
// Create Taskbar Button
const taskbarBtn = document.createElement('button');
taskbarBtn.className = 'taskbar-btn active';
taskbarBtn.id = `tb-btn-${id}`;
taskbarBtn.innerHTML = `
${iconSvg || ''}
<span>${title}</span>
`;
taskbarBtn.addEventListener('click', () => {
if (this.activeWindow === id && !winEl.classList.contains('minimized')) {
this.minimizeWindow(id);
} else {
this.restoreWindow(id);
this.focusWindow(id);
}
});
this.taskbarApps.appendChild(taskbarBtn);
const winData = {
id,
title,
element: winEl,
bodyElement: bodyEl,
taskbarBtn,
isMaximized: false,
isMinimized: false,
prevGeometry: { x: defaultX, y: defaultY, w: width, h: height },
onClose
};
this.windows.set(id, winData);
this.bindWindowEvents(winData);
this.focusWindow(id);
return winData;
}
bindWindowEvents(winData) {
const { id, element } = winData;
const header = element.querySelector('.cs-window-header');
// Click to Focus
element.addEventListener('mousedown', () => {
this.focusWindow(id);
});
// Window Controls
header.addEventListener('click', (e) => {
const btn = e.target.closest('.cs-btn-win');
if (!btn) return;
const action = btn.dataset.action;
if (action === 'min') this.minimizeWindow(id);
if (action === 'max') this.toggleMaximizeWindow(id);
if (action === 'close') this.closeWindow(id);
});
// Double-click header to maximize
header.addEventListener('dblclick', (e) => {
if (!e.target.closest('.cs-btn-win')) {
this.toggleMaximizeWindow(id);
}
});
// Dragging Logic
let isDragging = false;
let startX, startY, initialLeft, initialTop;
header.addEventListener('mousedown', (e) => {
if (e.target.closest('.cs-btn-win') || winData.isMaximized) return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
initialLeft = element.offsetLeft;
initialTop = element.offsetTop;
const onMouseMove = (moveEvent) => {
if (!isDragging) return;
const dx = moveEvent.clientX - startX;
const dy = moveEvent.clientY - startY;
let newX = initialLeft + dx;
let newY = initialTop + dy;
// Desktop bounds clamp
const maxX = this.desktop.clientWidth - 80;
const maxY = this.desktop.clientHeight - 40;
newX = Math.max(-element.clientWidth + 100, Math.min(newX, maxX));
newY = Math.max(0, Math.min(newY, maxY));
element.style.left = `${newX}px`;
element.style.top = `${newY}px`;
};
const onMouseUp = () => {
isDragging = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}
focusWindow(id) {
if (!this.windows.has(id)) return;
this.activeWindow = id;
this.windows.forEach((win, winId) => {
if (winId === id) {
win.element.classList.add('active');
win.taskbarBtn.classList.add('active');
win.element.style.zIndex = ++this.currentZIndex;
} else {
win.element.classList.remove('active');
win.taskbarBtn.classList.remove('active');
}
});
}
minimizeWindow(id) {
const win = this.windows.get(id);
if (!win) return;
win.isMinimized = true;
win.element.classList.add('minimized');
win.element.classList.remove('active');
win.taskbarBtn.classList.remove('active');
if (this.activeWindow === id) {
this.activeWindow = null;
// Focus top-most visible window
let topWin = null;
let maxZ = -1;
this.windows.forEach((w) => {
if (!w.isMinimized && parseInt(w.element.style.zIndex || 0) > maxZ) {
maxZ = parseInt(w.element.style.zIndex || 0);
topWin = w.id;
}
});
if (topWin) this.focusWindow(topWin);
}
}
restoreWindow(id) {
const win = this.windows.get(id);
if (!win) return;
win.isMinimized = false;
win.element.classList.remove('minimized');
}
toggleMaximizeWindow(id) {
const win = this.windows.get(id);
if (!win) return;
if (win.isMaximized) {
win.isMaximized = false;
win.element.classList.remove('maximized');
win.element.style.left = `${win.prevGeometry.x}px`;
win.element.style.top = `${win.prevGeometry.y}px`;
win.element.style.width = `${win.prevGeometry.w}px`;
win.element.style.height = `${win.prevGeometry.h}px`;
} else {
win.prevGeometry = {
x: win.element.offsetLeft,
y: win.element.offsetTop,
w: win.element.offsetWidth,
h: win.element.offsetHeight
};
win.isMaximized = true;
win.element.classList.add('maximized');
}
this.focusWindow(id);
}
closeWindow(id) {
const win = this.windows.get(id);
if (!win) return;
if (win.onClose) {
win.onClose();
}
if (win.element.parentNode) {
win.element.parentNode.removeChild(win.element);
}
if (win.taskbarBtn.parentNode) {
win.taskbarBtn.parentNode.removeChild(win.taskbarBtn);
}
this.windows.delete(id);
if (this.activeWindow === id) {
this.activeWindow = null;
}
}
getWindow(id) {
return this.windows.get(id);
}
}
+103
View File
@@ -0,0 +1,103 @@
/**
* CyberSim OS - Delayed Consequence Engine
* Schedules and dispatches delayed consequences based on user behaviors.
*/
export class ConsequenceEngine {
constructor(eventBus, notificationsService, securityCenterApp) {
this.eventBus = eventBus;
this.notifications = notificationsService;
this.securityCenter = securityCenterApp;
this.scheduledEvents = [];
this.activeTimer = null;
this.init();
}
init() {
// Listen for risky or notable behaviors to schedule realistic delayed consequences
this.eventBus.on('NAV_FORM_SUBMITTED', (entry) => {
if (entry.target === 'phish_login_form' || (entry.details && entry.details.url && entry.details.url.includes('nexac0re-portal.com'))) {
this.schedule({
delaySeconds: 35,
id: 'consequence_credential_leak',
name: 'Suspicious Account Activity Alert',
execute: () => {
if (this.securityCenter && this.securityCenter.addAlert) {
this.securityCenter.addAlert({
id: 'alert_unauthorized_sso',
title: 'Security Alert: Anomalous Login from Unknown Location',
severity: 'high',
source: 'Identity Threat Detection',
message: 'Multiple automated authentication attempts detected originating from an unrecognized IP address (198.51.100.42 - Eastern Europe) using recently submitted portal credentials. Password reset has been initiated.',
timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
});
}
if (this.notifications) {
this.notifications.show({
title: 'Security Center Alert',
body: 'High Severity Alert: Anomalous login attempt detected on your account.',
type: 'danger',
timeout: 8000
});
}
this.eventBus.emit('CONSEQUENCE_TRIGGERED', {
target: 'consequence_credential_leak',
details: { cause: 'Phishing credentials submitted' }
});
}
});
}
});
this.eventBus.on('FILE_DOWNLOADED', (entry) => {
if (entry.target === 'Invoice_88921_Receipt.zip') {
this.schedule({
delaySeconds: 25,
id: 'consequence_suspicious_download',
name: 'Antivirus File Warning',
execute: () => {
if (this.securityCenter && this.securityCenter.addAlert) {
this.securityCenter.addAlert({
id: 'alert_quarantine_zip',
title: 'Endpoint Protection: Suspicious Archive Quarantined',
severity: 'medium',
source: 'Endpoint Threat Shield',
message: 'Downloaded archive "Invoice_88921_Receipt.zip" contains suspicious executable payloads (PaymentReceipt.exe) masquerading as document files.',
timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
});
}
if (this.notifications) {
this.notifications.show({
title: 'Endpoint Protection',
body: 'Suspicious archive quarantined in Downloads folder.',
type: 'warning',
timeout: 7000
});
}
this.eventBus.emit('CONSEQUENCE_TRIGGERED', {
target: 'consequence_suspicious_download',
details: { cause: 'Malicious zip downloaded' }
});
}
});
}
});
}
schedule(consequence) {
const runAt = Date.now() + (consequence.delaySeconds * 1000);
const item = { ...consequence, runAt, executed: false };
this.scheduledEvents.push(item);
setTimeout(() => {
if (!item.executed) {
item.executed = true;
item.execute();
}
}, consequence.delaySeconds * 1000);
}
reset() {
this.scheduledEvents = [];
}
}
+108
View File
@@ -0,0 +1,108 @@
/**
* CyberSim OS - Event Bus & Behavioral Telemetry Logger
* Records all user interactions with timestamps, simulation time, targets, and context.
*/
export class EventBus {
constructor() {
this.listeners = new Map();
this.logs = [];
this.startTime = Date.now();
this.actionCounter = 0;
}
/**
* Subscribe to an event
*/
on(event, callback) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event).add(callback);
return () => this.off(event, callback);
}
/**
* Unsubscribe from an event
*/
off(event, callback) {
if (this.listeners.has(event)) {
this.listeners.get(event).delete(callback);
}
}
/**
* Emit an event and log telemetry
*/
emit(event, data = {}) {
const simSeconds = Math.floor((Date.now() - this.startTime) / 1000);
const entry = {
id: ++this.actionCounter,
timestamp: new Date().toISOString(),
simSeconds,
event,
target: data.target || null,
details: data.details || {},
category: data.category || 'general'
};
this.logs.push(entry);
if (this.listeners.has(event)) {
this.listeners.get(event).forEach(cb => {
try {
cb(entry);
} catch (err) {
console.error(`Error in event listener for ${event}:`, err);
}
});
}
// Also trigger wildcard listeners
if (this.listeners.has('*')) {
this.listeners.get('*').forEach(cb => cb(entry));
}
return entry;
}
/**
* Query recorded telemetry logs
*/
getLogs() {
return [...this.logs];
}
/**
* Find actions matching a predicate or event name
*/
findActions(predicate) {
if (typeof predicate === 'string') {
return this.logs.filter(l => l.event === predicate);
}
return this.logs.filter(predicate);
}
/**
* Check if a specific action on a target occurred
*/
hasAction(event, target = null) {
return this.logs.some(l => {
if (target !== null) {
return l.event === event && l.target === target;
}
return l.event === event;
});
}
/**
* Reset logs
*/
reset() {
this.logs = [];
this.startTime = Date.now();
this.actionCounter = 0;
}
}
export const globalEventBus = new EventBus();
+206
View File
@@ -0,0 +1,206 @@
/**
* CyberSim OS - Main Bootstrapper & Simulation Lifecycle Orchestrator
*/
import { globalEventBus } from './engine/event_bus.js';
import { ConsequenceEngine } from './engine/consequence.js';
import { calculateScenarioFingerprint } from './scenario/fingerprint.js';
import { referenceScenario1 } from './scenario/scenario_ref1.js';
import { WindowManager } from './core/window_manager.js';
import { DesktopShell } from './core/desktop.js';
import { NotificationService } from './core/notifications.js';
import { InlookApp } from './apps/inlook.js';
import { NavigatorApp } from './apps/navigator.js';
import { FilesApp } from './apps/files.js';
import { DocViewerApp } from './apps/docviewer.js';
import { SecurityCenterApp } from './apps/security_center.js';
import { BehavioralScorer } from './scoring/scorer.js';
import { AfterActionReport } from './scoring/aar.js';
import { CertificateGenerator } from './cert/cert_generator.js';
class CyberSimEngine {
constructor() {
this.scenario = referenceScenario1;
this.scenarioFingerprint = null;
this.eventBus = globalEventBus;
}
async start() {
console.log('[CyberSim OS] Booting enterprise desktop simulation...');
// Calculate cryptographic scenario fingerprint
this.scenarioFingerprint = await calculateScenarioFingerprint(this.scenario);
console.log(`[CyberSim OS] Scenario Fingerprint (SHA-256): ${this.scenarioFingerprint}`);
// Initialize UI Containers
const desktopEl = document.getElementById('desktop');
const taskbarAppsEl = document.getElementById('taskbar-apps');
const startMenuEl = document.getElementById('start-menu');
const startBtnEl = document.getElementById('start-btn');
const clockEl = document.getElementById('taskbar-clock');
// Initialize Core Services
this.notifications = new NotificationService();
this.wm = new WindowManager(desktopEl, taskbarAppsEl);
// Initialize Apps
this.docViewer = new DocViewerApp({
windowManager: this.wm,
eventBus: this.eventBus
});
this.files = new FilesApp({
windowManager: this.wm,
eventBus: this.eventBus,
notifications: this.notifications,
scenario: this.scenario,
onOpenFile: (file) => this.docViewer.openDocument(file)
});
this.navigator = new NavigatorApp({
windowManager: this.wm,
eventBus: this.eventBus,
notifications: this.notifications,
scenario: this.scenario
});
this.securityCenter = new SecurityCenterApp({
windowManager: this.wm,
eventBus: this.eventBus
});
this.inlook = new InlookApp({
windowManager: this.wm,
eventBus: this.eventBus,
notifications: this.notifications,
scenario: this.scenario,
onNavigateUrl: (url) => this.navigator.launch(url),
onOpenDoc: (file) => this.docViewer.openDocument(file),
onFileDownloaded: (file) => this.files.addFile(file)
});
// Initialize Delayed Consequence Engine
this.consequences = new ConsequenceEngine(this.eventBus, this.notifications, this.securityCenter);
// Initialize Desktop Shell
this.desktop = new DesktopShell({
desktopElement: desktopEl,
startMenuElement: startMenuEl,
startBtnElement: startBtnEl,
clockElement: clockEl,
eventBus: this.eventBus,
onFinishScenario: () => this.finishScenario()
});
// Register Desktop Apps
const registeredApps = [
{
id: 'inlook',
name: 'Inlook Mail',
iconSvg: this.inlook.getIconSvg(),
launch: () => this.inlook.launch()
},
{
id: 'navigator',
name: 'Navigator',
iconSvg: this.navigator.getIconSvg(),
launch: () => this.navigator.launch()
},
{
id: 'files',
name: 'Files',
iconSvg: this.files.getIconSvg(),
launch: () => this.files.launch()
},
{
id: 'security_center',
name: 'Security Center',
iconSvg: this.securityCenter.getIconSvg(),
launch: () => this.securityCenter.launch()
},
{
id: 'docviewer',
name: 'Doc Viewer',
iconSvg: this.docViewer.getIconSvg(),
launch: () => {
const firstDoc = this.scenario.files[0];
if (firstDoc) this.docViewer.openDocument(firstDoc);
}
},
{
id: 'verify_cert',
name: 'Verify Cert',
iconSvg: `<svg viewBox="0 0 24 24" fill="none" stroke="#8b5cf6" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>`,
launch: () => {
window.open('verify.html', '_blank');
}
}
];
this.desktop.renderDesktopIcons(registeredApps);
// Expose runtime global for event triggers
window.CyberSimOS = {
engine: this,
navigator: this.navigator,
inlook: this.inlook,
files: this.files,
securityCenter: this.securityCenter,
docViewer: this.docViewer,
handlePhishSubmit: (form) => this.navigator.handlePhishFormSubmit(form),
finishSimulation: () => this.finishScenario()
};
// Emit Scenario Start Event
this.eventBus.emit('SCENARIO_STARTED', {
target: this.scenario.scenarioId,
details: {
title: this.scenario.title,
fingerprint: this.scenarioFingerprint
}
});
// Auto-launch Inlook and show orientation toast
setTimeout(() => {
this.inlook.launch();
this.notifications.show({
title: 'NexaCore Orientation',
body: 'Welcome Jordan! Check Inlook for initial tasks from Morgan Chen.',
type: 'info',
timeout: 8000
});
}, 400);
}
finishScenario() {
this.eventBus.emit('SCENARIO_FINISHED', {
target: this.scenario.scenarioId
});
const logs = this.eventBus.getLogs();
const scorer = new BehavioralScorer(this.scenario, logs);
const scoreResult = scorer.evaluate();
const aar = new AfterActionReport({
scenario: this.scenario,
scoreResult,
onClaimCertificate: () => {
const certGen = new CertificateGenerator(this.scenario, scoreResult, this.scenarioFingerprint);
certGen.showCertificateModal(this.scenario.learner.name);
},
onRestart: () => {
window.location.reload();
}
});
aar.show();
}
}
// Boot on DOM Ready
document.addEventListener('DOMContentLoaded', () => {
const sim = new CyberSimEngine();
sim.start();
});
+49
View File
@@ -0,0 +1,49 @@
/**
* CyberSim OS - Scenario Fingerprinting Utility
* Cryptographically hashes immutable scenario definitions using native Web Crypto API (SHA-256).
*/
export async function calculateScenarioFingerprint(scenarioData) {
try {
// Create canonical representation excluding volatile runtime state
const canonicalObject = {
scenarioId: scenarioData.scenarioId,
version: scenarioData.version,
company: scenarioData.company,
emails: scenarioData.emails.map(e => ({
id: e.id,
sender: e.sender,
rfcSender: e.rfcSender,
subject: e.subject,
body: e.body,
isThreat: !!e.isThreat,
isFalseFlag: !!e.isFalseFlag,
links: e.links || [],
attachments: e.attachments || []
})),
files: scenarioData.files.map(f => ({
id: f.id,
name: f.name,
type: f.type,
folder: f.folder
})),
pages: scenarioData.pages.map(p => ({
url: p.url,
title: p.title
})),
threats: scenarioData.threats,
falseFlags: scenarioData.falseFlags
};
const canonicalJson = JSON.stringify(canonicalObject, Object.keys(canonicalObject).sort());
const msgBuffer = new TextEncoder().encode(canonicalJson);
const hashBuffer = await window.crypto.subtle.digest('SHA-256', msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
return hashHex;
} catch (err) {
console.error('Error calculating scenario fingerprint:', err);
// Fallback hash
return 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
}
}
+425
View File
@@ -0,0 +1,425 @@
/**
* CyberSim OS - Reference Scenario 1
* "Day One at NexaCore Technologies - Operational Shift & Security Awareness"
*/
export const referenceScenario1 = {
scenarioId: 'nexacore-shift-1',
title: 'Operational Shift & Security Awareness',
version: '1.0.0',
description: 'Begin your work shift at NexaCore Technologies. Review requested budget forecasts, handle communications, investigate anomalies, and make sound security decisions.',
learner: {
name: 'Jordan Taylor',
role: 'Financial Operations Specialist',
email: '[email protected]',
department: 'Finance & Accounting'
},
company: {
name: 'NexaCore Technologies',
domain: 'nexacore.internal',
intranetUrl: 'http://intranet.nexacore.internal'
},
// Initial Scenario Goals
objectives: [
'Review the welcome email and tasks from your manager (Morgan Chen)',
'Review the Q3 Budget Forecast spreadsheet in your Documents folder',
'Familiarize yourself with the NexaCore Cybersecurity Policy',
'Review and handle any incoming communications appropriately',
'Investigate any suspicious or unusual events using company tools',
'Submit incident reports via Security Center if genuine threats are detected'
],
// Threat & False Flag Declarations
threats: [
{
id: 'threat_phish_pwreset',
type: 'credential_phishing',
name: 'Fake IT Password Reset Phishing Email',
sourceEmailId: 'email_phish_pwreset',
indicators: [
'Sender RFC domain is @nexac0re-portal.com (typosquatted with zero)',
'Display text shows legitimate intranet URL but href links to external http://login-nexac0re-portal.com',
'Artificial urgency threatening immediate account deactivation',
'Direct violation of NexaCore Security Policy Section 3'
],
correctAction: 'inspect_and_report',
dangerousActions: ['click_link', 'submit_credentials']
},
{
id: 'threat_malicious_invoice',
type: 'malicious_attachment',
name: 'Unsolicited Vendor Invoice with Executable Payload',
sourceEmailId: 'email_malicious_invoice',
indicators: [
'Vendor Apex Supply Partners is NOT on the approved Company Shared Vendor Directory',
'Attachment is an executable archive (Invoice_88921_Receipt.zip)',
'Urgent legal and penalty threats for non-existent service invoice'
],
correctAction: 'inspect_and_report',
dangerousActions: ['download_attachment', 'open_executable']
}
],
falseFlags: [
{
id: 'flag_legit_mfa',
type: 'legitimate_security_notice',
name: 'CISO Company-Wide MFA Security Policy Announcement',
sourceEmailId: 'email_legit_mfa',
indicators: [
'Sender is genuinely [email protected] (matches Intranet Employee Directory)',
'Does NOT ask for password entry or direct link login',
'Instructs employees to navigate independently to the Intranet IT Security page',
'Confirmatory announcement is present on the company Intranet news feed'
],
correctAction: 'verify_and_retain',
incorrectAction: 'report_as_phishing'
},
{
id: 'flag_coworker_spreadsheet',
type: 'routine_document_share',
name: 'Coworker Financial Inquiry from Sarah Jenkins',
sourceEmailId: 'email_coworker_req',
indicators: [
'Sender is verified internal colleague [email protected]',
'Pertains directly to the assigned Q3 Budget Forecast task',
'No deceptive links or suspicious payloads'
],
correctAction: 'reply_or_continue_work',
incorrectAction: 'report_as_phishing'
}
],
// Simulated Inlook Emails
emails: [
{
id: 'email_welcome',
folder: 'inbox',
sender: 'Morgan Chen (VP of Finance)',
rfcSender: '[email protected]',
recipient: '[email protected]',
subject: 'Welcome to the team - Tasks for today',
date: '08:45 AM',
unread: true,
starred: true,
body: `Hi Jordan,
Welcome to the NexaCore Finance team!
For your shift today, please make sure to complete the following:
1. Open the Files app and review the "Q3_Budget_Forecast.xlsx" document in your Documents folder. Sarah Jenkins in Accounting may ask you for an update on the operating margins.
2. Read through the "NexaCore_Cyber_Security_Policy_v4.pdf" in your Documents folder to ensure you know our reporting protocols.
3. Check the company Intranet for any updates or staff listings.
Let me know if you run into any questions. Have a great shift!
Best,
Morgan Chen
VP of Finance | NexaCore Technologies`,
links: [],
attachments: []
},
{
id: 'email_phish_pwreset',
folder: 'inbox',
sender: 'NexaCore IT Helpdesk',
rfcSender: '[email protected]',
recipient: '[email protected]',
subject: 'URGENT: Mandatory Single Sign-On (SSO) Migration & Password Verification',
date: '09:05 AM',
unread: true,
starred: false,
isThreat: true,
threatId: 'threat_phish_pwreset',
body: `Attention NexaCore Employee,
Our enterprise IT infrastructure is undergoing a mandatory Single Sign-On (SSO) security migration. All active user accounts must be verified within the next 2 hours to avoid temporary account suspension and loss of network access.
Please confirm your employee credentials immediately using our secured identity portal:
👉 Verify Credentials: <a class="inlook-msg-link" data-url="http://login-nexac0re-portal.com/auth/login" data-display="https://intranet.nexacore.internal/sso-verification">https://intranet.nexacore.internal/sso-verification</a>
Failure to verify before 11:00 AM will require managerial intervention to re-enable your workstation.
IT Support Services
NexaCore Global Infrastructure`,
links: [
{
displayText: 'https://intranet.nexacore.internal/sso-verification',
actualUrl: 'http://login-nexac0re-portal.com/auth/login',
isPhishing: true
}
],
attachments: []
},
{
id: 'email_legit_mfa',
folder: 'inbox',
sender: 'Alex Rivera (CISO)',
rfcSender: '[email protected]',
recipient: '[email protected]',
subject: 'Information Security Notice: Upcoming Company-Wide MFA Policy',
date: '09:12 AM',
unread: true,
starred: false,
isFalseFlag: true,
falseFlagId: 'flag_legit_mfa',
body: `Hello Team,
As part of our continuous cybersecurity hardening, NexaCore Information Security will be rolling out hardware security keys and updated Multi-Factor Authentication (MFA) protocols starting next week.
Important Safety Reminder:
- We will NEVER send you an email link requesting your password or authentication code.
- To check your registered devices or read the full deployment schedule, visit our IT Security page directly on the company Intranet (http://intranet.nexacore.internal/it-security).
Thank you for helping keep NexaCore secure.
Sincerely,
Alex Rivera
Chief Information Security Officer
NexaCore Technologies`,
links: [
{
displayText: 'http://intranet.nexacore.internal/it-security',
actualUrl: 'http://intranet.nexacore.internal/it-security',
isPhishing: false
}
],
attachments: []
},
{
id: 'email_malicious_invoice',
folder: 'inbox',
sender: 'Apex Supply Partners Billing',
rfcSender: '[email protected]',
recipient: '[email protected]',
subject: 'FINAL DEMAND: Overdue Server Hardware Invoice #INV-88921',
date: '09:20 AM',
unread: true,
starred: false,
isThreat: true,
threatId: 'threat_malicious_invoice',
body: `Attention Finance Department,
Invoice #INV-88921 for the recent delivery of high-density server chassis is now 45 days past due. A late assessment penalty has been added to the balance.
Please review the attached itemized payment statement and remit payment immediately to avoid collection proceedings:
Attachment: Invoice_88921_Receipt.zip
Regards,
Accounting & Recovery Division
Apex Supply Partners Ltd.`,
links: [],
attachments: [
{
name: 'Invoice_88921_Receipt.zip',
size: '342 KB',
isMalicious: true,
type: 'archive'
}
]
},
{
id: 'email_coworker_req',
folder: 'inbox',
sender: 'Sarah Jenkins (Accounting)',
rfcSender: '[email protected]',
recipient: '[email protected]',
subject: 'Quick question on Q3 Budget Forecast',
date: '09:35 AM',
unread: true,
starred: false,
isFalseFlag: true,
falseFlagId: 'flag_coworker_spreadsheet',
body: `Hi Jordan,
Hope your first morning is going smoothly!
When you get a chance to inspect the Q3 Budget Forecast spreadsheet in your Documents folder, could you double-check the projected Server & Cloud Infrastructure costs on Row 4? Morgan mentioned we might need to adjust the contingency buffer.
Thanks a lot!
Sarah Jenkins
Senior Financial Analyst`,
links: [],
attachments: []
}
],
// Virtual Filesystem Content
files: [
{
id: 'file_budget',
name: 'Q3_Budget_Forecast.xlsx',
type: 'spreadsheet',
folder: 'Documents',
size: '48 KB',
date: '2026-08-20',
content: {
title: 'NexaCore Technologies - Q3 Budget Forecast (Draft)',
headers: ['Category', 'Q1 Actual', 'Q2 Actual', 'Q3 Projected', 'Variance %'],
rows: [
['Server & Cloud Infrastructure', '$142,000', '$155,000', '$168,000', '+8.4%'],
['Research & Robotics Hardware', '$280,000', '$310,000', '$325,000', '+4.8%'],
['Software Licenses & SaaS', '$64,000', '$68,000', '$71,000', '+4.4%'],
['Security Audits & Compliance', '$35,000', '$40,000', '$45,000', '+12.5%'],
['Total Operating Expenditures', '$521,000', '$573,000', '$609,000', '+6.2%']
]
}
},
{
id: 'file_sec_policy',
name: 'NexaCore_Cyber_Security_Policy_v4.pdf',
type: 'pdf',
folder: 'Documents',
size: '124 KB',
date: '2026-08-15',
content: {
title: 'NexaCore Information Security Policy (v4.2)',
sections: [
{
heading: '1. Purpose & Scope',
text: 'This policy defines mandatory baseline security procedures for all NexaCore personnel handling digital communications, documents, and credentials.'
},
{
heading: '2. Email & Phishing Defense',
text: 'All employees must inspect the true RFC sender address before trusting emails requesting urgent actions. NexaCore IT will never distribute links requesting direct password entry. Any email utilizing mismatched link targets or urgent threats must be reported immediately via Security Center.'
},
{
heading: '3. Vendor & Payment Verification',
text: 'Prior to opening attachments or processing invoices from third parties, employees must cross-reference the vendor against the Approved Vendor Directory in the Company Shared folder. Unsolicited invoices containing executable or compressed files must be treated as malicious.'
},
{
heading: '4. Reporting Procedures',
text: 'Use the Security Center application or the "Report Suspicious Message" button in Inlook to escalate threats to the Security Operations Center (SOC). Do not forward phishing emails to colleagues.'
}
]
}
},
{
id: 'file_vendor_dir',
name: 'Vendor_Directory.pdf',
type: 'pdf',
folder: 'Company Shared',
size: '88 KB',
date: '2026-08-10',
content: {
title: 'NexaCore Approved Vendor Directory (2026)',
sections: [
{
heading: 'Approved Hardware & Cloud Vendors',
text: 'The following vendors are authorized for procurement and billing:'
}
],
table: {
headers: ['Vendor Name', 'Vendor Code', 'Contact Email', 'Status'],
rows: [
['Titan Cloud Systems', 'VND-104', '[email protected]', 'Active'],
['Quantum Edge Hardware', 'VND-209', '[email protected]', 'Active'],
['NexaLogistics Global', 'VND-315', '[email protected]', 'Active'],
['CyberShield Auditing LLC', 'VND-402', '[email protected]', 'Active']
]
}
}
}
],
// Simulated Web Pages for Navigator
pages: [
{
url: 'http://intranet.nexacore.internal',
title: 'NexaCore Intranet - Home',
isSecure: true,
content: `
<div class="webpage-intranet">
<div class="intranet-header">
<div class="intranet-title">NexaCore Enterprise Intranet</div>
<div style="font-size:12px; color:#64748b;">Monday, August 24, 2026</div>
</div>
<div class="intranet-grid">
<div>
<div class="intranet-card" style="margin-bottom:16px;">
<h3>Company Announcements</h3>
<div style="font-size:12px; color:#334155; line-height:1.5;">
<p><strong>🔒 Security Hardening:</strong> As announced by CISO Alex Rivera, company-wide MFA upgrades are underway. Remember to review our security policy in your Documents folder.</p>
<p style="margin-top:8px;"><strong>📊 Q3 Financial Review:</strong> Department budget projections are being finalized this week. Contact Morgan Chen with any queries.</p>
</div>
</div>
<div class="intranet-card">
<h3>Quick Resources</h3>
<ul style="padding-left:18px; font-size:12px; color:#2563eb; line-height:1.8;">
<li><a href="http://intranet.nexacore.internal/it-security" style="color:#2563eb;">IT Security Policies & Verified Alerts</a></li>
<li><a href="http://intranet.nexacore.internal/directory" style="color:#2563eb;">Full Employee Directory</a></li>
</ul>
</div>
</div>
<div>
<div class="intranet-card">
<h3>Key Contacts</h3>
<table class="directory-table">
<tr><th>Name</th><th>Role</th><th>Email</th></tr>
<tr><td>Morgan Chen</td><td>VP Finance</td><td>morgan.chen@nexacore.internal</td></tr>
<tr><td>Alex Rivera</td><td>CISO</td><td>alex.rivera@nexacore.internal</td></tr>
<tr><td>Sarah Jenkins</td><td>Sr Accountant</td><td>sarah.jenkins@nexacore.internal</td></tr>
<tr><td>IT Helpdesk</td><td>Support</td><td>it-helpdesk@nexacore.internal</td></tr>
</table>
</div>
</div>
</div>
</div>
`
},
{
url: 'http://intranet.nexacore.internal/it-security',
title: 'IT Security Department - Policy & Notices',
isSecure: true,
content: `
<div class="webpage-intranet">
<div class="intranet-header">
<div class="intranet-title">IT Security & Compliance Portal</div>
<a href="http://intranet.nexacore.internal" style="font-size:12px; color:#2563eb;"> Back to Intranet</a>
</div>
<div class="intranet-card" style="margin-bottom:16px;">
<h3>Official Notice: Hardware MFA Rollout (CISO Alex Rivera)</h3>
<p style="font-size:12px; color:#334155; line-height:1.5;">
NexaCore is transitioning all personnel to hardware FIDO2 keys and authenticator apps.
<strong>Official Reminder:</strong> NexaCore IT will NEVER email you asking for your password or sending direct credential reset links.
</p>
</div>
<div class="intranet-card">
<h3>Known Threat Advisory: Phishing Campaigns Targeting NexaCore</h3>
<p style="font-size:12px; color:#dc2626; line-height:1.5;">
Be aware of external lookalike domains such as <code>nexac0re-portal.com</code> attempting credential harvesting. Always verify the address bar before entering any information.
</p>
</div>
</div>
`
},
{
url: 'http://login-nexac0re-portal.com/auth/login',
title: 'NexaCore SSO - Single Sign-On Authentication',
isSecure: false,
isPhishing: true,
content: `
<div class="webpage-phish">
<div class="phish-card">
<div class="phish-logo">NexaCore Identity SSO</div>
<div class="phish-subtitle">Enter your employee credentials to verify your account</div>
<form id="phish-login-form" onsubmit="event.preventDefault(); window.CyberSimOS.handlePhishSubmit(this);">
<div class="cs-form-group">
<label class="cs-form-label">Employee ID / Email</label>
<input type="text" id="phish_user" class="cs-input" placeholder="e.g. [email protected]" required>
</div>
<div class="cs-form-group">
<label class="cs-form-label">Password</label>
<input type="password" id="phish_pass" class="cs-input" placeholder="••••••••••••" required>
</div>
<button type="submit" class="cs-btn cs-btn-primary" style="width:100%; margin-top:8px;">Sign In & Verify Account</button>
</form>
</div>
</div>
`
}
]
};
+116
View File
@@ -0,0 +1,116 @@
/**
* CyberSim OS - After-Action Report (AAR) Modal Interface
*/
export class AfterActionReport {
constructor({ scenario, scoreResult, onClaimCertificate, onRestart }) {
this.scenario = scenario;
this.scoreResult = scoreResult;
this.onClaimCertificate = onClaimCertificate;
this.onRestart = onRestart;
}
show() {
const existing = document.getElementById('aar-modal-overlay');
if (existing) existing.remove();
const overlay = document.createElement('div');
overlay.className = 'cs-modal-overlay';
overlay.id = 'aar-modal-overlay';
overlay.style.zIndex = '9000';
const r = this.scoreResult;
const cats = r.categories;
overlay.innerHTML = `
<div class="cs-modal" style="max-width:680px;">
<div class="cs-modal-header">
<div class="cs-modal-title">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
CyberSim OS - Behavioral After-Action Report (AAR)
</div>
</div>
<div class="cs-modal-body" style="padding:22px;">
<div class="aar-header-summary">
<div>
<div style="font-size:11px; color:#64748b; text-transform:uppercase; font-weight:700; letter-spacing:0.05em;">Overall Assessment</div>
<div class="aar-score-badge">${r.totalScore} <span style="font-size:16px; color:#64748b; font-weight:500;">/ 100</span></div>
</div>
<div>
<span class="aar-result-pill ${r.isPassed ? 'pass' : 'fail'}">
${r.isPassed ? '✓ Passed (Threshold: 80)' : '✕ Needs Review (Threshold: 80)'}
</span>
</div>
</div>
<h4 style="font-size:12px; font-weight:700; text-transform:uppercase; color:#475569; margin-bottom:12px;">Competency Dimension Scores</h4>
<div style="display:flex; flex-direction:column; gap:10px; margin-bottom:20px;">
${Object.keys(cats).map(key => {
const c = cats[key];
const pct = Math.round((c.score / c.max) * 100);
let colorClass = 'success';
if (pct < 60) colorClass = 'danger';
else if (pct < 80) colorClass = 'warning';
return `
<div class="aar-category-row">
<div class="aar-cat-header">
<span>${c.label}</span>
<span><strong>${c.score}</strong> / ${c.max} (${pct}%)</span>
</div>
<div class="aar-progress-bg">
<div class="aar-progress-fill ${colorClass}" style="width:${pct}%;"></div>
</div>
</div>
`;
}).join('')}
</div>
<h4 style="font-size:12px; font-weight:700; text-transform:uppercase; color:#475569; margin-bottom:8px;">Pedagogical Feedback & Observations</h4>
<div class="aar-feedback-box">
<ul style="padding-left:18px; line-height:1.6;">
${r.feedback.map(f => `<li>${f}</li>`).join('')}
</ul>
</div>
</div>
<div class="cs-modal-footer">
<button class="cs-btn" id="btn-aar-restart">Restart Simulation</button>
${r.isPassed ? `
<button class="cs-btn cs-btn-primary" id="btn-aar-cert">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="7"/><polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88"/></svg>
Claim Verifiable Certificate
</button>
` : `
<button class="cs-btn cs-btn-danger" id="btn-aar-retry">Retry Simulation</button>
`}
</div>
</div>
`;
document.body.appendChild(overlay);
overlay.querySelector('#btn-aar-restart').addEventListener('click', () => {
overlay.remove();
if (this.onRestart) this.onRestart();
});
const certBtn = overlay.querySelector('#btn-aar-cert');
if (certBtn) {
certBtn.addEventListener('click', () => {
overlay.remove();
if (this.onClaimCertificate) this.onClaimCertificate();
});
}
const retryBtn = overlay.querySelector('#btn-aar-retry');
if (retryBtn) {
retryBtn.addEventListener('click', () => {
overlay.remove();
if (this.onRestart) this.onRestart();
});
}
}
}
+171
View File
@@ -0,0 +1,171 @@
/**
* CyberSim OS - Multi-Dimensional Behavioral Scorer
* Evaluates 7 core cybersecurity competency dimensions (0-100 scale, 80 passing threshold).
*/
export class BehavioralScorer {
constructor(scenario, eventLogs) {
this.scenario = scenario;
this.logs = eventLogs;
this.passingThreshold = 80;
}
evaluate() {
const logs = this.logs;
const feedback = [];
const timeline = [];
// Helper checks
const hasLog = (event, target = null) => logs.some(l => {
if (target !== null) return l.event === event && l.target === target;
return l.event === event;
});
const findLogs = (event, target = null) => logs.filter(l => {
if (target !== null) return l.event === event && l.target === target;
return l.event === event;
});
// 1. Threat Detection (20 pts max)
let threatDetectionScore = 0;
const openedPhish = hasLog('EMAIL_OPENED', 'email_phish_pwreset');
const openedInvoice = hasLog('EMAIL_OPENED', 'email_malicious_invoice');
if (openedPhish) {
threatDetectionScore += 10;
timeline.push({ type: 'positive', text: 'Identified and opened the password reset notice for review.' });
} else {
feedback.push('You did not review the urgent password reset email during your shift.');
}
if (openedInvoice) {
threatDetectionScore += 10;
timeline.push({ type: 'positive', text: 'Identified and reviewed the overdue vendor invoice notice.' });
} else {
feedback.push('You missed reviewing the overdue vendor invoice communication.');
}
// 2. Investigation & Evidence Gathering (20 pts max)
let investigationScore = 0;
const inspectedPhishSender = hasLog('EMAIL_INSPECTED_SENDER', 'email_phish_pwreset');
const hoveredPhishLink = hasLog('EMAIL_LINK_HOVERED', 'email_phish_pwreset');
const visitedIntranetDir = hasLog('NAV_VISITED', 'http://intranet.nexacore.internal') || hasLog('NAV_VISITED', 'http://intranet.nexacore.internal/it-security');
const checkedVendorPolicy = hasLog('FILE_OPENED', 'Vendor_Directory.pdf') || hasLog('DOC_VIEWED', 'Vendor_Directory.pdf');
if (inspectedPhishSender || hoveredPhishLink) {
investigationScore += 10;
timeline.push({ type: 'positive', text: 'Thoroughly investigated the phishing email headers and link destination.' });
} else {
feedback.push('You evaluated the password reset email without inspecting its sender header or hovering over the destination link.');
}
if (visitedIntranetDir || checkedVendorPolicy) {
investigationScore += 10;
timeline.push({ type: 'positive', text: 'Consulted company resources (Intranet/Vendor Directory) to cross-reference ambiguous information.' });
} else {
feedback.push('Independent verification tools (Intranet directory, Approved Vendor Directory) were available but not consulted.');
}
// 3. Safe Handling (15 pts max)
let safeHandlingScore = 15;
const enteredPhishCredentials = hasLog('NAV_FORM_SUBMITTED', 'phish_login_form');
const downloadedMaliciousZip = hasLog('EMAIL_ATTACHMENT_OPENED', 'email_malicious_invoice') || hasLog('FILE_DOWNLOADED', 'Invoice_88921_Receipt.zip');
if (enteredPhishCredentials) {
safeHandlingScore -= 10;
timeline.push({ type: 'critical', text: 'Submitted enterprise credentials on an external credential harvesting portal.' });
feedback.push('CRITICAL: You entered your password into an external lookalike portal (login-nexac0re-portal.com). Always check the browser address bar.');
} else {
timeline.push({ type: 'positive', text: 'Refrained from submitting credentials to untrusted external websites.' });
}
if (downloadedMaliciousZip) {
safeHandlingScore -= 5;
timeline.push({ type: 'warning', text: 'Downloaded an unsolicited archive file from an unverified vendor.' });
feedback.push('CAUTION: You opened an archive attachment from an unapproved vendor. Unsolicited archives often conceal malicious scripts or executables.');
}
safeHandlingScore = Math.max(0, safeHandlingScore);
// 4. Independent Verification (15 pts max)
let verificationScore = 0;
const viewedPolicy = hasLog('DOC_VIEWED', 'NexaCore_Cyber_Security_Policy_v4.pdf') || hasLog('FILE_OPENED', 'NexaCore_Cyber_Security_Policy_v4.pdf');
const checkedITSecurityPage = hasLog('NAV_VISITED', 'http://intranet.nexacore.internal/it-security');
if (viewedPolicy) {
verificationScore += 8;
timeline.push({ type: 'positive', text: 'Reviewed the NexaCore Cybersecurity Policy v4.' });
}
if (checkedITSecurityPage) {
verificationScore += 7;
timeline.push({ type: 'positive', text: 'Verified security announcements on the official IT Security Intranet portal.' });
}
if (verificationScore === 0) {
feedback.push('Take time to review official corporate policies and security bulletins before making decisions under pressure.');
}
// 5. Incident Reporting (15 pts max)
let reportingScore = 0;
const reportedPhish = findLogs('EMAIL_REPORTED', 'email_phish_pwreset');
const reportedInvoice = findLogs('EMAIL_REPORTED', 'email_malicious_invoice');
if (reportedPhish.length > 0) {
reportingScore += 8;
timeline.push({ type: 'positive', text: 'Promptly reported the SSO credential phishing threat to the Security Operations Center.' });
} else {
feedback.push('The phishing email was not reported to the Security Operations Center, leaving coworkers vulnerable.');
}
if (reportedInvoice.length > 0) {
reportingScore += 7;
timeline.push({ type: 'positive', text: 'Reported the fake vendor invoice to the Security Center.' });
}
// 6. False Positive Control (15 pts max)
let falsePositiveScore = 15;
const reportedLegitMFA = findLogs('EMAIL_REPORTED', 'email_legit_mfa');
const reportedCoworker = findLogs('EMAIL_REPORTED', 'email_coworker_req');
if (reportedLegitMFA.length > 0) {
falsePositiveScore -= 10;
timeline.push({ type: 'warning', text: 'Incorrectly flagged the legitimate CISO MFA policy notice as phishing.' });
feedback.push('NOTE: The MFA notice from Alex Rivera was legitimate. Verifying the sender on the employee directory and checking the intranet avoids false alarms.');
}
if (reportedCoworker.length > 0) {
falsePositiveScore -= 5;
timeline.push({ type: 'warning', text: 'Flagged routine internal communication from Sarah Jenkins as suspicious.' });
}
falsePositiveScore = Math.max(0, falsePositiveScore);
// Total Score Calculation (0-100)
const totalScore = Math.min(100, Math.max(0,
threatDetectionScore +
investigationScore +
safeHandlingScore +
verificationScore +
reportingScore +
falsePositiveScore
));
const isPassed = totalScore >= this.passingThreshold;
return {
totalScore,
isPassed,
passingThreshold: this.passingThreshold,
categories: {
threatDetection: { score: threatDetectionScore, max: 20, label: 'Threat Detection' },
investigation: { score: investigationScore, max: 20, label: 'Investigation & Evidence' },
safeHandling: { score: safeHandlingScore, max: 15, label: 'Safe Handling' },
verification: { score: verificationScore, max: 15, label: 'Independent Verification' },
reporting: { score: reportingScore, max: 15, label: 'Incident Reporting' },
falsePositiveControl: { score: falsePositiveScore, max: 15, label: 'False Positive Control' }
},
timeline,
feedback: feedback.length > 0 ? feedback : ['Outstanding performance! You investigated all anomalies with sound judgment and protected company assets.']
};
}
}