emilkowalski / vaul

An unstyled drawer component for React.
https://vaul.emilkowal.ski
MIT License
5.27k stars 170 forks source link

Content Security Policy (CSP) Concerns Due to Inline CSS Injection in Vaul Drawer Library #283

Open pieric-ux opened 4 months ago

pieric-ux commented 4 months ago

Content Security Policy (CSP) Concerns Due to Inline CSS Injection in Vaul Drawer Library

Issue Description

Problem

The current implementation of the Vaul Drawer library injects CSS styles directly into the <head> element of the document during the build process. This practice poses a security concern as it does not support the use of nonces, resulting in compatibility issues with Content Security Policy (CSP).

Affected Files

The injection is performed in the Index.mjs file of the library, specifically through the Te function.

// Index.mjs

function Te(e, { insertAt: n } = {}) {
  if (!e || typeof document == "undefined") return;
  let t = document.head || document.getElementsByTagName("head")[0],
    r = document.createElement("style");
  r.type = "text/css";
  if (n === "top" && t.firstChild) {
    t.insertBefore(r, t.firstChild);
  } else {
    t.appendChild(r);
  }
  if (r.styleSheet) {
    r.styleSheet.cssText = e;
  } else {
    r.appendChild(document.createTextNode(e));
  }
}

Te(`
  [vaul-drawer] {
    touch-action: none;
    transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
  }

  /* ... Other styles ... */

  @media (hover: hover) and (pointer: fine) {
    [vaul-drawer] {
      user-select: none;
    }
  }
`);

The injected styles include:

/* src/style.css */

<style type="text/css">
[vaul-drawer] {
  touch-action: none;
  transition: transform 0.5s cubic-bezier(0.32, 0.72, 0, 1);
}
[vaul-drawer][vaul-drawer-direction=bottom] {
  transform: translate3d(0, 100%, 0);
}
[vaul-drawer][vaul-drawer-direction=top] {
  transform: translate3d(0, -100%, 0);
}
[vaul-drawer][vaul-drawer-direction=left] {
  transform: translate3d(-100%, 0, 0);
}
[vaul-drawer][vaul-drawer-direction=right] {
  transform: translate3d(100%, 0, 0);
}
.vaul-dragging .vaul-scrollable [vault-drawer-direction=top] {
  overflow-y: hidden !important;
}
.vaul-dragging .vaul-scrollable [vault-drawer-direction=bottom] {
  overflow-y: hidden !important;
}
.vaul-dragging .vaul-scrollable [vault-drawer-direction=left] {
  overflow-x: hidden !important;
}
.vaul-dragging .vaul-scrollable [vault-drawer-direction=right] {
  overflow-x: hidden !important;
}
[vaul-drawer][vaul-drawer-visible=true][vaul-drawer-direction=top] {
  transform: translate3d(0, var(--snap-point-height, 0), 0);
}
[vaul-drawer][vaul-drawer-visible=true][vaul-drawer-direction=bottom] {
  transform: translate3d(0, var(--snap-point-height, 0), 0);
}
[vaul-drawer][vaul-drawer-visible=true][vaul-drawer-direction=left] {
  transform: translate3d(var(--snap-point-height, 0), 0, 0);
}
[vaul-drawer][vaul-drawer-visible=true][vaul-drawer-direction=right] {
  transform: translate3d(var(--snap-point-height, 0), 0, 0);
}
[vaul-overlay] {
  opacity: 0;
  transition: opacity 0.5s cubic-bezier(0.32, 0.72, 0, 1);
}
[vaul-overlay][vaul-drawer-visible=true] {
  opacity: 1;
}
[vaul-drawer]::after {
  content: "";
  position: absolute;
  background: inherit;
  background-color: inherit;
}
[vaul-drawer][vaul-drawer-direction=top]::after {
  top: initial;
  bottom: 100%;
  left: 0;
  right: 0;
  height: 200%;
}
[vaul-drawer][vaul-drawer-direction=bottom]::after {
  top: 100%;
  bottom: initial;
  left: 0;
  right: 0;
  height: 200%;
}
[vaul-drawer][vaul-drawer-direction=left]::after {
  left: initial;
  right: 100%;
  top: 0;
  bottom: 0;
  width: 200%;
}
[vaul-drawer][vaul-drawer-direction=right]::after {
  left: 100%;
  right: initial;
  top: 0;
  bottom: 0;
  width: 200%;
}
[vaul-overlay][vaul-snap-points=true]:not([vaul-snap-points-overlay="true"]):not([data-state="closed"]) {
  opacity: 0;
}
[vaul-overlay][vaul-snap-points-overlay=true]:not([vaul-drawer-visible="false"]) {
  opacity: 1;
}
@keyframes fake-animation {
  from {
  }
  to {
  }
}
@media (hover: hover) and (pointer: fine) {
  [vaul-drawer] {
    user-select: none;
  }
}
</style>

Issue Description

Impact on Secure Web Development

The inability to apply a nonce to these styles impacts adherence to best practices in secure web development.

Requested Action

I propose addressing this issue by enhancing the Vaul Drawer library to support nonces for injected styles. This improvement would enable users to seamlessly integrate the library into applications with a strict Content Security Policy (CSP).

Additional Context

The injected styles include critical elements for the functionality of the Vaul Drawer, and without the ability to use a nonce, users face challenges in maintaining a secure application environment. The issue particularly affects users who prioritize CSP in their web applications and seek a more robust security posture.

Proposed Solution

Implement a mechanism within the Vaul Drawer library to allow users to specify a nonce for the injected styles. This enhancement would align with best practices for secure web development and provide a more flexible integration process for users with stringent CSP policies.

Thank you for your attention to this matter, and I appreciate your efforts to continually improve the Vaul Drawer library.

Sincerly, Demont Pieric

r34son commented 1 month ago

That's why: previously lib was using the injectStyles option from tsup. https://github.com/egoist/tsup/pull/483.

But in #260 @huozhi moved to bunchee which is doing same thing under the hood

Question: Can i somehow use libs with css that are built with bunchee without specifying 'unsafe-inline' in CSP?