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 installing3. 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:
- Do not disclose the vulnerability publicly
- Email security details to: security@openwebf.com
- Include steps to reproduce the issue
- 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).