From 9028325dfcfce8174e45fe90f10138e90776a506 Mon Sep 17 00:00:00 2001 From: Labyricorn Date: Sun, 23 Aug 2026 21:53:39 -0700 Subject: [PATCH] Implement CyberSim-OS Phase 1 MVP simulation environment --- .labyricorn/devlog/contents.lr | 4 +- .labyricorn/project/contents.lr | 12 +- README.md | 341 +++++------- launcher.py | 52 ++ src/css/apps.css | 871 +++++++++++++++++++++++++++++++ src/css/components.css | 227 ++++++++ src/css/desktop.css | 553 ++++++++++++++++++++ src/css/theme-windows.css | 47 ++ src/index.html | 73 +++ src/js/apps/docviewer.js | 123 +++++ src/js/apps/files.js | 124 +++++ src/js/apps/inlook.js | 417 +++++++++++++++ src/js/apps/navigator.js | 185 +++++++ src/js/apps/security_center.js | 132 +++++ src/js/cert/cert_generator.js | 142 +++++ src/js/cert/cert_verifier.js | 54 ++ src/js/core/desktop.js | 125 +++++ src/js/core/notifications.js | 134 +++++ src/js/core/window_manager.js | 271 ++++++++++ src/js/engine/consequence.js | 103 ++++ src/js/engine/event_bus.js | 108 ++++ src/js/main.js | 206 ++++++++ src/js/scenario/fingerprint.js | 49 ++ src/js/scenario/scenario_ref1.js | 425 +++++++++++++++ src/js/scoring/aar.js | 116 ++++ src/js/scoring/scorer.js | 171 ++++++ src/verify.html | 164 ++++++ 27 files changed, 5008 insertions(+), 221 deletions(-) create mode 100644 launcher.py create mode 100644 src/css/apps.css create mode 100644 src/css/components.css create mode 100644 src/css/desktop.css create mode 100644 src/css/theme-windows.css create mode 100644 src/index.html create mode 100644 src/js/apps/docviewer.js create mode 100644 src/js/apps/files.js create mode 100644 src/js/apps/inlook.js create mode 100644 src/js/apps/navigator.js create mode 100644 src/js/apps/security_center.js create mode 100644 src/js/cert/cert_generator.js create mode 100644 src/js/cert/cert_verifier.js create mode 100644 src/js/core/desktop.js create mode 100644 src/js/core/notifications.js create mode 100644 src/js/core/window_manager.js create mode 100644 src/js/engine/consequence.js create mode 100644 src/js/engine/event_bus.js create mode 100644 src/js/main.js create mode 100644 src/js/scenario/fingerprint.js create mode 100644 src/js/scenario/scenario_ref1.js create mode 100644 src/js/scoring/aar.js create mode 100644 src/js/scoring/scorer.js create mode 100644 src/verify.html diff --git a/.labyricorn/devlog/contents.lr b/.labyricorn/devlog/contents.lr index 164045c..9f2dc02 100644 --- a/.labyricorn/devlog/contents.lr +++ b/.labyricorn/devlog/contents.lr @@ -2,6 +2,6 @@ _model: devlog --- schema_version: 1 --- -title: Labyricorn Project Template development log +title: CyberSim-OS development log --- -summary: Milestones and development log entries for the Labyricorn project template. \ No newline at end of file +summary: Milestones and development log entries for the CyberSim-OS simulation platform. \ No newline at end of file diff --git a/.labyricorn/project/contents.lr b/.labyricorn/project/contents.lr index d0b5ff6..7d0b188 100644 --- a/.labyricorn/project/contents.lr +++ b/.labyricorn/project/contents.lr @@ -2,13 +2,13 @@ _model: project --- schema_version: 1 --- -project_id: labyricorn-project-template +project_id: cybersim-os --- -title: Labyricorn Project Template +title: CyberSim-OS --- summary: -Initial template and publishing structure for Labyricorn-compatible projects and development logs. +Scriptable CS End User Teaching Environment with Certificate Verification. --- status: active --- @@ -16,12 +16,12 @@ started: 2026-08-23 --- author: Labyricorn --- -repository_url: https://git.labyricorn.com/Labyricorn/labyricorn-project-template +repository_url: https://git.labyricorn.com/Labyricorn/CyberSim-OS --- default_branch: main --- -tags: template, lektor, python +tags: simulation, cybersecurity, training, offline-first, static-first, education, certificates --- body: -Starter template providing the standard .labyricorn/ publishing records, assistant instructions, devlog structure, and repository tooling for publication on Labyricorn. \ No newline at end of file +CyberSim-OS is an offline-first, browser-native cybersecurity simulation environment designed for ordinary end users. Learners navigate a convincing fictional enterprise desktop, perform routine tasks, investigate legitimate and malicious events (phishing, social engineering, malicious attachments), make meaningful decisions, receive multi-axis behavioral after-action reports, and earn cryptographically verifiable completion certificates. \ No newline at end of file diff --git a/README.md b/README.md index 775887c..e3be220 100644 --- a/README.md +++ b/README.md @@ -1,213 +1,128 @@ -# Labyricorn Project Template - -A starter template and baseline structure for projects compatible with -[Labyricorn](https://www.labyricorn.com). - -This template provides the required `.labyricorn/` publishing records, assistant -guidance files (`AGENTS.md`), devlog structure, and the standalone -`devlog_editor.py` management utility for local validation and GUI editing. - ---- - -## Overview - -Projects published on Labyricorn maintain their own exhibition pages and -development logs directly inside their Git repositories under the `.labyricorn/` -directory. The Labyricorn site importer periodically synchronizes these records -as read-only content. - -This template includes: - -- **`.labyricorn/` publishing structure**: Validated Lektor-format records for - project identity and development logs. -- **`devlog_editor.py`**: Standalone GUI and CLI tool for managing devlog entries, - validating publishing contracts, and safely publishing changes. -- **`AGENTS.md` instructions**: Scoped assistant instructions at repository root - and publishing directories to ensure AI assistants adhere to the publishing - contract. -- **`.gitignore`**: Configured exclusions for Python environments, caches, and build - artifacts. - ---- - -## Directory Structure - -```text -. -├── .gitignore -├── .labyricorn/ -│ ├── AGENTS.md # Assistant instructions for publishing content -│ ├── README.md # Publishing structure documentation -│ ├── project/ -│ │ ├── AGENTS.md # Instructions for the project exhibition record -│ │ ├── contents.lr # Project identity and exhibition narrative -│ │ └── # Project logo (PNG, JPEG, WebP) -│ └── devlog/ -│ ├── AGENTS.md # Instructions for devlog records -│ ├── contents.lr # Devlog index record -│ └── / # (Optional) Devlog entry directories -│ ├── contents.lr # Devlog entry record -│ └── # Entry attachments (PNG, JPEG, WebP) -├── AGENTS.md # Assistant instructions for the repository -├── devlog_editor.py # Standalone devlog editor & validator -├── LICENSE # License file -└── README.md # Project documentation -``` - ---- - -## Getting Started - -### 1. Configure the Project Identity - -Edit `.labyricorn/project/contents.lr` to set your project details: - -```ini -_model: project ---- -schema_version: 1 ---- -project_id: my-project-id ---- -title: My Project Title ---- -summary: - -A concise summary of the project for listings and cards. ---- -status: active ---- -started: 2026-08-23 ---- -author: Author Name ---- -repository_url: https://git.labyricorn.com/Owner/my-project ---- -default_branch: main ---- -tags: python, web, tool ---- -body: - -An exhibition narrative describing the project background, goals, and features. -``` - -#### Project Fields: -- `project_id`: Stable lowercase hyphenated slug (e.g. `my-project`). -- `title`: Display title for the project. -- `summary`: Short multi-line summary. -- `status`: One of `active`, `released`, `maintained`, or `archived`. -- `started`: Start date in `YYYY-MM-DD` format. -- `author`: Author or organization name. -- `repository_url`: Public credential-free HTTPS Git URL. -- `default_branch`: Target branch name (e.g. `main`). -- `logo`: (Optional) Local filename of an image inside `.labyricorn/project/`. -- `tags`: Comma-separated technology or topic labels. -- `body`: Markdown narrative of the project. - -### 2. Configure the Devlog Index - -Edit `.labyricorn/devlog/contents.lr` to customize the devlog title and summary: - -```ini -_model: devlog ---- -schema_version: 1 ---- -title: My Project development log ---- -summary: Milestones and development updates for My Project. -``` - -### 3. Add Devlog Entries - -To add an entry manually, create a directory under `.labyricorn/devlog//` -containing `contents.lr`: - -```ini -_model: devlog-entry ---- -schema_version: 1 ---- -title: Initial Project Milestone ---- -date: 2026-08-23 ---- -author: Author Name ---- -summary: - -Overview of the milestone accomplishments. ---- -tags: release, setup ---- -source_commit: <40-character-commit-hash> ---- -body: - -Detailed Markdown description of the milestone, technical decisions, and progress. -``` - -Alternatively, use `devlog_editor.py` to create and edit entries interactively. - ---- - -## Devlog Editor - -The included `devlog_editor.py` script is a standalone utility with zero external -dependencies (uses Python standard library and Tkinter). - -### CLI Validation - -Validate all project and devlog records without opening a GUI: - -```bash -python devlog_editor.py --validate -``` - -### Graphical Editor - -Launch the Tkinter management interface to view repository status, draft entries, -and publish updates: - -```bash -python devlog_editor.py -``` - -Features of the GUI editor: -- **Repository status**: Displays current branch, remote, and ahead/behind counts. -- **Entry manager**: Create, edit, and delete devlog entries with automatic slug and date handling. -- **Publishing safety**: Stages and commits only `.labyricorn/` publishing files, leaving application source files untouched. - ---- - -## Publishing Rules & Constraints - -The Labyricorn site importer enforces strict validation rules on imported repositories: - -1. **Native Lektor Records**: Records must be named `contents.lr` and declare `schema_version: 1`. -2. **No Raw HTML**: HTML tags (` + + diff --git a/src/js/apps/docviewer.js b/src/js/apps/docviewer.js new file mode 100644 index 0000000..3276a48 --- /dev/null +++ b/src/js/apps/docviewer.js @@ -0,0 +1,123 @@ +/** + * CyberSim OS - Document & Spreadsheet Viewer + */ + +export class DocViewerApp { + constructor({ windowManager, eventBus }) { + this.wm = windowManager; + this.eventBus = eventBus; + } + + getIconSvg() { + return ``; + } + + 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 ` +
+
+
+ 📊 ${file.name} + Read-Only +
+
Format: XLSX Tabular
+
+
+
+

