@pnx-mixtape/mxds 0.1.0-rc.3 → 0.1.0-rc.5
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/.claude/skills/create-mxds-component/SKILL.md +77 -0
- package/DESIGN.md +595 -0
- package/README.md +1 -2
- package/dist/build/accordion.css +1 -1
- package/dist/build/accordion.entry.js +2 -1
- package/dist/build/autocomplete.entry.js +1 -1
- package/dist/build/back-to-top.css +1 -0
- package/dist/build/back-to-top.entry.js +1 -0
- package/dist/build/base.css +1 -1
- package/dist/build/button.css +1 -1
- package/dist/build/card.css +1 -1
- package/dist/build/carousel.css +1 -1
- package/dist/build/chunks/Accordion-D2wp8hBG.js +44 -0
- package/dist/build/chunks/AccordionGroup-BYtoAFxg.js +47 -0
- package/dist/build/chunks/AccordionMobile-D3sTeiM5.js +26 -0
- package/dist/build/chunks/Autocomplete-kRo8RNbC.js +135 -0
- package/dist/build/chunks/BackToTop-4M-Wl7z3.js +64 -0
- package/dist/build/chunks/ClosableAlert-yy84gae6.js +55 -0
- package/dist/build/chunks/CookieConsent-wd8gQ2Oe.js +18 -0
- package/dist/build/chunks/Dialog-DWoXW-7Z.js +48 -0
- package/dist/build/chunks/DropMenu-CSTZq0Zt.js +68 -0
- package/dist/build/chunks/FeedbackForm-BochXEDx.js +64 -0
- package/dist/build/chunks/Gallery-Rkfv7Mbb.js +165 -0
- package/dist/build/chunks/GlobalToggle-CzlkpLpc.js +82 -0
- package/dist/build/chunks/InPageNavigation-D1Kk-rCm.js +86 -0
- package/dist/build/chunks/Navigation-CDQnN61h.js +147 -0
- package/dist/build/chunks/Popover-CiTAftNU.js +30 -0
- package/dist/build/chunks/Sticky-DoVndENm.js +51 -0
- package/dist/build/chunks/Tabs-BDlxCDNh.js +160 -0
- package/dist/build/chunks/Translate-bmM38j6z.js +46 -0
- package/dist/build/chunks/cookie-compliance-CG6x-TcV.js +119 -0
- package/dist/build/chunks/enums-BrHxPZZ_.js +36 -0
- package/dist/build/chunks/polyfills-D-qh1a_2.js +33 -0
- package/dist/build/chunks/utilities-Dd-08SW6.js +255 -0
- package/dist/build/constants.css +1 -1
- package/dist/build/content-block.css +1 -1
- package/dist/build/cookie-consent.css +1 -1
- package/dist/build/cookie-consent.entry.js +1 -1
- package/dist/build/dialog.css +1 -1
- package/dist/build/dialog.entry.js +1 -1
- package/dist/build/drop-menu.entry.js +1 -1
- package/dist/build/elements.js +147 -1
- package/dist/build/feedback-form.css +1 -0
- package/dist/build/feedback-form.entry.js +1 -0
- package/dist/build/filters.entry.js +145 -1
- package/dist/build/form.css +1 -1
- package/dist/build/gallery.entry.js +1 -1
- package/dist/build/global-alert.css +1 -1
- package/dist/build/global-alert.entry.js +1 -1
- package/dist/build/grid.css +1 -1
- package/dist/build/header.css +1 -1
- package/dist/build/header.entry.js +1 -1
- package/dist/build/icon.css +1 -1
- package/dist/build/in-page-navigation.entry.js +1 -1
- package/dist/build/navigation.css +1 -1
- package/dist/build/navigation.entry.js +1 -1
- package/dist/build/pagination.css +1 -1
- package/dist/build/popover.css +1 -1
- package/dist/build/popover.entry.js +1 -1
- package/dist/build/react.js +2270 -1
- package/dist/build/sticky.entry.js +1 -1
- package/dist/build/tabs.css +1 -1
- package/dist/build/tabs.entry.js +1 -1
- package/dist/build/translate.css +1 -0
- package/dist/build/translate.entry.js +1 -0
- package/dist/build/utility-list.entry.js +53 -1
- package/dist/types/Component/BackToTop/BackToTop.d.ts +20 -0
- package/dist/types/Component/BackToTop/BackToTop.d.ts.map +1 -0
- package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts +27 -0
- package/dist/types/Component/BackToTop/Elements/BackToTop.d.ts.map +1 -0
- package/dist/types/Component/BackToTop/back-to-top.entry.d.ts +2 -0
- package/dist/types/Component/BackToTop/back-to-top.entry.d.ts.map +1 -0
- package/dist/types/Component/DropMenu/DropMenu.d.ts +2 -1
- package/dist/types/Component/DropMenu/DropMenu.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Components/TabDropMenu.d.ts.map +1 -1
- package/dist/types/Component/Tabs/Elements/Tabs.d.ts.map +1 -1
- package/dist/types/Component/Translate/Elements/Translate.d.ts +23 -0
- package/dist/types/Component/Translate/Elements/Translate.d.ts.map +1 -0
- package/dist/types/Component/Translate/Translate.d.ts +37 -0
- package/dist/types/Component/Translate/Translate.d.ts.map +1 -0
- package/dist/types/Component/Translate/languages.d.ts +11 -0
- package/dist/types/Component/Translate/languages.d.ts.map +1 -0
- package/dist/types/Component/Translate/translate.entry.d.ts +2 -0
- package/dist/types/Component/Translate/translate.entry.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts +37 -0
- package/dist/types/Form/FeedbackForm/Elements/FeedbackForm.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts +36 -0
- package/dist/types/Form/FeedbackForm/FeedbackForm.d.ts.map +1 -0
- package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts +2 -0
- package/dist/types/Form/FeedbackForm/feedback-form.entry.d.ts.map +1 -0
- package/dist/types/Utility/Elements/google-translate-widget.d.ts +91 -0
- package/dist/types/Utility/Elements/google-translate-widget.d.ts.map +1 -0
- package/dist/types/Utility/utilities.d.ts +2 -0
- package/dist/types/Utility/utilities.d.ts.map +1 -1
- package/dist/types/elements.d.ts +3 -0
- package/dist/types/elements.d.ts.map +1 -1
- package/dist/types/enums.d.ts +3 -1
- package/dist/types/enums.d.ts.map +1 -1
- package/dist/types/react.d.ts +3 -0
- package/dist/types/react.d.ts.map +1 -1
- package/docs/API.md +1215 -0
- package/docs/CSSGuidelines.md +891 -0
- package/docs/CodeQuality.md +940 -0
- package/docs/ComponentArchitecture.md +714 -0
- package/docs/Contributing.md +626 -0
- package/docs/MemoryManagement.md +598 -0
- package/docs/Migrations.md +6 -0
- package/docs/README.md +100 -0
- package/docs/Security.md +444 -0
- package/docs/Testing.md +739 -0
- package/docs/Troubleshooting.md +674 -0
- package/package.json +6 -3
- package/src/Atom/Button/Button.tsx +1 -1
- package/src/Atom/Button/__snapshots__/Button.stories.tsx.snap +2 -2
- package/src/Atom/Button/_buttons.css +1 -0
- package/src/Atom/Icon/README.md +1 -1
- package/src/Atom/Icon/_icon.css +1 -0
- package/src/Atom/Icon/images/thumb.svg +1 -0
- package/src/Atom/Media/__snapshots__/Media.stories.ts.snap +4 -9
- package/src/Atom/Media/media.twig +4 -5
- package/src/Atom/_generic.css +0 -10
- package/src/Component/Accordion/accordion.css +3 -1
- package/src/Component/BackToTop/BackToTop.stories.ts +127 -0
- package/src/Component/BackToTop/BackToTop.stories.tsx +53 -0
- package/src/Component/BackToTop/BackToTop.tsx +120 -0
- package/src/Component/BackToTop/Elements/BackToTop.ts +119 -0
- package/src/Component/BackToTop/README.md +164 -0
- package/src/Component/BackToTop/__snapshots__/BackToTop.stories.ts.snap +24 -0
- package/src/Component/BackToTop/__snapshots__/BackToTop.stories.tsx.snap +3 -0
- package/src/Component/BackToTop/back-to-top.css +70 -0
- package/src/Component/BackToTop/back-to-top.entry.js +1 -0
- package/src/Component/BackToTop/back-to-top.twig +26 -0
- package/src/Component/Card/Components/CardContent.tsx +1 -1
- package/src/Component/Card/Components/CardMedia.tsx +1 -1
- package/src/Component/Card/__snapshots__/Card.stories.tsx.snap +3 -3
- package/src/Component/Card/card.css +1 -1
- package/src/Component/Carousel/Carousel.stories.ts +66 -36
- package/src/Component/Carousel/__snapshots__/Carousel.stories.tsx.snap +5 -5
- package/src/Component/Carousel/carousel.css +1 -1
- package/src/Component/ContentBlock/Components/ContentBlockMedia.tsx +1 -1
- package/src/Component/ContentBlock/content-block.css +1 -1
- package/src/Component/CookieConsent/cookie-consent.css +3 -0
- package/src/Component/Dialog/dialog.css +5 -2
- package/src/Component/DropMenu/DropMenu.stories.ts +21 -2
- package/src/Component/DropMenu/DropMenu.tsx +14 -4
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.ts.snap +3 -3
- package/src/Component/DropMenu/__snapshots__/DropMenu.stories.tsx.snap +2 -2
- package/src/Component/DropMenu/drop-menu.twig +7 -1
- package/src/Component/GlobalAlert/global-alert.css +3 -0
- package/src/Component/HeroBanner/__snapshots__/HeroBanner.stories.tsx.snap +1 -1
- package/src/Component/InPageNavigation/__snapshots__/InPageNavigation.stories.ts.snap +48 -13
- package/src/Component/InPageNavigation/in-page-navigation.twig +4 -6
- package/src/Component/ListItem/Components/ListItemMedia.tsx +1 -1
- package/src/Component/Navigation/_navigation-collapsible.css +3 -1
- package/src/Component/Navigation/_navigation-dropdown.css +3 -1
- package/src/Component/Pagination/Pagination.stories.ts +57 -27
- package/src/Component/Pagination/pagination.css +16 -7
- package/src/Component/Popover/__snapshots__/Popover.stories.tsx.snap +3 -3
- package/src/Component/Popover/popover.css +3 -1
- package/src/Component/Tabs/Components/TabDropMenu.tsx +7 -1
- package/src/Component/Tabs/Elements/Tabs.ts +13 -3
- package/src/Component/Tabs/Tabs.stories.ts +33 -12
- package/src/Component/Tabs/Tabs.stories.tsx +28 -14
- package/src/Component/Tabs/__snapshots__/Tabs.stories.ts.snap +3 -3
- package/src/Component/Tabs/__snapshots__/Tabs.stories.tsx.snap +2 -2
- package/src/Component/Tabs/tabs.css +4 -5
- package/src/Component/Translate/Elements/Translate.ts +76 -0
- package/src/Component/Translate/README.md +283 -0
- package/src/Component/Translate/Translate.stories.ts +87 -0
- package/src/Component/Translate/Translate.stories.tsx +77 -0
- package/src/Component/Translate/Translate.tsx +213 -0
- package/src/Component/Translate/__snapshots__/Translate.stories.ts.snap +18 -0
- package/src/Component/Translate/__snapshots__/Translate.stories.tsx.snap +3 -0
- package/src/Component/Translate/languages.ts +39 -0
- package/src/Component/Translate/translate.css +62 -0
- package/src/Component/Translate/translate.entry.js +1 -0
- package/src/Component/Translate/translate.twig +44 -0
- package/src/Form/Autocomplete/Autocomplete.stories.tsx +2 -1
- package/src/Form/FeedbackForm/Elements/FeedbackForm.ts +122 -0
- package/src/Form/FeedbackForm/FeedbackForm.stories.ts +99 -0
- package/src/Form/FeedbackForm/FeedbackForm.stories.tsx +73 -0
- package/src/Form/FeedbackForm/FeedbackForm.tsx +196 -0
- package/src/Form/FeedbackForm/README.md +233 -0
- package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.ts.snap +125 -0
- package/src/Form/FeedbackForm/__snapshots__/FeedbackForm.stories.tsx.snap +5 -0
- package/src/Form/FeedbackForm/feedback-form.css +74 -0
- package/src/Form/FeedbackForm/feedback-form.entry.js +1 -0
- package/src/Form/FeedbackForm/feedback-form.twig +80 -0
- package/src/Form/form.css +5 -3
- package/src/Layout/Grid/Grid.stories.ts +1 -0
- package/src/Layout/Grid/Grid.stories.tsx +26 -0
- package/src/Layout/Grid/__snapshots__/Grid.stories.tsx.snap +2 -0
- package/src/Layout/Grid/grid.css +12 -0
- package/src/Layout/Header/Components/GlobalToggle.tsx +1 -1
- package/src/Layout/Header/Header.stories.ts +53 -48
- package/src/Layout/Header/Header.stories.tsx +35 -22
- package/src/Layout/Header/Header.tsx +2 -2
- package/src/Layout/Header/__snapshots__/Header.stories.ts.snap +14 -10
- package/src/Layout/Header/__snapshots__/Header.stories.tsx.snap +2 -2
- package/src/Layout/Header/_header.css +4 -2
- package/src/Layout/Header/_toggles.css +9 -6
- package/src/Layout/Page/__snapshots__/Page.stories.tsx.snap +1 -1
- package/src/Utility/Elements/google-translate-widget.ts +293 -0
- package/src/Utility/_mixins.css +35 -13
- package/src/Utility/global.d.ts +4 -0
- package/src/Utility/utilities.ts +2 -0
- package/src/_custom-media.css +2 -2
- package/src/constants.css +3 -13
- package/src/elements.ts +3 -0
- package/src/enums.ts +2 -0
- package/src/react.ts +3 -0
- package/dist/build/chunks/Accordion-GN0sME4W.js +0 -1
- package/dist/build/chunks/AccordionGroup-Bzqp5__q.js +0 -1
- package/dist/build/chunks/AccordionMobile-DG5jSe8Q.js +0 -1
- package/dist/build/chunks/Autocomplete-B4RtkAyh.js +0 -1
- package/dist/build/chunks/ClosableAlert-DEhj8Nxm.js +0 -1
- package/dist/build/chunks/CookieConsent-BkCMRVST.js +0 -1
- package/dist/build/chunks/Dialog-Haahn-Yn.js +0 -1
- package/dist/build/chunks/DropMenu-1baQc8Yc.js +0 -1
- package/dist/build/chunks/Gallery-D8NGXy2Q.js +0 -1
- package/dist/build/chunks/GlobalToggle-B52fdBQu.js +0 -1
- package/dist/build/chunks/InPageNavigation-Dyn2TR-N.js +0 -1
- package/dist/build/chunks/Navigation-hOrfLq7h.js +0 -1
- package/dist/build/chunks/Popover-Cxg-s5Jn.js +0 -1
- package/dist/build/chunks/Sticky-BGCuuEJK.js +0 -1
- package/dist/build/chunks/Tabs-DYy1YTdU.js +0 -15
- package/dist/build/chunks/cookie-compliance-Bbm-0vpj.js +0 -1
- package/dist/build/chunks/enums-DLV_1NAf.js +0 -1
- package/dist/build/chunks/polyfills-C7pRt54M.js +0 -1
- package/dist/build/chunks/utilities-_Bq3TxAQ.js +0 -1
package/docs/Security.md
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# Security Guidelines
|
|
2
|
+
|
|
3
|
+
Security best practices for the Mixtape Design System.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [XSS Prevention](#xss-prevention)
|
|
8
|
+
- [Input Validation](#input-validation)
|
|
9
|
+
- [URL Safety](#url-safety)
|
|
10
|
+
- [Data Sanitization](#data-sanitization)
|
|
11
|
+
- [Secure Dependencies](#secure-dependencies)
|
|
12
|
+
- [Information Disclosure](#information-disclosure)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## XSS Prevention
|
|
17
|
+
|
|
18
|
+
### Never Use dangerouslySetInnerHTML
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
// ❌ BAD - XSS vulnerability
|
|
22
|
+
<div dangerouslySetInnerHTML={{ __html: userContent }} />
|
|
23
|
+
|
|
24
|
+
// User provides: <img src=x onerror='alert(document.cookie)'>
|
|
25
|
+
// Result: JavaScript executes!
|
|
26
|
+
|
|
27
|
+
// ✅ GOOD - Safe rendering
|
|
28
|
+
<div>{userContent}</div>
|
|
29
|
+
|
|
30
|
+
// React automatically escapes content
|
|
31
|
+
// Result: Displays as text, doesn't execute
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Never Use innerHTML
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// ❌ BAD - XSS vulnerability
|
|
38
|
+
element.innerHTML = htmlString
|
|
39
|
+
|
|
40
|
+
// ✅ GOOD - Use DOMParser
|
|
41
|
+
const parser = new DOMParser()
|
|
42
|
+
const doc = parser.parseFromString(htmlString, "text/html")
|
|
43
|
+
const safeElement = doc.body.firstElementChild
|
|
44
|
+
|
|
45
|
+
// Or use the createElement utility
|
|
46
|
+
import { createElement } from "../Utility/utilities"
|
|
47
|
+
const safeElement = createElement(htmlString)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### The createElement Utility
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// Safe HTML parsing without script execution
|
|
54
|
+
export const createElement = (string: string): Element | null => {
|
|
55
|
+
try {
|
|
56
|
+
const parser = new DOMParser()
|
|
57
|
+
const doc = parser.parseFromString(string, "text/html")
|
|
58
|
+
return doc.body.firstElementChild
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (process.env.NODE_ENV !== "production") console.error(error)
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Usage
|
|
66
|
+
const element = createElement('<div class="safe">Content</div>')
|
|
67
|
+
// Scripts in the string won't execute
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Input Validation
|
|
73
|
+
|
|
74
|
+
### Use the validators Utility
|
|
75
|
+
|
|
76
|
+
Always validate user input before using it:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { validators } from "../Utility/utilities"
|
|
80
|
+
|
|
81
|
+
// ✅ GOOD - Validate everything
|
|
82
|
+
function handlePageChange(pageParam: string) {
|
|
83
|
+
// Validate page is an integer within bounds
|
|
84
|
+
if (!validators.integer(pageParam, 1, lastPage)) {
|
|
85
|
+
console.warn("Invalid page parameter")
|
|
86
|
+
return 1 // Safe fallback
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return parseInt(pageParam, 10)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Validate Numbers
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { validators } from "../Utility/utilities"
|
|
97
|
+
|
|
98
|
+
// ❌ BAD - No validation
|
|
99
|
+
const page = parseInt(userInput) // Could be NaN, Infinity, etc.
|
|
100
|
+
|
|
101
|
+
// ✅ GOOD - Validate first
|
|
102
|
+
if (!validators.integer(userInput, 1, 100)) {
|
|
103
|
+
throw new Error("Page must be between 1 and 100")
|
|
104
|
+
}
|
|
105
|
+
const page = parseInt(userInput, 10)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Validate JSON
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { validators } from "../Utility/utilities"
|
|
112
|
+
|
|
113
|
+
// ❌ BAD - Uncaught parse error
|
|
114
|
+
const data = JSON.parse(localStorage.getItem("data"))
|
|
115
|
+
|
|
116
|
+
// ✅ GOOD - Validate before parsing
|
|
117
|
+
const stored = localStorage.getItem("data")
|
|
118
|
+
if (!validators.json(stored)) {
|
|
119
|
+
return defaultValue // Safe fallback
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const data = JSON.parse(stored)
|
|
124
|
+
return data
|
|
125
|
+
} catch {
|
|
126
|
+
return defaultValue
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## URL Safety
|
|
133
|
+
|
|
134
|
+
### Validate All URLs
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { validators } from "../Utility/utilities"
|
|
138
|
+
|
|
139
|
+
// ❌ BAD - Accepts any URL including javascript:
|
|
140
|
+
window.open(userProvidedUrl)
|
|
141
|
+
|
|
142
|
+
// ✅ GOOD - Validate protocol
|
|
143
|
+
if (!validators.url(userProvidedUrl)) {
|
|
144
|
+
console.error("Invalid URL")
|
|
145
|
+
userProvidedUrl = window.location.href // Safe fallback
|
|
146
|
+
}
|
|
147
|
+
window.open(userProvidedUrl)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### The validators.url Function
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// Only allows http: and https: by default
|
|
154
|
+
validators.url("https://example.com") // true
|
|
155
|
+
validators.url("http://example.com") // true
|
|
156
|
+
validators.url("javascript:alert(1)") // false
|
|
157
|
+
validators.url("data:text/html,...") // false
|
|
158
|
+
validators.url("file:///etc/passwd") // false
|
|
159
|
+
|
|
160
|
+
// Custom protocols if needed
|
|
161
|
+
validators.url("ftp://example.com", ["ftp:", "ftps:"]) // true
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### External Links
|
|
165
|
+
|
|
166
|
+
Always add `rel="noopener noreferrer"` to external links with `target="_blank"`:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
// ❌ BAD - Vulnerable to tabnapping
|
|
170
|
+
<a href="https://external.com" target="_blank">
|
|
171
|
+
Link
|
|
172
|
+
</a>
|
|
173
|
+
|
|
174
|
+
// ✅ GOOD - Secure
|
|
175
|
+
<a href="https://external.com" target="_blank" rel="noopener noreferrer">
|
|
176
|
+
Link
|
|
177
|
+
</a>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Why?** Without `noopener`, the opened page can access `window.opener` and redirect your page to a phishing site.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Data Sanitization
|
|
185
|
+
|
|
186
|
+
### localStorage Security
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
// ⚠️ WARNING: localStorage is NOT secure
|
|
190
|
+
// - Data is stored UNENCRYPTED
|
|
191
|
+
// - Accessible to all scripts on same origin
|
|
192
|
+
// - Persists across sessions
|
|
193
|
+
|
|
194
|
+
// ❌ BAD - Sensitive data
|
|
195
|
+
localStorage.setItem("authToken", token)
|
|
196
|
+
localStorage.setItem("password", password)
|
|
197
|
+
localStorage.setItem("creditCard", cc)
|
|
198
|
+
|
|
199
|
+
// ✅ GOOD - Non-sensitive data only
|
|
200
|
+
localStorage.setItem("theme", "dark")
|
|
201
|
+
localStorage.setItem("language", "en")
|
|
202
|
+
localStorage.setItem("preferences", JSON.stringify(prefs))
|
|
203
|
+
|
|
204
|
+
// For sensitive data, use:
|
|
205
|
+
// - httpOnly cookies (server-side only)
|
|
206
|
+
// - sessionStorage (cleared on close)
|
|
207
|
+
// - Memory-only (JavaScript variables)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Cookie Security
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import Cookies from "js-cookie"
|
|
214
|
+
|
|
215
|
+
// ✅ GOOD - Secure cookie settings
|
|
216
|
+
Cookies.set("session", value, {
|
|
217
|
+
path: "/",
|
|
218
|
+
expires: 7,
|
|
219
|
+
sameSite: "Lax", // CSRF protection
|
|
220
|
+
secure: true, // HTTPS only (production)
|
|
221
|
+
httpOnly: false, // Client-side access needed
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// For authentication tokens:
|
|
225
|
+
// - Use httpOnly: true (JavaScript can't access)
|
|
226
|
+
// - Set by server, not client-side
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Secure Dependencies
|
|
232
|
+
|
|
233
|
+
### Regular Audits
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# Run security audit
|
|
237
|
+
pnpm audit
|
|
238
|
+
|
|
239
|
+
# Fix vulnerabilities
|
|
240
|
+
pnpm audit --fix
|
|
241
|
+
|
|
242
|
+
# Check for outdated packages
|
|
243
|
+
pnpm outdated
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Monitor CVEs
|
|
247
|
+
|
|
248
|
+
The design system uses:
|
|
249
|
+
|
|
250
|
+
- **Oxlint** instead of ESLint (faster, safer)
|
|
251
|
+
- **DOMParser** instead of innerHTML (safer)
|
|
252
|
+
- **Validators utility** instead of heavy validation libs (smaller attack surface)
|
|
253
|
+
- **AbortController** for cleanup (browser standard, no deps)
|
|
254
|
+
|
|
255
|
+
### Keep Dependencies Updated
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Update dependencies
|
|
259
|
+
pnpm update
|
|
260
|
+
|
|
261
|
+
# Update to latest versions
|
|
262
|
+
pnpm update --latest
|
|
263
|
+
|
|
264
|
+
# Check for security advisories
|
|
265
|
+
pnpm audit
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Information Disclosure
|
|
271
|
+
|
|
272
|
+
### Environment-Specific Logging
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// ❌ BAD - Logs sensitive info in production
|
|
276
|
+
console.error("Failed to authenticate:", email, password)
|
|
277
|
+
console.log("Database query:", query)
|
|
278
|
+
console.warn("API key:", apiKey)
|
|
279
|
+
|
|
280
|
+
// ✅ GOOD - Only log in development
|
|
281
|
+
if (process.env.NODE_ENV !== "production") {
|
|
282
|
+
console.error("Failed to authenticate:", email)
|
|
283
|
+
// Don't log password ever!
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// For user-facing errors (OK in production)
|
|
287
|
+
console.error("Could not copy URL to clipboard") // Generic message
|
|
288
|
+
console.error("Invalid email address") // No sensitive data
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Error Messages
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// ❌ BAD - Reveals internal structure
|
|
295
|
+
throw new Error(`User ${userId} not found in database table users_auth`)
|
|
296
|
+
|
|
297
|
+
// ✅ GOOD - Generic public message
|
|
298
|
+
throw new Error("User not found")
|
|
299
|
+
|
|
300
|
+
// Log details only in development
|
|
301
|
+
if (process.env.NODE_ENV !== "production") {
|
|
302
|
+
console.error("Database error:", details)
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Security Checklist
|
|
309
|
+
|
|
310
|
+
Before shipping, verify:
|
|
311
|
+
|
|
312
|
+
- [ ] No `dangerouslySetInnerHTML` usage
|
|
313
|
+
- [ ] No `innerHTML` usage
|
|
314
|
+
- [ ] All URLs validated with `validators.url()`
|
|
315
|
+
- [ ] All numbers validated with `validators.integer()` or `validators.number()`
|
|
316
|
+
- [ ] All JSON validated with `validators.json()` before parsing
|
|
317
|
+
- [ ] External links have `rel="noopener noreferrer"`
|
|
318
|
+
- [ ] No sensitive data in localStorage
|
|
319
|
+
- [ ] Environment-specific logging (NODE_ENV checks)
|
|
320
|
+
- [ ] Generic error messages (no internal details)
|
|
321
|
+
- [ ] Dependencies audited (`pnpm audit`)
|
|
322
|
+
- [ ] No hardcoded credentials
|
|
323
|
+
- [ ] HTTPS used in production
|
|
324
|
+
- [ ] CSRF protection (SameSite cookies)
|
|
325
|
+
- [ ] Input sanitization on all user data
|
|
326
|
+
- [ ] XSS testing done (try `<script>alert(1)</script>`)
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Testing for Vulnerabilities
|
|
331
|
+
|
|
332
|
+
### XSS Testing
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
// Test 1: Try to inject script in all inputs
|
|
336
|
+
const xssPayloads = [
|
|
337
|
+
"<script>alert(1)</script>",
|
|
338
|
+
"<img src=x onerror=alert(1)>",
|
|
339
|
+
"javascript:alert(1)",
|
|
340
|
+
"<svg onload=alert(1)>",
|
|
341
|
+
'"><script>alert(1)</script>',
|
|
342
|
+
]
|
|
343
|
+
|
|
344
|
+
xssPayloads.forEach(payload => {
|
|
345
|
+
// Try in every input field
|
|
346
|
+
// Expected: Should be escaped/rejected
|
|
347
|
+
})
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### URL Injection Testing
|
|
351
|
+
|
|
352
|
+
```javascript
|
|
353
|
+
// Test 2: Try malicious URLs
|
|
354
|
+
const maliciousUrls = [
|
|
355
|
+
"javascript:alert(document.cookie)",
|
|
356
|
+
"data:text/html,<script>alert(1)</script>",
|
|
357
|
+
"file:///etc/passwd",
|
|
358
|
+
"vbscript:msgbox(1)",
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
maliciousUrls.forEach(url => {
|
|
362
|
+
validators.url(url) // Should return false
|
|
363
|
+
})
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Input Validation Testing
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
369
|
+
// Test 3: Try to break validation
|
|
370
|
+
validators.integer("999999999999999", 1, 10) // Should reject
|
|
371
|
+
validators.integer("-1", 1, 10) // Should reject
|
|
372
|
+
validators.integer("5.5", 1, 10) // Should reject (not integer)
|
|
373
|
+
validators.integer("abc", 1, 10) // Should reject
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## OWASP Top 10 Coverage
|
|
379
|
+
|
|
380
|
+
### A03:2021 – Injection
|
|
381
|
+
|
|
382
|
+
- ✅ No innerHTML usage (XSS prevention)
|
|
383
|
+
- ✅ No dangerouslySetInnerHTML (XSS prevention)
|
|
384
|
+
- ✅ DOMParser for safe HTML parsing
|
|
385
|
+
- ✅ Input validation with validators utility
|
|
386
|
+
|
|
387
|
+
### A04:2021 – Insecure Design
|
|
388
|
+
|
|
389
|
+
- ✅ URL protocol validation
|
|
390
|
+
- ✅ Bounds checking on all numbers
|
|
391
|
+
- ✅ Secure-by-default patterns
|
|
392
|
+
|
|
393
|
+
### A05:2021 – Security Misconfiguration
|
|
394
|
+
|
|
395
|
+
- ✅ Environment-specific logging
|
|
396
|
+
- ✅ Generic error messages
|
|
397
|
+
- ✅ Secure cookie defaults (SameSite, Secure)
|
|
398
|
+
|
|
399
|
+
### A06:2021 – Vulnerable Components
|
|
400
|
+
|
|
401
|
+
- ✅ Regular `pnpm audit` runs
|
|
402
|
+
- ✅ Dependencies kept updated
|
|
403
|
+
- ✅ Minimal dependencies (smaller attack surface)
|
|
404
|
+
|
|
405
|
+
### A07:2021 – Identification and Authentication
|
|
406
|
+
|
|
407
|
+
- ✅ No credentials in localStorage
|
|
408
|
+
- ✅ Secure cookie practices
|
|
409
|
+
- ✅ httpOnly recommended for auth tokens
|
|
410
|
+
|
|
411
|
+
### A08:2021 – Software and Data Integrity
|
|
412
|
+
|
|
413
|
+
- ✅ JSON validation before parsing
|
|
414
|
+
- ✅ Try-catch around all parsing
|
|
415
|
+
- ✅ Graceful fallbacks on bad data
|
|
416
|
+
|
|
417
|
+
### A09:2021 – Logging Failures
|
|
418
|
+
|
|
419
|
+
- ✅ Environment-specific logging
|
|
420
|
+
- ✅ No sensitive data in logs
|
|
421
|
+
- ✅ User-facing errors are generic
|
|
422
|
+
|
|
423
|
+
### A10:2021 – Server-Side Request Forgery
|
|
424
|
+
|
|
425
|
+
- ✅ URL validation before requests
|
|
426
|
+
- ✅ Protocol whitelisting
|
|
427
|
+
- ✅ Same-origin checks where applicable
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Resources
|
|
432
|
+
|
|
433
|
+
- [OWASP Top 10](https://owasp.org/Top10/)
|
|
434
|
+
- [OWASP Cheat Sheets](https://cheatsheetseries.owasp.org/)
|
|
435
|
+
- [MDN Security](https://developer.mozilla.org/en-US/docs/Web/Security)
|
|
436
|
+
- [Web.dev Security](https://web.dev/secure/)
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Related Documentation
|
|
441
|
+
|
|
442
|
+
- [Code Quality Guidelines](./CodeQuality.md)
|
|
443
|
+
- [Input Validation (API.md)](./API.md#validators)
|
|
444
|
+
- [Memory Management](./MemoryManagement.md)
|