Skip to content

Instantly share code, notes, and snippets.

@isocroft
Last active April 23, 2022 13:56
Show Gist options
  • Select an option

  • Save isocroft/e2a0ec03f829f41031402f27a13a8aeb to your computer and use it in GitHub Desktop.

Select an option

Save isocroft/e2a0ec03f829f41031402f27a13a8aeb to your computer and use it in GitHub Desktop.
Monkey-patching the document.createElement('a').setAttribute() function to be able to sanitise the URLs that are passed to it.
/* @HINT: Extract the native definitions of these APIs from the DOM Interfaces */
const originalSetAttributeMethod = HTMLElement.prototype.setAttribute
/* @HINT: Create a new definition for `setAttribute` that instruments the API to detect suspicious URIs */
HTMLElement.prototype.setAttribute = function setAttribute (attributeName, newValue) {
const that = this;
const previousValue = that.getAttribute(attributeName);
const timerID = window.setTimeout(function () {
/* @HINT: Stop [ DOMSubtreeModified ] event from firing before [ DOMAttrModified ] event */
originalSetAttributeMethod.call(that, attributeName, newValue);
}, 0);
/* @HINT: Whenever the attribute name is `href`, then check the URL that is the value */
if (attributeName === 'href') {
/* @HINT: Fire a custom event `beforeinclude` to track manual whitelisting of URL endpoints */
let event = new window.CustomEvent('beforeinclude', {
detail: {
endpoint: newValue,
sink: "HTMLElement.setAttribute",
data: null
},
bubbles: true,
cancelable: true
});
/* @HINT: Detect if the dispatched custom event was cancelled by a call to `event.preventDefault()` */
/* @HINT: If the event was cancelled, it means the URL endpoint above was disallowed by the checks */
const cancelled = !document.dispatchEvent(event)
/* @HINT: If it's cancelled, stop the `setTimeout` call above from being executed by clearing the timeout */
/* @HINT: Also, we throw an error to stop the call to `setAttribute` from being requested */
if (cancelled) {
window.clearTimeout(timerID)
throw new Error(
"Suspicious Activity: "
+
event.detail.endpoint
+
" request, using [ " + event.detail.data + " ] in "
+
" [ " + event.detail.sink + " ]"
)
}
}
/* @HINT: When listening to mutation events, might be okay to stagger certain event sequences properly */
if (newValue !== previousValue) {
let event = document.createEvent("MutationEvent");
event.initMutationEvent(
"DOMAttrModified",
true,
false,
that,
previousValue || "",
newValue || "",
attributeName,
(previousValue === null) ? event.ADDITION : event.MODIFICATION
);
that.dispatchEvent(
event
);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment