Skip to Content

Security

The security model of WebF is fundamentally different from that of a web browser. Understanding this is crucial for building a secure application.

The Application is the Sandbox

A web browser’s primary security feature is the sandbox, which strictly isolates web content from the user’s operating system to protect against malicious websites.

WebF removes this sandbox to enable powerful features like the Native Binding System. The core security principle is that you are running your own trusted WebF application code, not arbitrary code from the internet. Your application bundle itself is the security boundary.

Best Practices

1. Do Not Load Untrusted Content

The most important rule is to only load code and assets that are part of your application. WebF is not designed to safely render arbitrary third-party web pages.

Important: window.location is not supported for navigation in WebF. Use the routing APIs instead (see Hybrid Routing Guide).

// ❌ NEVER do this - window.location is not supported const userInput = document.getElementById('url-input').value; window.location = userInput; // Does not work! // ❌ Also don't do this - even for internal routes window.location = '/settings'; // Does not work! // ✅ Correct - use the routing API for navigation import { WebFRouter } from '@openwebf/react-router'; WebFRouter.push('/settings');

2. Vet Your Native Plugins

When you add a WebF Native Plugin from npm, you are adding native code to your application that runs with the full permissions of the app. Only use plugins from authors and sources that you trust.

Before installing a plugin:

  • Check the author and maintainer reputation
  • Review the plugin’s source code on GitHub
  • Check for recent updates and maintenance
  • Read reviews and check download statistics
  • Verify the plugin’s permissions and access requirements
# Check plugin info before installing npm info @openwebf/plugin-name # Review source code git clone https://github.com/openwebf/plugin-name cd plugin-name # Review the code before installing

3. Secure Your Native Plugins

When creating your own native plugins, be careful about the functionality you expose to JavaScript. Follow these security principles:

Principle of Least Privilege:

  • Only expose specific, necessary functions
  • Never expose broad system-level APIs
  • Validate and sanitize all inputs from JavaScript
  • Use type-safe bindings with proper error handling

Example - Don’t expose dangerous operations:

// ❌ Bad - exposing dangerous file system access // Never expose raw file system operations Future<String> readAnyFile(String path) { return File(path).readAsString(); // Dangerous! } // ✅ Good - expose specific, controlled operations Future<Map<String, dynamic>> getUserProfile() { // Only read from app's secure storage return secureStorage.read('user_profile'); } Future<bool> saveUserProfile(Map<String, dynamic> profile) { // Validate input before saving if (!_isValidProfile(profile)) { throw ArgumentError('Invalid profile data'); } return secureStorage.write('user_profile', profile); }

For guidance on creating native plugins, see the Build Native Plugins guide.

4. Handle Data with Care

Treat any sensitive data passed between the JavaScript and native layers with the same security standards as you would in a fully native application.

// ✅ Good - encrypt sensitive data before storing async function saveApiKey(apiKey) { // Use a native plugin for secure storage (e.g., Flutter's secure_storage) // Or encrypt before storing in localStorage const encrypted = await encryptData(apiKey); localStorage.setItem('api-key', encrypted); } // ❌ Bad - storing sensitive data insecurely function saveApiKey(apiKey) { localStorage.setItem('api-key', apiKey); // Insecure - plaintext! }

Input Validation

Always validate and sanitize user input to prevent injection attacks.

Prevent XSS (Cross-Site Scripting)

// ❌ Bad - vulnerable to XSS function displayUsername(username) { document.getElementById('user').innerHTML = username; // Dangerous! } // ✅ Good - safe from XSS function displayUsername(username) { // React automatically escapes content return <div>{username}</div>; // Or use textContent instead of innerHTML document.getElementById('user').textContent = username; }

Sanitize HTML Content

If you must render user-provided HTML, sanitize it first:

import DOMPurify from 'dompurify'; function renderUserContent(html) { const clean = DOMPurify.sanitize(html); return <div dangerouslySetInnerHTML={{ __html: clean }} />; }

Validate Input

function validateEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } function validateInput(input, rules) { // Length check if (rules.maxLength && input.length > rules.maxLength) { throw new Error('Input too long'); } // Format check if (rules.pattern && !rules.pattern.test(input)) { throw new Error('Invalid format'); } // Whitelist check if (rules.allowedChars && !rules.allowedChars.test(input)) { throw new Error('Contains invalid characters'); } return true; }

API Security

Use HTTPS

Always use HTTPS for network requests:

// ✅ Good - secure connection fetch('https://api.example.com/data') // ❌ Bad - insecure connection fetch('http://api.example.com/data')

Implement Authentication

Use proper authentication mechanisms:

// Example: JWT authentication async function authenticatedFetch(url, options = {}) { const token = await SecureStorage.get('auth-token'); return fetch(url, { ...options, headers: { ...options.headers, 'Authorization': `Bearer ${token}`, }, }); }

Protect API Keys

Never hardcode API keys in your code:

// ❌ Bad - API key in code const API_KEY = 'sk_live_abc123...'; // ✅ Good - API key from environment or secure storage const API_KEY = import.meta.env.VITE_API_KEY; // ✅ Better - sensitive operations on backend async function makePayment(amount) { // Let backend handle sensitive API keys return fetch('https://your-backend.com/api/payment', { method: 'POST', body: JSON.stringify({ amount }), }); }

Rate Limiting

Implement rate limiting to prevent abuse:

class RateLimiter { constructor(maxRequests, timeWindow) { this.maxRequests = maxRequests; this.timeWindow = timeWindow; this.requests = []; } canMakeRequest() { const now = Date.now(); this.requests = this.requests.filter( time => now - time < this.timeWindow ); if (this.requests.length < this.maxRequests) { this.requests.push(now); return true; } return false; } } // Usage const limiter = new RateLimiter(10, 60000); // 10 requests per minute async function makeApiCall() { if (!limiter.canMakeRequest()) { throw new Error('Rate limit exceeded'); } return fetch('https://api.example.com/data'); }

Data Storage Security

Use Secure Storage for Sensitive Data

// ✅ Good - encrypt sensitive data or use a secure storage plugin // Create a native plugin using Flutter's secure_storage or similar const encrypted = await encryptData(token); localStorage.setItem('user-token-encrypted', encrypted); // ✅ Okay - localStorage for non-sensitive data localStorage.setItem('theme-preference', 'dark'); localStorage.setItem('language', 'en'); // ❌ Bad - storing sensitive data in plaintext localStorage.setItem('user-token', token); // Never do this!

Encrypt Sensitive Data

// Example: Encrypt data before storing import CryptoJS from 'crypto-js'; function encryptData(data, secret) { return CryptoJS.AES.encrypt(JSON.stringify(data), secret).toString(); } function decryptData(encrypted, secret) { const bytes = CryptoJS.AES.decrypt(encrypted, secret); return JSON.parse(bytes.toString(CryptoJS.enc.Utf8)); } // Usage const encryptedData = encryptData(sensitiveInfo, userSecret); localStorage.setItem('encrypted-data', encryptedData);

Clear Sensitive Data

Clear sensitive data when no longer needed:

import { WebFRouter } from '@openwebf/react-router'; async function logout() { // Clear all sensitive data await SecureStorage.delete('auth-token'); await SecureStorage.delete('user-data'); localStorage.clear(); sessionStorage.clear(); // Navigate to login page WebFRouter.push('/login'); }

Permission Management

Request Permissions Explicitly

Always ask for user permission before accessing sensitive capabilities:

import { WebFShare } from '@openwebf/webf-share'; async function shareContent() { try { // Check if sharing is available if (!WebFShare.isAvailable()) { alert('Sharing is not available on this device'); return; } const success = await WebFShare.shareText({ text: 'Check out this app!', url: 'https://example.com' }); return success; } catch (error) { console.error('Share failed:', error); alert('Failed to share content'); } }

Explain Why You Need Permissions

function CameraFeature() { const [showExplanation, setShowExplanation] = useState(true); const handleTakePhoto = async () => { if (showExplanation) { const confirmed = confirm( 'This app needs camera access to take your profile photo. ' + 'Your photos are stored locally and never shared without your permission.' ); if (!confirmed) return; setShowExplanation(false); } try { const photo = await Camera.takePicture(); // Process photo... } catch (error) { console.error('Camera error:', error); } }; return <button onClick={handleTakePhoto}>Take Photo</button>; }

Content Security

Validate URLs

function isValidUrl(url) { try { const parsed = new URL(url); // Only allow HTTPS if (parsed.protocol !== 'https:') { return false; } // Whitelist allowed domains const allowedDomains = ['api.example.com', 'cdn.example.com']; return allowedDomains.includes(parsed.hostname); } catch { return false; } } // Usage async function loadData(url) { if (!isValidUrl(url)) { throw new Error('Invalid URL'); } return fetch(url); }

Prevent Clickjacking

// Ensure your app can't be embedded in an iframe if (window.top !== window.self) { window.top.location = window.self.location; }

Security Checklist

  • All network requests use HTTPS
  • API keys are stored securely, not in code
  • User input is validated and sanitized
  • Sensitive data uses secure storage
  • Permissions are requested with clear explanations
  • Authentication tokens are handled securely
  • XSS prevention measures are in place
  • Native plugins are from trusted sources only
  • Rate limiting is implemented for API calls
  • Sensitive data is cleared on logout
  • URLs are validated before use
  • Error messages don’t leak sensitive information
  • Security updates are applied promptly
  • Code undergoes security review before deployment

Reporting Security Issues

If you discover a security vulnerability in WebF or a WebF plugin, please report it responsibly:

  1. Do not disclose the vulnerability publicly
  2. Email security details to: security@openwebf.com
  3. Include steps to reproduce the issue
  4. Allow time for the issue to be fixed before public disclosure

Security researchers who report vulnerabilities responsibly may be acknowledged in our security advisories (with their permission).