layerpro 3.3.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
  ![OXlint](https://img.shields.io/badge/Oxlint-gray?logo=oxc)
19
19
  ![Playwright](https://img.shields.io/badge/Playwright-gray?logo=playwright)
20
20
 
21
- LayerPro replaces the browser's native `alert()`, `confirm()`, and `prompt()` with beautiful, fully customizable modals that work everywhere — no more blocking the main thread, no more default browser styling.
21
+ LayerPro replaces the browser's native `alert()`, `confirm()`, and `prompt()` with beautiful, fully customizable modals that work everywhere - no more blocking the main thread, no more default browser styling.
22
22
 
23
23
  ## Table of Contents
24
24
 
@@ -43,12 +43,12 @@ LayerPro replaces the browser's native `alert()`, `confirm()`, and `prompt()` wi
43
43
 
44
44
  ## Features
45
45
 
46
- - **Zero runtime dependencies** — no jQuery, pure vanilla DOM helpers; `react`/`react-dom` are peer dependencies, only loaded when React bodies are used, powered by [`reattach`](https://npmjs.com/package/reattach)
47
- - **Multiple modal types** — alert, confirm, prompt, message
48
- - **Fully customizable** — appearance, behavior, and content
49
- - **Dockable windows** (taskbar-style) — click a dock icon to restore, focus, or minimize, just like an OS taskbar
50
- - **Draggable & resizable** — instant, with no transition lag while resizing
51
- - **CSS-only maximize** — uses `position: fixed` + insets, so it follows the viewport with no JS resize listeners
46
+ - **Zero runtime dependencies** - no jQuery, pure vanilla DOM helpers; `react`/`react-dom` are peer dependencies, only loaded when React bodies are used, powered by [`reattach`](https://npmjs.com/package/reattach)
47
+ - **Multiple modal types** - alert, confirm, prompt, message
48
+ - **Fully customizable** - appearance, behavior, and content
49
+ - **Dockable windows** (taskbar-style) - click a dock icon to restore, focus, or minimize, just like an OS taskbar
50
+ - **Draggable & resizable** - instant, with no transition lag while resizing
51
+ - **CSS-only maximize** - uses `position: fixed` + insets, so it follows the viewport with no JS resize listeners
52
52
  - **Fade in/out transitions** for open/close
53
53
  - **Full TypeScript support**
54
54
 
@@ -72,7 +72,7 @@ Import LayerPro once, in your app's entry point:
72
72
  import "layerpro";
73
73
  ```
74
74
 
75
- That's it — `alert()`, `confirm()`, `prompt()`, `message()`, and `popup()` are now available globally.
75
+ That's it - `alert()`, `confirm()`, `prompt()`, `message()`, and `popup()` are now available globally.
76
76
 
77
77
  The `popup` alias is a shorthand for `layerpro.popup`:
78
78
 
@@ -120,7 +120,7 @@ popup.open({
120
120
 
121
121
  ### React Components
122
122
 
123
- LayerPro's React adapter is powered by [`reattach`](https://npmjs.com/package/reattach), a zero-dependency library for mounting React components anywhere in the DOM. It handles root creation, automatic root reuse on duplicate-id reopen, **Context Bridging** across detached roots, and **auto-healing DOM** recovery — all without extra setup.
123
+ LayerPro's React adapter is powered by [`reattach`](https://npmjs.com/package/reattach), a zero-dependency library for mounting React components anywhere in the DOM. It handles root creation, automatic root reuse on duplicate-id reopen, **Context Bridging** across detached roots, and **auto-healing DOM** recovery - all without extra setup.
124
124
 
125
125
  ```jsx
126
126
  popup.open({
@@ -174,7 +174,7 @@ popup.open({
174
174
  id: string, // Unique ID (auto-generated as popup_1, popup_2, ... if omitted)
175
175
  body: any, // Content (text, HTML, or React)
176
176
  name: string, // Modal title
177
- icon: string, // Icon glyph — HTML entity (e.g. "⚠") or HTML string
177
+ icon: string, // Icon glyph - HTML entity (e.g. "⚠") or HTML string
178
178
 
179
179
  // Dimensions (defaults: width 340, height 220; minWidth 340, minHeight 220)
180
180
  width: number,
@@ -184,12 +184,13 @@ popup.open({
184
184
  maxWidth: number,
185
185
  maxHeight: number,
186
186
 
187
- // Remote content (same-origin only, enforced)
188
- source: string,
187
+ // Remote content (same-origin only, enforced). Rendered as raw HTML;
188
+ // sanitize server-side if the endpoint is not fully trusted.
189
+ source: string,
189
190
 
190
- // React adapter (via reattach)
191
- inheritContext: boolean,
192
- resilient: boolean,
191
+ // React adapter (via reattach)
192
+ inheritContext: boolean,
193
+ resilient: boolean,
193
194
 
194
195
  // Position (numbers are px; strings applied as-is, e.g. "10%")
195
196
  top: number | string,
@@ -210,8 +211,7 @@ popup.open({
210
211
  iconize: boolean, // allow minimize to dock
211
212
  dockable: boolean, // show an icon in the dock bar
212
213
  close: boolean, // show the close button
213
- raised: boolean, // alias: modal — full-screen overlay that blocks pointer events beneath
214
- modal: boolean, // alias of raised
214
+ modal: boolean, // full-screen overlay that blocks pointer events beneath
215
215
 
216
216
  // Buttons
217
217
  buttons: {
@@ -221,44 +221,59 @@ popup.open({
221
221
  }
222
222
  ```
223
223
 
224
- > **`raised` / `modal`** injects a full-viewport overlay (`popup_raised_<id>`) that intercepts every pointer event — the page underneath becomes unclickable until the popup closes. Both names are accepted.
224
+ > **`modal`** injects a full-viewport overlay (`popup_modal_<id>`) that intercepts every pointer event - the page underneath becomes unclickable until the popup closes. Use `modal: true` for alert/confirm/prompt/message pop ups.
225
225
 
226
- > **Duplicate id** — opening a popup whose `id` already exists brings that window to the front and refreshes its `body`/`buttons` instead of doing nothing.
226
+ > **Duplicate id** - opening a popup whose `id` already exists brings that window to the front and refreshes its `body`/`buttons` instead of doing nothing.
227
227
 
228
- ## Global Overrides
228
+ ## Global Overrides (Opt-in in 4.0)
229
229
 
230
- Importing LayerPro replaces the native `alert`, `confirm`, and `prompt` globals with callback-based versions, and adds a `message` global:
230
+ In 4.0, `import "layerpro"` no longer overrides the native `alert`, `confirm`,
231
+ and `prompt` globals. The library exposes its own methods on `window.layerpro`
232
+ and provides Promise-based alternatives. To override the browser globals, use
233
+ the **explicit entrypoint**:
231
234
 
232
235
  ```javascript
236
+ // Default entry - library installed, globals NOT overridden
233
237
  import "layerpro";
234
238
 
235
- alert("Hello World!"); // LayerPro alert
236
- confirm("Sure?", yes => {...}); // LayerPro confirm (callback, not a boolean return!)
237
- prompt("Name?", name => {...}); // LayerPro prompt (callback, not a string return!)
238
- message("Done!"); // LayerPro message
239
+ layerpro.alert("Hello World!"); // Promise<void>
240
+ const confirmed = await layerpro.confirm("Sure?"); // Promise<boolean>
241
+ const name = await layerpro.prompt("Name?"); // Promise<string>
242
+ layerpro.message("Done!"); // Promise<void>
239
243
  ```
240
244
 
241
- > **⚠️ Important:** the overridden `confirm()` no longer returns a `boolean`, and `prompt()` no longer returns a `string` — they are callback-based and return `undefined`. Any third-party code that does `if (confirm("..."))` will silently fall into the falsy branch. If your codebase depends on native synchronous return values, call **`layerpro.noGlobals()`** right after importing LayerPro to restore the originals:
242
- >
243
- > ```javascript
244
- > import "layerpro";
245
- > layerpro.noGlobals(); // restores native alert/confirm/prompt, drops `message`
246
- > ```
245
+ ### Override browser globals (opt-in)
246
+
247
+ If you need `window.alert` / `window.confirm` / `window.prompt` / `window.message`
248
+ replaced with LayerPro's Promise-based versions, import the global entrypoint:
249
+
250
+ ```javascript
251
+ import "layerpro/global"; // installs Promise-based overrides on window
252
+
253
+ // These now return Promises instead of native values:
254
+ await alert("Hello World!"); // Promise<void>
255
+ const ok = await confirm("Sure?"); // Promise<boolean>
256
+ const val = await prompt("Name?"); // Promise<string>
257
+ ```
258
+
259
+ > **⚠️ Important:** overridden `confirm()` returns `Promise<boolean>`, not a
260
+ > synchronous `boolean`. Code that does `if (confirm("..."))` must be
261
+ > `await`-ed. Only use `layerpro/global` if you control all call sites.
247
262
 
248
263
  ## Methods
249
264
 
250
265
  | Method | Description |
251
266
  |--------|-------------|
252
- | `popup.open(props)` | Open a modal; returns the props. Re-opens a same-id window to front and refreshes its body. |
267
+ | `popup.open(props)` | Open a modal; returns `Promise<GenLayerProps>` - resolves when the popup closes. |
253
268
  | `popup.close(id)` | Close (and unmount) a modal. |
254
269
  | `popup.center(id, offset?)` | Center a modal in the viewport; optional pixel offset. |
255
270
  | `popup.maximize(props)` | Toggle maximize (pure CSS). |
256
271
  | `popup.iconize(id)` | Toggle minimize to the dock. |
257
272
  | `popup.index(id)` | Bring a modal to the front (select). |
258
- | `popup.zIndex()` | Get the next z-index counter value. |
259
273
  | `popup.movable(props)` | Enable dragging. |
260
274
  | `popup.resizable(props)` | Enable resizing. |
261
- | `layerpro.noGlobals()` | Restore native `alert`/`confirm`/`prompt`; remove `message`. |
275
+ | `layerpro.installGlobals()` | Override `window.alert/confirm/prompt/message` with LayerPro's Promise-based versions. |
276
+ | `layerpro.config(opts)` | Runtime config. Currently supports `{ nonce: string }` for CSP; sets `window.__layerproNonce` consumed by the injected `<style>`. Call before importing LayerPro for the nonce to be present at injection. |
262
277
 
263
278
  ## Styling & z-index
264
279
 
@@ -280,25 +295,15 @@ The title bar, window surface, internal area, and body all read from CSS custom
280
295
  | `--window-border-radius` | popup border radius |
281
296
  | `--internal-background` | body wrapper |
282
297
  | `--body-color` | body text color |
283
- | `--raised-background` | modal overlay |
298
+ | `--modal-background` | modal overlay |
284
299
  | `--popup-z-index` | base z-index for the stack |
285
300
 
286
301
  ## Security
287
302
 
288
- - **Same-origin fetch guard** — the `source` prop only accepts same-origin URLs; cross-origin requests are rejected before any network call is made, preventing SSRF and data exfiltration.
289
- - **HTML escaping** — all user-provided text content (button labels, body content, titles) is automatically escaped via `escapeHtml()` to prevent XSS.
290
- - **CSP-friendly** — no inline scripts or `eval()` are used. React components are rendered via `reattach.mount()` (backed by `createRoot()`).
291
- - **Opt-out of global overrides** — call `layerpro.noGlobals()` after import to restore native `alert`, `confirm`, `prompt`, and remove the `message` global if your codebase depends on synchronous return values.
292
-
293
- ## Browser Support
294
-
295
- Chrome 80+ • Firefox 75+ • Safari 13+ • Edge 80+
296
-
297
- ## Contributing
298
-
299
- LayerPro's source is currently private, but contributions and discussion are welcome. Get in touch:
300
-
301
- - **Dario Passariello** — https://dario.passariello.ca/ — dariopassariello@gmail.com
303
+ - **Same-origin fetch guard** - the `source` prop only accepts same-origin URLs; cross-origin requests are rejected before any network call is made, preventing SSRF and data exfiltration.
304
+ - **HTML escaping** - all user-provided text content (button labels, body content, titles) is automatically escaped via `escapeHtml()` to prevent XSS.
305
+ - **CSP-friendly** - no inline scripts or `eval()` are used. React components are rendered via `reattach.mount()` (backed by `createRoot()`). For strict CSP environments, the injected stylesheet auto-detects the nonce from `window.__layerproNonce`, existing `<style>`/`<script>` elements, or `document.body.dataset.nonce`. Alternatively, import the stylesheet as a static file via `import 'layerpro/style.css'` to avoid inline style injection entirely. Use `layerpro.config({ nonce })` for explicit nonce control.
306
+ - **No global side effects** - `import 'layerpro'` does NOT override `window.alert`/`confirm`/`prompt`. Use `import 'layerpro/global'` or `layerpro.installGlobals()` to opt in to the global overrides.
302
307
 
303
308
  ## License
304
309
 
@@ -0,0 +1,265 @@
1
+ # Security Hardening Guide
2
+
3
+ This document is a security reference for LayerPro maintainers and consumers.
4
+ It documents every threat surface in the library, the defense-in-depth controls
5
+ that mitigate it, and the residual risks that must be managed at the application
6
+ level.
7
+
8
+ ---
9
+
10
+ ## 1. Threat Surface Summary
11
+
12
+ | Threat | Vector | Mitigation | Residual Risk |
13
+ |--------|--------|------------|---------------|
14
+ | Global override side effects | `import 'layerpro/global'` rewrites `window.alert/confirm/prompt` | Globals are opt-in (not auto-applied) in 4.0; `layerpro.installGlobals()` for programmatic opt-in; native globals untouched by default | Consumers who import `layerpro/global` accept Promise-based globals |
15
+ | XSS via popup body | `body: '<script>...'` or `buttons.confirm.text: '<img onerror=...>'` | `escapeHtml()` on all string bodies, button labels, names, icons | Does **not** protect against stored XSS from same-origin `source` (see §5) |
16
+ | SSRF via `source` prop | `source: 'https://evil.example.com/...'` | Same-origin URL check (`url.origin !== window.location.origin`) + `fetch` mode `same-origin` | Same-origin XSS (server compromise or stored content on own domain) is still possible |
17
+ | CSP bypass via inline `<style>` | CSS injected via `document.head.appendChild(<style>)` | Nonce auto-detection (`window.__layerproNonce`, `<style[nonce]>`, `<script[nonce]>`, `document.body.dataset.nonce`) | If no nonce is detectable, strict CSP will silently drop the stylesheet |
18
+ | React DOM injection | `body: <Component />` executes arbitrary component code | React adapter loaded lazily via dynamic `import('layerpro/react')` - `react-dom` is never in the core bundle | Consumer is fully responsible for the React components they pass |
19
+ | Prototype pollution | `setProps` iterates `Reflect.ownKeys` and reassigns descriptors | `setProps` only operates on the frozen `layerpro` object (non-enumerable, non-configurable, non-writable) | Low - `layerpro` is frozen in `index.ts:21` |
20
+
21
+ ---
22
+
23
+ ## 2. CSP (Content Security Policy) Support
24
+
25
+ LayerPro supports three strategies for strict CSP environments, in order
26
+ of preference:
27
+
28
+ ### Strategy A - Static stylesheet import (recommended)
29
+
30
+ ```js
31
+ import 'layerpro/style.css'
32
+ ```
33
+
34
+ This serves the CSS as a static file under `style-src 'self'`. No inline
35
+ `<style>` element is created at all, so no nonce or hash is needed.
36
+
37
+ In `package.json`:
38
+
39
+ ```json
40
+ {
41
+ "exports": {
42
+ "./style.css": "./dist/index.css"
43
+ },
44
+ "files": ["dist/index.css", "..."]
45
+ }
46
+ ```
47
+
48
+ ### Strategy B - Nonce auto-detection
49
+
50
+ When the CSS-in-JS injection path is used (i.e., `import 'layerpro'` without
51
+ the static stylesheet), the injected `<style>` element reads the nonce from:
52
+
53
+ 1. `window.__layerproNonce` - set explicitly via `layerpro.config({ nonce })`
54
+ 2. Any existing `<style>` or `<script>` element with a `nonce` attribute
55
+ 3. `document.body.dataset.nonce`
56
+
57
+ This is implemented in `tsup.plugin.injectCss.ts`:
58
+
59
+ ```js
60
+ const n = window.__layerproNonce
61
+ || window.layerpro?.nonce
62
+ || document.querySelector('style[nonce],script[nonce]')?.nonce
63
+ || document.body?.dataset?.nonce
64
+ if (n) s.setAttribute('nonce', n)
65
+ ```
66
+
67
+ Under a strict `style-src` policy with no detectable nonce, the stylesheet
68
+ is **silently dropped** (the browser rejects it). Popups will render unstyled.
69
+
70
+ ### Strategy C - Explicit nonce configuration
71
+
72
+ ```js
73
+ // Call BEFORE importing LayerPro so the nonce is set at injection time
74
+ window.__layerproNonce = 'your-base64-nonce-here'
75
+ // OR
76
+ import 'layerpro'
77
+ layerpro.config({ nonce: 'your-base64-nonce-here' })
78
+ ```
79
+
80
+ > **Important**: `layerpro.config({ nonce })` must be called **before** the
81
+ > CSS is first injected (i.e., before `import 'layerpro'`). If the module
82
+ > was already imported, the nonce will not be retroactively applied to the
83
+ > already-injected `<style>`.
84
+
85
+ ---
86
+
87
+ ## 3. Global Override Security Model
88
+
89
+ ### Risk (legacy 3.x pattern)
90
+
91
+ In LayerPro 3.x, `import 'layerpro'` unconditionally replaced
92
+ `window.alert`, `window.confirm`, `window.prompt`, and added `window.message`.
93
+ The overridden versions were **callback-based and asynchronous** - they did not
94
+ return native values:
95
+
96
+ ```js
97
+ import 'layerpro'
98
+
99
+ confirm('Are you sure?') // returns undefined, NOT a boolean
100
+ if (confirm('Proceed?')) { ... } // ALWAYS falsey - silently breaks!
101
+ ```
102
+
103
+ Any third-party code in your `node_modules` that relied on synchronous return
104
+ values would break **silently** (no error, no warning, just the falsy branch).
105
+
106
+ ### 4.0 Mitigation: opt-in globals
107
+
108
+ LayerPro 4.0 **removes the auto-override**. The main entry (`import 'layerpro'`)
109
+ no longer touches browser globals at all. To opt in to global overrides, use:
110
+
111
+ ```js
112
+ import 'layerpro/global' // explicitly overrides window.alert/confirm/prompt/message
113
+
114
+ const confirmed = await confirm('Are you sure?') // Promise<boolean>
115
+ ```
116
+
117
+ Or programmatically:
118
+
119
+ ```js
120
+ import 'layerpro'
121
+ layerpro.installGlobals() // same effect, explicit call
122
+ ```
123
+
124
+ ### Recommendation
125
+
126
+ In 4.0, simply **do not import `layerpro/global`** if you need the native
127
+ synchronous `alert`/`confirm`/`prompt`. Use the Promise-based methods on the
128
+ `layerpro` object directly instead:
129
+
130
+ ```js
131
+ import 'layerpro'
132
+
133
+ const confirmed = await layerpro.confirm('Are you sure?')
134
+ ```
135
+
136
+ ---
137
+
138
+ ## 4. XSS Prevention
139
+
140
+ All user-provided string content is escaped via `escapeHtml()` (regex-based
141
+ replacement of `& < > " '` → HTML entities) before being inserted into the DOM:
142
+
143
+ - **Popup body (text/HTML strings)**: `core/genLayer.ts:escapeHtml()`
144
+ - **Button labels**: `core/genLayer.ts:escapeHtml()` (confirm/cancel text)
145
+ - **Popup name/icon**: `core/genLayer.ts:escapeHtml()`
146
+ - **`alert()`/`confirm()`/`prompt()`/`message()` bodies**: `core/message.ts:escapeHtml()`
147
+
148
+ This is **not** a sanitization library - it does not allow any HTML through.
149
+ Raw HTML rendering is deliberately not supported for string bodies. If you
150
+ need raw HTML, use the React adapter with a component that explicitly
151
+ renders `dangerouslySetInnerHTML`.
152
+
153
+ ---
154
+
155
+ ## 5. `source` Prop (Remote Content Loading)
156
+
157
+ The `source` prop fetches same-origin HTML and injects it into the popup body.
158
+
159
+ ### Same-origin enforcement
160
+
161
+ - `core/genLayer.ts:loadSource()` constructs a `URL` and checks
162
+ `url.origin !== window.location.origin`
163
+ - The `fetch()` call also uses `mode: 'same-origin'` as a second layer
164
+ - The same-origin guard is the **primary defense** against SSRF
165
+
166
+ ### What is rendered
167
+
168
+ Remote HTML is injected as **raw, unescaped HTML** (as of 3.4.1 - previously
169
+ it was double-escaped, rendering all tags as visible text). This means:
170
+
171
+ - `<p>Hello</p>` renders as a real paragraph (not visible tags)
172
+ - `<script>` tags in the response are **executed** when the HTML is parsed
173
+ - Event handlers (`onclick="..."`) in the response are **activated**
174
+
175
+ ### Consumer responsibility
176
+
177
+ The same-origin guard protects against cross-origin SSRF, but **not** against
178
+ same-origin XSS. If an attacker can inject malicious HTML into an endpoint
179
+ on your own domain (stored XSS), the `source` prop will render it.
180
+
181
+ **Always sanitize server-side content before serving it as a `source` URL.**
182
+
183
+ ### Abort safety
184
+
185
+ An `AbortController` is associated with each `source` fetch and is aborted
186
+ when the popup closes (`layerpro.popup.abortSource(id)`), preventing
187
+ callbacks from targeting a destroyed layer.
188
+
189
+ ---
190
+
191
+ ## 6. React Adapter Isolation
192
+
193
+ The React adapter (`layerpro/react`) is loaded via dynamic `import('layerpro/react')`
194
+ only when a React element is passed as the `body` prop:
195
+
196
+ - `react-dom` is **never** imported in the core bundle (verified by `verify-fixes.mjs`)
197
+ - The adapter is a separate entry point declared in `package.json` `exports`
198
+ - `reattach` is bundled **inside** the adapter, not as a separate dependency
199
+ - The adapter externalizes only `react` and `react-dom` as peer dependencies
200
+
201
+ This ensures consumers who don't use React bodies never load `react-dom`.
202
+
203
+ ### Risk
204
+
205
+ The `import('layerpro/react')` bare-specifier requires a bundler that
206
+ understands the `exports` field in `package.json`. In plain `<script type="module">`
207
+ environments or old bundlers, this dynamic import will fail. The failure is
208
+ caught and logged:
209
+
210
+ ```js
211
+ import('layerpro/react').then(adapter => { ... }).catch(error => {
212
+ console.error('LayerPro Error: React adapter failed to load.')
213
+ console.debug(error)
214
+ })
215
+ ```
216
+
217
+ No error is surfaced to the user - the popup renders with an empty body.
218
+
219
+ ---
220
+
221
+ ## 7. Accessibility & Security Interaction
222
+
223
+ LayerPro renders popups with the following ARIA attributes (as of 3.4.1):
224
+
225
+ - `role="dialog"` on the popup window container
226
+ - `aria-modal="true"` on modal popups
227
+ - `aria-label` (from `props.name`) or `aria-labelledby` if a named title exists
228
+ - `aria-hidden="true"` on all `<body>` children except `#popup` while a modal is open
229
+ - `tabindex="-1"` on the popup window (focusable via script)
230
+
231
+ ### Focus management
232
+
233
+ - The currently focused element is saved when a popup opens
234
+ - Focus is moved into the popup via `requestAnimationFrame` + `.focus()`
235
+ - A `Tab` key listener traps focus within the popup container (cycles to
236
+ first/last focusable element)
237
+ - On close, focus is restored to the previously active element and
238
+ `aria-hidden` is removed from page content
239
+
240
+ ### Limitations
241
+
242
+ - This is a **lightweight** focus trap (Tab key only). It does not handle
243
+ screen reader virtual cursor navigation, nested modals, or `inert` polyfill.
244
+ - `aria-hidden` on page content is not equivalent to `inert` - focus can
245
+ still technically reach page elements via direct `.focus()` calls from
246
+ application code.
247
+
248
+ ---
249
+
250
+ ## 8. Build-time Security
251
+
252
+ - `tsup.plugin.injectCss.ts` injects CSS at build time with nonce support
253
+ - `tsup.plugin.copyStatic.ts` rewrites the dist `package.json` exports map
254
+ - `tsup.plugin.types.ts` generates `react.d.ts` and patches `index.d.ts`
255
+ with a `dphelper` reference (tsc strips `/// <reference>` from `.ts` files)
256
+ - `react.d.ts` is included in the npm `files` array so `import layerpro/react`
257
+ resolves types under `moduleResolution: bundler`
258
+
259
+ ---
260
+
261
+ ## 9. Reporting
262
+
263
+ If you have a potential security vulnerability to report, please email
264
+ [@passariello](https://github.com/passariello) or see
265
+ <https://dario.passariello.ca/contact/>.
package/SECURITY.md CHANGED
@@ -1,3 +1,11 @@
1
1
  # Security
2
2
 
3
3
  Please email [@passariello](https://github.com/passariello) or see <https://dario.passariello.ca/contact/> if you have a potential security vulnerability to report.
4
+
5
+ ## Source feature (same-origin fetch)
6
+
7
+ The `source` prop fetches HTML from a same-origin URL and renders it as-is.
8
+ The same-origin guard prevents SSRF to other hosts, but the fetched markup is
9
+ NOT sanitized by LayerPro. Any same-origin endpoint used as a `source` must
10
+ sanitize its output server-side. Consumers needing client-side sanitization
11
+ should pass a React element in `body` instead.