${file.content.title}

+

Confidential — Internal Use Only — Prepared for NexaCore Leadership

+ + + + ${file.content.headers.map(h => ``).join('')} + + + + ${file.content.rows.map(row => ` + + ${row.map((cell, idx) => ``).join('')} + + `).join('')} + +
${h}
${cell}
+
+ Notes: Operating margin buffer is currently aligned with Q2 audit targets. Prepared by Finance Ops. +
+
+
+
+ `; + } + + if (file.type === 'pdf' && file.content) { + return ` +
+
+
+ 📑 ${file.name} + NexaCore Document +
+
Format: PDF Document
+
+
+
+

${file.content.title}

+ ${file.content.sections ? file.content.sections.map(s => ` +

${s.heading}

+

${s.text}

+ `).join('') : ''} + + ${file.content.table ? ` + + + + ${file.content.table.headers.map(h => ``).join('')} + + + + ${file.content.table.rows.map(r => ` + + ${r.map(c => ``).join('')} + + `).join('')} + +
${h}
${c}
+ ` : ''} +
+
+
+ `; + } + + return ` +
+
+
${file.name}
+
+
+
+

Archive Preview

+

Archive file "${file.name}" contents cannot be executed directly within simulated document viewer.

+
+
+
+ `; + } +} diff --git a/src/js/apps/files.js b/src/js/apps/files.js new file mode 100644 index 0000000..6ab9ee9 --- /dev/null +++ b/src/js/apps/files.js @@ -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 ``; + } + + 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 ` +
+
+
+ [Docs] Documents +
+
+ [Down] Downloads +
+
+ [Share] Company Shared +
+
+
+
Location: /Documents
+
+
+
+ `; + } + + 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 = `
This folder is empty.
`; + 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 ` +
+
[${icon}]
+
${f.name}
+
${f.size}
+
+ `; + }).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); + } + } + }); + }); + } +} diff --git a/src/js/apps/inlook.js b/src/js/apps/inlook.js new file mode 100644 index 0000000..583d629 --- /dev/null +++ b/src/js/apps/inlook.js @@ -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 ``; + } + + 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 ` +
+
+ +
+ 📥 Inbox + 0 +
+
+ 📤 Sent +
+
+ 🗑️ Trash +
+
+
+
+
+ Select an email message to view its contents. +
+
+
+ `; + } + + 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 = `
No messages in ${this.currentFolder}
`; + return; + } + + listEl.innerHTML = filtered.map(email => ` +
+
+ ${email.sender.split('(')[0]} + ${email.date} +
+
${email.subject}
+
${email.body.replace(/<[^>]*>?/gm, '').substring(0, 48)}...
+
+ `).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 = ` +
+ + + +
+ +
+
${email.subject}
+
+
+
${email.sender.charAt(0)}
+
+
${email.sender}
+
${email.rfcSender}
+
+ +
+
${email.date}
+
+ + ${email.attachments && email.attachments.length > 0 ? ` +
+ Attachments (${email.attachments.length}): + ${email.attachments.map(att => ` +
+ + ${att.name} + (${att.size}) +
+ `).join('')} +
+ ` : ''} +
+ +
+ ${email.body.replace(/\n/g, '
')} +
+ `; + + 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: \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 = ` +
+
+
+ + Report Suspicious Message to SOC +
+ +
+
+

You are about to report the following email to the NexaCore Security Operations Center:

+
+ Subject: ${email.subject}
+ Sender: ${email.rfcSender} +
+
+ + +
+
+ + +
+
+ +
+ `; + + 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 = ` +
+
+
Reply to: ${email.sender}
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ `; + + 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' + }); + }); + } +} diff --git a/src/js/apps/navigator.js b/src/js/apps/navigator.js new file mode 100644 index 0000000..f758b85 --- /dev/null +++ b/src/js/apps/navigator.js @@ -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 ``; + } + + 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 ` + + `; + } + + 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 = ` +
+

404 Page Not Found

+

The simulated URL ${url} was not found on this simulation network.

+ +
+ `; + } + } + } + + 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 = ` +
+
+

Account Verification Complete

+

Your credentials have been verified with our external single sign-on synchronization gateway. You may return to your workplace desktop.

+
+ `; + } + } +} diff --git a/src/js/apps/security_center.js b/src/js/apps/security_center.js new file mode 100644 index 0000000..f838640 --- /dev/null +++ b/src/js/apps/security_center.js @@ -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 ``; + } + + 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 ` +
+
+
+ ${this.getIconSvg()} Security Center +
+
Dashboard
+
Alerts & Logs
+
Reported Incidents
+
+
+
+ `; + } + + render() { + const main = document.getElementById('sec-main-content'); + if (!main) return; + + const hasHighAlert = this.alerts.some(a => a.severity === 'high'); + + main.innerHTML = ` +
+
Workstation Security Status
+
NexaCore Enterprise Zero-Trust Endpoint Protection
+
+ +
+
${hasHighAlert ? '⚠️' : '🛡️'}
+
+
${hasHighAlert ? 'Security Attention Required' : 'Workstation Protected & Monitored'}
+
${hasHighAlert ? 'One or more high severity security alerts require attention.' : 'All security telemetry feeds are operational. Zero active threats detected.'}
+
+
+ +

Recent Security Alerts & Notifications

+
+ ${this.alerts.map(a => ` +
+
+
+ ${a.title} + ${a.timestamp} +
+
${a.message}
+
Source: ${a.source}
+
+
+ `).join('')} +
+ +

User Incident Reports (${this.reports.length})

+ ${this.reports.length === 0 ? ` +
+ No suspicious messages or incidents reported yet during this shift. +
+ ` : ` +
+ ${this.reports.map(r => ` +
+
+
Report: ${r.reason.replace(/_/g, ' ')}
+
Target: ${r.target} | Time: ${r.time}
+
+ ${r.status} +
+ `).join('')} +
+ `} + `; + } +} diff --git a/src/js/cert/cert_generator.js b/src/js/cert/cert_generator.js new file mode 100644 index 0000000..f188a74 --- /dev/null +++ b/src/js/cert/cert_generator.js @@ -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 = ` +
+
+
+ + CyberSim OS - Official Certificate of Competency +
+ +
+ +
+
+
NexaCore Technologies • CyberSim Environment
+
Certificate of Competency
+
End-User Cybersecurity Simulation & Behavioral Verification
+ +
This certifies that
+
${certData.learner.name}
+ +
+ has successfully completed the ${certData.scenario_title} simulation, demonstrating sound investigative judgment, threat detection, safe credential handling, and policy adherence. +
+ +
+ Final Score: ${certData.evaluation.score} / 100 (PASSED) +
+ +
+
+ Certificate ID: ${certData.certificate_id}
+ Date: ${new Date(certData.issued_at).toLocaleDateString()}
+ Engine Version: ${certData.engine_version} +
+
+ Scenario Hash:
${certData.scenario_fingerprint.substring(0, 32)}...
+ Integrity Hash:
${certData.integrity_hash.substring(0, 32)}... +
+
+
+
+ + +
+ `; + + 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(); + }); + } +} diff --git a/src/js/cert/cert_verifier.js b/src/js/cert/cert_verifier.js new file mode 100644 index 0000000..7047008 --- /dev/null +++ b/src/js/cert/cert_verifier.js @@ -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}` }; + } + } +} diff --git a/src/js/core/desktop.js b/src/js/core/desktop.js new file mode 100644 index 0000000..d503341 --- /dev/null +++ b/src/js/core/desktop.js @@ -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 = ` +
${app.iconSvg}
+
${app.name}
+ `; + + 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} +
${app.name}
+ `; + 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 = ` +
${timeStr}
+
${dateStr}
+ `; + } +} diff --git a/src/js/core/notifications.js b/src/js/core/notifications.js new file mode 100644 index 0000000..203ac16 --- /dev/null +++ b/src/js/core/notifications.js @@ -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 = ``; + + toast.innerHTML = ` +
${iconSvg || defaultIcon}
+
+
${title}
+
${body}
+
+ + `; + + 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(); + } +} diff --git a/src/js/core/window_manager.js b/src/js/core/window_manager.js new file mode 100644 index 0000000..51839c5 --- /dev/null +++ b/src/js/core/window_manager.js @@ -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 = ` +
+
+ ${iconSvg || ''} + ${title} +
+
+ + + +
+
+
+
+ `; + + 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 || ''} + ${title} + `; + 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); + } +} diff --git a/src/js/engine/consequence.js b/src/js/engine/consequence.js new file mode 100644 index 0000000..08e86f6 --- /dev/null +++ b/src/js/engine/consequence.js @@ -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 = []; + } +} diff --git a/src/js/engine/event_bus.js b/src/js/engine/event_bus.js new file mode 100644 index 0000000..6d4f621 --- /dev/null +++ b/src/js/engine/event_bus.js @@ -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(); diff --git a/src/js/main.js b/src/js/main.js new file mode 100644 index 0000000..05ce114 --- /dev/null +++ b/src/js/main.js @@ -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: ``, + 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(); +}); diff --git a/src/js/scenario/fingerprint.js b/src/js/scenario/fingerprint.js new file mode 100644 index 0000000..3c9ab11 --- /dev/null +++ b/src/js/scenario/fingerprint.js @@ -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'; + } +} diff --git a/src/js/scenario/scenario_ref1.js b/src/js/scenario/scenario_ref1.js new file mode 100644 index 0000000..58b1a61 --- /dev/null +++ b/src/js/scenario/scenario_ref1.js @@ -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: 'jordan.taylor@nexacore.internal', + 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 alex.rivera@nexacore.internal (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 sarah.jenkins@nexacore.internal', + '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: 'morgan.chen@nexacore.internal', + recipient: 'jordan.taylor@nexacore.internal', + 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: 'support@nexac0re-portal.com', + recipient: 'jordan.taylor@nexacore.internal', + 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: https://intranet.nexacore.internal/sso-verification + +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: 'alex.rivera@nexacore.internal', + recipient: 'all-employees@nexacore.internal', + 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: 'billing@apex-global-supplies.net', + recipient: 'finance@nexacore.internal', + 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: 'sarah.jenkins@nexacore.internal', + recipient: 'jordan.taylor@nexacore.internal', + 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', 'billing@titancloud.com', 'Active'], + ['Quantum Edge Hardware', 'VND-209', 'invoices@quantumedge.io', 'Active'], + ['NexaLogistics Global', 'VND-315', 'accounts@nexalogistics.com', 'Active'], + ['CyberShield Auditing LLC', 'VND-402', 'finance@cybershield.net', 'Active'] + ] + } + } + } + ], + + // Simulated Web Pages for Navigator + pages: [ + { + url: 'http://intranet.nexacore.internal', + title: 'NexaCore Intranet - Home', + isSecure: true, + content: ` +
+
+
NexaCore Enterprise Intranet
+
Monday, August 24, 2026
+
+
+
+
+

