fix(security): resolve 82 CodeQL code-scanning alerts#37
Open
erseco wants to merge 6 commits into
Open
Conversation
Fix the safe, mechanical security-warning classes flagged by CodeQL across
the backend, frontend, iDevice runtimes, exporters and legacy importers.
Every change is minimal and behaviour-preserving, with a colocated test.
- double-escaping: decode & last so &lt; no longer collapses to <
- incomplete-(multi-character-)sanitization: loop tag/handler strips to a
fixed point; add global flags; escape backslash before the delimiter
- bad-tag-filter: tolerant </script > end-tag regex (whitespace/newline/case)
- incomplete-url-scheme-check: also reject vbscript:/data: (entity/ws tolerant)
- incomplete-url-substring-sanitization: new URL() exact host/path checks
- prototype-pollution: guard __proto__/prototype/constructor in deep-merge
- cross-window-information-leak: drop postMessage('*') fallback
- insecure-randomness: Web Crypto instead of the non-crypto PRNG
- insecure-download / functionality-from-untrusted-source: scheme validation, SRI
- overly-large-range: narrow [a-zA-z] to [a-zA-Z] (export + edition twins)
- actions/missing-workflow-permissions: least-privilege permissions block
Guards a RangeError in the numeric-entity decoder and keeps the slide SVG
scrubber's src/export twins in sync. 2 stored-xss (inert DOMParser round-trip)
and 4 url-substring alerts inside test bodies are skipped with documented
rationale. The 146 js/xss-through-dom alerts in iDevice rendering are deferred.
Resolve conflicts in two files: - src/routes/project.ts: drop the inline broken-link validator in favor of the shared SSRF-hardened link-validator service (single source of truth); the surrounding code already used the shared validateLink/toBrokenLinkInfo. - electrical-circuits.test.js: keep both test suites (importGlosary HTML sanitization + TikZ/SVG rendering), deduplicating the loadIdevice helper onto the shared global.loadIdevice.
Bundle ReportChanges will increase total bundle size by 503 bytes (0.03%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: app-bundleAssets Changed:
view changes for bundle: exporters-bundleAssets Changed:
|
|
- EmbeddingBridge: route _announceDocumentLoaded through postToParent so the DOCUMENT_LOADED metadata (projectId, isDirty, pageCount) is no longer broadcast to '*' when the parent origin is unknown. Closes the residual js/cross-window-information-leak the postToParent fix had missed. Updated the pre-existing test that asserted the old wildcard behavior. - link-validator: make the server-side normalizeUrlForSchemeCheck decode the full named/numeric HTML-entity set (e.g. &exelearning#106;avascript:, javascript&exelearning#58;, data:) with an out-of-range-safe code-point helper, matching the client LinkValidationAdapter._hasDangerousScheme so both ends reject the same links. Added tests for entity-encoded scheme chars and out-of-range entities. - project.ts: remove the now-dead isUnsafeLinkScheme() (superseded by the shared link-validator service; zero call sites) and its spec block.
…ted-source) - jQuery 1.6.1 (ajax.googleapis.com): upgrade protocol-relative // to https and add Subresource Integrity (sha384, verified against the served bytes) plus crossorigin, matching the FileSaver.js tag fixed earlier in this PR. - Filestack legacy loader: upgrade to https. Its URL is unversioned/mutable, so an SRI hash cannot be pinned without breaking on the next update; documented inline. This at least removes the protocol-downgrade vector.
|
erseco
force-pushed
the
hotfix/fix-codeql-warnings
branch
5 times, most recently
from
July 15, 2026 20:09
1ed29af to
345f79c
Compare
Resolve legacy importer conflicts while preserving the security hardening and the latest main fixes.
github-actions
Bot
force-pushed
the
hotfix/fix-codeql-warnings
branch
from
July 15, 2026 20:11
50d5bb7 to
9f55fd0
Compare
erseco
force-pushed
the
hotfix/fix-codeql-warnings
branch
2 times, most recently
from
July 15, 2026 20:12
345f79c to
9f55fd0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves 82 CodeQL "code scanning" alerts (all
high/mediumseverity) across the backend, the frontend, the iDevice runtimes, the exporters and the legacy importers. Every fix is a minimal, behaviour-preserving change to a real source file, paired with a colocated test asserting the security property. No iDevice behaviour, export output or accepted input is altered for legitimate content.Alerts come from the upstream code-scanning dashboard (
exelearning/exelearning→ Security → Code scanning). This PR targets the safe, mechanical, low-risk classes; the largerjs/xss-through-domcampaign in shipped iDevice rendering is intentionally deferred (see below).What's fixed (by rule)
js/incomplete-multi-character-sanitization<script>/on*=/scheme matchjs/incomplete-sanitizationescapeCatalogueKey)js/double-escaping&last in entity-decode chains so&lt;no longer collapses to<js/incomplete-url-substring-sanitizationurl.includes('host')/startsWith('https://host')withnew URL()+ exact hostname/pathname checks (rejectshost.evil.com)js/incomplete-url-scheme-checkvbscript:/data:(case-insensitive, whitespace/control/entity tolerant); allowlist where possiblejs/bad-tag-filter</script >(whitespace/newline/case)js/prototype-pollution-utility__proto__/prototype/constructorkeys in the deep-mergejs/cross-window-information-leakpostMessage(msg, '*')fallback inEmbeddingBridge; log insteadjs/insecure-randomnesscrypto.getRandomValues/randomUUID) instead of the non-crypto PRNGactions/missing-workflow-permissionspermissions:block one2e-db-matrix.ymljs/insecure-downloadjs/functionality-from-untrusted-sourcehttps+ Subresource Integrity (verifiedsha512against cdnjs) for the FileSaver.js CDN tagjs/overly-large-range[a-zA-z]→[a-zA-Z]in the mathproblems placeholder regex (both export and edition twins)Notable details
slide) —src/sanitizer.tsand its hand-maintainedexport/slide.jstwin: fixed-point scrubbing loop, tolerant</script\s*>,vbscript:/data:text/htmlremoval (legitdata:image/...preserved), and the<foreignObject>strip is now present in both twins.LinkValidationAdapter._hasDangerousScheme— numeric-entity decoding is guarded so out-of-range code points (e.g.�) can't throw aRangeError(regression test added).common.jsmedia URLs — Google Drive / mediateca extraction now parses the URL and matches the exact host + path; relative URLs and look-alike hosts are rejected, all legitimate links still resolve.Deferred / not changed (deliberate)
js/stored-xssinLatexPreRenderer.js— skipped, documented as safe. The flow parses author HTML into an inertDOMParserdocument and re-serialises it; the only newly-injected markup is the library-rendered MathJax<svg>/<math>wrapper whosedata-latexis attribute-escaped. Scrubbing here would delete legitimate author markup (TikZ/code iDevices, embedded widgets). A security-note comment plus round-trip tests were added.js/incomplete-url-substring-sanitizationlocated inside test bodies — skipped; these are intentional assertions/matchers over hardcoded, controlled strings (not URL-trust decisions). Rewriting them would weaken the tests for zero security benefit.js/xss-through-domin shipped iDevice rendering — out of scope for this PR. They are heterogeneous and behaviour-sensitive (jQuery$(attr)selectors,.html('<p>'+el.text()+'</p>')round-trips, trusted rendered SVG/mermaid) and warrant their own focused, E2E-backed effort.CodeQL fixture/vendor alerts (context, no action here)
The dashboard also lists ~304 alerts inside generated export fixtures (
test/fixtures/export/**) and a couple in vendored libs. These are copies of source that regenerate; they can't be fixed by editing the generated files. The repo uses CodeQL default setup, which ignores.github/codeql/codeql-config.yml, so apaths-ignoreentry would not clear them. They are left untouched here (they age out as fixtures are regenerated from the fixed source, or can be dismissed in the UI).Testing
make fix— clean (the single remaining Biome warning,noExplicitAnyinsrc/routes/export.ts, pre-exists onmainand is unrelated).make test-unit— 7160 pass / 0 fail, and all files meet the 90% coverage threshold.make test-frontend— green (Vitest + happy-dom).make test-integration— green.Each fix was additionally checked by an independent adversarial review pass (correctness, behaviour preservation, regex backtracking, twin-sync). Two blockers it surfaced — a
RangeErrorin the entity decoder and an out-of-syncmathproblemsedition twin — are fixed in this PR.Review follow-ups (post-review hardening)
A multi-agent review of this branch surfaced three items, now fixed in commit
b53a50f:js/cross-window-information-leak— residual wildcardpostMessageclosed.EmbeddingBridge._announceDocumentLoadedstill postedDOCUMENT_LOADED(projectId,isDirty,pageCount) tothis.parentOrigin || '*', so project metadata could broadcast to any embedding origin if the document loaded before the parent handshake set a trusted origin. It now routes throughpostToParent, inheriting the known-origin guard (dropped instead of broadcast when the origin is unknown). The pre-existing test that asserted the old'*'behaviour was updated to assert the non-leak.normalizeUrlForSchemeCheck(link-validator.ts) only decoded whitespace/control entities, so entity-encoded scheme characters (javascript:,javascript:,data:…) slipped past the broken-link checker while the clientLinkValidationAdapter._hasDangerousSchemecaught them. The server now decodes the full named/numeric entity set with the same out-of-range-safe code-point helper, keeping both ends in sync. New tests cover the entity-encoded scheme chars and out-of-range numeric entities.isUnsafeLinkScheme()inproject.tshad zero call sites (superseded by the sharedlink-validatorservice'sremoveInvalidLinks); it and its spec block were removed so coverage isn't inflated by an unused export. The dangerous-scheme property remains covered server-side bylink-validator.spec.ts.js/functionality-from-untrusted-source). The remaining protocol-relative CDN scripts inmindmaps/src/index.htmlwere upgraded tohttps; jQuery 1.6.1 now carries a verifiedsha384Subresource Integrity hash +crossorigin, matching the FileSaver.js tag. Filestack's legacy loader is an unversioned/mutable endpoint, so an SRI hash cannot be pinned without breaking on its next update — it is forced tohttps(documented inline).Verified after the fixes:
link-validator.spec.ts+project.spec.ts(260 pass),EmbeddingBridge.test.js+LinkValidationAdapter.test.js(109 pass), Biome clean on the changedsrc/files. jQuery's SRI hash was computed from the bytes served by the CDN.One item is deferred to its own PR: consolidating the duplicated fixed-point tag-strip into a shared sanitizer. The strip is a per-iDevice method duplicated across 30+ devices spanning two runtimes (the authoring iframe and the exported student package) that share no module system, so a clean dedup needs a new cross-runtime delivery mechanism — an architectural change better made on its own, with full test runs across all gamification iDevices, rather than expanding this 90-file security PR. The current strip code is correct and well-tested as-is.