page-integrity-js
Version:
A library for monitoring and controlling DOM mutations and script execution, essential for PCI DSS compliance and security audits
243 lines • 10.5 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
/**
* Intercepts a script element and checks if it should be blocked.
* @param script The script element to check
* @param scriptBlocker The script blocker instance
*/
export function interceptScriptElement(script, scriptBlocker) {
return __awaiter(this, void 0, void 0, function* () {
if (!scriptBlocker) {
console.error('ScriptBlocker is not initialized');
return;
}
try {
let result;
if (script.src) {
result = yield scriptBlocker.shouldBlockScript(script.src, '');
}
else if (script.textContent) {
result = yield scriptBlocker.shouldBlockScript('inline', script.textContent);
}
if (result === null || result === void 0 ? void 0 : result.blocked) {
// Remove the script element
script.remove();
console.warn(`Blocked script: ${script.src || 'inline'} - ${result.reason}`);
}
}
catch (error) {
console.error('Error intercepting script:', error);
}
});
}
/**
* Intercepts global methods that can execute scripts.
* @param scriptBlocker The script blocker instance
*/
export function interceptGlobalMethods(scriptBlocker) {
if (!scriptBlocker) {
console.error('ScriptBlocker is not initialized');
return;
}
const originalEval = window.eval;
const originalFunction = Function;
const originalSetTimeout = window.setTimeout;
const originalSetInterval = window.setInterval;
const originalXHROpen = XMLHttpRequest.prototype.open;
const originalXHRSend = XMLHttpRequest.prototype.send;
const originalFetch = window.fetch;
window.eval = function (code) {
const result = scriptBlocker.shouldBlockScript('eval', code);
if (result instanceof Promise) {
return result.then((res) => {
if (res === null || res === void 0 ? void 0 : res.blocked) {
console.warn(`Blocked eval: ${res.reason}`);
return undefined;
}
return originalEval.call(window, code);
});
}
return originalEval.call(window, code);
};
Function = function (...args) {
const code = args[args.length - 1];
const result = scriptBlocker.shouldBlockScript('Function', code);
if (result instanceof Promise) {
console.warn('Async script blocking not supported for Function constructor');
return function () { return undefined; };
}
const blockResult = result;
if (blockResult === null || blockResult === void 0 ? void 0 : blockResult.blocked) {
console.warn(`Blocked Function: ${blockResult.reason}`);
return function () { return undefined; };
}
return originalFunction.apply(null, args);
};
window.setTimeout = function (handler, timeout, ...args) {
if (typeof handler === 'string') {
const result = scriptBlocker.shouldBlockScript('setTimeout', handler);
if (result instanceof Promise) {
const promise = result.then((res) => {
if (res === null || res === void 0 ? void 0 : res.blocked) {
console.warn(`Blocked setTimeout: ${res.reason}`);
return 0;
}
return originalSetTimeout.call(window, handler, timeout, ...args);
});
return promise;
}
}
return originalSetTimeout.call(window, handler, timeout, ...args);
};
window.setInterval = function (handler, timeout, ...args) {
if (typeof handler === 'string') {
const result = scriptBlocker.shouldBlockScript('setInterval', handler);
if (result instanceof Promise) {
const promise = result.then((res) => {
if (res === null || res === void 0 ? void 0 : res.blocked) {
console.warn(`Blocked setInterval: ${res.reason}`);
return 0;
}
return originalSetInterval.call(window, handler, timeout, ...args);
});
return promise;
}
}
return originalSetInterval.call(window, handler, timeout, ...args);
};
XMLHttpRequest.prototype.open = function (method_1, url_1) {
return __awaiter(this, arguments, void 0, function* (method, url, async = true, username = null, password = null) {
if (url.toString().endsWith('.js')) {
try {
const result = yield scriptBlocker.shouldBlockScript(url.toString(), '');
if (result === null || result === void 0 ? void 0 : result.blocked) {
console.warn(`Blocked XHR: ${result.reason}`);
throw new Error(`Blocked script: ${result.reason}`);
}
}
catch (error) {
console.error('Error in XHR open:', error);
throw error;
}
}
return originalXHROpen.call(this, method, url, async, username, password);
});
};
XMLHttpRequest.prototype.send = function (body) {
return originalXHRSend.call(this, body);
};
window.fetch = function (input, init) {
return __awaiter(this, void 0, void 0, function* () {
const url = input instanceof Request ? input.url : input.toString();
if (url.endsWith('.js')) {
try {
const result = yield scriptBlocker.shouldBlockScript(url, '');
if (result === null || result === void 0 ? void 0 : result.blocked) {
console.warn(`Blocked fetch: ${result.reason}`);
throw new Error(`Blocked script: ${result.reason}`);
}
}
catch (error) {
console.error('Error in fetch:', error);
throw error;
}
}
return originalFetch.call(window, input, init);
});
};
}
/**
* Manages script interception and monitoring.
*/
export class ScriptInterceptor {
constructor(scriptBlocker) {
this._isRunning = false;
this.scriptBlocker = scriptBlocker;
this.originalCreateElement = document.createElement;
this.originalSetAttribute = Element.prototype.setAttribute;
this.originalAppendChild = Node.prototype.appendChild;
this.originalInsertBefore = Node.prototype.insertBefore;
this.originalReplaceChild = Node.prototype.replaceChild;
}
static createInstance(scriptBlocker) {
return new ScriptInterceptor(scriptBlocker);
}
start() {
if (this._isRunning)
return;
this._isRunning = true;
this.interceptCreateElement();
this.interceptSetAttribute();
this.interceptAppendChild();
this.interceptInsertBefore();
this.interceptReplaceChild();
interceptGlobalMethods(this.scriptBlocker);
}
stop() {
if (!this._isRunning)
return;
this._isRunning = false;
// Restore original methods
document.createElement = this.originalCreateElement;
Element.prototype.setAttribute = this.originalSetAttribute;
Node.prototype.appendChild = this.originalAppendChild;
Node.prototype.insertBefore = this.originalInsertBefore;
Node.prototype.replaceChild = this.originalReplaceChild;
}
interceptCreateElement() {
const self = this;
document.createElement = function (tagName, options) {
const element = self.originalCreateElement.call(document, tagName, options);
if (tagName.toLowerCase() === 'script') {
const script = element;
Promise.resolve().then(() => interceptScriptElement(script, self.scriptBlocker));
}
return element;
};
}
interceptSetAttribute() {
const self = this;
Element.prototype.setAttribute = function (name, value) {
self.originalSetAttribute.call(this, name, value);
if (this instanceof HTMLScriptElement) {
const script = this;
Promise.resolve().then(() => interceptScriptElement(script, self.scriptBlocker));
}
};
}
interceptAppendChild() {
const self = this;
Node.prototype.appendChild = function (newChild) {
if (newChild instanceof HTMLScriptElement) {
Promise.resolve().then(() => interceptScriptElement(newChild, self.scriptBlocker));
}
return self.originalAppendChild.call(this, newChild);
};
}
interceptInsertBefore() {
const self = this;
Node.prototype.insertBefore = function (newChild, refChild) {
if (newChild instanceof HTMLScriptElement) {
Promise.resolve().then(() => interceptScriptElement(newChild, self.scriptBlocker));
}
return self.originalInsertBefore.call(this, newChild, refChild);
};
}
interceptReplaceChild() {
const self = this;
Node.prototype.replaceChild = function (newChild, oldChild) {
if (newChild instanceof HTMLScriptElement) {
Promise.resolve().then(() => interceptScriptElement(newChild, self.scriptBlocker));
}
return self.originalReplaceChild.call(this, newChild, oldChild);
};
}
}
//# sourceMappingURL=script-interceptor.js.map