@salesforce/uibundle-sourcemap-validator-engine
v12.5.3
Published
Salesforce Code Analyzer (SFCA v5+) custom engine that validates UI Bundle build-output integrity by cross-checking each shipped dist/ artifact against its declared sourcemap and the submitted src/ tree.
Readme
uibundle-sourcemap-validator-engine
A Salesforce Code Analyzer (SFCA v5+) custom engine that verifies UI Bundle build-output integrity by cross-checking each shipped dist/ artifact against its declared sourcemap and the submitted src/ tree.
At a glance
| | |
| ---------------------------------------------------------------------- | ------------------------------------------------- |
| npm package | @salesforce/uibundle-sourcemap-validator-engine |
| Engine identifier (in --rule-selector, engines:, violation output) | uibundle-sourcemap-validator |
| Aggregate tag (selects every rule this engine ships) | UIBundleIntegrity |
| Common tags every rule carries | Recommended, Security, Javascript |
What it does
Eight rules total. Each rule is tagged Recommended + Security + Javascript + UIBundleIntegrity. Five are gating (Layer 1: findings mean the bundle should fail review); three are informational (Layers 2–4: signal-generating diagnostics for suspicious mappings that do not on their own warrant a hard fail).
Layer 1 — Gating (Critical / High / Moderate):
| Rule | Severity | What it checks |
| ----------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| missing-sourcemap | High | Every .js in dist/ has a co-located .js.map or an inline //# sourceMappingURL. Also flags orphan .js files that contain dangerous API substrings (e.g. document.cookie, XMLHttpRequest, dynamic-code sinks). |
| path-leakage | Moderate | sources[] entries are relative — no /Users/..., C:\..., file://... |
| invalid-source-references | High | Every sources[] entry (without inline sourcesContent) resolves to a real file on disk |
| vlq-integrity | High | mappings field is valid JSON, valid VLQ, and source/name indices are in range |
| source-content-verification | Critical | Byte-equal sourcesContent vs submitted source, virtual-source ratio cap (>20% flagged), AST coverage vs threshold (95% for files ≥500 nodes, 70% otherwise), AST type-mismatch (0.20 threshold with a compatibility matrix), dangerous-API pattern check on unmapped AST snippets. |
Layers 2–4 — Informational (Info):
| Rule | Severity | What it checks |
| ---------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| coverage-analysis | Info | Character-level coverage: per-line unmapped regions ≥ 50 chars, cumulative unmapped percentage > 2% (line-1 preamble discounted up to 150 chars). |
| structural-coherence | Info | Bounds check (mapped positions inside source file), whitespace/comment-only sampling every 10th mapping (>80% suspicious), cross-file jump ratio (>50% on consecutive same-line tokens). |
| token-consistency | Info | Every 20th token: source-vs-generated token category match (StringLiteral / NumericLiteral / Identifier / Punctuation / Other), name existence check within ±3 col of claimed position, verdict bands 0.85 warn / 0.70 suspicious. |
All eight rules are pure TypeScript — no native binaries, one npm install.
Lifecycle
This engine runs on build output, not source. Every UI Bundle must be built (npm run build) before sf code-analyzer run — otherwise the engine finds no dist/ directory and logs an info message with zero violations (not a failure). This is a deliberate mismatch with SFCA's usual source-static model; documented at length in the design discussion in docs/.
Install
npm install --save-dev @salesforce/uibundle-sourcemap-validator-engineEnable in code-analyzer.yml at the SFDX project root. Note the split: the plugin is registered by its npm package name (@salesforce/uibundle-sourcemap-validator-engine), but the engine identifier used everywhere else in the config — and in --rule-selector on the CLI — is uibundle-sourcemap-validator (no -engine suffix, matching the SFCA convention where @salesforce/code-analyzer-retirejs-engine exposes an engine named retire-js, code-analyzer-eslint-engine exposes eslint, etc.).
custom_engine_plugin_modules:
- "@salesforce/uibundle-sourcemap-validator-engine" # npm package (has -engine suffix)
engines:
uibundle-sourcemap-validator: # engine identifier (no suffix)
disable_engine: falseRecommended code-analyzer.yml
The following config is what the two demo projects use. It (a) registers the plugin, (b) tells every other engine to ignore dist/** so they don't false-positive on minified bundler runtime code (the sourcemap-validator engine locates each UI Bundle via its ui-bundle.json marker in src/ and reads the sibling dist/ directly from disk, so it is unaffected by this ignore), and (c) disables the JVM-based engines that don't apply to a JS/UI-Bundle codebase:
custom_engine_plugin_modules:
- "@salesforce/uibundle-sourcemap-validator-engine"
# dist/** is built output. Excluding it from the workspace file list means the
# other engines (eslint, retire-js, regex, ...) never see it — no false positives
# on minified bundler runtime code. The sourcemap validator locates each UI
# Bundle via its `ui-bundle.json` / `*.uibundle-meta.xml` marker in src/ and
# reads the sibling dist/ directly from disk, so it is unaffected by this ignore.
ignores:
files:
- "**/dist/**"
suppressions:
"force-app/main/default/uiBundles":
- rule_selector: "eslint:@lwc/lwc"
- rule_selector: "eslint:@lwc/lwc-platform"
engines:
pmd:
disable_engine: true
cpd:
disable_engine: true
sfge:
disable_engine: true
flow:
disable_engine: true
uibundle-sourcemap-validator:
disable_engine: falseRecommended invocation
Build every UI Bundle first (otherwise the engine finds no dist/ and emits zero violations), then run the canonical AppExchange + Recommended-Security scan with the engine explicitly named:
# 1. Build every UI Bundle in the SFDX project
for bundle in force-app/main/default/uiBundles/*/; do
(cd "$bundle" && npm run build)
done
# 2. Run the scan
sf code-analyzer run \
--rule-selector AppExchange \
--rule-selector Recommended:Security \
--rule-selector UIBundleIntegrity \
--output-file CodeAnalyzerReport.htmlEvery selector is a tag — a label attached to rules, matched against the shape SFCA already uses (AppExchange, Recommended:Security). The three compose as a union:
| Selector | What it selects |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| AppExchange | Every rule tagged for AppExchange package review (from pmd, regex, retire-js, eslint, etc.) |
| Recommended:Security | Every rule tagged both Recommended and Security — this already picks up the eight uibundle-sourcemap-validator rules by their tags. |
| UIBundleIntegrity | Belt-and-braces: an engine-specific tag on every rule in uibundle-sourcemap-validator, so all eight run even if a future recommendation-tag pruning removed one of them from Recommended:Security. |
What the engine looks for
The engine scans targeted files for two UI Bundle markers:
- Bundle roots — directories containing
ui-bundle.jsonor*.uibundle-meta.xml dist/ancestors — any targeted file living inside adist/folder
For each bundle root found, the engine runs all eight validators against <bundleRoot>/dist. source-content-verification, structural-coherence, and token-consistency additionally require <bundleRoot>/src as the source path — if it doesn't exist those rules are skipped with a warn-level log message; the other five rules still run.
Sample output
Both scans below were produced with the recommended invocation against the two demo UI Bundles that ship with this repo (demo/testb2epkgapp1 clean and demo/testb2epkgapp1_vuln after node scripts/tamper.mjs --src <path> planted six independent tamper vectors).
Clean bundle — 14 violations, 1 Critical + 13 Info. The single Critical is source-content-verification flagging two GraphQL ?raw imports that the bundler inlines as pseudo-sources; every other finding is Info-tier signal (coverage anomalies on line 1 preamble, JSX runtime name narrowing, Radix member-expression name reporting).

