Add Fortify scanner THREAT_MODEL.md to third_party/jquery repo Bug: b/548455274 Change-Id: Iede575191dd3ac6ad7b3cc3e8dff0853be1757b7 Reviewed-on: https://gnocchi-internal-review.git.corp.google.com/c/third_party/jquery/+/316628 Reviewed-by: Adam Stone <stoneadam@google.com>
diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md new file mode 100644 index 0000000..8b3f46d --- /dev/null +++ b/THREAT_MODEL.md
@@ -0,0 +1,328 @@ +# Security Threat Model: jQuery (AppSheet Trusted-Types Fork) + +## Asset Definition & Scope + +- **Component:** jQuery — client-side JavaScript DOM manipulation, traversal, + event, effects, and AJAX library. +- **Repository:** Git-on-Borg + (`https://gnocchi-internal.googlesource.com/third_party/jquery`, branch + `main`). Mirrored on the legacy host + `https://appsheet-third-party.googlesource.com/jquery`, which is the URL the + AppSheet frontend actually resolves at install time. +- **Upstream baseline:** jQuery **3.6.2** (upstream release 2022-12-13), + including the bundled **Sizzle 2.3.8** selector engine + (`external/sizzle/dist/sizzle.js`). Declared in `METADATA` as + `version: "3.6.2"`, `last_upgrade_date: 2023-03-01`. +- **Fork delta:** This is a **Trusted-Types-patched** build of jQuery. Relative + to the pristine upstream 3.6.2 tarball, only four source files differ, all of + them to remove `innerHTML` writes performed during *browser feature + detection*: + - `src/core/support.js` — under Trusted Types, short-circuits the Safari 8 + `support.createHTMLDocument` probe instead of writing + `body.innerHTML = "<form></form><form></form>"`. + - `src/manipulation/support.js` — under Trusted Types, hardcodes + `support.noCloneChecked = true` and `support.option = true` instead of + probing via `div.innerHTML = "<textarea>x</textarea>"` / + `"<option></option>"`. + - `external/sizzle/dist/sizzle.js` — three sites: two `assert()` probes + rebuilt with `document.createElement` / `setAttribute` / + `appendChild` instead of `innerHTML`, and the legacy IE<8 / IE<9 + `addHandle( "type|href|height|width" )` and `addHandle( "value" )` + attribute-interpolation probes skipped entirely when + `window.trustedTypes.createPolicy` exists. + - `src/core.js` — `version` reverted to the `"@VERSION"` build placeholder. + - Additionally the repo commits a locally produced `dist/jquery.js`, + `dist/jquery.min.js`, and `dist/jquery.min.map` (banner + `v3.6.2-pre`, `Date: 2023-03-15T20:55Z`), plus Google `METADATA` and + `LICENSE` files. +- **Explicitly NOT changed:** no CVE backports, no security hardening, and no + modification to any runtime HTML sink. `jQuery.fn.html`, `buildFragment`, + `jQuery.parseHTML`, `jQuery.htmlPrefilter`, `jQuery.globalEval`, `DOMEval`, + and the Sizzle selector parser are byte-identical to upstream 3.6.2. +- **Scope:** The full library source (`src/`), the vendored selector engine + (`external/sizzle/`), and the committed prebuilt bundles (`dist/`) that are + the artifacts actually consumed by AppSheet. + +### Consumption by AppSheet + +- Pinned by exact commit in + `jeenee/Nirvana/Content/package.json`: + `"jquery": "git+https://appsheet-third-party.googlesource.com/jquery#1d60e5afef1b0287e855686a4d524d8cf3aa61ee"` + (with `@types/jquery` 3.5.14). +- A single ES import in + `Nirvana/Content/scripts/_shared/external/jqueryInit.js` (`import $ from + 'jquery'; window.$ = window.jQuery = $;`) publishes jQuery as a **global**; + roughly 75 first-party `.ts/.tsx` files then consume `$` / `jQuery` without + importing it, concentrated in `scripts/legacy/**` (Form, List, Gallery, + Deck, Calendar, Dashboard, GoogleMap, Graph, Search, FastTable, SlideShow + controllers), `scripts/_shared/lib/**`, and + `scripts/_shared/models/LegacyViewManager.tsx`. +- `webpack.config.js` resolves the package `main` (`dist/jquery.js`, + unminified) into every SPA entry point (`app-main`, `editor-main`, + `site-main`, `my-apps-main`, `templates-main`, `app-gallery-main`) and + Terser-minifies it into the shipped `build/[name].js`; jQuery is explicitly + excluded from `babel-loader`. +- Server-rendered Razor views additionally ship a pre-minified copy via + `<script nonce="@CspNonce" src="/Scripts/jquery-3.6.2.min.js">` + (`V3/MainServer/Views/Shared/_LegacyContentLayout.cshtml` and friends). +- Bundled jQuery plugins that extend the same `$` object: + `_shared/external/jqueryFileTree`, `jquery.exif`, `jquery.canvasresize`. + +## Prioritization Signals + +- **1P OSS:** No (third-party open-source library maintained as an internal + customized fork for Google AppSheet; upstream is + `https://github.com/jquery/jquery`, MIT). +- **1P Proprietary Shipped Software:** Yes. Although the code itself is + third-party OSS, this fork's `dist/jquery.js` is **compiled and minified + into the proprietary AppSheet web bundles that are served to customer + browsers**, and a minified copy is served directly as + `/Scripts/jquery-3.6.2.min.js`. It therefore executes as shipped, + minified first-party JavaScript inside every AppSheet end-user and app-editor + session, in the same origin and with the same privileges as AppSheet's own + code. A defect here is a defect in shipped AppSheet client software, not + merely in a build-time dependency. +- **High-Risk Code Surface:** Yes. jQuery is fundamentally a collection of + DOM-injection sinks: string-to-DOM HTML parsing + (`buildFragment` → `tmp.innerHTML`, `jQuery.htmlPrefilter`, + `jQuery.parseHTML`, `jQuery.fn.init` HTML-string coercion), dynamic script + execution (`jQuery.globalEval` → `DOMEval` script-element injection, the + `text script` AJAX converter, JSONP callback injection in + `src/ajax/jsonp.js`), a hand-written regex-based CSS selector parser + (Sizzle), attribute/property writers, and a deep-merge utility + (`jQuery.extend`). The fork additionally *disables* several + feature-detection code paths, which changes runtime behavior. +- **Perimeter Exposure:** Yes. AppSheet is a 1P Google Cloud service exposed to + the public Internet; this library is downloaded and executed by every + unauthenticated and authenticated visitor of AppSheet app pages, the app + editor, the site/marketing pages, and the app gallery. +- **Data Sensitivity:** High. jQuery-driven legacy view controllers render + customer business records, PII, file/attachment metadata, account and + billing surfaces, and app-definition metadata directly into the DOM. Because + it runs in the AppSheet origin, a compromise yields access to session + cookies, `localStorage`/`localforage` caches of record data, and the ability + to issue authenticated first-party API calls on the victim's behalf. +- **Untrusted Input Handling:** Yes. Strings that reach jQuery APIs originate + from (a) **AppSheet app definitions** authored by customers — column names, + view names, action labels, format rules, expressions; (b) **end-user record + data** from customer spreadsheets/databases, including free-text and + Long-Text columns and uploaded file names; (c) **first-party API responses** + (`JeeneeBrowserAPI.ts`, `JSONHelp.ts`) whose payloads are derived from (a) + and (b); and (d) URL/hash and `postMessage`-adjacent values in the legacy + controllers. None of these are trustworthy: an AppSheet app author is a + different, lower-privileged principal than the end users of the app they + publish. +- **Business Value:** jQuery is load-bearing for AppSheet's entire legacy view + layer and the app editor shell; it cannot be trivially removed. It is also + the specific reason this fork exists — the patches were required so AppSheet + could roll out `require-trusted-types-for 'script'` (see + `go/appsheet-tt-oss`). A regression in these patches either breaks the + Trusted Types rollout (losing AppSheet's primary DOM-XSS backstop) or breaks + app rendering for all customers. A DOM-XSS in this library is a + cross-customer, cross-tenant account-takeover primitive. + +## Scanning Harness Prompts + +Direct the agentic scanner as follows. Note that the *only* Google-authored code +in this repository is the Trusted Types patch set; findings in that delta are +strictly higher priority than findings in pristine upstream code. + +1. **Audit the Trusted Types patch delta first.** Review the four TT-gated + branches in `src/core/support.js`, `src/manipulation/support.js`, and + `external/sizzle/dist/sizzle.js`. Verify that (a) the + `window.trustedTypes && window.trustedTypes.createPolicy` guard is a sound + proxy for the conditions the original probes tested, (b) hardcoding + `support.noCloneChecked = true` and `support.option = true` does not cause + `jQuery.fn.clone` / `wrapMap` to skip a correctness or safety step, and (c) + skipping the Sizzle `addHandle( "type|href|height|width" )` and + `addHandle( "value" )` registrations does not change how attribute selectors + resolve `href`/`src`/`type`/`value` on any supported browser. +2. **Flag the `support.createHTMLDocument` type deviation.** In the TT branch of + `src/core/support.js` the IIFE is *not invoked* — `support.createHTMLDocument` + is assigned a **function object**, where upstream assigns a **boolean**. Its + sole consumer, `jQuery.parseHTML` in `src/core/parseHTML.js`, uses a truthy + check, so the *safe* inert-`document.implementation.createHTMLDocument` + branch is still taken; but confirm no other code (including jQuery plugins + such as `jqueryFileTree`, `jquery.exif`, `jquery.canvasresize`, or + `jquery-migrate`) performs a strict `=== true` comparison that would silently + fall through to parsing untrusted HTML in the **live** document, which would + execute inline event handlers and load subresources immediately. +3. **Trace all string→DOM parsing sinks for XSS.** Focus on + `src/manipulation/buildFragment.js` (`tmp.innerHTML = wrap[1] + + jQuery.htmlPrefilter( elem ) + wrap[2]`), `jQuery.fn.html` + (`src/manipulation.js`, including the `rnoInnerhtml` shortcut and the + `try { elem.innerHTML = value }` fallback), `jQuery.htmlPrefilter`, + `jQuery.parseHTML`, and the HTML-string branch of `jQuery.fn.init` + (`src/core/init.js`, `rquickExpr`). Confirm the trac-9521 / trac-11290 + mitigations (leading-`<` requirement, `#id` prioritization) are intact and + that no wrapper-map path can be used to smuggle `<script>`, `<style>`, + `<svg>`, `<math>`, or mXSS-style mutation payloads. Explicitly re-verify the + CVE-2020-11022 / CVE-2020-11023 `htmlPrefilter` remediations are present. +4. **Trace all dynamic script-execution sinks.** `jQuery.globalEval` + (`src/core.js`), `DOMEval` (`src/core/DOMEval.js` — creates a `<script>` + element, copies `type`/`src`/`nonce`/`noModule`, appends to `doc.head`), + `src/manipulation/_evalUrl.js`, the `"text script"` converter in + `src/ajax/script.js`, and JSONP callback-name handling in + `src/ajax/jsonp.js` (`rjsonp`, `jsonpCallback`, the `window[ callbackName ]` + assignment). Pay particular attention to whether `DOMEval`'s **nonce + propagation** can be abused to launder attacker-controlled script past + AppSheet's `script-src 'nonce-…' 'strict-dynamic'` CSP. +5. **Analyze the Sizzle selector parser for injection and ReDoS.** + `external/sizzle/dist/sizzle.js` tokenizer regexes (`rcomma`, `rcombinators`, + `rattributeQuotes`, `matchExpr`, `rpseudo`, `runescape`) and the `:has()`, + `:not()`, `:contains()`, and attribute-selector handlers, for catastrophic + backtracking and for cases where a selector string containing `<` is + reinterpreted as HTML by `jQuery.fn.init`. +6. **Check `jQuery.extend` prototype pollution.** `src/core.js` contains the + CVE-2019-11358 fix (`if ( name === "__proto__" || target === copy ) continue;`). + Verify it is present and complete in both `src/` and the committed + `dist/jquery.js`, and that `jQuery.isPlainObject` / + `jQuery.parseJSON`-adjacent paths cannot reach `constructor.prototype`. +7. **Verify `dist/` provenance.** The committed `dist/jquery.js` / + `jquery.min.js` / `jquery.min.map` were built locally (banner `3.6.2-pre`) + and are the artifacts AppSheet actually ships. Diff them against `src/` + + `external/sizzle/` to confirm they contain exactly the TT patch set and no + unreviewed additional code. +8. **Report upstream version drift.** Baseline 3.6.2 is materially behind + current upstream jQuery (3.7.x). Enumerate upstream security-relevant fixes + and advisories landed after 3.6.2 that are absent here. + +## Entry Points and Untrusted Inputs + +All entry points are **client-side JavaScript API calls** made by AppSheet +frontend code; the library has no network listeners of its own. "Trusted?" +describes the data that AppSheet actually passes. + +| Entry Point | Type | Trusted? | Validation | +|---|---|---|---| +| `jQuery()` / `$( selector )` (`src/core/init.js`, `jQuery.fn.init`) | Selector string or HTML string → DOM | No | `rquickExpr` requires a leading `<` (and trailing `>`, length ≥ 3) to treat input as HTML; prioritizes `#id` over `<tag>` to blunt `location.hash` XSS (trac-9521). Anything else is handed to Sizzle as a selector. | +| `jQuery.fn.html( value )` (`src/manipulation.js`) | HTML string → `innerHTML` | No | `rnoInnerhtml = /<script\|<style\|<link/i` gates the direct-`innerHTML` fast path; `jQuery.htmlPrefilter` normalizes self-closing tags. **No sanitization.** | +| `jQuery.fn.append` / `prepend` / `after` / `before` / `replaceWith` / `wrap` (`domManip` → `buildFragment`) | HTML string or node → DOM | No | `rhtml = /<\|&#?\w+;/` decides string-vs-text; HTML strings go to `buildFragment` and are parsed via `innerHTML`. **No sanitization.** | +| `jQuery.parseHTML( data, context, keepScripts )` (`src/core/parseHTML.js`) | HTML string → detached nodes | No | Parses into an inert `document.implementation.createHTMLDocument( "" )` when `support.createHTMLDocument` is truthy (so scripts/inline handlers do not auto-run); strips `<script>` unless `keepScripts`. Note `jQuery.fn.init` calls it with `keepScripts = true`. | +| `jQuery.globalEval( code, options, doc )` (`src/core.js` → `src/core/DOMEval.js`) | Arbitrary JS string | No | None. Injects a `<script>` into `doc.head`, propagating `type`/`src`/`nonce`/`noModule`. Direct code-execution primitive. | +| `jQuery.ajax` / `$.get` / `$.post` / `$.getJSON` (`src/ajax.js`, `src/ajax/xhr.js`) | URL, headers, body, response | No | URL is not validated or scheme-checked; `crossDomain` is inferred by comparing against `src/ajax/var/location.js`. Response is run through `converters` selected by `dataType`/`Content-Type`. | +| `dataType: "script"` responses (`src/ajax/script.js`) | Remote response body | No | Auto-execution suppressed for cross-domain responses without an explicit `dataType`; otherwise the `"text script"` converter calls `jQuery.globalEval`. | +| JSONP requests (`src/ajax/jsonp.js`) | Remote response body + callback name | No | Response is executed as script by design; `jsonpCallback` name is written to `window`. | +| `jQuery.fn.load( url, params, callback )` (`src/ajax/load.js`) | Remote HTML fragment | No | Response is passed to `jQuery.parseHTML` and injected via `.append(...).find( selector )`. | +| `jQuery.fn.attr` / `prop` / `css` / `addClass` (`src/attributes/*.js`, `src/css.js`) | Attribute / style values | No | No URL-scheme validation — a dynamic `href`/`src` value of `javascript:` or `data:` is written verbatim. | +| `jQuery.extend( true, target, src )` (`src/core.js`) | Object graph (often parsed JSON) | No | Skips `__proto__` and self-referential copies (CVE-2019-11358 fix). | +| Sizzle selector strings (`external/sizzle/dist/sizzle.js`) | CSS selector | No | Regex tokenizer with an internal compile cache; unrecognized pseudo-selectors raise a syntax error. | +| `window.trustedTypes` (`src/core/support.js`, `src/manipulation/support.js`, `sizzle.js`) | Browser global / feature flag | Yes (browser-provided) | Presence check only (`window.trustedTypes && window.trustedTypes.createPolicy`). Determines which of the two behavior branches this fork executes. | + +## Trust Boundaries and Auth Assumptions + +- **Authentication**: None at the library level. jQuery executes in the + browser with whatever ambient authority the AppSheet origin already has — + session cookies are attached automatically to same-origin `jQuery.ajax` + calls (`xhrFields`/`withCredentials` semantics are decided by the caller). +- **Authorization**: None at the library level. Access control is enforced + server-side by AppSheet (`jeenee` MainServer / ControllerHandler); jQuery + will faithfully issue whatever request the calling code constructs. +- **Implicit trust**: The library implicitly trusts every string handed to its + HTML and script sinks. It provides **no sanitizer** and makes no attempt to + distinguish app-author content from end-user content. AppSheet's frontend + is responsible for choosing `.text()` over `.html()`, and for routing + genuinely-HTML content through `dompurify` / `sanitize-html` / `safevalues`. +- **Trusted Types as the compensating control**: This fork exists precisely + because AppSheet enforces + `require-trusted-types-for 'script'` + (`Security/SecurityHeadersFramework/SecurityHeaderConstants.cs`; + `Security/SecurityHeaders/SecurityHeaders.cs` defaults to + `TrustedTypesMode.PartialEnforce`, flag-gated by + `ITrustedTypesEnforcementModeFeatureGate`, with a report-only variant for + rollout). The client-side `'default'` policy in + `Nirvana/Content/src/app/globals/trusted_types.ts` is deny-by-default: its + `createHTML` returns a value only for `''`, `'&'`, and `'​'` and + returns `null` for everything else, so an unreviewed raw-HTML sink **throws** + rather than executing. Critically, the fork's patches only remove `innerHTML` + writes from *feature detection*; **jQuery's runtime HTML sinks still assign + to `innerHTML`** and therefore still depend on that default policy for + safety. Two important corollaries for scanners: (i) the protection is only as + strong as the enforcement flag's rollout state — any user or surface where + the gate is off, or where CSP is report-only, has no backstop; and (ii) the + default policy is a global, so any code path that broadens it (e.g. the + named `unsafeCreateHtmlPolicy`, `'filepicker-fix'`, or `'jquery-modal-fix'` + policies elsewhere in `jeenee`) re-opens the sinks. +- **Boundary crossings**: AppSheet app author (customer) authors an app + definition → AppSheet backend stores/serves it → end user (a different, + often external principal) loads the app in a browser → app-definition + strings and record data cross into the **same-origin DOM** through jQuery → + rendered alongside the victim's authenticated session. A second crossing: + first-party API responses (`JeeneeBrowserAPI.ts`, `JSONHelp.ts`) → jQuery + AJAX converters → DOM. A third: third-party origins reachable via + `dataType: "script"` / JSONP → same-origin script execution. + +## Sensitive Data Paths + +| Data Type | Source | Destination | Protection | +|---|---|---|---| +| Customer record data & PII (rows, free-text and Long-Text columns, file names) | AppSheet backend API (`JeeneeBrowserAPI.ts`, `JSONHelp.ts` → `$.ajax` / `$.getJSONUnsigned` / `$.postJSON`) | Legacy view controllers → DOM via jQuery | HTTPS transport; server-side authz; rendered predominantly through `.text()` and React, with Trusted Types as the backstop for any raw-HTML path | +| App-definition metadata (view/column/action labels, format rules) | Customer app author → AppSheet backend | `LegacyViewManager.tsx` and legacy controllers → DOM | Same-origin rendering; **author is a distinct, lower-trust principal from the end user** — this is the primary stored-XSS vector into jQuery sinks | +| Session cookies & auth context | Browser cookie jar | Same-origin `jQuery.ajax` requests (`src/ajax/xhr.js`) | Ambient same-origin credentials; not read or persisted by jQuery itself | +| CSP nonce | `@CspNonce` on `<script>` tags | Copied onto generated `<script>` elements by `DOMEval` (`preservedScriptAttributes.nonce`) | Nonce is propagated verbatim from the reference node — a script-execution laundering surface worth explicit review under `'strict-dynamic'` | +| Cloud-storage file listings (`path`, `name`) | AppSheet server file-listing API via the `getTree` callback | `scripts/_shared/external/jqueryFileTree.js` → `$( '<li…><a href="#" rel="' + data[i].path + '">' + data[i].name + '</a></li>' )` → `.append()` | **Raw HTML string concatenation, no escaping.** Mitigated today only because the sole consumer (`components/app/FileTree.tsx`) is unreferenced, plus Trusted Types | +| Action button markup (`buttonHtml`) | Would be app-definition-derived | `LegacyViewManager.tsx` `ShowOverlayActionButtons()` → `.html( actions[i].buttonHtml )` | **Unescaped `.html()` sink.** Currently dead code (no callers, property never assigned) but present in the shipped bundle | +| Static third-party map plugin source | Bundled constant `MarkerWithLabelRaw` | `scripts/_shared/lib/GoogleMaps.ts` → `$.globalEval` | Wrapped in `safevalues` `scriptSafeByReview`; source is a compile-time constant, not attacker-influenced | + +## Privileged Actions + +| Action | Location | Guard | +|---|---|---| +| HTML string parsing into DOM nodes | `manipulation/buildFragment.js:buildFragment` (`tmp.innerHTML = wrap[1] + jQuery.htmlPrefilter( elem ) + wrap[2]`) | None in-library; relies on the Trusted Types `'default'` policy and on callers passing safe values | +| Direct `innerHTML` assignment | `manipulation.js:jQuery.fn.html` | `rnoInnerhtml` (`/<script\|<style\|<link/i`) gate + `try/catch` fallback to `empty()` + `append()`; no sanitization | +| Inert-document HTML parsing | `core/parseHTML.js:jQuery.parseHTML` | Uses `document.implementation.createHTMLDocument( "" )` when `support.createHTMLDocument` is truthy; strips `<script>` unless `keepScripts` is set | +| Arbitrary script execution | `core/DOMEval.js:DOMEval`, reached via `core.js:jQuery.globalEval`, `manipulation/_evalUrl.js`, `ajax/script.js` `"text script"` converter | None. Propagates `nonce` from the reference node so injected scripts satisfy a nonce-based CSP | +| Deferred/injected script re-execution on DOM insertion | `manipulation.js:domManip`, `manipulation/setGlobalEval.js`, `manipulation/getAll.js` (`rscriptType` check) | Only executes nodes whose `type` matches `rscriptType`; tracks already-evaluated nodes via `dataPriv` | +| Cross-origin network requests | `ajax.js:jQuery.ajax`, `ajax/xhr.js`, `ajax/script.js` script-tag transport, `ajax/jsonp.js` | Browser SOP/CORS only; no in-library URL allowlist or scheme validation | +| Global namespace mutation | `ajax/jsonp.js` (`window[ callbackName ]`), `jqueryInit.js` (`window.$ = window.jQuery = $`) | None | +| Attribute / property / style writes | `attributes/attr.js`, `attributes/prop.js`, `css.js:jQuery.fn.css` | Boolean-attribute and `nodeHook` normalization only; **no URL-scheme validation** on `href` / `src` / `action` | +| Deep object merge | `core.js:jQuery.extend` | Skips `__proto__` and self-referential copies (CVE-2019-11358 remediation) | +| Feature-detection branch selection (fork-specific) | `core/support.js`, `manipulation/support.js`, `external/sizzle/dist/sizzle.js` | `window.trustedTypes && window.trustedTypes.createPolicy` presence check only | + +## Priority Review Areas + +1. **The Trusted Types patch delta itself** (`src/core/support.js`, + `src/manipulation/support.js`, `external/sizzle/dist/sizzle.js`) — this is + the only Google-authored code in the repository, it is unreviewed by + upstream, and it changes `support.*` flags that gate cloning, `wrapMap` + wrapping, and attribute resolution throughout the library. +2. **The `support.createHTMLDocument` function-vs-boolean deviation** — the + TT branch assigns an *uninvoked* IIFE where upstream assigns a boolean; it is + truthy so `jQuery.parseHTML` still takes the safe inert-document path today, + but any strict `=== true` comparison in a plugin or a future upstream merge + would silently downgrade parsing into the live document, executing inline + handlers and firing subresource loads. +3. **HTML-string sinks reachable from customer content** — + `buildFragment`/`htmlPrefilter`, `jQuery.fn.html`, `jQuery.parseHTML`, and + the HTML branch of `jQuery.fn.init`; these are the direct stored-DOM-XSS path + for app-definition and record data authored by one AppSheet principal and + rendered in another's authenticated session. +4. **Script-execution sinks and CSP-nonce laundering** — `jQuery.globalEval` → + `DOMEval`, `_evalUrl`, the `"text script"` AJAX converter, and JSONP; the + deliberate copying of the `nonce` attribute onto generated `<script>` + elements is exactly the primitive that defeats a nonce + `'strict-dynamic'` + policy if an attacker controls the evaluated string. +5. **Selector injection and ReDoS in Sizzle** — a hand-rolled regex tokenizer + that AppSheet feeds with dynamically constructed selectors (e.g. the + `window`-exposed `SubscribeEmail( id )` in `components/account/Settings.tsx`), + where a string beginning with `<` is silently reinterpreted as HTML. +6. **Prebuilt `dist/` provenance** — `dist/jquery.js`/`.min.js`/`.min.map` were + generated locally (`3.6.2-pre`, 2023-03-15) rather than taken from an + upstream release, and they, not `src/`, are what webpack bundles and ships + to customers; they must be proven to correspond exactly to the reviewed + source. +7. **Upstream version drift and absent CVE backports** — the fork is frozen at + 3.6.2 (Dec 2022) with zero security patches applied on top, so every + post-3.6.2 upstream security fix is missing and the pinned-by-SHA + `package.json` dependency will never pick one up automatically. +8. **Known unescaped call sites in AppSheet's own jQuery usage** — the + `data[i].path` / `data[i].name` concatenation in + `scripts/_shared/external/jqueryFileTree.js` and the + `.html( actions[i].buttonHtml )` call in `LegacyViewManager.tsx`; both are + presently unreachable but ship in the bundle and would become live XSS the + moment a caller is added. + +## Out of Scope + +Teams can fill out this section with any known filed vulnerability bugs that +they've determined should be out of scope from future Fortify bug filing.