The x86 lesson progress pipeline now works end-to-end: the SPA grades locally via the bundled Blink WASM emulator, ships the run outcome (stdout / exit code / register snapshot) through the offline-batch path (POST /api/Lessons/progress/batch), and the worker's new BlinkX86LessonGradingExecutor replays the lesson assertion against that snapshot to produce a verified pass.
However, the executor trusts the client-reported snapshot. A malicious client could hand-craft an outcome JSON that satisfies the assertion without ever running any assembly. This is the same trust profile Regex lessons currently accept (the SPA evaluates the regex locally and reports the result), and it's acceptable for a learning platform aimed at students — but it's not a hardened trust boundary.
What needs to be done
Close the trust gap by running the student's x86 assembly server-side in the worker, the same way C# / Python / JavaScript lessons re-execute student code through Roslyn / CPython / Node.
Implementation steps
Bundle the Blink WASM module + assembler ELF binaries into the SpikerSoft.EventHandlers.CodeExecution worker image.
The Blink emulator already ships as a self-contained WASM module (blink-x86_64.wasm + JS glue). Package it alongside Node in the worker's Docker image.
The three assembler binaries (gnu-as.2.43.50.elf, gnu-ld.2.43.50.elf, fasm.1.73.32.elf, nasm.3.00.elf) are already in the SPA's assets/ — copy them into the worker image or fetch them from a shared artifact store at build time.
Create a BlinkNodeHarness (Node.js script) that:
Loads the Blink WASM module.
Writes the student's assembly source to the virtual filesystem.
Invokes the correct assembler ELF (GNU as / Fasm / NASM) through Blink's execvp to produce an output ELF.
Links (GNU ld) if the assembler doesn't self-link (Fasm does; GNU as and NASM need ld).
Runs the linked ELF to completion (with a 5-second wall-clock cap, matching the SPA).
Captures stdout, the exit code, and a GP-register snapshot at program stop.
Outputs a JSON result to stdout matching the X86RunOutcomeDto shape ({ stdout, exitCode, registers, failureReason }).
Replace the current replay-only logic in BlinkX86LessonGradingExecutor.ExecuteLessonAsync with a two-phase approach:
Phase A: invoke the BlinkNodeHarness via JavaScriptLessonExecutor.ExecuteHarnessAsync(script, nodeExecutable, timeout, ct) — same pattern RegexLessonGradingExecutor already uses to call Node.
Phase B: parse the harness output, extract the X86RunOutcomeDto, then grade via the existing assertion-replay logic (already written).
Keep the current snapshot-trust path as a fallback behind a feature flag or a config toggle (CodeExecution:BlinkX86ServerSideEnabled = true/false) so you can roll back without a code deploy if the harness is flaky.
Update CodeExecutionConfiguration with:
BlinkWasmPath — path to the Blink WASM + JS glue on the worker filesystem.
AssemblerBinariesPath — path to the assembler ELF directory.
BlinkX86ServerSideEnabled — boolean feature toggle (default false until the harness is validated in CI).
Add CI smoke tests:
A new BlinkX86HarnessTests spec that invokes the harness with each of the three assembler dialects against a known-good assembly snippet and asserts the JSON output matches the expected outcome.
Run the harness against the existing x86 reference solutions (from X86ReferenceSolutions.cs) — one test case per (lesson, dialect) pair, asserting that the server-side outcome matches the lesson's assertion the same way the SPA's X86LessonGraderService would.
Docker image delta:
The code-runner image already has Node installed (for JS lessons). Add the Blink WASM artifacts + assembler ELFs to a /opt/blink-x86/ directory. Expected size increase: ~15 MB (Blink WASM ~8 MB compressed; assembler ELFs ~5 MB total; JS glue trivial).
Acceptance criteria
With BlinkX86ServerSideEnabled = true, the worker re-assembles + re-runs the student's x86 source server-side and grades the real run outcome — a hand-crafted fake outcome JSON in studentCode no longer produces a verified pass.
With BlinkX86ServerSideEnabled = false (default), the current replay-only behaviour is preserved (trust the client snapshot).
CI harness tests pass for all three dialects against reference solutions.
No regression for Regex / C# / Python / JavaScript grading runtimes.
Docker image size delta documented in the PR.
Why this matters
Without this, any user who opens DevTools and patches the studentCode JSON in the offline outbox (or replays a cURL with a faked outcome) can mark every x86 lesson as complete and earn skills they didn't actually demonstrate. For a kids' learning platform with visible skill badges and sponsorship tracking, that undermines the integrity signal sponsors rely on.
## Background
The x86 lesson progress pipeline now works end-to-end: the SPA grades locally via the bundled Blink WASM emulator, ships the run outcome (stdout / exit code / register snapshot) through the offline-batch path (`POST /api/Lessons/progress/batch`), and the worker's new `BlinkX86LessonGradingExecutor` replays the lesson assertion against that snapshot to produce a verified pass.
However, the executor **trusts the client-reported snapshot**. A malicious client could hand-craft an outcome JSON that satisfies the assertion without ever running any assembly. This is the same trust profile Regex lessons currently accept (the SPA evaluates the regex locally and reports the result), and it's acceptable for a learning platform aimed at students — but it's not a hardened trust boundary.
## What needs to be done
Close the trust gap by running the student's x86 assembly **server-side** in the worker, the same way C# / Python / JavaScript lessons re-execute student code through Roslyn / CPython / Node.
### Implementation steps
1. **Bundle the Blink WASM module + assembler ELF binaries into the `SpikerSoft.EventHandlers.CodeExecution` worker image.**
- The Blink emulator already ships as a self-contained WASM module (`blink-x86_64.wasm` + JS glue). Package it alongside Node in the worker's Docker image.
- The three assembler binaries (`gnu-as.2.43.50.elf`, `gnu-ld.2.43.50.elf`, `fasm.1.73.32.elf`, `nasm.3.00.elf`) are already in the SPA's `assets/` — copy them into the worker image or fetch them from a shared artifact store at build time.
2. **Create a `BlinkNodeHarness` (Node.js script) that:**
- Loads the Blink WASM module.
- Writes the student's assembly source to the virtual filesystem.
- Invokes the correct assembler ELF (GNU as / Fasm / NASM) through Blink's `execvp` to produce an output ELF.
- Links (GNU ld) if the assembler doesn't self-link (Fasm does; GNU as and NASM need ld).
- Runs the linked ELF to completion (with a 5-second wall-clock cap, matching the SPA).
- Captures stdout, the exit code, and a GP-register snapshot at program stop.
- Outputs a JSON result to stdout matching the `X86RunOutcomeDto` shape (`{ stdout, exitCode, registers, failureReason }`).
3. **Replace the current replay-only logic in `BlinkX86LessonGradingExecutor.ExecuteLessonAsync` with a two-phase approach:**
- Phase A: invoke the `BlinkNodeHarness` via `JavaScriptLessonExecutor.ExecuteHarnessAsync(script, nodeExecutable, timeout, ct)` — same pattern `RegexLessonGradingExecutor` already uses to call Node.
- Phase B: parse the harness output, extract the `X86RunOutcomeDto`, then grade via the existing assertion-replay logic (already written).
- Keep the current snapshot-trust path as a **fallback** behind a feature flag or a config toggle (`CodeExecution:BlinkX86ServerSideEnabled = true/false`) so you can roll back without a code deploy if the harness is flaky.
4. **Update `CodeExecutionConfiguration` with:**
- `BlinkWasmPath` — path to the Blink WASM + JS glue on the worker filesystem.
- `AssemblerBinariesPath` — path to the assembler ELF directory.
- `BlinkX86ServerSideEnabled` — boolean feature toggle (default `false` until the harness is validated in CI).
5. **Add CI smoke tests:**
- A new `BlinkX86HarnessTests` spec that invokes the harness with each of the three assembler dialects against a known-good assembly snippet and asserts the JSON output matches the expected outcome.
- Run the harness against the existing x86 reference solutions (from `X86ReferenceSolutions.cs`) — one test case per (lesson, dialect) pair, asserting that the server-side outcome matches the lesson's assertion the same way the SPA's `X86LessonGraderService` would.
6. **Docker image delta:**
- The `code-runner` image already has Node installed (for JS lessons). Add the Blink WASM artifacts + assembler ELFs to a `/opt/blink-x86/` directory. Expected size increase: ~15 MB (Blink WASM ~8 MB compressed; assembler ELFs ~5 MB total; JS glue trivial).
### Acceptance criteria
- [ ] With `BlinkX86ServerSideEnabled = true`, the worker re-assembles + re-runs the student's x86 source server-side and grades the real run outcome — a hand-crafted fake `outcome` JSON in `studentCode` no longer produces a verified pass.
- [ ] With `BlinkX86ServerSideEnabled = false` (default), the current replay-only behaviour is preserved (trust the client snapshot).
- [ ] CI harness tests pass for all three dialects against reference solutions.
- [ ] No regression for Regex / C# / Python / JavaScript grading runtimes.
- [ ] Docker image size delta documented in the PR.
### Why this matters
Without this, any user who opens DevTools and patches the `studentCode` JSON in the offline outbox (or replays a cURL with a faked outcome) can mark every x86 lesson as complete and earn skills they didn't actually demonstrate. For a kids' learning platform with visible skill badges and sponsorship tracking, that undermines the integrity signal sponsors rely on.
### References
- `BlinkX86LessonGradingExecutor` — `spikersoft-backend/SpikerSoft.Business/Domain/CodeExecution/Execution/BlinkX86LessonGradingExecutor.cs` (current replay-only implementation)
- `RegexLessonGradingExecutor` — pattern for Node-harness-based re-grading
- `JavaScriptLessonExecutor.ExecuteHarnessAsync` — the Node execution primitive
- `docs/tickets/x86-playground-lessons.md` § "Open questions" item 1 (CI assembler binaries)
- Blink WASM module — `spikersoft-angular/projects/spikersoft/src/assets/blink/`
- Assembler ELFs — `spikersoft-angular/projects/spikersoft/src/assets/x86-assemblers/`
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Background
The x86 lesson progress pipeline now works end-to-end: the SPA grades locally via the bundled Blink WASM emulator, ships the run outcome (stdout / exit code / register snapshot) through the offline-batch path (
POST /api/Lessons/progress/batch), and the worker's newBlinkX86LessonGradingExecutorreplays the lesson assertion against that snapshot to produce a verified pass.However, the executor trusts the client-reported snapshot. A malicious client could hand-craft an outcome JSON that satisfies the assertion without ever running any assembly. This is the same trust profile Regex lessons currently accept (the SPA evaluates the regex locally and reports the result), and it's acceptable for a learning platform aimed at students — but it's not a hardened trust boundary.
What needs to be done
Close the trust gap by running the student's x86 assembly server-side in the worker, the same way C# / Python / JavaScript lessons re-execute student code through Roslyn / CPython / Node.
Implementation steps
Bundle the Blink WASM module + assembler ELF binaries into the
SpikerSoft.EventHandlers.CodeExecutionworker image.blink-x86_64.wasm+ JS glue). Package it alongside Node in the worker's Docker image.gnu-as.2.43.50.elf,gnu-ld.2.43.50.elf,fasm.1.73.32.elf,nasm.3.00.elf) are already in the SPA'sassets/— copy them into the worker image or fetch them from a shared artifact store at build time.Create a
BlinkNodeHarness(Node.js script) that:execvpto produce an output ELF.X86RunOutcomeDtoshape ({ stdout, exitCode, registers, failureReason }).Replace the current replay-only logic in
BlinkX86LessonGradingExecutor.ExecuteLessonAsyncwith a two-phase approach:BlinkNodeHarnessviaJavaScriptLessonExecutor.ExecuteHarnessAsync(script, nodeExecutable, timeout, ct)— same patternRegexLessonGradingExecutoralready uses to call Node.X86RunOutcomeDto, then grade via the existing assertion-replay logic (already written).CodeExecution:BlinkX86ServerSideEnabled = true/false) so you can roll back without a code deploy if the harness is flaky.Update
CodeExecutionConfigurationwith:BlinkWasmPath— path to the Blink WASM + JS glue on the worker filesystem.AssemblerBinariesPath— path to the assembler ELF directory.BlinkX86ServerSideEnabled— boolean feature toggle (defaultfalseuntil the harness is validated in CI).Add CI smoke tests:
BlinkX86HarnessTestsspec that invokes the harness with each of the three assembler dialects against a known-good assembly snippet and asserts the JSON output matches the expected outcome.X86ReferenceSolutions.cs) — one test case per (lesson, dialect) pair, asserting that the server-side outcome matches the lesson's assertion the same way the SPA'sX86LessonGraderServicewould.Docker image delta:
code-runnerimage already has Node installed (for JS lessons). Add the Blink WASM artifacts + assembler ELFs to a/opt/blink-x86/directory. Expected size increase: ~15 MB (Blink WASM ~8 MB compressed; assembler ELFs ~5 MB total; JS glue trivial).Acceptance criteria
BlinkX86ServerSideEnabled = true, the worker re-assembles + re-runs the student's x86 source server-side and grades the real run outcome — a hand-crafted fakeoutcomeJSON instudentCodeno longer produces a verified pass.BlinkX86ServerSideEnabled = false(default), the current replay-only behaviour is preserved (trust the client snapshot).Why this matters
Without this, any user who opens DevTools and patches the
studentCodeJSON in the offline outbox (or replays a cURL with a faked outcome) can mark every x86 lesson as complete and earn skills they didn't actually demonstrate. For a kids' learning platform with visible skill badges and sponsorship tracking, that undermines the integrity signal sponsors rely on.References
BlinkX86LessonGradingExecutor—spikersoft-backend/SpikerSoft.Business/Domain/CodeExecution/Execution/BlinkX86LessonGradingExecutor.cs(current replay-only implementation)RegexLessonGradingExecutor— pattern for Node-harness-based re-gradingJavaScriptLessonExecutor.ExecuteHarnessAsync— the Node execution primitivedocs/tickets/x86-playground-lessons.md§ "Open questions" item 1 (CI assembler binaries)spikersoft-angular/projects/spikersoft/src/assets/blink/spikersoft-angular/projects/spikersoft/src/assets/x86-assemblers/