I recently started working on adding custom code to HubSpot forms after their form is loaded via an iFrame. Initially, I was using the following code to access the iFrame to change it:

var form = window.docuemnt.querySelector("[data-reactid='.hbspt-forms-']"]
if(form != null && form.getAttribute("data-abc") == null){
    form.setAttribute("data-abc", "button-abc");

This would try to access the button which is connected to HubSpot but it returned null as the iFrame was on a different domain and my page. When I would try to print the form out, it would print "null", which confirmed the fact that I could not access the element.

I tried the following code afterwards:

var form = document.getElementById("hs-form-iframe-0").contentDocument.querySelector("[data-reactid='.hbspt-forms-']")
if(form != null && form.getAttribute("data-abc") == null){
    form.setAttribute("data-abc", "button-abc");

This allowed me to access it via my third party script and also add new attributes to the specific object. Is this a vulnerability or am I misunderstanding something?

Additionally, both of the codes worked when I ran them in the Chrome dev console, so the codes are correct, I am wondering on why one allows me to modify content in an iFrame?

I hope this question makes sense!

  • Is the parent document that contains the iframe hosted on the same domain as the document loaded in the iframe?
    – Polynomial
    Sep 15, 2021 at 19:10
  • Also your first one probably broke because you typo'd docuemnt instead of document.
    – Polynomial
    Sep 15, 2021 at 19:17
  • Sorry, I typed it out here, the issue was not syntax/wording. As per your question, no, the document that contains the iframe is on example.com but the document being loaded is on hubspot.com.
    – Pshivvy
    Sep 15, 2021 at 19:25
  • Can you provide an example URL that we can take a look at? I tried on one of their demo pages and was unable to access contentDocument.
    – Polynomial
    Sep 15, 2021 at 19:32
  • Yeah! You can go to nostratest.com. If you scroll down, you will see a "Submit" button. That entire form is in an iFrame, so you can via the button's properties and notice that I was able to add the following attribute: "data-nostra=button-reward".
    – Pshivvy
    Sep 15, 2021 at 19:43

2 Answers 2


The frame is not being loaded from a remote source.

What you're adding to the page is something like this:

<script src="//js.hsforms.net/forms/shell.js"></script>
region: "na1",
portalId: "123456",
formId: "deadbeef-1234-1234-1234-123456789abc"

That script makes a request to HubSpot's servers (using XHR or Fetch) to fetch the content that should be displayed inside an iframe. The request URL is something like:


The response to that request is then used to build the form on the client-side, inside an iframe, using JavaScript. The iframe itself is just added to the DOM by the JavaScript, so its origin is the same as the parent page. If you use your browser's console to inspect the iframe's src property, you'll get a blank string, because the iframe was not loaded from a source - it is just a container for the newly loaded content.

  • That makes a lot of sense! Thanks! Also, I apologize for a random edit I made. Did not realize the info was already obscured.
    – Pshivvy
    Sep 15, 2021 at 20:35
  • No problem at all :)
    – Polynomial
    Sep 15, 2021 at 20:37

Generic answer since I'm not familiar with HubSpot in particular.

You can't, ever, use client-side script on the parent page to modify content in an iframe that is loaded from another domain (or that is sandboxed without the allow-same-origin sandbox exception). If you are able to access the contentDocument of an iframe, then it is a same-origin iframe. That's part of the "Same-Origin Policy" security feature implemented in all web browsers worth mention.

In this case, that clearly shows that your HubSpot iframe is, in fact, hosted from the same origin as the page where your script is running (which is presumably the top-level page).

As for why the first script didn't work, that's actually quite simple: you're doing a query lookup that's specific to a document, which is contained in a particular "browsing context" (typically window or frame) and does not query any other contexts' (including child contexts') documents. You can use querySelector (or getElementById) to get the iframe itself - that's part of your document - but then you need to get the iframe's own document (the content of the iframe browsing context) and run the query on it instead.

  • Could you be more specific on how this is the case: In this case, that clearly shows that your HubSpot iframe is, in fact, hosted from the same origin as the page where your script is running? You can go to nostratest.com and see how I am loading the iFrame in. It is being loaded by the following script: <p><code><script charset="utf-8" type="text/javascript" src="//js.hsforms.net/forms/shell.js"></script><script>hbspt.forms.create(INFO); The script and iFrame are being loaded via js.hsforms.net, so it is not on the same origin unless I am missing something.
    – Pshivvy
    Sep 15, 2021 at 20:12
  • I understand what you mean by the first script, though. It was weird as when I ran that in the Chrome dev console, it ran fine but when I ran it via my script, it would not work.
    – Pshivvy
    Sep 15, 2021 at 20:16
  • It's possible (I'm not sure) that the iframe not actually coming from another source is the reason that the first query worked in the dev console. Another possibility is simply that, if your script is running before the script that injects the iframe (and its content) into the DOM, then of course the contents of the iframe will not yet be available; they won't yet exist.
    – CBHacking
    Sep 15, 2021 at 21:14
  • What's actually happening with the iframe is that you're including a script (across origins, but that doesn't matter; SOP mostly doesn't apply to script elements) and that script - now executing within your origin - generates an iframe and all of its content, and injects it into the DOM. The iframe doesn't have any src, so it inherits its parent page's origin. It's a neat trick, but also highlights the risks of including third-party scripts: once they are loaded into your page, they are same-origin and can read, and write, everything on the page or in its scripts.
    – CBHacking
    Sep 15, 2021 at 21:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .