Skip to content

✨(frontend) new custom block "embed" - #2513

Open
AntoLC wants to merge 2 commits into
mainfrom
feat/iframe-block
Open

✨(frontend) new custom block "embed"#2513
AntoLC wants to merge 2 commits into
mainfrom
feat/iframe-block

Conversation

@AntoLC

@AntoLC AntoLC commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Purpose

We introduce a new custom block called "embed" that allows users to embed external content into their documents. This block can be used to include interactive widgets or other web content directly within the document editor.
It is a powerful custom block that can make your document much more interactive.

Proposal

Includes directly your favorite tools in Docs.
If the tool is collaborative like Grist or TLDraw, you will be able to collaborate in it directly from Docs.

Enregistrement.2026-07-08.171352.mp4

Settings

2 new settings are included to control this feature, ex:

FRONTEND_EMBED_BLOCK_ENABLED=<boolean>
FRONTEND_EMBED_BLOCK_ALLOWED_ORIGINS={
  "grist.numerique.gouv.fr": "allow-scripts allow-same-origin",
   "*.numerique.gouv.fr": "allow-scripts",
   "tldraw.com": "allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-form",
}

@AntoLC AntoLC self-assigned this Jul 8, 2026
@AntoLC AntoLC added frontend feature add a new feature editor labels Jul 8, 2026
@AntoLC
AntoLC force-pushed the feat/iframe-block branch from d849479 to 4ce76de Compare July 8, 2026 15:19
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Size Change: +905 B (+0.02%)

Total Size: 4.38 MB

📦 View Changed
Filename Size Change
apps/impress/out/_next/static/40a02c2c/_buildManifest.js 694 B +694 B (new file) 🆕
apps/impress/out/_next/static/chunks/5149.js 0 B -23 kB (removed) 🏆
apps/impress/out/_next/static/chunks/6875.js 23.9 kB +23.9 kB (new file) 🆕
apps/impress/out/_next/static/d4507f9c/_buildManifest.js 0 B -696 B (removed) 🏆

compressed-size-action

@AntoLC
AntoLC force-pushed the feat/iframe-block branch from 4ce76de to 9775a22 Compare July 9, 2026 09:58
@AntoLC
AntoLC requested review from jbpenrath and sneko July 10, 2026 08:51
@AntoLC
AntoLC marked this pull request as draft July 10, 2026 09:02
@AntoLC
AntoLC requested a review from lunika July 10, 2026 09:14
@AntoLC
AntoLC force-pushed the feat/iframe-block branch 4 times, most recently from 87f3ee2 to 4d71fbd Compare July 10, 2026 13:50
@AntoLC
AntoLC marked this pull request as ready for review July 10, 2026 13:51
@AntoLC
AntoLC force-pushed the feat/iframe-block branch from 4d71fbd to 7765b81 Compare July 10, 2026 14:07
AntoLC added 2 commits July 20, 2026 10:51
We introduce a new custom block called "embed" that
allows users to embed external content into their
documents. This block can be used to include
videos, interactive widgets, or other web content
directly within the document editor.
It is a powerfull custom block that can make
your document much more interactive.
We added 2 settings for the embed block:
- FRONTEND_EMBED_BLOCK_ENABLED: to enable or disable
  the embed block in the editor
- FRONTEND_EMBED_BLOCK_ALLOWED_ORIGINS: to define a
  list of allowed origins for the embed block, and
  the associated sandbox attributes (allow-scripts,
  allow-same-origin, etc.)
This will allow to control the usage of the embed
block in the editor, and to restrict the origins
that can be embedded.
@AntoLC
AntoLC force-pushed the feat/iframe-block branch from 7765b81 to 76dd9c1 Compare July 20, 2026 08:52
@AntoLC

AntoLC commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author

🚀 Preview will be available at https://2513-docs.ppr-docs.beta.numerique.gouv.fr/

You can use the existing account with these credentials:

  • username: docs
  • password: docs

You can also create a new account if you want to.

Once this Pull Request is merged, the preview will be destroyed.

