fieldshield 1.0.0 → 1.1.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/CHANGELOG.md +27 -0
- package/README.md +5 -43
- package/dist/assets/fieldshield.css +1 -1
- package/dist/fieldshield.js +254 -239
- package/dist/fieldshield.umd.cjs +4 -4
- package/package.json +5 -5
- package/dist/assets/fieldshield.worker.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,33 @@ Pattern updates are **minor releases**, not patches. A new pattern could start f
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [1.1.0] — 2026-04-08
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **Worker instantiation** (`useFieldShield.ts`) — replaced `new URL("../workers/fieldshield.worker.ts", import.meta.url)` with a blob URL via Vite's `?worker&inline` import. The previous approach referenced the TypeScript source file which does not exist in the published npm package, causing a runtime worker failure for all npm consumers. The worker is now compiled and inlined into `fieldshield.js` at build time; no separate worker file, no bundler configuration required.
|
|
19
|
+
- **CSS cursor drift** (`fieldshield.css`) — added `letter-spacing: 0`, `word-spacing: 0`, and `font-weight: inherit` to `.fieldshield-mask-layer`, `.fieldshield-real-input`, and `.fieldshield-grow`. Without these, consumer stylesheets that set non-zero letter or word spacing on a parent element would cascade unevenly into both overlay layers, causing the cursor to appear offset from the displayed masked text.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **`CREDIT_CARD` pattern** — broadened Mastercard prefix from `5[1-5]` to `5\d` to cover all IIN ranges; added `6\d{3}` variant for Discover and UnionPay cards. Luhn validation is still recommended post-match in production.
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
|
|
27
|
+
- Updated **Framework compatibility** section — the worker is now bundled inline; no per-bundler configuration (worker-loader, publicPath) is needed for any framework.
|
|
28
|
+
- Updated **CSP section** — `worker-src 'self' blob:` is now **required** (not optional). The `blob:` source is mandatory for the inlined worker to load.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## [1.0.1] — 2026-04-07
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- Corrected repository URL, homepage, and bugs URL in `package.json` — links now point to the correct GitHub repository.
|
|
37
|
+
- Updated `author` field in `package.json`.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
14
41
|
## [1.0.0] — 2026
|
|
15
42
|
|
|
16
43
|
Initial public release.
|
package/README.md
CHANGED
|
@@ -67,53 +67,15 @@ import "fieldshield/dist/assets/fieldshield.css";
|
|
|
67
67
|
|
|
68
68
|
## Framework compatibility
|
|
69
69
|
|
|
70
|
-
FieldShield
|
|
70
|
+
FieldShield's Web Worker is compiled and inlined into the bundle at build time. **No bundler configuration is required** — the worker loads via a blob URL embedded in `fieldshield.js`, so there is no separate worker file to serve or configure.
|
|
71
71
|
|
|
72
|
-
### Vite
|
|
72
|
+
### Vite, Webpack 5, Parcel, esbuild, Rollup
|
|
73
73
|
|
|
74
74
|
Works out of the box. No configuration required.
|
|
75
75
|
|
|
76
|
-
### Webpack 5
|
|
77
|
-
|
|
78
|
-
Works out of the box with Webpack 5's built-in Web Worker support.
|
|
79
|
-
|
|
80
|
-
### Webpack 4
|
|
81
|
-
|
|
82
|
-
Requires `worker-loader`:
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
npm install --save-dev worker-loader
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
```js
|
|
89
|
-
// webpack.config.js
|
|
90
|
-
module.exports = {
|
|
91
|
-
module: {
|
|
92
|
-
rules: [
|
|
93
|
-
{
|
|
94
|
-
test: /\.worker\.ts$/,
|
|
95
|
-
use: { loader: "worker-loader" },
|
|
96
|
-
},
|
|
97
|
-
],
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
```
|
|
101
|
-
|
|
102
76
|
### Next.js
|
|
103
77
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
```js
|
|
107
|
-
// next.config.js
|
|
108
|
-
module.exports = {
|
|
109
|
-
webpack(config) {
|
|
110
|
-
config.output.publicPath = "/_next/";
|
|
111
|
-
return config;
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
If you encounter issues with the worker URL resolution in Next.js, use the `NEXT_PUBLIC_` environment variable pattern to set the base URL explicitly, or open an issue — Next.js worker support is an active area of improvement.
|
|
78
|
+
No webpack configuration needed. The blob URL approach works in Next.js without any changes to `next.config.js`.
|
|
117
79
|
|
|
118
80
|
### Server-Side Rendering (SSR)
|
|
119
81
|
|
|
@@ -828,11 +790,11 @@ FieldShield's worker isolation guarantee can be enforced at the infrastructure l
|
|
|
828
790
|
|
|
829
791
|
```
|
|
830
792
|
Content-Security-Policy:
|
|
831
|
-
worker-src 'self'
|
|
793
|
+
worker-src 'self' blob:;
|
|
832
794
|
script-src 'self';
|
|
833
795
|
```
|
|
834
796
|
|
|
835
|
-
**`worker-src 'self' blob:`** —
|
|
797
|
+
**`worker-src 'self' blob:`** — the `blob:` source is **required**. FieldShield's worker loads via a blob URL embedded in the bundle; without it the worker will be blocked by CSP and the field will fall back to `a11yMode`.
|
|
836
798
|
|
|
837
799
|
**`script-src 'self'`** — restricts all script execution to same-origin. Combined with `worker-src`, this ensures neither the main thread nor the worker can load or execute scripts from external origins.
|
|
838
800
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--fieldshield-font-family: inherit;--fieldshield-font-size: .9375rem;--fieldshield-line-height: 1.5;--fieldshield-padding-y: .5rem;--fieldshield-padding-x: .75rem;--fieldshield-gap: .375rem;--fieldshield-findings-gap: .375rem;--fieldshield-min-height: 2.5rem;--fieldshield-border-radius: .375rem;--fieldshield-tag-border-radius: .25rem;--fieldshield-bg: #ffffff;--fieldshield-border-color: #d1d5db;--fieldshield-border-color-focus: #3b82f6;--fieldshield-text-color: #111827;--fieldshield-placeholder-color: #9ca3af;--fieldshield-label-color: #374151;--fieldshield-caret-color: #111827;--fieldshield-unsafe-border-color: #f59e0b;--fieldshield-unsafe-bg: #fffbeb;--fieldshield-unsafe-focus-ring: #f59e0b;--fieldshield-warning-color: #92400e;--fieldshield-tag-bg: #fde68a;--fieldshield-tag-color: #78350f;--fieldshield-mask-color: #111827;--fieldshield-mask-blocked-color: #b45309;--fieldshield-focus-ring-width: 3px;--fieldshield-focus-ring-offset: 2px;--fieldshield-transition-duration: .15s;--fieldshield-transition-easing: ease-in-out}.fieldshield-container{position:relative;display:flex;flex-direction:column;gap:var(--fieldshield-gap);width:100%;font-family:var(--fieldshield-font-family);font-size:var(--fieldshield-font-size);line-height:var(--fieldshield-line-height)}.fieldshield-label{display:block;color:var(--fieldshield-label-color);font-weight:500;cursor:default;-webkit-user-select:none;user-select:none}.fieldshield-field-wrapper{position:relative;min-height:var(--fieldshield-min-height);border:1px solid var(--fieldshield-border-color);border-radius:var(--fieldshield-border-radius);background-color:var(--fieldshield-bg);transition:border-color var(--fieldshield-transition-duration) var(--fieldshield-transition-easing),background-color var(--fieldshield-transition-duration) var(--fieldshield-transition-easing),box-shadow var(--fieldshield-transition-duration) var(--fieldshield-transition-easing)}.fieldshield-mask-layer{position:absolute;inset:0;display:flex;align-items:center;padding:var(--fieldshield-padding-y) var(--fieldshield-padding-x);color:var(--fieldshield-mask-color);font-family:var(--fieldshield-font-family);font-size:var(--fieldshield-font-size);line-height:var(--fieldshield-line-height);white-space:pre-wrap;word-break:break-all;pointer-events:none;-webkit-user-select:none;user-select:none;overflow:hidden}.fieldshield-field-wrapper:has(textarea) .fieldshield-mask-layer{display:block;align-items:normal}.fieldshield-mask-layer .fieldshield-blocked{color:var(--fieldshield-mask-blocked-color)}.fieldshield-placeholder{color:var(--fieldshield-placeholder-color);font-style:normal}.fieldshield-mask-unsafe{background-color:var(--fieldshield-unsafe-bg)}.fieldshield-grow{visibility:hidden;display:block;width:100%;min-height:var(--fieldshield-min-height);padding:var(--fieldshield-padding-y) var(--fieldshield-padding-x);font-family:var(--fieldshield-font-family);font-size:var(--fieldshield-font-size);line-height:var(--fieldshield-line-height);white-space:pre-wrap;word-break:break-all;pointer-events:none;-webkit-user-select:none;user-select:none;box-sizing:border-box}.fieldshield-field-wrapper:has(textarea){overflow:visible}.fieldshield-real-input{position:absolute;inset:0;width:100%;height:100%;padding:var(--fieldshield-padding-y) var(--fieldshield-padding-x);font-family:var(--fieldshield-font-family);font-size:var(--fieldshield-font-size);line-height:var(--fieldshield-line-height);color:transparent;caret-color:var(--fieldshield-caret-color);background:transparent;border:none;border-radius:var(--fieldshield-border-radius);outline:none;resize:none;cursor:text}.fieldshield-real-input::selection{color:transparent;background-color:color-mix(in srgb,var(--fieldshield-border-color-focus) 30%,transparent)}.fieldshield-a11y-input{display:block;width:100%;min-height:var(--fieldshield-min-height);padding:var(--fieldshield-padding-y) var(--fieldshield-padding-x);font-family:var(--fieldshield-font-family);font-size:var(--fieldshield-font-size);line-height:var(--fieldshield-line-height);color:var(--fieldshield-text-color);background-color:var(--fieldshield-bg);border:1px solid var(--fieldshield-border-color);border-radius:var(--fieldshield-border-radius);outline:none;box-sizing:border-box;transition:border-color var(--fieldshield-transition-duration) var(--fieldshield-transition-easing),box-shadow var(--fieldshield-transition-duration) var(--fieldshield-transition-easing)}.fieldshield-a11y-input::placeholder{color:var(--fieldshield-placeholder-color)}.fieldshield-findings{display:flex;flex-wrap:wrap;align-items:center;gap:var(--fieldshield-findings-gap);min-height:0;font-size:.8125rem;line-height:1.4}.fieldshield-warning-icon{color:var(--fieldshield-warning-color);flex-shrink:0}.fieldshield-warning-text{color:var(--fieldshield-warning-color);font-weight:500}.fieldshield-tag{display:inline-flex;align-items:center;padding:.125rem .4375rem;background-color:var(--fieldshield-tag-bg);color:var(--fieldshield-tag-color);border-radius:var(--fieldshield-tag-border-radius);font-size:.75rem;font-weight:600;letter-spacing:.02em;white-space:nowrap}.fieldshield-sr-only:not(:focus):not(:active){position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);clip-path:inset(50%);white-space:nowrap;border-width:0}.fieldshield-field-wrapper:has(input[aria-invalid=true]),.fieldshield-a11y-input[aria-invalid=true]{border-color:var(--fieldshield-unsafe-border-color);background-color:var(--fieldshield-unsafe-bg)}.fieldshield-field-wrapper:focus-within{border-color:var(--fieldshield-border-color-focus);box-shadow:0 0 0 var(--fieldshield-focus-ring-width) color-mix(in srgb,var(--fieldshield-border-color-focus) 25%,transparent);outline:none}.fieldshield-field-wrapper:has(input[aria-invalid=true]):focus-within{border-color:var(--fieldshield-unsafe-focus-ring);box-shadow:0 0 0 var(--fieldshield-focus-ring-width) color-mix(in srgb,var(--fieldshield-unsafe-focus-ring) 25%,transparent)}.fieldshield-a11y-input:focus{border-color:var(--fieldshield-border-color-focus);box-shadow:0 0 0 var(--fieldshield-focus-ring-width) color-mix(in srgb,var(--fieldshield-border-color-focus) 25%,transparent);outline:none}.fieldshield-a11y-input[aria-invalid=true]:focus{border-color:var(--fieldshield-unsafe-focus-ring);box-shadow:0 0 0 var(--fieldshield-focus-ring-width) color-mix(in srgb,var(--fieldshield-unsafe-focus-ring) 25%,transparent)}@media(prefers-color-scheme:dark){:root{--fieldshield-bg: #1f2937;--fieldshield-border-color: #4b5563;--fieldshield-border-color-focus: #60a5fa;--fieldshield-text-color: #f9fafb;--fieldshield-placeholder-color: #6b7280;--fieldshield-label-color: #d1d5db;--fieldshield-caret-color: #f9fafb;--fieldshield-mask-color: #f9fafb;--fieldshield-unsafe-border-color: #d97706;--fieldshield-unsafe-bg: #1c1508;--fieldshield-unsafe-focus-ring: #d97706;--fieldshield-warning-color: #fbbf24;--fieldshield-tag-bg: #451a03;--fieldshield-tag-color: #fde68a;--fieldshield-mask-blocked-color: #d97706}}@media(forced-colors:active){.fieldshield-field-wrapper,.fieldshield-a11y-input{border:2px solid ButtonText;forced-color-adjust:auto}.fieldshield-field-wrapper:focus-within,.fieldshield-a11y-input:focus{outline:3px solid Highlight;outline-offset:var(--fieldshield-focus-ring-offset);box-shadow:none}.fieldshield-real-input{caret-color:ButtonText}.fieldshield-tag{border:1px solid ButtonText;background-color:Canvas;color:ButtonText}.fieldshield-warning-icon,.fieldshield-warning-text{color:ButtonText}}@media(prefers-reduced-motion:reduce){.fieldshield-field-wrapper,.fieldshield-a11y-input{transition:none}}[data-disabled] .fieldshield-field-wrapper,[data-disabled] .fieldshield-a11y-input{opacity:.5;cursor:not-allowed;pointer-events:none}[data-disabled] .fieldshield-label{opacity:.5;cursor:not-allowed}[data-disabled] .fieldshield-mask-layer{cursor:not-allowed}
|
|
1
|
+
:root{--fieldshield-font-family: inherit;--fieldshield-font-size: .9375rem;--fieldshield-line-height: 1.5;--fieldshield-padding-y: .5rem;--fieldshield-padding-x: .75rem;--fieldshield-gap: .375rem;--fieldshield-findings-gap: .375rem;--fieldshield-min-height: 2.5rem;--fieldshield-border-radius: .375rem;--fieldshield-tag-border-radius: .25rem;--fieldshield-bg: #ffffff;--fieldshield-border-color: #d1d5db;--fieldshield-border-color-focus: #3b82f6;--fieldshield-text-color: #111827;--fieldshield-placeholder-color: #9ca3af;--fieldshield-label-color: #374151;--fieldshield-caret-color: #111827;--fieldshield-unsafe-border-color: #f59e0b;--fieldshield-unsafe-bg: #fffbeb;--fieldshield-unsafe-focus-ring: #f59e0b;--fieldshield-warning-color: #92400e;--fieldshield-tag-bg: #fde68a;--fieldshield-tag-color: #78350f;--fieldshield-mask-color: #111827;--fieldshield-mask-blocked-color: #b45309;--fieldshield-focus-ring-width: 3px;--fieldshield-focus-ring-offset: 2px;--fieldshield-transition-duration: .15s;--fieldshield-transition-easing: ease-in-out}.fieldshield-container{position:relative;display:flex;flex-direction:column;gap:var(--fieldshield-gap);width:100%;font-family:var(--fieldshield-font-family);font-size:var(--fieldshield-font-size);line-height:var(--fieldshield-line-height)}.fieldshield-label{display:block;color:var(--fieldshield-label-color);font-weight:500;cursor:default;-webkit-user-select:none;user-select:none}.fieldshield-field-wrapper{position:relative;min-height:var(--fieldshield-min-height);border:1px solid var(--fieldshield-border-color);border-radius:var(--fieldshield-border-radius);background-color:var(--fieldshield-bg);transition:border-color var(--fieldshield-transition-duration) var(--fieldshield-transition-easing),background-color var(--fieldshield-transition-duration) var(--fieldshield-transition-easing),box-shadow var(--fieldshield-transition-duration) var(--fieldshield-transition-easing)}.fieldshield-mask-layer{position:absolute;inset:0;display:flex;align-items:center;padding:var(--fieldshield-padding-y) var(--fieldshield-padding-x);color:var(--fieldshield-mask-color);font-family:var(--fieldshield-font-family);font-size:var(--fieldshield-font-size);font-weight:inherit;line-height:var(--fieldshield-line-height);letter-spacing:0;word-spacing:0;white-space:pre-wrap;word-break:break-all;pointer-events:none;-webkit-user-select:none;user-select:none;overflow:hidden}.fieldshield-field-wrapper:has(textarea) .fieldshield-mask-layer{display:block;align-items:normal}.fieldshield-mask-layer .fieldshield-blocked{color:var(--fieldshield-mask-blocked-color)}.fieldshield-placeholder{color:var(--fieldshield-placeholder-color);font-style:normal}.fieldshield-mask-unsafe{background-color:var(--fieldshield-unsafe-bg)}.fieldshield-grow{visibility:hidden;display:block;width:100%;min-height:var(--fieldshield-min-height);padding:var(--fieldshield-padding-y) var(--fieldshield-padding-x);font-family:var(--fieldshield-font-family);font-size:var(--fieldshield-font-size);font-weight:inherit;line-height:var(--fieldshield-line-height);letter-spacing:0;word-spacing:0;white-space:pre-wrap;word-break:break-all;pointer-events:none;-webkit-user-select:none;user-select:none;box-sizing:border-box}.fieldshield-field-wrapper:has(textarea){overflow:visible}.fieldshield-real-input{position:absolute;inset:0;width:100%;height:100%;padding:var(--fieldshield-padding-y) var(--fieldshield-padding-x);font-family:var(--fieldshield-font-family);font-size:var(--fieldshield-font-size);font-weight:inherit;line-height:var(--fieldshield-line-height);letter-spacing:0;word-spacing:0;color:transparent;caret-color:var(--fieldshield-caret-color);background:transparent;border:none;border-radius:var(--fieldshield-border-radius);outline:none;resize:none;cursor:text}.fieldshield-real-input::selection{color:transparent;background-color:color-mix(in srgb,var(--fieldshield-border-color-focus) 30%,transparent)}.fieldshield-a11y-input{display:block;width:100%;min-height:var(--fieldshield-min-height);padding:var(--fieldshield-padding-y) var(--fieldshield-padding-x);font-family:var(--fieldshield-font-family);font-size:var(--fieldshield-font-size);line-height:var(--fieldshield-line-height);color:var(--fieldshield-text-color);background-color:var(--fieldshield-bg);border:1px solid var(--fieldshield-border-color);border-radius:var(--fieldshield-border-radius);outline:none;box-sizing:border-box;transition:border-color var(--fieldshield-transition-duration) var(--fieldshield-transition-easing),box-shadow var(--fieldshield-transition-duration) var(--fieldshield-transition-easing)}.fieldshield-a11y-input::placeholder{color:var(--fieldshield-placeholder-color)}.fieldshield-findings{display:flex;flex-wrap:wrap;align-items:center;gap:var(--fieldshield-findings-gap);min-height:0;font-size:.8125rem;line-height:1.4}.fieldshield-warning-icon{color:var(--fieldshield-warning-color);flex-shrink:0}.fieldshield-warning-text{color:var(--fieldshield-warning-color);font-weight:500}.fieldshield-tag{display:inline-flex;align-items:center;padding:.125rem .4375rem;background-color:var(--fieldshield-tag-bg);color:var(--fieldshield-tag-color);border-radius:var(--fieldshield-tag-border-radius);font-size:.75rem;font-weight:600;letter-spacing:.02em;white-space:nowrap}.fieldshield-sr-only:not(:focus):not(:active){position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);clip-path:inset(50%);white-space:nowrap;border-width:0}.fieldshield-field-wrapper:has(input[aria-invalid=true]),.fieldshield-a11y-input[aria-invalid=true]{border-color:var(--fieldshield-unsafe-border-color);background-color:var(--fieldshield-unsafe-bg)}.fieldshield-field-wrapper:focus-within{border-color:var(--fieldshield-border-color-focus);box-shadow:0 0 0 var(--fieldshield-focus-ring-width) color-mix(in srgb,var(--fieldshield-border-color-focus) 25%,transparent);outline:none}.fieldshield-field-wrapper:has(input[aria-invalid=true]):focus-within{border-color:var(--fieldshield-unsafe-focus-ring);box-shadow:0 0 0 var(--fieldshield-focus-ring-width) color-mix(in srgb,var(--fieldshield-unsafe-focus-ring) 25%,transparent)}.fieldshield-a11y-input:focus{border-color:var(--fieldshield-border-color-focus);box-shadow:0 0 0 var(--fieldshield-focus-ring-width) color-mix(in srgb,var(--fieldshield-border-color-focus) 25%,transparent);outline:none}.fieldshield-a11y-input[aria-invalid=true]:focus{border-color:var(--fieldshield-unsafe-focus-ring);box-shadow:0 0 0 var(--fieldshield-focus-ring-width) color-mix(in srgb,var(--fieldshield-unsafe-focus-ring) 25%,transparent)}@media(prefers-color-scheme:dark){:root{--fieldshield-bg: #1f2937;--fieldshield-border-color: #4b5563;--fieldshield-border-color-focus: #60a5fa;--fieldshield-text-color: #f9fafb;--fieldshield-placeholder-color: #6b7280;--fieldshield-label-color: #d1d5db;--fieldshield-caret-color: #f9fafb;--fieldshield-mask-color: #f9fafb;--fieldshield-unsafe-border-color: #d97706;--fieldshield-unsafe-bg: #1c1508;--fieldshield-unsafe-focus-ring: #d97706;--fieldshield-warning-color: #fbbf24;--fieldshield-tag-bg: #451a03;--fieldshield-tag-color: #fde68a;--fieldshield-mask-blocked-color: #d97706}}@media(forced-colors:active){.fieldshield-field-wrapper,.fieldshield-a11y-input{border:2px solid ButtonText;forced-color-adjust:auto}.fieldshield-field-wrapper:focus-within,.fieldshield-a11y-input:focus{outline:3px solid Highlight;outline-offset:var(--fieldshield-focus-ring-offset);box-shadow:none}.fieldshield-real-input{caret-color:ButtonText}.fieldshield-tag{border:1px solid ButtonText;background-color:Canvas;color:ButtonText}.fieldshield-warning-icon,.fieldshield-warning-text{color:ButtonText}}@media(prefers-reduced-motion:reduce){.fieldshield-field-wrapper,.fieldshield-a11y-input{transition:none}}[data-disabled] .fieldshield-field-wrapper,[data-disabled] .fieldshield-a11y-input{opacity:.5;cursor:not-allowed;pointer-events:none}[data-disabled] .fieldshield-label{opacity:.5;cursor:not-allowed}[data-disabled] .fieldshield-mask-layer{cursor:not-allowed}
|
package/dist/fieldshield.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import { useState as
|
|
1
|
+
import { jsxs as O, jsx as o, Fragment as le } from "react/jsx-runtime";
|
|
2
|
+
import { useState as K, useRef as Z, useEffect as ee, useCallback as M, forwardRef as be, useId as me, useImperativeHandle as Se } from "react";
|
|
3
3
|
const te = Object.freeze({
|
|
4
4
|
// ── AI / Cloud credentials ────────────────────────────────────────────────
|
|
5
5
|
/**
|
|
@@ -79,7 +79,7 @@ const te = Object.freeze({
|
|
|
79
79
|
//
|
|
80
80
|
// IBAN was moved to OPT_IN_PATTERNS — see that export for the rationale.
|
|
81
81
|
/**
|
|
82
|
-
* Visa, Mastercard, and American Express — with optional space or hyphen
|
|
82
|
+
* Visa, Mastercard, Discover and American Express — with optional space or hyphen
|
|
83
83
|
* separators between digit groups.
|
|
84
84
|
*
|
|
85
85
|
* Previous pattern required consecutive digits, missing the most common
|
|
@@ -87,7 +87,8 @@ const te = Object.freeze({
|
|
|
87
87
|
*
|
|
88
88
|
* Matches:
|
|
89
89
|
* Visa 16-digit: `4111111111111111` / `4111 1111 1111 1111` / `4111-1111-1111-1111`
|
|
90
|
-
* Mastercard:
|
|
90
|
+
* Mastercard 16-digit: `5500005555555559` / `5500 0055 5555 5559`
|
|
91
|
+
* Discover 16-digit: `6500005555555559` / `6500 0055 5555 5559`
|
|
91
92
|
* Amex 15-digit: `378282246310005` / `3782 822463 10005`
|
|
92
93
|
*
|
|
93
94
|
* Does not run a Luhn checksum — add post-match validation in production
|
|
@@ -95,7 +96,8 @@ const te = Object.freeze({
|
|
|
95
96
|
*/
|
|
96
97
|
CREDIT_CARD: [
|
|
97
98
|
"\\b4\\d{3}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b",
|
|
98
|
-
"\\b5
|
|
99
|
+
"\\b5\\d{3}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b",
|
|
100
|
+
"\\b6\\d{3}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b",
|
|
99
101
|
"\\b3[47]\\d{2}[-\\s]?\\d{6}[-\\s]?\\d{5}\\b"
|
|
100
102
|
].join("|"),
|
|
101
103
|
/**
|
|
@@ -223,7 +225,7 @@ const te = Object.freeze({
|
|
|
223
225
|
* `QQ 12 34 56 A` valid prefix
|
|
224
226
|
*/
|
|
225
227
|
UK_NIN: "\\b[A-CEGHJ-PR-TW-Z][A-CEGHJ-NPR-TW-Z]\\s?\\d{2}\\s?\\d{2}\\s?\\d{2}\\s?[A-D]\\b"
|
|
226
|
-
}),
|
|
228
|
+
}), we = Object.freeze({
|
|
227
229
|
/**
|
|
228
230
|
* International Bank Account Number (IBAN) — with or without spaces.
|
|
229
231
|
*
|
|
@@ -343,33 +345,46 @@ const te = Object.freeze({
|
|
|
343
345
|
* IN: 1 letter + 7 digits e.g. `A1234567`
|
|
344
346
|
*/
|
|
345
347
|
PASSPORT_NUMBER: "\\b[A-Z]{1,2}[0-9]{6,9}\\b"
|
|
346
|
-
}),
|
|
347
|
-
|
|
348
|
+
}), ue = '(function(){"use strict";let l={},c={},n="";const i=(s,e)=>{const t={};for(const[a,o]of Object.entries(s))try{t[a]=new RegExp(o,"gi")}catch{console.warn(`[FieldShield] Skipping invalid ${e} pattern "${a}".`)}return t},d=s=>{let e=s;const t=[],a={...l,...c};for(const[o,r]of Object.entries(a))r.lastIndex=0,r.test(s)&&(t.push(o),r.lastIndex=0,e=e.replace(r,p=>"█".repeat(p.length)));return{masked:e,findings:[...new Set(t)]}};self.onmessage=s=>{const e=s.data;switch(e.type){case"CONFIG":{l=i(e.payload.defaultPatterns,"default"),c=i(e.payload.customPatterns,"custom");break}case"PROCESS":{n=e.payload.text;const{masked:t,findings:a}=d(n);self.postMessage({type:"UPDATE",masked:t,findings:a});break}case"GET_TRUTH":{const t=s.ports[0];t?t.postMessage({text:n}):console.warn("[FieldShield] GET_TRUTH received with no MessagePort — caller will time out. Pass port2 via the transfer array.");break}case"PURGE":{n="",self.postMessage({type:"PURGED"});break}default:{console.warn(`[FieldShield] Worker received unknown message type: "${e.type}"`);break}}}})();\n', ce = typeof self < "u" && self.Blob && new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);", ue], { type: "text/javascript;charset=utf-8" });
|
|
349
|
+
function ve(n) {
|
|
350
|
+
let t;
|
|
351
|
+
try {
|
|
352
|
+
if (t = ce && (self.URL || self.webkitURL).createObjectURL(ce), !t) throw "";
|
|
353
|
+
const d = new Worker(t, {
|
|
354
|
+
name: n?.name
|
|
355
|
+
});
|
|
356
|
+
return d.addEventListener("error", () => {
|
|
357
|
+
(self.URL || self.webkitURL).revokeObjectURL(t);
|
|
358
|
+
}), d;
|
|
359
|
+
} catch {
|
|
360
|
+
return new Worker(
|
|
361
|
+
"data:text/javascript;charset=utf-8," + encodeURIComponent(ue),
|
|
362
|
+
{
|
|
363
|
+
name: n?.name
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const oe = 3e3, Ee = 1e5, de = (n) => Object.fromEntries(n.map((t) => [t.name, t.regex])), ke = (n = [], t = Ee, d, g) => {
|
|
369
|
+
const [E, b] = K(""), [$, D] = K([]), [C, v] = K(!1), r = Z(null), I = JSON.stringify(n);
|
|
348
370
|
ee(() => {
|
|
349
371
|
let m = !1;
|
|
350
372
|
try {
|
|
351
|
-
|
|
352
|
-
new URL(
|
|
353
|
-
/* @vite-ignore */
|
|
354
|
-
"/assets/fieldshield.worker.js",
|
|
355
|
-
import.meta.url
|
|
356
|
-
),
|
|
357
|
-
{ type: "module" }
|
|
358
|
-
);
|
|
373
|
+
r.current = new ve();
|
|
359
374
|
} catch (l) {
|
|
360
375
|
console.error(
|
|
361
376
|
"[FieldShield] Worker failed to initialize — falling back to a11yMode.",
|
|
362
377
|
l
|
|
363
|
-
), queueMicrotask(() =>
|
|
378
|
+
), queueMicrotask(() => v(!0));
|
|
364
379
|
return;
|
|
365
380
|
}
|
|
366
|
-
return
|
|
367
|
-
m || l.data?.type === "UPDATE" && typeof l.data.masked == "string" && Array.isArray(l.data.findings) && (
|
|
368
|
-
},
|
|
381
|
+
return r.current.onmessage = (l) => {
|
|
382
|
+
m || l.data?.type === "UPDATE" && typeof l.data.masked == "string" && Array.isArray(l.data.findings) && (b(l.data.masked), D(l.data.findings));
|
|
383
|
+
}, r.current.onerror = (l) => {
|
|
369
384
|
m || (console.error(
|
|
370
385
|
`[FieldShield] Worker runtime error: ${l.message ?? "unknown error"}`
|
|
371
|
-
),
|
|
372
|
-
},
|
|
386
|
+
), b(""), D([]), g?.(l));
|
|
387
|
+
}, r.current.postMessage({
|
|
373
388
|
type: "CONFIG",
|
|
374
389
|
payload: {
|
|
375
390
|
defaultPatterns: te,
|
|
@@ -377,239 +392,239 @@ const te = Object.freeze({
|
|
|
377
392
|
// Effect 2 delivers custom patterns after mount
|
|
378
393
|
}
|
|
379
394
|
}), () => {
|
|
380
|
-
m = !0,
|
|
395
|
+
m = !0, r.current?.terminate(), r.current = null;
|
|
381
396
|
};
|
|
382
397
|
}, []), ee(() => {
|
|
383
|
-
|
|
398
|
+
r.current && r.current.postMessage({
|
|
384
399
|
type: "CONFIG",
|
|
385
400
|
payload: {
|
|
386
401
|
defaultPatterns: te,
|
|
387
|
-
customPatterns: de(
|
|
402
|
+
customPatterns: de(n)
|
|
388
403
|
}
|
|
389
404
|
});
|
|
390
405
|
}, [I]);
|
|
391
|
-
const
|
|
392
|
-
(m) => m.length >
|
|
393
|
-
`[FieldShield] Input length ${m.length} exceeds maxProcessLength (${
|
|
394
|
-
),
|
|
406
|
+
const _ = M(
|
|
407
|
+
(m) => m.length > t ? (console.warn(
|
|
408
|
+
`[FieldShield] Input length ${m.length} exceeds maxProcessLength (${t}). Keystroke blocked to prevent unprotected data beyond the limit. Raise maxProcessLength if this field requires longer input.`
|
|
409
|
+
), d?.(m.length, t), !1) : (r.current?.postMessage({
|
|
395
410
|
type: "PROCESS",
|
|
396
411
|
payload: { text: m }
|
|
397
412
|
}), !0),
|
|
398
|
-
[
|
|
399
|
-
),
|
|
400
|
-
if (!
|
|
413
|
+
[t, d]
|
|
414
|
+
), R = M(() => new Promise((m, l) => {
|
|
415
|
+
if (!r.current) {
|
|
401
416
|
m("");
|
|
402
417
|
return;
|
|
403
418
|
}
|
|
404
|
-
const { port1:
|
|
405
|
-
|
|
419
|
+
const { port1: w, port2: H } = new MessageChannel(), W = setTimeout(() => {
|
|
420
|
+
w.close(), l(
|
|
406
421
|
new Error(
|
|
407
|
-
`[FieldShield] getSecureValue timed out after ${
|
|
422
|
+
`[FieldShield] getSecureValue timed out after ${oe}ms.`
|
|
408
423
|
)
|
|
409
424
|
);
|
|
410
|
-
},
|
|
411
|
-
|
|
412
|
-
clearTimeout(
|
|
413
|
-
},
|
|
414
|
-
}), []),
|
|
415
|
-
|
|
425
|
+
}, oe);
|
|
426
|
+
w.onmessage = (V) => {
|
|
427
|
+
clearTimeout(W), w.close(), m(V.data.text);
|
|
428
|
+
}, r.current.postMessage({ type: "GET_TRUTH" }, [H]);
|
|
429
|
+
}), []), L = M(() => {
|
|
430
|
+
r.current?.postMessage({ type: "PURGE" });
|
|
416
431
|
}, []);
|
|
417
|
-
return { masked:
|
|
418
|
-
},
|
|
432
|
+
return { masked: E, findings: $, processText: _, getSecureValue: R, purge: L, workerFailed: C };
|
|
433
|
+
}, ye = be(
|
|
419
434
|
({
|
|
420
|
-
label:
|
|
421
|
-
type:
|
|
422
|
-
placeholder:
|
|
423
|
-
customPatterns:
|
|
424
|
-
className:
|
|
425
|
-
style:
|
|
435
|
+
label: n,
|
|
436
|
+
type: t = "text",
|
|
437
|
+
placeholder: d,
|
|
438
|
+
customPatterns: g = [],
|
|
439
|
+
className: E,
|
|
440
|
+
style: b,
|
|
426
441
|
onChange: $,
|
|
427
|
-
a11yMode:
|
|
428
|
-
onSensitiveCopyAttempt:
|
|
429
|
-
onSensitivePaste:
|
|
430
|
-
onFocus:
|
|
442
|
+
a11yMode: D = !1,
|
|
443
|
+
onSensitiveCopyAttempt: C,
|
|
444
|
+
onSensitivePaste: v,
|
|
445
|
+
onFocus: r,
|
|
431
446
|
onBlur: I,
|
|
432
|
-
disabled:
|
|
433
|
-
required:
|
|
434
|
-
maxLength:
|
|
447
|
+
disabled: _ = !1,
|
|
448
|
+
required: R = !1,
|
|
449
|
+
maxLength: L,
|
|
435
450
|
rows: m = 3,
|
|
436
451
|
inputMode: l = "text",
|
|
437
|
-
maxProcessLength:
|
|
438
|
-
onMaxLengthExceeded:
|
|
439
|
-
onWorkerError:
|
|
440
|
-
},
|
|
452
|
+
maxProcessLength: w = 1e5,
|
|
453
|
+
onMaxLengthExceeded: H,
|
|
454
|
+
onWorkerError: W
|
|
455
|
+
}, V) => {
|
|
441
456
|
const {
|
|
442
|
-
masked:
|
|
443
|
-
findings:
|
|
444
|
-
processText:
|
|
445
|
-
getSecureValue:
|
|
446
|
-
purge:
|
|
447
|
-
workerFailed:
|
|
448
|
-
} =
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
),
|
|
454
|
-
|
|
455
|
-
|
|
457
|
+
masked: B,
|
|
458
|
+
findings: U,
|
|
459
|
+
processText: j,
|
|
460
|
+
getSecureValue: se,
|
|
461
|
+
purge: ae,
|
|
462
|
+
workerFailed: fe
|
|
463
|
+
} = ke(
|
|
464
|
+
g,
|
|
465
|
+
w,
|
|
466
|
+
H,
|
|
467
|
+
W
|
|
468
|
+
), pe = D || fe, u = U.length > 0, P = n ?? "Protected field", J = Z(null), ne = Z(null), z = Z(null), f = Z(""), A = me(), T = `${A}-warning`, G = `${A}-desc`;
|
|
469
|
+
Se(
|
|
470
|
+
V,
|
|
456
471
|
() => ({
|
|
457
|
-
getSecureValue:
|
|
472
|
+
getSecureValue: se,
|
|
458
473
|
purge: () => {
|
|
459
|
-
|
|
474
|
+
ae(), j(""), f.current = "";
|
|
460
475
|
const e = J.current ?? ne.current;
|
|
461
|
-
e && (e.value = "",
|
|
476
|
+
e && (e.value = "", z.current && (z.current.textContent = `
|
|
462
477
|
`));
|
|
463
478
|
}
|
|
464
479
|
}),
|
|
465
|
-
[
|
|
480
|
+
[se, ae, j]
|
|
466
481
|
), ee(() => {
|
|
467
|
-
$?.(
|
|
468
|
-
}, [
|
|
469
|
-
const Y = (e,
|
|
470
|
-
if (!
|
|
471
|
-
const
|
|
472
|
-
e.value =
|
|
473
|
-
const
|
|
474
|
-
e.setSelectionRange(
|
|
482
|
+
$?.(B, U);
|
|
483
|
+
}, [B, U, $]);
|
|
484
|
+
const Y = (e, s, a) => {
|
|
485
|
+
if (!j(s)) {
|
|
486
|
+
const S = f.current.replace(/[^\n]/g, "x");
|
|
487
|
+
e.value = S;
|
|
488
|
+
const h = Math.min(a, f.current.length);
|
|
489
|
+
e.setSelectionRange(h, h);
|
|
475
490
|
return;
|
|
476
491
|
}
|
|
477
|
-
|
|
478
|
-
const
|
|
479
|
-
e.value =
|
|
492
|
+
f.current = s;
|
|
493
|
+
const i = s.replace(/[^\n]/g, "x");
|
|
494
|
+
e.value = i, e.setSelectionRange(a, a), z.current && (z.current.textContent = i + `
|
|
480
495
|
`);
|
|
481
496
|
}, re = (e) => {
|
|
482
|
-
const
|
|
483
|
-
let
|
|
484
|
-
if (
|
|
485
|
-
const c = Math.max(0, p -
|
|
486
|
-
|
|
487
|
-
} else if (
|
|
488
|
-
const c = p,
|
|
489
|
-
|
|
497
|
+
const s = e.target, a = s.value, p = s.selectionStart ?? a.length, i = f.current, S = a.length - i.length;
|
|
498
|
+
let h;
|
|
499
|
+
if (S > 0) {
|
|
500
|
+
const c = Math.max(0, p - S), k = a.slice(c, p);
|
|
501
|
+
h = i.slice(0, c) + k + i.slice(c);
|
|
502
|
+
} else if (S < 0) {
|
|
503
|
+
const c = p, k = c - S;
|
|
504
|
+
h = i.slice(0, c) + i.slice(k);
|
|
490
505
|
} else {
|
|
491
|
-
let c = -1,
|
|
506
|
+
let c = -1, k = -1;
|
|
492
507
|
for (let y = 0; y < a.length; y++)
|
|
493
508
|
a[y] !== "x" && a[y] !== `
|
|
494
|
-
` && (c === -1 && (c = y),
|
|
509
|
+
` && (c === -1 && (c = y), k = y + 1);
|
|
495
510
|
if (c !== -1) {
|
|
496
|
-
const y = a.slice(c,
|
|
497
|
-
|
|
511
|
+
const y = a.slice(c, k);
|
|
512
|
+
h = i.slice(0, c) + y + i.slice(k);
|
|
498
513
|
} else
|
|
499
|
-
|
|
514
|
+
h = i;
|
|
500
515
|
}
|
|
501
|
-
Y(
|
|
502
|
-
},
|
|
503
|
-
const
|
|
504
|
-
|
|
516
|
+
Y(s, h, p);
|
|
517
|
+
}, he = (e) => {
|
|
518
|
+
const s = e.target.value;
|
|
519
|
+
f.current = s, j(s);
|
|
505
520
|
}, X = (e) => {
|
|
506
521
|
e.preventDefault();
|
|
507
|
-
const
|
|
508
|
-
if (
|
|
522
|
+
const s = e.clipboardData.getData("text/plain"), a = e.target, p = a.selectionStart ?? 0, i = a.selectionEnd ?? 0, S = f.current, h = S.length - (i - p) + s.length;
|
|
523
|
+
if (h > w) {
|
|
509
524
|
console.warn(
|
|
510
|
-
`[FieldShield] Paste blocked — result length ${
|
|
511
|
-
),
|
|
525
|
+
`[FieldShield] Paste blocked — result length ${h} would exceed maxProcessLength (${w}). DOM unchanged.`
|
|
526
|
+
), H?.(h, w);
|
|
512
527
|
return;
|
|
513
528
|
}
|
|
514
|
-
const c =
|
|
515
|
-
if (Y(a, c,
|
|
529
|
+
const c = S.slice(0, p) + s + S.slice(i), k = p + s.length;
|
|
530
|
+
if (Y(a, c, k), v && s) {
|
|
516
531
|
const y = [
|
|
517
532
|
...Object.entries(te),
|
|
518
|
-
...
|
|
533
|
+
...g.map((N) => [N.name, N.regex])
|
|
519
534
|
], ie = [];
|
|
520
|
-
for (const [
|
|
535
|
+
for (const [N, F] of y)
|
|
521
536
|
try {
|
|
522
|
-
ie.push([
|
|
537
|
+
ie.push([N, new RegExp(F, "gi")]);
|
|
523
538
|
} catch {
|
|
524
539
|
}
|
|
525
540
|
const q = [];
|
|
526
|
-
let Q =
|
|
527
|
-
for (const [
|
|
528
|
-
F.lastIndex = 0, F.test(
|
|
541
|
+
let Q = s;
|
|
542
|
+
for (const [N, F] of ie)
|
|
543
|
+
F.lastIndex = 0, F.test(s) && (q.push(N), F.lastIndex = 0, Q = Q.replace(
|
|
529
544
|
F,
|
|
530
|
-
(
|
|
545
|
+
(ge) => "█".repeat(ge.length)
|
|
531
546
|
));
|
|
532
|
-
q.length > 0 &&
|
|
547
|
+
q.length > 0 && v({
|
|
533
548
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
534
|
-
fieldLabel:
|
|
549
|
+
fieldLabel: P,
|
|
535
550
|
findings: [...new Set(q)],
|
|
536
551
|
masked: Q,
|
|
537
552
|
eventType: "paste"
|
|
538
|
-
}) === !1 && Y(a,
|
|
553
|
+
}) === !1 && Y(a, S, p);
|
|
539
554
|
}
|
|
540
555
|
}, x = (e) => {
|
|
541
556
|
e.preventDefault();
|
|
542
|
-
const
|
|
543
|
-
if (
|
|
557
|
+
const s = e.target, a = s.selectionStart ?? 0, p = s.selectionEnd ?? f.current.length, i = B.slice(a, p), S = i.includes("█");
|
|
558
|
+
if (u && S ? (e.clipboardData.setData("text/plain", i), C?.({
|
|
544
559
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
545
|
-
fieldLabel:
|
|
546
|
-
findings: [...
|
|
560
|
+
fieldLabel: P,
|
|
561
|
+
findings: [...U],
|
|
547
562
|
// snapshot — receiver must not hold a reference to internal state
|
|
548
|
-
masked:
|
|
563
|
+
masked: i,
|
|
549
564
|
eventType: e.type === "cut" ? "cut" : "copy"
|
|
550
565
|
})) : e.clipboardData.setData(
|
|
551
566
|
"text/plain",
|
|
552
|
-
|
|
567
|
+
f.current.slice(a, p)
|
|
553
568
|
), e.type === "cut") {
|
|
554
|
-
const
|
|
555
|
-
|
|
556
|
-
const
|
|
557
|
-
|
|
569
|
+
const h = f.current.slice(0, a), c = f.current.slice(p);
|
|
570
|
+
f.current = h + c, j(f.current);
|
|
571
|
+
const k = "x".repeat(f.current.length);
|
|
572
|
+
s.value = k, s.setSelectionRange(a, a);
|
|
558
573
|
}
|
|
559
574
|
};
|
|
560
|
-
return
|
|
575
|
+
return pe ? /* @__PURE__ */ O(
|
|
561
576
|
"div",
|
|
562
577
|
{
|
|
563
|
-
className: `fieldshield-container${
|
|
564
|
-
style:
|
|
578
|
+
className: `fieldshield-container${E ? ` ${E}` : ""}`,
|
|
579
|
+
style: b,
|
|
565
580
|
role: "group",
|
|
566
|
-
"aria-labelledby":
|
|
567
|
-
"data-disabled":
|
|
581
|
+
"aria-labelledby": A,
|
|
582
|
+
"data-disabled": _ || void 0,
|
|
568
583
|
children: [
|
|
569
|
-
|
|
570
|
-
/* @__PURE__ */
|
|
571
|
-
/* @__PURE__ */
|
|
584
|
+
n && /* @__PURE__ */ o("label", { htmlFor: A, className: "fieldshield-label", children: n }),
|
|
585
|
+
/* @__PURE__ */ o("span", { id: G, className: "fieldshield-sr-only", children: "This field is protected. Sensitive data patterns will be detected and blocked from copying." }),
|
|
586
|
+
/* @__PURE__ */ o(
|
|
572
587
|
"input",
|
|
573
588
|
{
|
|
574
|
-
id:
|
|
589
|
+
id: A,
|
|
575
590
|
ref: J,
|
|
576
591
|
type: "password",
|
|
577
592
|
className: "fieldshield-a11y-input",
|
|
578
|
-
placeholder:
|
|
579
|
-
onChange:
|
|
593
|
+
placeholder: d,
|
|
594
|
+
onChange: he,
|
|
580
595
|
onCopy: x,
|
|
581
596
|
onCut: x,
|
|
582
597
|
onPaste: X,
|
|
583
|
-
onFocus:
|
|
598
|
+
onFocus: r,
|
|
584
599
|
onBlur: I,
|
|
585
|
-
disabled:
|
|
586
|
-
required:
|
|
587
|
-
maxLength:
|
|
600
|
+
disabled: _,
|
|
601
|
+
required: R,
|
|
602
|
+
maxLength: L,
|
|
588
603
|
inputMode: l,
|
|
589
604
|
spellCheck: !1,
|
|
590
605
|
autoComplete: "off",
|
|
591
|
-
"aria-required":
|
|
592
|
-
"aria-label":
|
|
593
|
-
"aria-describedby": `${
|
|
594
|
-
"aria-invalid":
|
|
595
|
-
"aria-errormessage":
|
|
606
|
+
"aria-required": R,
|
|
607
|
+
"aria-label": n ? void 0 : P,
|
|
608
|
+
"aria-describedby": `${G} ${u ? T : ""}`.trim(),
|
|
609
|
+
"aria-invalid": u ? "true" : "false",
|
|
610
|
+
"aria-errormessage": u ? T : void 0
|
|
596
611
|
}
|
|
597
612
|
),
|
|
598
|
-
/* @__PURE__ */
|
|
613
|
+
/* @__PURE__ */ o(
|
|
599
614
|
"div",
|
|
600
615
|
{
|
|
601
|
-
id:
|
|
616
|
+
id: T,
|
|
602
617
|
role: "status",
|
|
603
618
|
"aria-live": "polite",
|
|
604
619
|
"aria-atomic": "true",
|
|
605
620
|
className: "fieldshield-findings",
|
|
606
|
-
children:
|
|
607
|
-
/* @__PURE__ */
|
|
608
|
-
/* @__PURE__ */
|
|
621
|
+
children: u && /* @__PURE__ */ O(le, { children: [
|
|
622
|
+
/* @__PURE__ */ o("span", { className: "fieldshield-warning-icon", "aria-hidden": "true", children: "⚠" }),
|
|
623
|
+
/* @__PURE__ */ O("span", { className: "fieldshield-warning-text", children: [
|
|
609
624
|
"Sensitive data detected. Clipboard blocked for:",
|
|
610
625
|
" "
|
|
611
626
|
] }),
|
|
612
|
-
|
|
627
|
+
U.map((e) => /* @__PURE__ */ o(
|
|
613
628
|
"span",
|
|
614
629
|
{
|
|
615
630
|
className: "fieldshield-tag",
|
|
@@ -623,103 +638,103 @@ const te = Object.freeze({
|
|
|
623
638
|
)
|
|
624
639
|
]
|
|
625
640
|
}
|
|
626
|
-
) : /* @__PURE__ */
|
|
641
|
+
) : /* @__PURE__ */ O(
|
|
627
642
|
"div",
|
|
628
643
|
{
|
|
629
|
-
className: `fieldshield-container${
|
|
630
|
-
style:
|
|
644
|
+
className: `fieldshield-container${E ? ` ${E}` : ""}`,
|
|
645
|
+
style: b,
|
|
631
646
|
role: "group",
|
|
632
|
-
"aria-labelledby":
|
|
633
|
-
"data-disabled":
|
|
647
|
+
"aria-labelledby": A,
|
|
648
|
+
"data-disabled": _ || void 0,
|
|
634
649
|
children: [
|
|
635
|
-
|
|
636
|
-
/* @__PURE__ */
|
|
637
|
-
/* @__PURE__ */
|
|
638
|
-
/* @__PURE__ */
|
|
650
|
+
n && /* @__PURE__ */ o("label", { htmlFor: A, className: "fieldshield-label", children: n }),
|
|
651
|
+
/* @__PURE__ */ o("span", { id: G, className: "fieldshield-sr-only", children: "Sensitive field. Input is protected. Sensitive data patterns will be detected and blocked from copying." }),
|
|
652
|
+
/* @__PURE__ */ O("div", { className: "fieldshield-field-wrapper", children: [
|
|
653
|
+
/* @__PURE__ */ o(
|
|
639
654
|
"div",
|
|
640
655
|
{
|
|
641
|
-
className: `fieldshield-mask-layer${
|
|
656
|
+
className: `fieldshield-mask-layer${u ? " fieldshield-mask-unsafe" : ""}`,
|
|
642
657
|
"aria-hidden": "true",
|
|
643
|
-
children:
|
|
658
|
+
children: B || /* @__PURE__ */ o("span", { className: "fieldshield-placeholder", children: d })
|
|
644
659
|
}
|
|
645
660
|
),
|
|
646
|
-
|
|
661
|
+
t === "textarea" && /* @__PURE__ */ o(
|
|
647
662
|
"div",
|
|
648
663
|
{
|
|
649
|
-
ref:
|
|
664
|
+
ref: z,
|
|
650
665
|
className: "fieldshield-grow",
|
|
651
666
|
"aria-hidden": "true"
|
|
652
667
|
}
|
|
653
668
|
),
|
|
654
|
-
|
|
669
|
+
t === "textarea" ? /* @__PURE__ */ o(
|
|
655
670
|
"textarea",
|
|
656
671
|
{
|
|
657
672
|
ref: ne,
|
|
658
|
-
id:
|
|
673
|
+
id: A,
|
|
659
674
|
className: "fieldshield-real-input",
|
|
660
|
-
placeholder:
|
|
675
|
+
placeholder: d,
|
|
661
676
|
onChange: re,
|
|
662
677
|
onPaste: X,
|
|
663
678
|
onCopy: x,
|
|
664
679
|
onCut: x,
|
|
665
|
-
onFocus:
|
|
680
|
+
onFocus: r,
|
|
666
681
|
onBlur: I,
|
|
667
|
-
disabled:
|
|
668
|
-
required:
|
|
669
|
-
maxLength:
|
|
682
|
+
disabled: _,
|
|
683
|
+
required: R,
|
|
684
|
+
maxLength: L,
|
|
670
685
|
rows: m,
|
|
671
686
|
inputMode: l,
|
|
672
687
|
spellCheck: !1,
|
|
673
688
|
autoComplete: "off",
|
|
674
|
-
"aria-required":
|
|
675
|
-
"aria-label":
|
|
676
|
-
"aria-describedby": `${
|
|
677
|
-
"aria-invalid":
|
|
678
|
-
"aria-errormessage":
|
|
689
|
+
"aria-required": R,
|
|
690
|
+
"aria-label": u ? `${P} — sensitive data detected` : `${P} — protected input`,
|
|
691
|
+
"aria-describedby": `${G} ${u ? T : ""}`.trim(),
|
|
692
|
+
"aria-invalid": u ? "true" : "false",
|
|
693
|
+
"aria-errormessage": u ? T : void 0
|
|
679
694
|
}
|
|
680
|
-
) : /* @__PURE__ */
|
|
695
|
+
) : /* @__PURE__ */ o(
|
|
681
696
|
"input",
|
|
682
697
|
{
|
|
683
698
|
ref: J,
|
|
684
|
-
id:
|
|
699
|
+
id: A,
|
|
685
700
|
type: "text",
|
|
686
701
|
className: "fieldshield-real-input",
|
|
687
|
-
placeholder:
|
|
702
|
+
placeholder: d,
|
|
688
703
|
onChange: re,
|
|
689
704
|
onPaste: X,
|
|
690
705
|
onCopy: x,
|
|
691
706
|
onCut: x,
|
|
692
|
-
onFocus:
|
|
707
|
+
onFocus: r,
|
|
693
708
|
onBlur: I,
|
|
694
|
-
disabled:
|
|
695
|
-
required:
|
|
696
|
-
maxLength:
|
|
709
|
+
disabled: _,
|
|
710
|
+
required: R,
|
|
711
|
+
maxLength: L,
|
|
697
712
|
inputMode: l,
|
|
698
713
|
spellCheck: !1,
|
|
699
714
|
autoComplete: "off",
|
|
700
|
-
"aria-required":
|
|
701
|
-
"aria-label":
|
|
702
|
-
"aria-describedby": `${
|
|
703
|
-
"aria-invalid":
|
|
704
|
-
"aria-errormessage":
|
|
715
|
+
"aria-required": R,
|
|
716
|
+
"aria-label": u ? `${P} — sensitive data detected` : `${P} — protected input`,
|
|
717
|
+
"aria-describedby": `${G} ${u ? T : ""}`.trim(),
|
|
718
|
+
"aria-invalid": u ? "true" : "false",
|
|
719
|
+
"aria-errormessage": u ? T : void 0
|
|
705
720
|
}
|
|
706
721
|
)
|
|
707
722
|
] }),
|
|
708
|
-
/* @__PURE__ */
|
|
723
|
+
/* @__PURE__ */ o(
|
|
709
724
|
"div",
|
|
710
725
|
{
|
|
711
|
-
id:
|
|
726
|
+
id: T,
|
|
712
727
|
role: "status",
|
|
713
728
|
"aria-live": "polite",
|
|
714
729
|
"aria-atomic": "true",
|
|
715
730
|
className: "fieldshield-findings",
|
|
716
|
-
children:
|
|
717
|
-
/* @__PURE__ */
|
|
718
|
-
/* @__PURE__ */
|
|
731
|
+
children: u && /* @__PURE__ */ O(le, { children: [
|
|
732
|
+
/* @__PURE__ */ o("span", { className: "fieldshield-warning-icon", "aria-hidden": "true", children: "⚠" }),
|
|
733
|
+
/* @__PURE__ */ O("span", { className: "fieldshield-warning-text", children: [
|
|
719
734
|
"Sensitive data detected. Clipboard blocked for:",
|
|
720
735
|
" "
|
|
721
736
|
] }),
|
|
722
|
-
|
|
737
|
+
U.map((e) => /* @__PURE__ */ o(
|
|
723
738
|
"span",
|
|
724
739
|
{
|
|
725
740
|
className: "fieldshield-tag",
|
|
@@ -736,55 +751,55 @@ const te = Object.freeze({
|
|
|
736
751
|
);
|
|
737
752
|
}
|
|
738
753
|
);
|
|
739
|
-
|
|
740
|
-
const
|
|
741
|
-
const { maxEvents:
|
|
742
|
-
(
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
(I) => [{ ...
|
|
754
|
+
ye.displayName = "FieldShieldInput";
|
|
755
|
+
const Te = (n = {}) => {
|
|
756
|
+
const { maxEvents: t = 20 } = n, [d, g] = K([]), E = Z(0), b = M(
|
|
757
|
+
(C) => {
|
|
758
|
+
const v = ++E.current, r = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
759
|
+
g(
|
|
760
|
+
(I) => [{ ...C, id: v, timestamp: r }, ...I].slice(0, t)
|
|
746
761
|
);
|
|
747
762
|
},
|
|
748
|
-
[
|
|
749
|
-
), $ =
|
|
750
|
-
(
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
field:
|
|
754
|
-
type:
|
|
755
|
-
findings:
|
|
756
|
-
detail: `${
|
|
763
|
+
[t]
|
|
764
|
+
), $ = M(
|
|
765
|
+
(C) => (v) => {
|
|
766
|
+
const r = C === "paste" ? "PASTE_DETECTED" : v.eventType === "cut" ? "CUT_BLOCKED" : "COPY_BLOCKED";
|
|
767
|
+
b({
|
|
768
|
+
field: v.fieldLabel,
|
|
769
|
+
type: r,
|
|
770
|
+
findings: v.findings,
|
|
771
|
+
detail: `${v.masked.slice(0, 32)}${v.masked.length > 32 ? "…" : ""}`
|
|
757
772
|
});
|
|
758
773
|
},
|
|
759
|
-
[
|
|
760
|
-
),
|
|
761
|
-
|
|
774
|
+
[b]
|
|
775
|
+
), D = M(() => {
|
|
776
|
+
g([]), E.current = 0;
|
|
762
777
|
}, []);
|
|
763
|
-
return { events:
|
|
764
|
-
},
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
([,
|
|
778
|
+
return { events: d, pushEvent: b, makeClipboardHandler: $, clearLog: D };
|
|
779
|
+
}, Ce = async (n) => {
|
|
780
|
+
const t = Object.entries(n), d = await Promise.allSettled(
|
|
781
|
+
t.map(
|
|
782
|
+
([, g]) => g.current?.getSecureValue() ?? Promise.resolve("")
|
|
768
783
|
)
|
|
769
784
|
);
|
|
770
785
|
return Object.fromEntries(
|
|
771
|
-
|
|
772
|
-
const
|
|
773
|
-
return
|
|
774
|
-
`[FieldShield] collectSecureValues: field "${String(
|
|
775
|
-
|
|
776
|
-
), [
|
|
786
|
+
t.map(([g], E) => {
|
|
787
|
+
const b = d[E];
|
|
788
|
+
return b.status === "rejected" ? (console.warn(
|
|
789
|
+
`[FieldShield] collectSecureValues: field "${String(g)}" failed to retrieve value.`,
|
|
790
|
+
b.reason
|
|
791
|
+
), [g, ""]) : [g, b.value];
|
|
777
792
|
})
|
|
778
793
|
);
|
|
779
|
-
},
|
|
780
|
-
Object.values(
|
|
794
|
+
}, Ie = (n) => {
|
|
795
|
+
Object.values(n).forEach((t) => t.current?.purge());
|
|
781
796
|
};
|
|
782
797
|
export {
|
|
783
798
|
te as FIELDSHIELD_PATTERNS,
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
799
|
+
ye as FieldShieldInput,
|
|
800
|
+
we as OPT_IN_PATTERNS,
|
|
801
|
+
Ce as collectSecureValues,
|
|
802
|
+
Ie as purgeSecureValues,
|
|
803
|
+
ke as useFieldShield,
|
|
804
|
+
Te as useSecurityLog
|
|
790
805
|
};
|
package/dist/fieldshield.umd.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
2
|
-
`))}}),[
|
|
3
|
-
`)},
|
|
4
|
-
`&&(o===-1&&(o=
|
|
1
|
+
(function(p,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],t):(p=typeof globalThis<"u"?globalThis:p||self,t(p.FieldShield={},p.ReactJSXRuntime,p.React))})(this,(function(p,t,a){"use strict";const z=Object.freeze({AI_API_KEY:"(sk-[a-zA-Z0-9][a-zA-Z0-9-]{19,}|ant-api-[a-z0-9-]{20,}|AIza[0-9A-Za-z_-]{35})",AWS_ACCESS_KEY:"\\b(AKIA|ASIA)[0-9A-Z]{16}\\b",SSN:"\\b\\d{3}[-\\s.]?\\d{2}[-\\s.]?\\d{4}\\b",EMAIL:"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",PHONE:"\\b(?:\\+?1[-. ]?)?\\(?[2-9]\\d{2}\\)?[-. ]?\\d{3}[-. ]?\\d{4}\\b|\\+[1-9][\\s.-]?(?:\\d[\\s.-]?){6,14}\\d\\b",CREDIT_CARD:["\\b4\\d{3}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b","\\b5\\d{3}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b","\\b6\\d{3}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b","\\b3[47]\\d{2}[-\\s]?\\d{6}[-\\s]?\\d{5}\\b"].join("|"),DATE_OF_BIRTH:"\\b(?:0?[1-9]|1[0-2])[-/.](?:0?[1-9]|[12]\\d|3[01])[-/.](?:19|20)\\d{2}\\b|\\b(?:19|20)\\d{2}[-/.](?:0?[1-9]|1[0-2])[-/.](?:0?[1-9]|[12]\\d|3[01])\\b",TAX_ID:"\\b\\d{2}-\\d{7}\\b|\\b\\d{9}\\b",GITHUB_TOKEN:"\\b(ghp|gho|ghs|ghu|github_pat)_[a-zA-Z0-9_]{20,}\\b",STRIPE_KEY:"\\b(sk|pk|rk)_(live|test)_[a-zA-Z0-9]{20,}\\b",JWT:"\\beyJ[a-zA-Z0-9_-]+\\.[a-zA-Z0-9_-]+\\.[a-zA-Z0-9_-]+\\b",PRIVATE_KEY_BLOCK:"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",UK_NIN:"\\b[A-CEGHJ-PR-TW-Z][A-CEGHJ-NPR-TW-Z]\\s?\\d{2}\\s?\\d{2}\\s?\\d{2}\\s?[A-D]\\b"}),oe=Object.freeze({IBAN:"\\b[A-Z]{2}\\d{2}[-\\s]?(?:[A-Z0-9]{1,4}[-\\s]?){3,}[A-Z0-9]{1,4}\\b",DEA_NUMBER:"\\b[ABCDEFGHJKLMPRSTUX][A-Z]\\d{7}\\b",SWIFT_BIC:"\\b[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?\\b",NPI_NUMBER:"\\b[12]\\d{9}\\b",PASSPORT_NUMBER:"\\b[A-Z]{1,2}[0-9]{6,9}\\b"}),Q='(function(){"use strict";let l={},c={},n="";const i=(s,e)=>{const t={};for(const[a,o]of Object.entries(s))try{t[a]=new RegExp(o,"gi")}catch{console.warn(`[FieldShield] Skipping invalid ${e} pattern "${a}".`)}return t},d=s=>{let e=s;const t=[],a={...l,...c};for(const[o,r]of Object.entries(a))r.lastIndex=0,r.test(s)&&(t.push(o),r.lastIndex=0,e=e.replace(r,p=>"█".repeat(p.length)));return{masked:e,findings:[...new Set(t)]}};self.onmessage=s=>{const e=s.data;switch(e.type){case"CONFIG":{l=i(e.payload.defaultPatterns,"default"),c=i(e.payload.customPatterns,"custom");break}case"PROCESS":{n=e.payload.text;const{masked:t,findings:a}=d(n);self.postMessage({type:"UPDATE",masked:t,findings:a});break}case"GET_TRUTH":{const t=s.ports[0];t?t.postMessage({text:n}):console.warn("[FieldShield] GET_TRUTH received with no MessagePort — caller will time out. Pass port2 via the transfer array.");break}case"PURGE":{n="",self.postMessage({type:"PURGED"});break}default:{console.warn(`[FieldShield] Worker received unknown message type: "${e.type}"`);break}}}})();\n',ee=typeof self<"u"&&self.Blob&&new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);",Q],{type:"text/javascript;charset=utf-8"});function ue(l){let s;try{if(s=ee&&(self.URL||self.webkitURL).createObjectURL(ee),!s)throw"";const u=new Worker(s,{name:l?.name});return u.addEventListener("error",()=>{(self.URL||self.webkitURL).revokeObjectURL(s)}),u}catch{return new Worker("data:text/javascript;charset=utf-8,"+encodeURIComponent(Q),{name:l?.name})}}const te=3e3,fe=1e5,se=l=>Object.fromEntries(l.map(s=>[s.name,s.regex])),ae=(l=[],s=fe,u,S)=>{const[y,m]=a.useState(""),[F,$]=a.useState([]),[_,k]=a.useState(!1),i=a.useRef(null),N=JSON.stringify(l);a.useEffect(()=>{let E=!1;try{i.current=new ue}catch(d){console.error("[FieldShield] Worker failed to initialize — falling back to a11yMode.",d),queueMicrotask(()=>k(!0));return}return i.current.onmessage=d=>{E||d.data?.type==="UPDATE"&&typeof d.data.masked=="string"&&Array.isArray(d.data.findings)&&(m(d.data.masked),$(d.data.findings))},i.current.onerror=d=>{E||(console.error(`[FieldShield] Worker runtime error: ${d.message??"unknown error"}`),m(""),$([]),S?.(d))},i.current.postMessage({type:"CONFIG",payload:{defaultPatterns:z,customPatterns:se([])}}),()=>{E=!0,i.current?.terminate(),i.current=null}},[]),a.useEffect(()=>{i.current&&i.current.postMessage({type:"CONFIG",payload:{defaultPatterns:z,customPatterns:se(l)}})},[N]);const R=a.useCallback(E=>E.length>s?(console.warn(`[FieldShield] Input length ${E.length} exceeds maxProcessLength (${s}). Keystroke blocked to prevent unprotected data beyond the limit. Raise maxProcessLength if this field requires longer input.`),u?.(E.length,s),!1):(i.current?.postMessage({type:"PROCESS",payload:{text:E}}),!0),[s,u]),w=a.useCallback(()=>new Promise((E,d)=>{if(!i.current){E("");return}const{port1:I,port2:G}=new MessageChannel,V=setTimeout(()=>{I.close(),d(new Error(`[FieldShield] getSecureValue timed out after ${te}ms.`))},te);I.onmessage=K=>{clearTimeout(V),I.close(),E(K.data.text)},i.current.postMessage({type:"GET_TRUTH"},[G])}),[]),M=a.useCallback(()=>{i.current?.postMessage({type:"PURGE"})},[]);return{masked:y,findings:F,processText:R,getSecureValue:w,purge:M,workerFailed:_}},ne=a.forwardRef(({label:l,type:s="text",placeholder:u,customPatterns:S=[],className:y,style:m,onChange:F,a11yMode:$=!1,onSensitiveCopyAttempt:_,onSensitivePaste:k,onFocus:i,onBlur:N,disabled:R=!1,required:w=!1,maxLength:M,rows:E=3,inputMode:d="text",maxProcessLength:I=1e5,onMaxLengthExceeded:G,onWorkerError:V},K)=>{const{masked:B,findings:U,processText:Z,getSecureValue:re,purge:le,workerFailed:be}=ae(S,I,G,V),Se=$||be,f=U.length>0,O=l??"Protected field",W=a.useRef(null),ie=a.useRef(null),j=a.useRef(null),h=a.useRef(""),C=a.useId(),P=`${C}-warning`,H=`${C}-desc`;a.useImperativeHandle(K,()=>({getSecureValue:re,purge:()=>{le(),Z(""),h.current="";const e=W.current??ie.current;e&&(e.value="",j.current&&(j.current.textContent=`
|
|
2
|
+
`))}}),[re,le,Z]),a.useEffect(()=>{F?.(B,U)},[B,U,F]);const J=(e,n,r)=>{if(!Z(n)){const v=h.current.replace(/[^\n]/g,"x");e.value=v;const b=Math.min(r,h.current.length);e.setSelectionRange(b,b);return}h.current=n;const c=n.replace(/[^\n]/g,"x");e.value=c,e.setSelectionRange(r,r),j.current&&(j.current.textContent=c+`
|
|
3
|
+
`)},ce=e=>{const n=e.target,r=n.value,g=n.selectionStart??r.length,c=h.current,v=r.length-c.length;let b;if(v>0){const o=Math.max(0,g-v),T=r.slice(o,g);b=c.slice(0,o)+T+c.slice(o)}else if(v<0){const o=g,T=o-v;b=c.slice(0,o)+c.slice(T)}else{let o=-1,T=-1;for(let A=0;A<r.length;A++)r[A]!=="x"&&r[A]!==`
|
|
4
|
+
`&&(o===-1&&(o=A),T=A+1);if(o!==-1){const A=r.slice(o,T);b=c.slice(0,o)+A+c.slice(T)}else b=c}J(n,b,g)},me=e=>{const n=e.target.value;h.current=n,Z(n)},Y=e=>{e.preventDefault();const n=e.clipboardData.getData("text/plain"),r=e.target,g=r.selectionStart??0,c=r.selectionEnd??0,v=h.current,b=v.length-(c-g)+n.length;if(b>I){console.warn(`[FieldShield] Paste blocked — result length ${b} would exceed maxProcessLength (${I}). DOM unchanged.`),G?.(b,I);return}const o=v.slice(0,g)+n+v.slice(c),T=g+n.length;if(J(r,o,T),k&&n){const A=[...Object.entries(z),...S.map(D=>[D.name,D.regex])],de=[];for(const[D,x]of A)try{de.push([D,new RegExp(x,"gi")])}catch{}const X=[];let q=n;for(const[D,x]of de)x.lastIndex=0,x.test(n)&&(X.push(D),x.lastIndex=0,q=q.replace(x,Ee=>"█".repeat(Ee.length)));X.length>0&&k({timestamp:new Date().toISOString(),fieldLabel:O,findings:[...new Set(X)],masked:q,eventType:"paste"})===!1&&J(r,v,g)}},L=e=>{e.preventDefault();const n=e.target,r=n.selectionStart??0,g=n.selectionEnd??h.current.length,c=B.slice(r,g),v=c.includes("█");if(f&&v?(e.clipboardData.setData("text/plain",c),_?.({timestamp:new Date().toISOString(),fieldLabel:O,findings:[...U],masked:c,eventType:e.type==="cut"?"cut":"copy"})):e.clipboardData.setData("text/plain",h.current.slice(r,g)),e.type==="cut"){const b=h.current.slice(0,r),o=h.current.slice(g);h.current=b+o,Z(h.current);const T="x".repeat(h.current.length);n.value=T,n.setSelectionRange(r,r)}};return Se?t.jsxs("div",{className:`fieldshield-container${y?` ${y}`:""}`,style:m,role:"group","aria-labelledby":C,"data-disabled":R||void 0,children:[l&&t.jsx("label",{htmlFor:C,className:"fieldshield-label",children:l}),t.jsx("span",{id:H,className:"fieldshield-sr-only",children:"This field is protected. Sensitive data patterns will be detected and blocked from copying."}),t.jsx("input",{id:C,ref:W,type:"password",className:"fieldshield-a11y-input",placeholder:u,onChange:me,onCopy:L,onCut:L,onPaste:Y,onFocus:i,onBlur:N,disabled:R,required:w,maxLength:M,inputMode:d,spellCheck:!1,autoComplete:"off","aria-required":w,"aria-label":l?void 0:O,"aria-describedby":`${H} ${f?P:""}`.trim(),"aria-invalid":f?"true":"false","aria-errormessage":f?P:void 0}),t.jsx("div",{id:P,role:"status","aria-live":"polite","aria-atomic":"true",className:"fieldshield-findings",children:f&&t.jsxs(t.Fragment,{children:[t.jsx("span",{className:"fieldshield-warning-icon","aria-hidden":"true",children:"⚠"}),t.jsxs("span",{className:"fieldshield-warning-text",children:["Sensitive data detected. Clipboard blocked for:"," "]}),U.map(e=>t.jsx("span",{className:"fieldshield-tag","aria-label":`pattern: ${e}`,children:e},e))]})})]}):t.jsxs("div",{className:`fieldshield-container${y?` ${y}`:""}`,style:m,role:"group","aria-labelledby":C,"data-disabled":R||void 0,children:[l&&t.jsx("label",{htmlFor:C,className:"fieldshield-label",children:l}),t.jsx("span",{id:H,className:"fieldshield-sr-only",children:"Sensitive field. Input is protected. Sensitive data patterns will be detected and blocked from copying."}),t.jsxs("div",{className:"fieldshield-field-wrapper",children:[t.jsx("div",{className:`fieldshield-mask-layer${f?" fieldshield-mask-unsafe":""}`,"aria-hidden":"true",children:B||t.jsx("span",{className:"fieldshield-placeholder",children:u})}),s==="textarea"&&t.jsx("div",{ref:j,className:"fieldshield-grow","aria-hidden":"true"}),s==="textarea"?t.jsx("textarea",{ref:ie,id:C,className:"fieldshield-real-input",placeholder:u,onChange:ce,onPaste:Y,onCopy:L,onCut:L,onFocus:i,onBlur:N,disabled:R,required:w,maxLength:M,rows:E,inputMode:d,spellCheck:!1,autoComplete:"off","aria-required":w,"aria-label":f?`${O} — sensitive data detected`:`${O} — protected input`,"aria-describedby":`${H} ${f?P:""}`.trim(),"aria-invalid":f?"true":"false","aria-errormessage":f?P:void 0}):t.jsx("input",{ref:W,id:C,type:"text",className:"fieldshield-real-input",placeholder:u,onChange:ce,onPaste:Y,onCopy:L,onCut:L,onFocus:i,onBlur:N,disabled:R,required:w,maxLength:M,inputMode:d,spellCheck:!1,autoComplete:"off","aria-required":w,"aria-label":f?`${O} — sensitive data detected`:`${O} — protected input`,"aria-describedby":`${H} ${f?P:""}`.trim(),"aria-invalid":f?"true":"false","aria-errormessage":f?P:void 0})]}),t.jsx("div",{id:P,role:"status","aria-live":"polite","aria-atomic":"true",className:"fieldshield-findings",children:f&&t.jsxs(t.Fragment,{children:[t.jsx("span",{className:"fieldshield-warning-icon","aria-hidden":"true",children:"⚠"}),t.jsxs("span",{className:"fieldshield-warning-text",children:["Sensitive data detected. Clipboard blocked for:"," "]}),U.map(e=>t.jsx("span",{className:"fieldshield-tag","aria-label":`pattern: ${e}`,children:e},e))]})})]})});ne.displayName="FieldShieldInput";const pe=(l={})=>{const{maxEvents:s=20}=l,[u,S]=a.useState([]),y=a.useRef(0),m=a.useCallback(_=>{const k=++y.current,i=new Date().toLocaleTimeString();S(N=>[{..._,id:k,timestamp:i},...N].slice(0,s))},[s]),F=a.useCallback(_=>k=>{const i=_==="paste"?"PASTE_DETECTED":k.eventType==="cut"?"CUT_BLOCKED":"COPY_BLOCKED";m({field:k.fieldLabel,type:i,findings:k.findings,detail:`${k.masked.slice(0,32)}${k.masked.length>32?"…":""}`})},[m]),$=a.useCallback(()=>{S([]),y.current=0},[]);return{events:u,pushEvent:m,makeClipboardHandler:F,clearLog:$}},he=async l=>{const s=Object.entries(l),u=await Promise.allSettled(s.map(([,S])=>S.current?.getSecureValue()??Promise.resolve("")));return Object.fromEntries(s.map(([S],y)=>{const m=u[y];return m.status==="rejected"?(console.warn(`[FieldShield] collectSecureValues: field "${String(S)}" failed to retrieve value.`,m.reason),[S,""]):[S,m.value]}))},ge=l=>{Object.values(l).forEach(s=>s.current?.purge())};p.FIELDSHIELD_PATTERNS=z,p.FieldShieldInput=ne,p.OPT_IN_PATTERNS=oe,p.collectSecureValues=he,p.purgeSecureValues=ge,p.useFieldShield=ae,p.useSecurityLog=pe,Object.defineProperty(p,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fieldshield",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Sensitive input protection for React — prevents DOM scraping, clipboard exfiltration, and accidental PHI exposure in healthcare and fintech applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"author": "Anurag
|
|
8
|
-
"homepage": "https://github.com/
|
|
7
|
+
"author": "Anurag Nedunuri",
|
|
8
|
+
"homepage": "https://github.com/anuragnedunuri/fieldshield#readme",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "https://github.com/
|
|
11
|
+
"url": "git+https://github.com/anuragnedunuri/fieldshield.git"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/
|
|
14
|
+
"url": "https://github.com/anuragnedunuri/fieldshield/issues"
|
|
15
15
|
},
|
|
16
16
|
"keywords": [
|
|
17
17
|
"react",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
(function(){"use strict";let l={},c={},n="";const i=(s,e)=>{const t={};for(const[a,o]of Object.entries(s))try{t[a]=new RegExp(o,"gi")}catch{console.warn(`[FieldShield] Skipping invalid ${e} pattern "${a}".`)}return t},d=s=>{let e=s;const t=[],a={...l,...c};for(const[o,r]of Object.entries(a))r.lastIndex=0,r.test(s)&&(t.push(o),r.lastIndex=0,e=e.replace(r,p=>"█".repeat(p.length)));return{masked:e,findings:[...new Set(t)]}};self.onmessage=s=>{const e=s.data;switch(e.type){case"CONFIG":{l=i(e.payload.defaultPatterns,"default"),c=i(e.payload.customPatterns,"custom");break}case"PROCESS":{n=e.payload.text;const{masked:t,findings:a}=d(n);self.postMessage({type:"UPDATE",masked:t,findings:a});break}case"GET_TRUTH":{const t=s.ports[0];t?t.postMessage({text:n}):console.warn("[FieldShield] GET_TRUTH received with no MessagePort — caller will time out. Pass port2 via the transfer array.");break}case"PURGE":{n="",self.postMessage({type:"PURGED"});break}default:{console.warn(`[FieldShield] Worker received unknown message type: "${e.type}"`);break}}}})();
|