Last active
April 23, 2022 13:56
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* @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