Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,11 @@ Extension to log postMessage()
https://chrome.google.com/webstore/detail/aodfhblfhpcdadgcnpkfibjgjdoenoja
https://addons.mozilla.org/en-US/firefox/addon/postlogger/

## Security

This extension is designed to help developers and security researchers understand `postMessage` usage on web pages.
We take security seriously. A security audit of this extension has been performed, and the findings are documented in the [SECURITY.md](security.md) file.
Please review this document for details on the security aspects of this extension and recommendations. If you discover any security vulnerabilities, please report them responsibly.

# Warning
May cause unexpected behavour, if you find a security issue contact me.
79 changes: 70 additions & 9 deletions WindowScript.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
// Code here is exposed to the website.
(function() {
const handledByPostLoggerSymbol = Symbol.for('postLoggerHandled'); // Use Symbol.for for wider accessibility if needed, or just Symbol.
'use strict';

const proxies = new WeakMap();
const iframes = new WeakSet();
const uncheckedMessage = new Set();
const uncheckedSource = new Set();
const unusedMessages = new Set();
const anarchyDomains = new Set(['https://firebasestorage.googleapis.com', 'https://www.gstatic.com', 'https://ssl.gstatic.com', 'https://googlechromelabs.github.io', 'https://storage.googleapis.com']);
const anarchyDomains = new Set(['https://firebasestorage.googleapis.com', 'https://www.gstatic.com', 'https://ssl.gstatic.com', 'https://googlechromelabs.github.io', 'https://storage.googleapis.com', 'https://www.google.com']); // Added google.com for testing example

// Detects when MessageEvent.ports is used.
const portsDescriptor = Object.getOwnPropertyDescriptor(window.MessageEvent.prototype, 'ports');
Expand Down Expand Up @@ -198,18 +199,38 @@
return shadowRoot;
};

function handle(type, iframe) {
function handle(type, iframe) { // iframe is the original object, like the actual iframe DOM element for 'iframe' type, or the window object for 'parent' type.
return {
get: function(target, property) {
if (property === "postMessage") {
get: function(target, property, receiver) { // target is the actual window object, receiver is the proxy.
// target is the actual window object (e.g. window.parent, event.source)
// property is 'postMessage'
// receiver is the proxy object itself
if (property === "postMessage" && typeof target[property] === 'function') {
const originalTargetPostMessage = target[property]; // Native postMessage of the target
return function() {
hook(arguments, type, iframe);
return target[property].apply(target, arguments);
const currentCallArguments = arguments;
// If already handled by the global Window.prototype.postMessage proxy,
// just call the original method on the target.
if (currentCallArguments[handledByPostLoggerSymbol]) {
return Reflect.apply(originalTargetPostMessage, target, currentCallArguments);
}
// Mark that this specific proxy handle is processing it.
Object.defineProperty(currentCallArguments, handledByPostLoggerSymbol, { value: true, configurable: true });

hook(currentCallArguments, type, target); // Pass target as ref
let result = Reflect.apply(originalTargetPostMessage, target, currentCallArguments);
delete currentCallArguments[handledByPostLoggerSymbol]; // Clean up
return result;
}
}
// Handle other properties
try {
return Reflect.get(...arguments);
} catch {
return Reflect.get(target, property, receiver);
} catch (e) { // Catch errors during Reflect.get (e.g. illegal invocation)
// Fallback to direct property access if Reflect.get fails
if (typeof target[property] === 'function') {
return target[property].bind(target); // Bind if it's a function
}
return target[property];
}
},
Expand All @@ -219,7 +240,47 @@
if (window !== window.parent) {
window.parent = useProxy(window.parent, handle('parent'));
}
MessagePort.prototype.postMessage = hookFunction(MessagePort.prototype.postMessage, 'MessageChannel');
// MessagePort.prototype.postMessage is already a function, not an object property to be proxied by 'handle'
// hookFunction is designed for functions like window.open or MessagePort.prototype.postMessage
// Ensure hookFunction also respects and sets the handledByPostLoggerSymbol if its target.name is 'postMessage'
const originalMessagePortPostMessage = MessagePort.prototype.postMessage;
MessagePort.prototype.postMessage = function() { // arguments of this call
const currentCallArguments = arguments;
if (currentCallArguments[handledByPostLoggerSymbol]) {
return Reflect.apply(originalMessagePortPostMessage, this, currentCallArguments);
}
Object.defineProperty(currentCallArguments, handledByPostLoggerSymbol, { value: true, configurable: true });
hook(currentCallArguments, 'MessageChannel', this); // 'this' is the MessagePort instance
let result = Reflect.apply(originalMessagePortPostMessage, this, currentCallArguments);
delete currentCallArguments[handledByPostLoggerSymbol];
return result;
};

window.opener = useProxy(window.opener, handle('opener'));
window.open = hookFunction(window.open, 'popup', true);

// Globally proxy Window.prototype.postMessage
const originalWindowPostMessage = Window.prototype.postMessage;
Window.prototype.postMessage = new Proxy(originalWindowPostMessage, {
apply: function(target, thisArg, argumentsList) { // thisArg is the window on which postMessage was called
if (argumentsList[handledByPostLoggerSymbol]) {
return Reflect.apply(originalWindowPostMessage, thisArg, argumentsList);
}
Object.defineProperty(argumentsList, handledByPostLoggerSymbol, { value: true, configurable: true });
hook(argumentsList, 'prototype', thisArg); // 'prototype' type, 'thisArg' is the window itself
let result = Reflect.apply(originalWindowPostMessage, thisArg, argumentsList);
delete argumentsList[handledByPostLoggerSymbol];
return result;
}
});

// Enforcement block from the prompt for window.top
if (window.top && typeof window.top.postMessage === 'function' && window.top.postMessage !== Window.prototype.postMessage) {
try {
Object.defineProperty(window.top, 'postMessage', {
value: Window.prototype.postMessage, // Assign the proxied version from prototype
configurable: true, enumerable: true, writable: true
});
} catch (e) { /* Error handling: console.error might not be safe here depending on injection context */ }
}
})();
46 changes: 46 additions & 0 deletions security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Security Audit

This document outlines the security audit performed on the browser extension.

## Audit Process

The audit involved:
1. Manual review of the `WindowScript.js` to identify potential JavaScript vulnerabilities.
2. Manual review of `manifest.json` and `manifest-firefox.json` for insecure configurations.

## Findings

### `WindowScript.js`
- **Data Exposure**: The script logs all `postMessage` data (`event.data`) to the browser's developer console. While this is a core feature for its debugging and monitoring purpose, it means any sensitive information passed via `postMessage` will be visible in these logs. Users and developers should be aware of this when using the extension on pages that might handle sensitive data.
- **`anarchyDomains`**: A predefined set of domains are labeled "UNSAFE". This is an awareness feature of the script.
- **Complexity**: The script uses extensive proxying and prototype manipulation. While handled with apparent care, complex interactions could arise in some environments.
- **Enhanced `postMessage` Interception (including `window.top`)**: The script now employs a global proxy on `Window.prototype.postMessage` to more reliably intercept `postMessage` calls from all window contexts, including `window.top`. It also specifically attempts to ensure `window.top.postMessage` uses this proxied version. A deduplication mechanism using a Symbol (`Symbol.for('postLoggerHandled')`) has been implemented to prevent logging the same message multiple times if it passes through different hooks.
- **No Direct XSS**: The script itself does not appear to introduce XSS vulnerabilities.

### Manifest Files (`manifest.json` and `manifest-firefox.json`)
- **Broad Host Permissions**: The extension uses `"matches": ["<all_urls>"]` for its content script. This grants `WindowScript.js` access to all web pages visited by the user. This is necessary for the extension's core functionality of logging `postMessage` calls universally.
- **`MAIN` World Execution**: The content script runs in the `"world": "MAIN"`, giving it direct access to the web page's DOM and JavaScript environment (though with an isolated JS global scope).
- **Minimal Other Permissions**: No other excessive permissions (like `storage`, `tabs` beyond what's needed for content script injection) are requested.
- **No Explicit CSP**: No `content_security_policy` is defined in the manifest, which is acceptable as the extension does not have its own HTML pages. Content scripts operate under default CSPs.
- **No `externally_connectable` or `web_accessible_resources`**: These are not defined, which is good security practice as it limits the attack surface from external pages or extensions.

## Security Considerations & Recommendations

1. **Data Exposure via Console**:
* **Recommendation**: Users should be clearly informed that all `postMessage` data is logged to theconsole. If the extension is ever to be used in environments where highly sensitive data is common, consider adding a feature to selectively disable logging or mask sensitive patterns. However, for its current purpose as a developer tool, this logging is its primary function.
2. **Broad Host Permissions (`<all_urls>`)**:
* **Recommendation**: This is inherent to the extension's purpose. The security of the extension relies heavily on the benign nature and robustness of `WindowScript.js`. Ensure any future changes to this script are carefully reviewed for potential security impacts.
3. **Complexity of `WindowScript.js`**:
* **Recommendation**: Maintain thorough comments and documentation within `WindowScript.js` to manage its complexity. Rigorous testing is important if significant changes are made. The introduction of global prototype proxying and deduplication logic adds layers that require careful understanding during maintenance.
4. **Cross-Origin `window.top` Interactions**:
* **Recommendation**: While the script aims to provide comprehensive logging, direct modification or deep inspection of a `window.top` that is cross-origin to the frame where the script is executing (or where the content script is injected) may be restricted by browser security policies. The script attempts to handle such scenarios gracefully (e.g., via `try-catch` when applying proxies or property definitions), but full interception or identification capability for cross-origin `window.top` might be limited by the browser's Same-Origin Policy. Users should be aware of these inherent browser limitations.

## Regular Updates

This `security.md` file should be reviewed and updated if:
- The extension's functionality changes significantly.
- New permissions are added to the manifest files.
- `WindowScript.js` undergoes major revisions.
- New, relevant web security vulnerabilities or best practices emerge that affect browser extensions or `postMessage` handling.

A yearly review is recommended as a baseline.
Loading