opencloud icon indicating copy to clipboard operation
opencloud copied to clipboard

Paste external image sources into markdown editor

Open kulmann opened this issue 6 months ago • 14 comments

Description

Our current set of CSP rules doesn't allow pasting image sources as external URLs into a markdown file. This feels like a bug and we should improve it. At the same time we don't want to open an attack vector for XSS attacks, tracking etc via malicious images.

User Stories

  • As a user writing markdown files, I want to paste external image urls and have a rendered preview so that I don't need to bloat the file size of my markdown file.

  • As an admin I want to protect my users from XSS attacks and tracking via external image urls so that I sleep well.

Value

External images in markdown files.

Acceptance Criteria

  • Pasting a URL to an external image source leads to the image being rendered in the web ui
  • Downloading the markdown file doesn't break the image rendering in a locally installed editor/viewer
  • Uploading a markdown file with external image sources leads to the images being rendered in the web ui
  • The thumbnailer accepts external image URLs as input
  • The image is served via an internal (own) URL
  • The output is XSS-safe, i.e. sanitized and free of malicious code

note: a CSP rule of img-src: * makes all of this possible but is considered dangerous. Hence we need another solution.

Definition of ready

  • [ ] Everybody needs to understand the value written in the user story
  • [ ] Acceptance criteria have to be defined
  • [ ] All dependencies of the user story need to be identified
  • [ ] Feature should be seen from an end user perspective
  • [ ] Story has to be estimated
  • [ ] Story points need to be less than 20

Definition of done

  • Functional requirements
    • [ ] Functionality described in the user story works
    • [ ] Acceptance criteria are fulfilled
  • Quality
    • [ ] Code review happened
    • [ ] CI is green (that includes new and existing automated tests)
    • [ ] Critical code received unit tests by the developer
  • Non-functional requirements
    • [ ] No sonar cloud issues
  • Configuration changes
    • [ ] The next branch of the OpenCloud charts is compatible

kulmann avatar Jun 24 '25 10:06 kulmann

See https://github.com/opencloud-eu/opencloud/pull/1101 for discussions revolving around the topic.

kulmann avatar Jun 24 '25 10:06 kulmann

We could use our thumbnailing service to recreate images from external sites. That moves the attack vector to exploiting the thumbnail service, or rather the library we use, but IMO we should treat the thumbnailing service like antivirus or tika. They should run in a locked down docker container without internet access and we send them the bytes.

butonic avatar Jun 24 '25 10:06 butonic

We could convert images to base64 blobs and embed them?

![Logo](data:image/png;base64,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)

Which already works in our editor:

Image

micbar avatar Jun 24 '25 10:06 micbar

@micbar this is kind of ugly and not user friendly. No one wants to have their markdown file have thousands of lines with base64 glibberish

AlexAndBear avatar Jun 24 '25 10:06 AlexAndBear

@micbar this is kind of ugly and not user friendly. No one wants to have their markdown file have thousands of lines with base64 glibberish

I see. But that fulfills the requirement that the file can be downloaded and continues to work offline.

micbar avatar Jun 24 '25 11:06 micbar

Big no. Please let's try to implement software that not "somehow just works" but make it high quality.

I think the idea of GitHub is fantastic. They leave the URL's in the markdown as they are but replace them during the render process. Out thumbnailer could fulfill this, but needs to be able to read images from the www

AlexAndBear avatar Jun 24 '25 11:06 AlexAndBear

How would you fulfil the requirement:

downloading the markdown file doesn't break the image rendering in a locally installed editor/viewer

micbar avatar Jun 24 '25 11:06 micbar

How would you fulfil the requirement:

downloading the markdown file doesn't break the image rendering in a locally installed editor/viewer

Just means that we must not replace the image urls with our own urls in the original markdown content. The idea that @AlexAndBear mentions fulfills this.

kulmann avatar Jun 25 '25 06:06 kulmann

This is my web POC: https://github.com/opencloud-eu/web/pull/863

it proofs, that we are able to render images from different sources (e.G) the thumbnailer while leaving the original markdown-content intact

AlexAndBear avatar Jun 25 '25 16:06 AlexAndBear

Here's some info about what github is doing https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-anonymized-urls

For proxying the image downloads they're apparently using https://github.com/atmos/camo (archived since 2021 🤷‍♂)

rhafer avatar Jun 27 '25 09:06 rhafer

Ok, replacing the markdown with a request to the opencloud domain removes the necessity for a CSP rule. But it kind of defeats the purpose of having CSP rules to prevent potentially malicious external content from reaching the users browser. If we do this we absolutely have to pass the image through the thumbnailer. We can cache the image and make a conditional get request to the external source to prevent downloading and recreating the thumbnail again.

We must not implement a generic proxy that can felch any external URL to embed it.

I would prefer if we automatically save a dragged image to the space and allow embedding images that way.

That being said, I can imagine use cases that require embedding status icons from external systems.

I think for that we need to change our thumbnailer implementation quite significantly, because it currently can only create thumbnails from images that it fetches via CS3/Webdav. Related: https://github.com/opencloud-eu/opencloud/issues/1128

butonic avatar Jun 27 '25 09:06 butonic

Ok, replacing the markdown with a request to the opencloud domain removes the necessity for a CSP rule. But it kind of defeats the purpose of having CSP rules to prevent potentially malicious external content from reaching the users browser.

Right.

If we do this we absolutely have to pass the image through the thumbnailer.

Even with that, I don't think we can usefully prevent malicious content from reaching the browser. Unless we blacklist all the "interesting" formats (like e.g. SVG). Or are you imaging having the (somehow sandboxed) thumbnailer convert SVG so some less problematic format (e.g. jpeg)? I don't think that'll be of much use.

We can cache the imageand make a conditional get request to the external source to prevent downloading and recreating the thumbnail again.

As far as I understand that is basically what camo is doing.

I would prefer if we atomatically save a dragged image to the space and allow embedding images that way.

Agreed.

rhafer avatar Jun 27 '25 10:06 rhafer

I would prefer if we atomatically save a dragged image to the space and allow embedding images that way.

So you want to vendor-lock markdown files into OpenCloud? Because the markdown file wouldn't work anymore if I download it. That's not a useful approach. Also, if images change externally (see e.g. status images in github repo readmes) I will never get an update.

kulmann avatar Jun 27 '25 10:06 kulmann

Research Ticket: https://github.com/opencloud-eu/opencloud/issues/1145

Related issue with CSP Rules: https://github.com/opencloud-eu/opencloud/issues/1388

db-ot avatar Jun 30 '25 13:06 db-ot