Skip to content

Add SCAN injection defense, embedded fonts, shape SVG guard, and refine narrative - #5

Open
lizTheDeveloper wants to merge 1 commit into
mainfrom
claude/busy-hypatia-txx7c0
Open

Add SCAN injection defense, embedded fonts, shape SVG guard, and refine narrative#5
lizTheDeveloper wants to merge 1 commit into
mainfrom
claude/busy-hypatia-txx7c0

Conversation

@lizTheDeveloper

Copy link
Copy Markdown
Owner

Summary

Implements four critical security and presentation-quality features across the research pipeline (#2), presentation generator (#3), and worker seam:

  1. SCAN stage (§7.1) — Active prompt-injection classifier scan of all free-text fields before synthesis, with Llama Prompt Guard 2 integration
  2. Embedded typeface support (§7.1) — Self-hosted font files with CSP-clean @font-face declarations to guarantee sandbox↔viewer rendering fidelity
  3. Shape SVG trust boundary (§3/§9) — Fail-closed allowlist validator for untrusted shape blocks, rejecting scripts, handlers, external refs, and <foreignObject>
  4. Narrative refinement (§6) — Optional LLM wording polish of Compose output (fail-open; structure/layout/citations untouched)

Key Changes

Research Pipeline (#2)

  • src/research/scan.mjs (125 lines): SCAN stage implementation

    • chunkText() — splits long text into ≤512-token windows on whitespace boundaries
    • scanField() — scores a field; flags if ANY window exceeds threshold
    • scanCandidates() — scans all candidates; quarantines malicious free-text fields while retaining structured metadata (copy-on-write)
    • makeLocalScorer() — builds localhost scorer for self-hosted Prompt Guard 2 (dependency-injected; returns null when unconfigured)
    • Comprehensive test suite (src/research/scan.test.mjs) covering injection payloads, label-only scorers, and metadata retention
  • src/research/pipeline.mjs (modified): Wires SCAN into the pipeline

    • Accepts opts.scan = { scorer, threshold?, maxChars? }
    • Calls scanCandidates() after ranking, before synthesis
    • Trace records quarantine count and enabled/disabled status
    • Contract adds quarantined_sources field
  • src/research/schema.mjs (modified): Adds quarantined_sources to findings doc contract

  • src/research/cli.mjs (modified): CLI wires makeLocalScorer() when PROMPT_GUARD_URL is set

Presentation Generator (#3)

  • src/prezi/fonts.mjs (100 lines): Font embedding plumbing

    • buildFontFaceCss() — emits CSP-clean @font-face rules with assets/fonts/ same-origin paths
    • makeFontDeps() — assembles { embedded, faceCss, files, families } shape with fallback stacks
    • loadFontDeps() — reads font files from a directory using a manifest; pure stdlib (no network)
    • Test suite (src/prezi/fonts.test.mjs) validates CSS generation, file loading, and end-to-end SVG/styles.css emission
  • src/prezi/shape-guard.mjs (90 lines): Shape SVG validator

    • checkShapeSvg() — fail-closed allowlist: only inert vector elements (path, rect, circle, g, defs, gradients, clipPath, title, desc)
    • Rejects <script>, <foreignObject>, <image>, <use>, <a>, event handlers, href/xlink:href, style, and unsafe URLs (external schemes, javascript:, data:, non-fragment url())
    • isSafeShapeSvg() — boolean wrapper
    • Regex-based (no DOM parser; stdlib-only per #0)
    • Test suite (src/prezi/shape-guard.test.mjs) covers geometry, handlers, external content, and IR validation integration
  • src/prezi/ir-schema.mjs (modified): Integrates shape validation

    • validateBlock() now calls `

https://claude.ai/code/session_01YNZCWtNSiZ5bWkc56eYGeE

Completes the remaining work that does not depend on the Hermes/Playwright
drivability spike, leaving only the genuinely engine-dependent stages.

#2 research:
- §7.1 Prompt Guard 2 injection scan (src/research/scan.mjs): a SCAN stage
  between RANK and SYNTHESIZE classifies every ingested free-text field in
  ≤512-token windows; fields tripping a conservative threshold are blanked
  so they never reach a prompt or #3, while structured metadata survives.
  Records quarantined_sources. Scorer is dependency-injected/mockable;
  makeLocalScorer() wires the self-hosted service via PROMPT_GUARD_URL and is
  null-when-unconfigured so layer-1 defenses still hold.

#3 generator:
- llm into Compose (refineNarrative): optional, fail-open wording refinement
  that re-validates and leaves structure/layout/citations untouched.
- shape.svg trust boundary (shape-guard.mjs): fail-closed SVG allowlist
  enforced in the IR validator and again at SVG-emit time (defense in depth).
- embedded typeface plumbing (fonts.mjs): makeFontDeps/loadFontDeps build the
  deps.fonts shape; embedded family names now flow into the SVG, @font-face
  into styles.css, files under assets/fonts/ (font-src 'self', woff/ttf MIME).
- #2#3#1 worker seam: selectGenerator() picks the prezi pipeline when an LLM
  is configured, else the #1 stub; tests stay hermetic via an injected stub.

35 new tests; full suite 215 green.
lizTheDeveloper added a commit that referenced this pull request Jun 10, 2026
Realizes the capture/assessment half of C9 (the #5 §11 forward-doc):
run a #5 interactive deck as a class exercise and capture how students
respond, without ever exposing student data publicly.

Key decisions:
- Roster + join-code students, cohort-scoped (low-friction, minimal PII);
  student sessions separate from instructor magic-link, same origin.
- Class context = authenticated app-origin route (connect-src 'self');
  public slug origin stays connect-src 'none' (reconciles #5 §11 as
  realization, not reversal).
- One non-additive seam: #6 adds a CSP-clean prezi:interaction dispatch
  to the #5 runtime; capture adapter loads only on the class route.
- Free-text: always recorded; optional instructor-triggered batch agent
  grading (LLM-gated, same gate as the #3-generator wiring).
- Privacy-first (assume minors): minimal collection, join-time consent,
  retention auto-purge, cohort data delete, no public exposure.
- Three shippable layers: core capture loop (no LLM) -> optional grading
  -> retention/purge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants