| # 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. |