* every member's machine, so it is treated as untrusted content.
*
* ⚠️ To keep it secure, the block enforces three invariants:
* 1. The URL must be safe (https, no javascript: or data:).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently isSafeUrl allows http:

Comment on lines +570 to +573
"*": (
"allow-scripts allow-same-origin allow-popups "
"allow-popups-to-escape-sandbox allow-forms"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's too much permissive for a wildcard WDYT?
I'll allow the minimum and add required rights to specific domains.

default=False, environ_name="FRONTEND_SILENT_LOGIN_ENABLED", environ_prefix=None
)
FRONTEND_EMBED_BLOCK_ENABLED = values.BooleanValue(
default=True, environ_name="FRONTEND_EMBED_BLOCK_ENABLED", environ_prefix=None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure to enabled it by default?

<Box
as="iframe"
className="bn-visual-media"
role="presentation"

@jbpenrath jbpenrath Jul 27, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure about role=presentation ? The content of this iframe aims to be only decorative ?

@sneko

sneko commented Jul 30, 2026

Copy link
Copy Markdown

Hi, I will list some points I see as potential risks that would need to be discussed (live thoughts, so not tested), but of course in most cases everything will go fine, it depends if a critical public document is being modified by a malicious user and what is the audience...

  1. In general on mobile browser when having fullscreen page due to embeds, they show a top bar with the current https://xxx.com. I think that's a good practice to prevent phishing, because having a rendered iframe does not tell you where it's hosted (to be set for all device platforms).

  2. Due to (1) in the current state I would agree with @jbpenrath it's a bit risky by default to allow any websites. It seems too permissive to me in case that's the document editor that is malicious. I don't expect a lot of services that need to be embedded, and for specific cases I guess it's fine the user opens a link in a new tab.

  3. Same than @jbpenrath on http: being allowed, but it's mitigated since on a modern browser opening http: from https: should be forbidden.

  4. With *, if targeting a website that is wrongly built, with actions being reachable with GET method (instead of POST/UPDATE/PATCH/DELETE), while having no CSRF token feature there would be a risk. I agree there is a low probability to target that kind of site with the right resource URL... but that's possible since it would use iframe cookies/tokens to authenticate (if the user is).

  5. From what I understand, "Docs" (the parent frame) is making sure the initial URL will be the expected one, but it has no way to check any final redirected URL, or any subsequent navigation. I think frame-src with CSP headers could enforce this despite HTTP redirections or location.href navigation (having a doubt on this one). If that's a case it would be great to have both restrictions on your UI or when mounting the embed to avoid initializing wrong things, AND having this would help further. Also I guess it's a double-security, in case we missed a way to inject an iframe, we are safe due to this HTTP header.

  6. About ENABLED=false parameter, it looks like it's disabling the UI to select one, but I'm wondering if there is a way to inject one through JavaScript directly into the Yjs content of a document? Also, any embed set in the past would not be removed. I'm wondering if disabling the feature should replace those already in place by a "disabled feature block"?

  7. In the defaults I see allow-popups-to-escape-sandbox. If we keep * there is a risk having targets opening tabs on unwanted websites "unsandboxed". I guess this should be opt-in for specific websites?

  8. What are the risk someone defines an iframe and succeeds in making width or height too tiny it's not even seeable? Maybe directly by inserting in JS and setting the size? It would result in kind of a "pixel tracker", doing things without the user knowing about this embed.

  9. The (8) makes me think then if auto-loading iframes is really a good idea? In term of risk and also GDPR (IP&co are sent automatically, potentially to website using foreign services like Google Fonts, analytics...). I don't want to enforce "click each time" but maybe a button "reveal" could have an option "auto-reveal this domain in the future")?

  10. In the defaults there is allow-same-origin, it's to allow using cookies/storage the user had in a normal navigation to the target. To avoid tracking or so, I think it should be an opt-in too, just for services requiring auth (to avoid people to log in again). But we could also assume they log in from there too (it would be a "storage context" dedicated under the current Docs context).

  11. In addition if restricting to a limited allowed services list, would it make sense to allow pathname prefix in the allow patterns? Because it's unlikely a website to be embedded. Most of the time you have to target a specific URL that throws away all the layout (menu, sidebar...). So instead of allow https://maps.xxx.com/* you allow only https://maps.xxx.com/embedded/*?

  12. allow-scripts could be out of defaults too, but I guess if something is embeddable it has some interaction possible

  13. The iframe even sandboxed is I think able to talk to its parent frame (Docs) though postMessage. It means if the product has already a listener with window.addEventListener("message", (event) => { it has to check the origin of each message to not only rely on the message payload. For now Docs has not this logic, but since I'm working on encryption feature, it will (with origin checked). That's just for info ^^


Otherwise just to note that there are some settings in place already, and they help this iframe feature:

  • referrerPolicy="no-referrer"
  • X-Frame-Options: DENY from nginx and frame-ancestors 'none' (so it protects also other Docs instances)
  • default-src "none"
  • X-Content-Type-Options: nosniff

Just note I attempted to upload a .html file on Docs and it's served with content-type: text/html. It's fine because you prevent script-inline. So I uploaded a .js, this one is served as text/plain (measure of security I guess), and trying importing it with a new .html uploaded, but it's not valid due to your default-src. So here nothing to say it's great 👍 .

The risk I see here is if for some reasons due to the tool being open source, someone deploy another proxy without the rule default-src "none"... and so having .html being really interpreted (see https://2513-docs.ppr-docs.beta.numerique.gouv.fr/media/99570511-7e32-4e31-96f9-7c038912e5e7/attachments/40608b82-2a9f-4e72-88ab-05ed669c9c88-unsafe.html), with the ability to reach/run remote scripts. Except if I'm wrong, the "isSafeUrl" cannot prevent a malicious server URL to bring back the user to the docs domain.

Available to talk about that, maybe to evaluate if it would make sense:

  • To force a .html file to be interperted as text only (no text/html content-type possible, as done for .js files)
  • To make SECURE_CONTENT_TYPE_NOSNIFF = True being not only on Production class (this, in case of no content-type, prevents the browser to infer what it could be), but it seems only for things managed by Django.
  • To make all files not "viewable" from a browser to have the header Content-Disposition: attachment? And for others (images...) having header Content-Disposition: inline?
  • To add inside the DOCUMENT_UNSAFE_MIME_TYPES array image/svg+xml? (using <object data="./test.svg" type="image/svg+xml"></object> may run <script>alert(111)</script> if inside the SVG)
  • To separate the domain of medias from the main one? So IN CASE we do something wrong in the future (settings migration...), there is a separation so an interpreted HTML attachment could not perform requests from the product.
  • To be "ceinture-bretelles", do the same for iframe, opens them through an iframe proxy like <iframe src="https://embed.docs.numerique.gouv.fr/wrap?url=grist...">? To separate some of the risks (postMessage, window.parent usage if same-origin, credentials storage...)

Talking about the proxy I saw the project https://github.com/itteco/iframely . Don't know if it would help you, but it seems helpful to embed specific services without managing a lot of "Yjs blocks" or have a less quality experience with "iframe that is agnostic" (but then I don't think it helps with target URLs that need to be authenticated on their service).

Sorry for this huge reply 🙃!

EDIT: je confirme que le isSafeUrl n'est pas 100% secure, genre avec un serveur malicieux qui redirigerait sur le produit https://httpbin.org/redirect-to?url=https%3A%2F%2F2513-docs.ppr-docs.beta.numerique.gouv.fr%2Fmedia%2F99570511-7e32-4e31-96f9-7c038912e5e7%2Fattachments%2F40608b82-2a9f-4e72-88ab-05ed669c9c88-unsafe.html&status_code=302

Là en l'occurence j'affiche le HTML qui a été uploadé via la pièce jointe test.html qui est en-dessous.

image

@AntoLC

AntoLC commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

Thank you for the review and for your time :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants