CourseForge LCMS
A web-native authoring and SCORM publishing platform
Executive Summary
CourseForge is a web-native Learning Content Management System for building interactive training and publishing it as standards-compliant SCORM packages, the format Learning Management Systems in defense, government, and simulation training consume. Authors compose lessons in the browser; CourseForge renders them to a self-contained SCORM ZIP that runs on any conformant LMS, including air-gapped and strict-CSP networks.
The platform pairs a block-based authoring model, where authors stack reusable content blocks (text, media, quiz, hotspot, interactive video, 3D model, Adobe Animate) into frames, with a publishing pipeline that inlines every runtime and bundles every asset, so a course has no runtime CDN dependency. It ships alongside a family of focused side tools (ForgeShell, ForgePack, and ForgeScaffold) that feed the same ecosystem. The whole system is built and iterated through an AI-native workflow with Claude Code. The production system is privately deployed; this paper and its public mirrors document its behavior without exposing authoring access.
The Problem
Authoring tools for compliance and simulation training tend to fall into two camps. Heavyweight desktop suites (Storyline, Captivate) are powerful but produce opaque, often bloated output and assume a Windows authoring seat. Lightweight web builders are easy but rarely emit clean, offline-safe SCORM, and almost none handle real interactive media: 3D models, hotspot video, or Adobe Animate timelines.
Defense and simulation training adds two hard constraints most tools ignore. Content frequently runs on isolated networks where no external request will ever resolve, and it must meet Section 508 / WCAG 2.1 AA accessibility. A package that pulls a script from a CDN, or a 3D viewer with no keyboard path, is a non-starter. CourseForge is built so the default output satisfies both.
The in-preview 508 audit demonstrator lets you recolor a sample frame and watch the traffic-light verdict and per-element WCAG findings update live, with a manual-review flag and a one-click nearest-passing fix. It mirrors the audit that rides in the editor.
Block & Frame Types
The full library at a glance. Each type renders identically in the editor preview and in published SCORM, and each is authored in place on the frame.





















Forge3D: Interactive 3D Blocks
The 3D Model block renders an interactive GLB asset directly inside a training
frame. Learners orbit and zoom with mouse, touch, or keyboard; authors place annotation pins on the
model surface by clicking in the editor, with no external tooling. Per-mesh
part highlighting lets a lesson call out a sub-component (e.g. a valve or a lid)
with a label and description.
A standalone Forge3D conversion app (Electron + headless Blender) prepares assets: ingesting FBX / glTF with external textures and animations and re-exporting clean, self-contained GLB with modern metallic-roughness materials. At publish time the viewer runtime, Three.js, the glTF + Draco loaders, and the Draco WebAssembly decoder are all bundled into the SCORM ZIP and loaded local-first, so 3D works on air-gapped networks. There's a dedicated Forge3D mirror with a live viewer.
ForgeScript: The Adobe Animate Runtime
Subject-matter animation is often authored in Adobe Animate and exported as an
.oam bundle. ForgeScript is a standard runtime (forge-oam.js) that plays those
timelines as a native block. An author marks timeline stops and attaches
hotspots to movie-clip instances; the runtime emits an even/odd
command cue stream (stop = pause + prompt, start = play / advance) that the CMS
consumes to gate progression and drive the media bar.
ForgeScript ships an authoring kit (a custom Animate publish template, the runtime, frame scripts,
and a standalone test harness) so animators can validate an .oam against the real
runtime before it ever reaches CourseForge. Hotspot styling (color, hover, border, pulse) is a
project-wide setting baked into the published players.
Publishing & Delivery
The packager renders every frame's blocks through one pipeline and writes a conformant SCORM 1.2 or SCORM 2004 ZIP (a Web bundle is also available). The defining property is self-containment: interactive blocks generate vanilla-JS runtimes inlined into the SCO (no React at runtime), and every dependency is bundled.
- No CDN at runtime. Three.js, loaders, the Draco decoder, and block runtimes are bundled under the package and loaded locally, with no external request at runtime, so courses run on isolated / strict-CSP LMS environments (e.g. DoD networks).
- Assets travel with the package. 3D models live at a relative path inside the ZIP; no asset fetch is made at runtime.
- Standard completion / scoring. Quiz blocks drive SCORM completion and score reporting through the SCO shell, aggregated across every quiz on a SCO (including quizzes embedded in interactive video).
- Publish-time preflight. Before a package is written, a server-side gate validates every block type in the course. It hard-blocks anything a learner could never get past, an image with no alt text, an incomplete quiz, a graded hotspot with no correct region to click, or a branch pointing at a frame that is not in the course. The check knows each assessment type's rules, so it holds a Likert survey to its own completeness, a statement and a rating scale, rather than faulting it for lacking the right answer a survey never has. Everything else gets to publish, with advisory flags on unfinished authoring the runtime renders around rather than breaks on, an asset field still holding a draft placeholder, an empty navigation shell, or a branch wired to nowhere on either path. Every publish also captures a content snapshot, the full course JSON plus its hash, for provenance.
Draco-compressed models are the one case that asks the host CSP to permit WebAssembly
('wasm-unsafe-eval'); uncompressed GLBs need no such allowance.
Accessibility
Accessibility is a default, not a pass. Output targets Section 508 / WCAG 2.1 AA:
interactive blocks expose proper roles and ARIA, the 3D viewer is fully keyboard-operable (orbit,
zoom, reset, and annotation cycling) with a descriptive canvas label, and even the visual tasks
have a non-pointer path: graded hotspot questions tab through labeled regions and activate with
Enter, so a click-the-target question is fully operable from the keyboard. Focus management returns
focus to the triggering control after a popover closes, and animations honor
prefers-reduced-motion. Color choices are checked for contrast, and a high-contrast
viewing mode mirrors the rest of the lab's tools. A formal VPAT is on the
roadmap.
Security & Hardening
An authoring platform lives on two trust boundaries: it ingests untrusted uploads (SCORM and Adobe Animate archives, media ZIPs, Figma shell exports) and it renders authored content back into a live preview and a published package. CourseForge treats both as hostile input by default, and every guard ships with a test that reproduces the attack it stops.
Archive ingest is bounded before extraction. Every archive that enters the system is checked for entry count, total decompressed size, and compression ratio and rejected if any limit is exceeded, so a small malicious upload cannot expand to exhaust disk or memory (a "zip bomb"). Paths carried inside an archive, whether an Adobe Animate manifest entry point or an imported shell asset filename, are contained to their extraction root by component-aware path resolution, so a crafted archive cannot traverse out of its sandbox to read or overwrite a file elsewhere on the server.
Authored content is sanitized on the way out. Rich text an author writes passes
through a single shared sanitizer boundary before it renders, so a pasted
<script> or inline event handler never becomes live markup in the editor
preview. The standalone Web bundle escapes the project and frame names it writes
into the player chrome, so a course title can never inject script into the exported HTML.
The API fails closed and quiet. Server errors return a generic message while the underlying exception and stack trace are logged server-side and never surfaced to the client, and a publish request validates its target format before a job is created, so a malformed request cannot strand a running job.
Deployed access is gated by owner login. The production deployment requires an authenticated session for viewing, content edits, uploads, publishing, and reset operations. The owner signs in and the server issues a signed HttpOnly, SameSite session cookie that page scripts cannot read, so a framed package can never lift it the way it could a token kept in browser storage. No public guest or preview mode is offered on the production system. A separate shared token authenticates approved command-line and CI callers. Local development may run without deployment credentials, but that local behavior does not make the hosted product a public demo.
The rendered preview is a second trust boundary. Every response carries
X-Content-Type-Options: nosniff, and a report-only Content-Security-Policy watches
the editor for anything an enforced policy would later block. Uploaded navigation shells and
interactive-media packages run inside sandboxed frames, and their companion
assets are served with correct, explicit content types rather than guessed ones.
Executable author packages are isolated on their own origin. A navigation
shell or an Adobe Animate package is, by design, arbitrary author-supplied HTML and JavaScript.
Even inside a sandbox, a same-origin frame shares the editor's cookies and can reach
back into its DOM and API, so the sandbox alone is not a real boundary for code the platform
itself renders. CourseForge closes that gap by serving every executable package from a
dedicated, credential-free origin (packages.courseforge.dev),
now live. That origin carries an enforcing Content-Security-Policy whose
load-bearing line is connect-src 'none': a package can run its own code but cannot
call the editor API, phone home, or exfiltrate anywhere. frame-ancestors names
only the editor origin, so nothing else may frame a package;
Cross-Origin-Resource-Policy: cross-origin, an Origin-Agent-Cluster
header, and a disabled document.domain keep the two origins from re-coupling into a
shared agent; and the package origin never issues a cookie, so there is no credential on it to
steal. The one capability the editor still needs, a read-only content audit of
the framed package (used by the accessibility and completeness checks), runs over a narrow
MessageChannel bridge with an explicit handshake rather than by reaching across a
shared origin. The final cutover, pointing every package frame at that origin and dropping the
last same-origin escape hatch, is feature-flagged and rolling out behind an end-to-end gate.
Packaging is bounded for the target runtime, not just the author. Because a published course has to unzip and run intact on a locked-down LMS, the publish preflight also rejects package internals that would break there: a hard gate blocks any archive path long enough to exceed a strict Windows path limit once extracted, so an author-named Animate asset buried deep in a folder tree cannot produce a course that silently fails to unpack on a target system.
Each hardening measure carries an isolated regression test that reproduces the original failure, an oversized archive, a traversal path, an over-long extracted package path, an injected title, an unauthenticated write, a package attempting to reach its framer, and asserts the guard holds, so the protections cannot silently regress as the platform evolves.
The Tooling Ecosystem
CourseForge sits at the center of a family of focused tools that share its design language and feed its pipeline:
Each is independently deployable yet interoperable: ForgePack conditions media for blocks, ForgeShell produces the shells courses run inside, and Forge3D prepares the GLB assets the 3D block consumes.
Architecture & Stack
A Flask / Python server backs a React authoring client, with the block schema as the contract between editor, preview, and packager. Course data persists in a relational store with migrations; the SCORM packagers are Python modules that compose each SCO's HTML, inline the block runtimes, and bundle assets. The whole platform, and every side tool, deploys on Railway.
Sensitivity Marking
Training built for defense and government audiences often carries portions at different classification levels, and each has to be marked to policy. ForgeMark, CourseForge's marking subsystem, treats a course's marking policy as data: it derives the classification banner, the per-portion tags, and the derivative-authority block once, then renders them identically across the authoring preview, the SCORM packages, and the web bundle. The marking colors are sourced to DoD 5220.22-M and Astro UXDS and held to the WCAG AA contrast floor, and a course whose markings fail that floor, exceed the allowed level, or carry an incomplete authority block does not publish.
Read the ForgeMark whitepaper for the standards it follows, the level palette with measured contrast, the derivative-authority block, and the publish gates.
What This Demonstrates
CourseForge spans the full stack of a real product: a schema-driven authoring model, a browser-based editor with live preview, three custom interactive runtimes (3D, Adobe Animate, interactive video), a seven-type assessment engine with a tiered feedback ladder and keyboard-operable graded hotspots, a standards-compliant publishing pipeline with hard offline / CSP constraints, and accessibility as a baseline. It's surrounded by an ecosystem of tools that share its design language and feed its pipeline. Every notable decision (self-contained SCORM, the decoupled shell bridge, the cue-stream runtime, per-mesh part highlighting) is deliberate and has a rationale. Built and iterated AI-natively with Claude Code, where the limiting factor is judgment, not typing.