Tampered bundle — 48 violations across all planted vectors: absolute-path leakage in sources[0], phantom source reference in sources[1], byte-corrupted sourcesContent[], 5 appended stealth functions after //# sourceMappingURL, 35% virtual-source ratio, and a planted stealth-orphan.js with dangerous-API substrings. 14 Critical + 3 High + 1 Moderate + 30 Info.

Both HTML reports are checked into docs/report-clean.html and docs/report-vuln.html — open either in a browser to explore the full findings interactively.
Output
Violations are emitted in the standard SFCA format — SARIF, JSON, HTML, and CSV are all produced by SFCA's built-in formatters. CI integrations (GitHub code scanning, Sonar, etc.) that already ingest SFCA output work without changes.
Development
npm install
npm run build # tsc → dist/
npm test # vitestDemo scripts
Three helper scripts under scripts/ make it easy to see the engine catch real tampering without spinning up SFCA. All three require --src <uiBundleRoot> — the directory that contains the bundle's src/ and dist/:
# 1. Baseline scan of a clean bundle
node scripts/scan.mjs --src path/to/uiBundles/myBundle
# 2. Plant six tampering vectors in the bundle's dist/
node scripts/tamper.mjs --src path/to/uiBundles/myBundle
# 3. Re-scan and watch every vector get caught
node scripts/scan.mjs --src path/to/uiBundles/myBundle
# 4. Restore the bundle from .orig backups
node scripts/revert.mjs --src path/to/uiBundles/myBundleThe tamper script plants six independent vectors, one per Layer-1 rule surface:
path-leakage— rewritessources[0]to/Users/attacker/....invalid-source-references— rewritessources[1]to a phantom path and nulls its inlinesourcesContent.source-content-verification(AST/dangerous) — appends 5 exfil/backdoor functions after the//# sourceMappingURLcomment.source-content-verification(byte-equal) — corrupts asourcesContent[i]entry so it no longer matches the submitted source.missing-sourcemap(orphan-JS scan) — writesstealth-orphan.jsintodist/with dangerous API patterns and no map.source-content-verification(virtual-source ratio) — rewrites ~35% ofsources[]to bundler pseudo-paths (webpack/…,vite/…,<runtime>) to breach the 20% gate.
Tampering writes <file>.orig next to the modified files, so revert.mjs is always safe to run (it also deletes stealth-orphan.js).
Package layout:
src/
├── index.ts # Public exports
├── engine.ts # SourcemapValidatorEngine (implements SFCA Engine)
├── plugin.ts # SourcemapValidatorEnginePlugin (SFCA plugin entry)
├── rules.ts # RuleDescription[]
└── validators/
├── types.ts # ValidatorFinding, ValidatorResult
├── sourcemap-io.ts # Shared: walk + collectSourceMaps
├── classification.ts # Path helpers + dangerous-API pattern list
├── missing-sourcemap.ts # Rule 1 (+ orphan-JS API scan)
├── path-leakage.ts # Rule 2
├── invalid-source-references.ts # Rule 3
├── vlq-integrity.ts # Rule 4
├── source-content-verification.ts # Rule 5 — byte-equal + AST coverage/type/dangerous
├── coverage-analysis.ts # Rule 6 — Layer 2 char-level coverage (Info)
├── structural-coherence.ts # Rule 7 — Layer 3 bounds/whitespace/jump (Info)
└── token-consistency.ts # Rule 8 — Layer 4 token type + name (Info)
tests/
├── missing-sourcemap.test.ts
├── path-leakage.test.ts
├── invalid-source-references.test.ts
├── vlq-integrity.test.ts
├── source-content-verification.test.ts
├── coverage-analysis.test.ts
├── structural-coherence.test.ts
└── token-consistency.test.tsValidators are SFCA-agnostic — each takes a distPath: string and returns ValidatorResult. The engine layer maps findings to SFCA Violation objects. This keeps the validation logic independently testable and reusable outside SFCA.
Known limitations
source-content-verificationuses the bundle'ssrc/sibling as the source root. Bundles that use a different layout will produce spurious "source not found" findings — configurable source path is on the roadmap.invalid-source-referencesskips inlinesourcesContent. That's technically correct per the sourcemap spec (self-contained sources need not exist), but it also means an attacker can hide a fabricated source by embedding it inline. Thesource-content-verificationrule is the mitigation — it walks compiled AST nodes and confirms each maps into a source file that actually exists.@babel/parseris used withsourceType: 'unambiguous'and JSX + TypeScript plugins enabled witherrorRecovery: true, which handles the vast majority of UI Bundle build output. Extremely exotic dialects (e.g. Flow syntax, custom Babel plugin output) may fail to parse and surface a "compiled JS failed to parse" finding rather than a coverage measurement.
Inputs
The engine takes no CLI arguments — both the source and dist paths are derived automatically from the files SFCA passes in. For each targeted file, the engine walks up to find a dist/ ancestor (or a ui-bundle.json / *.uibundle-meta.xml marker) and treats its parent as the bundle root. It then uses:
<bundleRoot>/distfor all eight rules<bundleRoot>/srcforsource-content-verification,structural-coherence, andtoken-consistency(skipped with aWarnlog if missing; the other five rules still run)
Example — scanning a UI bundle via the helper script:
node scripts/scan.mjs --src path/to/uiBundles/myBundleWhy pure TypeScript
Earlier iterations of this engine wrapped a Rust binary (uibundle-sourcemap-validator) for the AST-based Layer 1 check. That was dropped in favor of a pure-TS implementation using @babel/parser and @jridgewell/trace-mapping:
- One npm install, no native binary — the engine ships as a single package and works out of the box in every environment SFCA runs in (Node ≥ 18).
- No output-format brittleness — the earlier design shelled out and regex-parsed unstructured text from the Rust tool; direct API calls remove that failure mode.
- Slower but fast enough —
@babel/parseris measurably slower thanoxcon very large files, but SFCA's per-bundle scan is not a hot path. - Behavior parity — all four Rust validation layers (source content, AST, coverage, coherence, token consistency) are ported line-for-line with the same thresholds (95/70% coverage, 0.20 type-mismatch, 20% virtual-source ratio, 2% cumulative unmapped, 50%/80% coherence, 0.70/0.85 token bands, ±3 col name tolerance) and the same significant-node set.