Company Announcements

+
+

🔒 Security Hardening: As announced by CISO Alex Rivera, company-wide MFA upgrades are underway. Remember to review our security policy in your Documents folder.

+

📊 Q3 Financial Review: Department budget projections are being finalized this week. Contact Morgan Chen with any queries.

+
+
+ +
+
+
+

Key Contacts

+ + + + + + +
NameRoleEmail
Morgan ChenVP Financemorgan.chen@nexacore.internal
Alex RiveraCISOalex.rivera@nexacore.internal
Sarah JenkinsSr Accountantsarah.jenkins@nexacore.internal
IT HelpdeskSupportit-helpdesk@nexacore.internal
+
+
+
+
+ ` + }, + { + url: 'http://intranet.nexacore.internal/it-security', + title: 'IT Security Department - Policy & Notices', + isSecure: true, + content: ` +
+
+
IT Security & Compliance Portal
+ ← Back to Intranet +
+
+

Official Notice: Hardware MFA Rollout (CISO Alex Rivera)

+

+ NexaCore is transitioning all personnel to hardware FIDO2 keys and authenticator apps. + Official Reminder: NexaCore IT will NEVER email you asking for your password or sending direct credential reset links. +

+
+
+

Known Threat Advisory: Phishing Campaigns Targeting NexaCore

