sveltejs/svelte
The highest-severity surfaces are the two `innerHTML` sinks (`{@html}` and the template reconciler) and the server-side CSP/hash path, all of which are correct-by-design but unforgiving of misuse. Structurally, the biggest fragility is module-global mutable state on both sides of the codebase — `compiler/state.js` makes `compile()` non-reentrant, and `active_reaction`/`active_effect`/`current_batch`/`old_values` make the runtime a single-threaded global machine where any missed `finally` leaks state across components. Blast radius is concentrated in `runtime.js`, `reactivity/batch.js`, and `phases/2-analyze/index.js`, each of which sits under thousands of fixtures. Legacy code is not dead but is a permanent second code path: legacy mode, legacy AST conversion, and `beforeUpdate`/`afterUpdate` all have their own branches.
- `{@html}` is the primary XSS surface and is documented as such: `html()` assigns user values straight to `parent_node.innerHTML` or `wrapper.innerHTML` with no sanitisation, relying only on the fact that `innerHTML` does not execute `<script>`.
- A second `innerHTML` sink exists in the template reconciler, where compiled template strings are assigned via `create_trusted_html(html.replaceAll('<!>', '<!---->'))` — safe only because the input is compiler-generated rather than user-supplied. (inferred)
- `escape_html` escapes only `&`, `<`, and (in attribute mode) `"` — single quotes and `>` are deliberately not escaped, so any compiler codegen path that emits a single-quoted attribute without also escaping would be an injection hole. (inferred)
- The server-side CSP path computes `sha256` hashes over inline script bodies and pushes them onto `global.csp.script_hashes`; `invalid_csp` is thrown only for the hash+nonce combination, and the nonce is interpolated into the attribute unescaped (` nonce="${this.global.csp.nonce}"`).
- `internal/server/crypto.js` performs a deliberately obfuscated dynamic import — `import(/* @vite-ignore */ module_name)` on the string `'node:crypto'` — specifically so static analysers cannot see the Node dependency; this defeats bundler and supply-chain tooling that relies on static import graphs.
- The compiler is not reentrant: `compiler/state.js` holds `warnings`, `source`, `filename`, `dev`, `runes`, `ignore_stack`, and `ignore_map` as module-level `let`s that `reset()` clobbers, so two concurrent `compile()` calls in one process would interleave diagnostics. (inferred)
- The runtime has the same shape of risk at larger scale: `active_reaction`, `active_effect`, `untracking`, `new_deps`, `skipped_deps`, `untracked_writes`, `write_version`, and `update_version` are all module-level mutable globals in `runtime.js`.
- `Batch.flush()` relies entirely on its `finally` block to reset seven pieces of cross-cutting state (`flush_count`, `last_scheduled_effect`, `collected_effects`, `legacy_updates`, `current_batch`, `batch_values`, `old_values`); an early return added above that block would leave the whole runtime wedged. (inferred)
- Signal semantics are encoded in 26 overlapping bitflags, and two of them collide by design — `MANAGED_EFFECT` and `DESTROYING` both use `1 << 24`, and `EFFECT_OFFSCREEN` and `DESTROYING` both use `1 << 25` — meaning correctness depends on flags never co-occurring on the same signal.
- Hydration failure is silently recoverable by default: any non-Svelte error triggers `console.warn` + full client re-render, so a genuine SSR/CSR divergence only surfaces as a warning unless the consumer passes `recover: false`.
- Legacy Svelte 4 support is a permanently parallel code path, not dead code: `legacy_mode_flag`, `compiler/legacy.js`'s AST `convert`, `legacy_pre_effect`/`legacy_pre_effect_reset`, `internal/client/dev/legacy.js`, and the `beforeUpdate`/`afterUpdate` callback queue all exist solely for it.
- Async mode is experimental and dual-pathed — `enable_async_mode_flag` is set only by importing `svelte/internal/flags/async`, and there is a `disable_async_mode_flag` explicitly annotated 'ONLY USE THIS DURING TESTING' — so the non-async path is only covered by the single `TestNoAsync` CI job.
- The `autofix.yml` workflow runs on `issue_comment` with `contents: write` and pushes a commit to the PR head branch; it mitigates the obvious fork risk by refusing non-same-repo PRs and gating on `OWNER`/`MEMBER`/`COLLABORATOR` author association.
- `ecosystem-ci-trigger.yml` mints a GitHub App token from `ECOSYSTEM_CI_GITHUB_APP_PRIVATE_KEY` and includes an explicit TOCTOU guard: it fails with a 'Security warning' if `pr.head.repo.pushed_at` is newer than the triggering comment's `created_at`.
- Four ESLint rules are disabled repo-wide with a `TODO: enable these rules` comment — `@stylistic/quotes`, `@typescript-eslint/no-unused-vars`, and `prefer-const` — so unused variables and accidental `let` are currently unenforced across all of `src/`.
- `playgrounds/sandbox/scripts/download.js` combines `execSync` from `node:child_process`, `new URL(url_arg)` on user input, and a Playwright Chromium launch against arbitrary URLs — the only shell-out + arbitrary-network path in the repo, and it is unlinted (`playgrounds/sandbox/**` is in the ESLint ignore list).