+

+ ⚠️ Be aware of external lookalike domains such as nexac0re-portal.com attempting credential harvesting. Always verify the address bar before entering any information. +

+
+
+ ` + }, + { + url: 'http://login-nexac0re-portal.com/auth/login', + title: 'NexaCore SSO - Single Sign-On Authentication', + isSecure: false, + isPhishing: true, + content: ` +
+
+ +
Enter your employee credentials to verify your account
+
+
+ + +
+
+ + +
+ +
+
+
+ ` + } + ] +}; diff --git a/src/js/scoring/aar.js b/src/js/scoring/aar.js new file mode 100644 index 0000000..307093a --- /dev/null +++ b/src/js/scoring/aar.js @@ -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 = ` +
+
+
+ + CyberSim OS - Behavioral After-Action Report (AAR) +
+
+ +
+
+
+
Overall Assessment
+
${r.totalScore} / 100
+
+
+ + ${r.isPassed ? '✓ Passed (Threshold: 80)' : '✕ Needs Review (Threshold: 80)'} + +
+
+ +

Competency Dimension Scores

+ +
+ ${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 ` +
+
+ ${c.label} + ${c.score} / ${c.max} (${pct}%) +
+
+
+
+
+ `; + }).join('')} +
+ +

Pedagogical Feedback & Observations

+
+
    + ${r.feedback.map(f => `
  • ${f}
  • `).join('')} +
+
+
+ + +
+ `; + + 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(); + }); + } + } +} diff --git a/src/js/scoring/scorer.js b/src/js/scoring/scorer.js new file mode 100644 index 0000000..9bbd4f7 --- /dev/null +++ b/src/js/scoring/scorer.js @@ -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.'] + }; + } +} diff --git a/src/verify.html b/src/verify.html new file mode 100644 index 0000000..3603fbe --- /dev/null +++ b/src/verify.html @@ -0,0 +1,164 @@ + + + + + + CyberSim OS - Standalone Certificate Verifier + + + + + + +
+
+ +

CyberSim Certificate Verifier

+
+

Offline-first cryptographic credential integrity and scoring verification engine.

+ +
+
📄
+
Drag & Drop a *.cybercert credential file here
+
or click to browse local files
+ +
+ + +
+ + + +