fieldshield 1.0.1 → 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 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 uses the `new URL('./fieldshield.worker.ts', import.meta.url)` pattern to instantiate its Web Worker. This is supported natively in Vite and in Webpack 5+ with no additional configuration.
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
- Next.js requires explicit worker configuration. Add the following to `next.config.js`:
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:`** — restricts Web Workers to same-origin scripts and blob URLs. The `blob:` source is required if you use the pre-compiled worker option (v1.1 roadmap). If you are certain you will only ever use the default source-file worker, `worker-src 'self'` without `blob:` is stricter.
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}
@@ -1,5 +1,5 @@
1
- import { jsxs as D, jsx as d, Fragment as le } from "react/jsx-runtime";
2
- import { useState as G, useRef as M, useEffect as ee, useCallback as z, forwardRef as he, useId as ge, useImperativeHandle as me } from "react";
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: `5500005555555559` / `5500 0055 5555 5559`
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[1-5]\\d{2}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b",
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
- }), ye = Object.freeze({
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
- }), ce = 3e3, be = 1e5, de = (i) => Object.fromEntries(i.map((n) => [n.name, n.regex])), Se = (i = [], n = be, v, h) => {
347
- const [A, g] = G(""), [$, O] = G([]), [N, S] = G(!1), s = M(null), I = JSON.stringify(i);
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
- s.current = new Worker(
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(() => S(!0));
378
+ ), queueMicrotask(() => v(!0));
364
379
  return;
365
380
  }
366
- return s.current.onmessage = (l) => {
367
- m || l.data?.type === "UPDATE" && typeof l.data.masked == "string" && Array.isArray(l.data.findings) && (g(l.data.masked), O(l.data.findings));
368
- }, s.current.onerror = (l) => {
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
- ), g(""), O([]), h?.(l));
372
- }, s.current.postMessage({
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, s.current?.terminate(), s.current = null;
395
+ m = !0, r.current?.terminate(), r.current = null;
381
396
  };
382
397
  }, []), ee(() => {
383
- s.current && s.current.postMessage({
398
+ r.current && r.current.postMessage({
384
399
  type: "CONFIG",
385
400
  payload: {
386
401
  defaultPatterns: te,
387
- customPatterns: de(i)
402
+ customPatterns: de(n)
388
403
  }
389
404
  });
390
405
  }, [I]);
391
- const R = z(
392
- (m) => m.length > n ? (console.warn(
393
- `[FieldShield] Input length ${m.length} exceeds maxProcessLength (${n}). Keystroke blocked to prevent unprotected data beyond the limit. Raise maxProcessLength if this field requires longer input.`
394
- ), v?.(m.length, n), !1) : (s.current?.postMessage({
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
- [n, v]
399
- ), T = z(() => new Promise((m, l) => {
400
- if (!s.current) {
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: k, port2: B } = new MessageChannel(), L = setTimeout(() => {
405
- k.close(), l(
419
+ const { port1: w, port2: H } = new MessageChannel(), W = setTimeout(() => {
420
+ w.close(), l(
406
421
  new Error(
407
- `[FieldShield] getSecureValue timed out after ${ce}ms.`
422
+ `[FieldShield] getSecureValue timed out after ${oe}ms.`
408
423
  )
409
424
  );
410
- }, ce);
411
- k.onmessage = (W) => {
412
- clearTimeout(L), k.close(), m(W.data.text);
413
- }, s.current.postMessage({ type: "GET_TRUTH" }, [B]);
414
- }), []), U = z(() => {
415
- s.current?.postMessage({ type: "PURGE" });
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: A, findings: $, processText: R, getSecureValue: T, purge: U, workerFailed: N };
418
- }, ve = he(
432
+ return { masked: E, findings: $, processText: _, getSecureValue: R, purge: L, workerFailed: C };
433
+ }, ye = be(
419
434
  ({
420
- label: i,
421
- type: n = "text",
422
- placeholder: v,
423
- customPatterns: h = [],
424
- className: A,
425
- style: g,
435
+ label: n,
436
+ type: t = "text",
437
+ placeholder: d,
438
+ customPatterns: g = [],
439
+ className: E,
440
+ style: b,
426
441
  onChange: $,
427
- a11yMode: O = !1,
428
- onSensitiveCopyAttempt: N,
429
- onSensitivePaste: S,
430
- onFocus: s,
442
+ a11yMode: D = !1,
443
+ onSensitiveCopyAttempt: C,
444
+ onSensitivePaste: v,
445
+ onFocus: r,
431
446
  onBlur: I,
432
- disabled: R = !1,
433
- required: T = !1,
434
- maxLength: U,
447
+ disabled: _ = !1,
448
+ required: R = !1,
449
+ maxLength: L,
435
450
  rows: m = 3,
436
451
  inputMode: l = "text",
437
- maxProcessLength: k = 1e5,
438
- onMaxLengthExceeded: B,
439
- onWorkerError: L
440
- }, W) => {
452
+ maxProcessLength: w = 1e5,
453
+ onMaxLengthExceeded: H,
454
+ onWorkerError: W
455
+ }, V) => {
441
456
  const {
442
- masked: V,
443
- findings: Z,
444
- processText: H,
445
- getSecureValue: ae,
446
- purge: se,
447
- workerFailed: oe
448
- } = Se(
449
- h,
450
- k,
451
- B,
452
- L
453
- ), ue = O || oe, o = Z.length > 0, w = i ?? "Protected field", J = M(null), ne = M(null), K = M(null), u = M(""), C = ge(), _ = `${C}-warning`, j = `${C}-desc`;
454
- me(
455
- W,
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: ae,
472
+ getSecureValue: se,
458
473
  purge: () => {
459
- se(), H(""), u.current = "";
474
+ ae(), j(""), f.current = "";
460
475
  const e = J.current ?? ne.current;
461
- e && (e.value = "", K.current && (K.current.textContent = `
476
+ e && (e.value = "", z.current && (z.current.textContent = `
462
477
  `));
463
478
  }
464
479
  }),
465
- [ae, se, H]
480
+ [se, ae, j]
466
481
  ), ee(() => {
467
- $?.(V, Z);
468
- }, [V, Z, $]);
469
- const Y = (e, t, a) => {
470
- if (!H(t)) {
471
- const b = u.current.replace(/[^\n]/g, "x");
472
- e.value = b;
473
- const f = Math.min(a, u.current.length);
474
- e.setSelectionRange(f, f);
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
- u.current = t;
478
- const r = t.replace(/[^\n]/g, "x");
479
- e.value = r, e.setSelectionRange(a, a), K.current && (K.current.textContent = r + `
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 t = e.target, a = t.value, p = t.selectionStart ?? a.length, r = u.current, b = a.length - r.length;
483
- let f;
484
- if (b > 0) {
485
- const c = Math.max(0, p - b), E = a.slice(c, p);
486
- f = r.slice(0, c) + E + r.slice(c);
487
- } else if (b < 0) {
488
- const c = p, E = c - b;
489
- f = r.slice(0, c) + r.slice(E);
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, E = -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), E = y + 1);
509
+ ` && (c === -1 && (c = y), k = y + 1);
495
510
  if (c !== -1) {
496
- const y = a.slice(c, E);
497
- f = r.slice(0, c) + y + r.slice(E);
511
+ const y = a.slice(c, k);
512
+ h = i.slice(0, c) + y + i.slice(k);
498
513
  } else
499
- f = r;
514
+ h = i;
500
515
  }
501
- Y(t, f, p);
502
- }, pe = (e) => {
503
- const t = e.target.value;
504
- u.current = t, H(t);
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 t = e.clipboardData.getData("text/plain"), a = e.target, p = a.selectionStart ?? 0, r = a.selectionEnd ?? 0, b = u.current, f = b.length - (r - p) + t.length;
508
- if (f > k) {
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 ${f} would exceed maxProcessLength (${k}). DOM unchanged.`
511
- ), B?.(f, k);
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 = b.slice(0, p) + t + b.slice(r), E = p + t.length;
515
- if (Y(a, c, E), S && t) {
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
- ...h.map((P) => [P.name, P.regex])
533
+ ...g.map((N) => [N.name, N.regex])
519
534
  ], ie = [];
520
- for (const [P, F] of y)
535
+ for (const [N, F] of y)
521
536
  try {
522
- ie.push([P, new RegExp(F, "gi")]);
537
+ ie.push([N, new RegExp(F, "gi")]);
523
538
  } catch {
524
539
  }
525
540
  const q = [];
526
- let Q = t;
527
- for (const [P, F] of ie)
528
- F.lastIndex = 0, F.test(t) && (q.push(P), F.lastIndex = 0, Q = Q.replace(
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
- (fe) => "█".repeat(fe.length)
545
+ (ge) => "█".repeat(ge.length)
531
546
  ));
532
- q.length > 0 && S({
547
+ q.length > 0 && v({
533
548
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
534
- fieldLabel: w,
549
+ fieldLabel: P,
535
550
  findings: [...new Set(q)],
536
551
  masked: Q,
537
552
  eventType: "paste"
538
- }) === !1 && Y(a, b, p);
553
+ }) === !1 && Y(a, S, p);
539
554
  }
540
555
  }, x = (e) => {
541
556
  e.preventDefault();
542
- const t = e.target, a = t.selectionStart ?? 0, p = t.selectionEnd ?? u.current.length, r = V.slice(a, p), b = r.includes("█");
543
- if (o && b ? (e.clipboardData.setData("text/plain", r), N?.({
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: w,
546
- findings: [...Z],
560
+ fieldLabel: P,
561
+ findings: [...U],
547
562
  // snapshot — receiver must not hold a reference to internal state
548
- masked: r,
563
+ masked: i,
549
564
  eventType: e.type === "cut" ? "cut" : "copy"
550
565
  })) : e.clipboardData.setData(
551
566
  "text/plain",
552
- u.current.slice(a, p)
567
+ f.current.slice(a, p)
553
568
  ), e.type === "cut") {
554
- const f = u.current.slice(0, a), c = u.current.slice(p);
555
- u.current = f + c, H(u.current);
556
- const E = "x".repeat(u.current.length);
557
- t.value = E, t.setSelectionRange(a, a);
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 ue ? /* @__PURE__ */ D(
575
+ return pe ? /* @__PURE__ */ O(
561
576
  "div",
562
577
  {
563
- className: `fieldshield-container${A ? ` ${A}` : ""}`,
564
- style: g,
578
+ className: `fieldshield-container${E ? ` ${E}` : ""}`,
579
+ style: b,
565
580
  role: "group",
566
- "aria-labelledby": C,
567
- "data-disabled": R || void 0,
581
+ "aria-labelledby": A,
582
+ "data-disabled": _ || void 0,
568
583
  children: [
569
- i && /* @__PURE__ */ d("label", { htmlFor: C, className: "fieldshield-label", children: i }),
570
- /* @__PURE__ */ d("span", { id: j, className: "fieldshield-sr-only", children: "This field is protected. Sensitive data patterns will be detected and blocked from copying." }),
571
- /* @__PURE__ */ d(
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: C,
589
+ id: A,
575
590
  ref: J,
576
591
  type: "password",
577
592
  className: "fieldshield-a11y-input",
578
- placeholder: v,
579
- onChange: pe,
593
+ placeholder: d,
594
+ onChange: he,
580
595
  onCopy: x,
581
596
  onCut: x,
582
597
  onPaste: X,
583
- onFocus: s,
598
+ onFocus: r,
584
599
  onBlur: I,
585
- disabled: R,
586
- required: T,
587
- maxLength: U,
600
+ disabled: _,
601
+ required: R,
602
+ maxLength: L,
588
603
  inputMode: l,
589
604
  spellCheck: !1,
590
605
  autoComplete: "off",
591
- "aria-required": T,
592
- "aria-label": i ? void 0 : w,
593
- "aria-describedby": `${j} ${o ? _ : ""}`.trim(),
594
- "aria-invalid": o ? "true" : "false",
595
- "aria-errormessage": o ? _ : void 0
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__ */ d(
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: o && /* @__PURE__ */ D(le, { children: [
607
- /* @__PURE__ */ d("span", { className: "fieldshield-warning-icon", "aria-hidden": "true", children: "⚠" }),
608
- /* @__PURE__ */ D("span", { className: "fieldshield-warning-text", children: [
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
- Z.map((e) => /* @__PURE__ */ d(
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__ */ D(
641
+ ) : /* @__PURE__ */ O(
627
642
  "div",
628
643
  {
629
- className: `fieldshield-container${A ? ` ${A}` : ""}`,
630
- style: g,
644
+ className: `fieldshield-container${E ? ` ${E}` : ""}`,
645
+ style: b,
631
646
  role: "group",
632
- "aria-labelledby": C,
633
- "data-disabled": R || void 0,
647
+ "aria-labelledby": A,
648
+ "data-disabled": _ || void 0,
634
649
  children: [
635
- i && /* @__PURE__ */ d("label", { htmlFor: C, className: "fieldshield-label", children: i }),
636
- /* @__PURE__ */ d("span", { id: j, className: "fieldshield-sr-only", children: "Sensitive field. Input is protected. Sensitive data patterns will be detected and blocked from copying." }),
637
- /* @__PURE__ */ D("div", { className: "fieldshield-field-wrapper", children: [
638
- /* @__PURE__ */ d(
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${o ? " fieldshield-mask-unsafe" : ""}`,
656
+ className: `fieldshield-mask-layer${u ? " fieldshield-mask-unsafe" : ""}`,
642
657
  "aria-hidden": "true",
643
- children: V || /* @__PURE__ */ d("span", { className: "fieldshield-placeholder", children: v })
658
+ children: B || /* @__PURE__ */ o("span", { className: "fieldshield-placeholder", children: d })
644
659
  }
645
660
  ),
646
- n === "textarea" && /* @__PURE__ */ d(
661
+ t === "textarea" && /* @__PURE__ */ o(
647
662
  "div",
648
663
  {
649
- ref: K,
664
+ ref: z,
650
665
  className: "fieldshield-grow",
651
666
  "aria-hidden": "true"
652
667
  }
653
668
  ),
654
- n === "textarea" ? /* @__PURE__ */ d(
669
+ t === "textarea" ? /* @__PURE__ */ o(
655
670
  "textarea",
656
671
  {
657
672
  ref: ne,
658
- id: C,
673
+ id: A,
659
674
  className: "fieldshield-real-input",
660
- placeholder: v,
675
+ placeholder: d,
661
676
  onChange: re,
662
677
  onPaste: X,
663
678
  onCopy: x,
664
679
  onCut: x,
665
- onFocus: s,
680
+ onFocus: r,
666
681
  onBlur: I,
667
- disabled: R,
668
- required: T,
669
- maxLength: U,
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": T,
675
- "aria-label": o ? `${w} — sensitive data detected` : `${w} — protected input`,
676
- "aria-describedby": `${j} ${o ? _ : ""}`.trim(),
677
- "aria-invalid": o ? "true" : "false",
678
- "aria-errormessage": o ? _ : void 0
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__ */ d(
695
+ ) : /* @__PURE__ */ o(
681
696
  "input",
682
697
  {
683
698
  ref: J,
684
- id: C,
699
+ id: A,
685
700
  type: "text",
686
701
  className: "fieldshield-real-input",
687
- placeholder: v,
702
+ placeholder: d,
688
703
  onChange: re,
689
704
  onPaste: X,
690
705
  onCopy: x,
691
706
  onCut: x,
692
- onFocus: s,
707
+ onFocus: r,
693
708
  onBlur: I,
694
- disabled: R,
695
- required: T,
696
- maxLength: U,
709
+ disabled: _,
710
+ required: R,
711
+ maxLength: L,
697
712
  inputMode: l,
698
713
  spellCheck: !1,
699
714
  autoComplete: "off",
700
- "aria-required": T,
701
- "aria-label": o ? `${w} — sensitive data detected` : `${w} — protected input`,
702
- "aria-describedby": `${j} ${o ? _ : ""}`.trim(),
703
- "aria-invalid": o ? "true" : "false",
704
- "aria-errormessage": o ? _ : void 0
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__ */ d(
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: o && /* @__PURE__ */ D(le, { children: [
717
- /* @__PURE__ */ d("span", { className: "fieldshield-warning-icon", "aria-hidden": "true", children: "⚠" }),
718
- /* @__PURE__ */ D("span", { className: "fieldshield-warning-text", children: [
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
- Z.map((e) => /* @__PURE__ */ d(
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
- ve.displayName = "FieldShieldInput";
740
- const Ce = (i = {}) => {
741
- const { maxEvents: n = 20 } = i, [v, h] = G([]), A = M(0), g = z(
742
- (N) => {
743
- const S = ++A.current, s = (/* @__PURE__ */ new Date()).toLocaleTimeString();
744
- h(
745
- (I) => [{ ...N, id: S, timestamp: s }, ...I].slice(0, n)
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
- [n]
749
- ), $ = z(
750
- (N) => (S) => {
751
- const s = N === "paste" ? "PASTE_DETECTED" : S.eventType === "cut" ? "CUT_BLOCKED" : "COPY_BLOCKED";
752
- g({
753
- field: S.fieldLabel,
754
- type: s,
755
- findings: S.findings,
756
- detail: `${S.masked.slice(0, 32)}${S.masked.length > 32 ? "…" : ""}`
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
- [g]
760
- ), O = z(() => {
761
- h([]), A.current = 0;
774
+ [b]
775
+ ), D = M(() => {
776
+ g([]), E.current = 0;
762
777
  }, []);
763
- return { events: v, pushEvent: g, makeClipboardHandler: $, clearLog: O };
764
- }, Te = async (i) => {
765
- const n = Object.entries(i), v = await Promise.allSettled(
766
- n.map(
767
- ([, h]) => h.current?.getSecureValue() ?? Promise.resolve("")
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
- n.map(([h], A) => {
772
- const g = v[A];
773
- return g.status === "rejected" ? (console.warn(
774
- `[FieldShield] collectSecureValues: field "${String(h)}" failed to retrieve value.`,
775
- g.reason
776
- ), [h, ""]) : [h, g.value];
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
- }, ke = (i) => {
780
- Object.values(i).forEach((n) => n.current?.purge());
794
+ }, Ie = (n) => {
795
+ Object.values(n).forEach((t) => t.current?.purge());
781
796
  };
782
797
  export {
783
798
  te as FIELDSHIELD_PATTERNS,
784
- ve as FieldShieldInput,
785
- ye as OPT_IN_PATTERNS,
786
- Te as collectSecureValues,
787
- ke as purgeSecureValues,
788
- Se as useFieldShield,
789
- Ce as useSecurityLog
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
  };
@@ -1,4 +1,4 @@
1
- (function(f,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):(f=typeof globalThis<"u"?globalThis:f||self,t(f.FieldShield={},f.ReactJSXRuntime,f.React))})(this,(function(f,t,s){"use strict";var B=typeof document<"u"?document.currentScript:null;const j=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[1-5]\\d{2}[-\\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"}),ce=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"}),ee=3e3,oe=1e5,te=d=>Object.fromEntries(d.map(l=>[l.name,l.regex])),se=(d=[],l=oe,A,b)=>{const[y,S]=s.useState(""),[R,$]=s.useState([]),[P,v]=s.useState(!1),r=s.useRef(null),w=JSON.stringify(d);s.useEffect(()=>{let m=!1;try{r.current=new Worker(new URL("/assets/fieldshield.worker.js",typeof document>"u"&&typeof location>"u"?require("url").pathToFileURL(__filename).href:typeof document>"u"?location.href:B&&B.tagName.toUpperCase()==="SCRIPT"&&B.src||new URL("fieldshield.umd.cjs",document.baseURI).href),{type:"module"})}catch(c){console.error("[FieldShield] Worker failed to initialize — falling back to a11yMode.",c),queueMicrotask(()=>v(!0));return}return r.current.onmessage=c=>{m||c.data?.type==="UPDATE"&&typeof c.data.masked=="string"&&Array.isArray(c.data.findings)&&(S(c.data.masked),$(c.data.findings))},r.current.onerror=c=>{m||(console.error(`[FieldShield] Worker runtime error: ${c.message??"unknown error"}`),S(""),$([]),b?.(c))},r.current.postMessage({type:"CONFIG",payload:{defaultPatterns:j,customPatterns:te([])}}),()=>{m=!0,r.current?.terminate(),r.current=null}},[]),s.useEffect(()=>{r.current&&r.current.postMessage({type:"CONFIG",payload:{defaultPatterns:j,customPatterns:te(d)}})},[w]);const D=s.useCallback(m=>m.length>l?(console.warn(`[FieldShield] Input length ${m.length} exceeds maxProcessLength (${l}). Keystroke blocked to prevent unprotected data beyond the limit. Raise maxProcessLength if this field requires longer input.`),A?.(m.length,l),!1):(r.current?.postMessage({type:"PROCESS",payload:{text:m}}),!0),[l,A]),k=s.useCallback(()=>new Promise((m,c)=>{if(!r.current){m("");return}const{port1:I,port2:V}=new MessageChannel,G=setTimeout(()=>{I.close(),c(new Error(`[FieldShield] getSecureValue timed out after ${ee}ms.`))},ee);I.onmessage=W=>{clearTimeout(G),I.close(),m(W.data.text)},r.current.postMessage({type:"GET_TRUTH"},[V])}),[]),x=s.useCallback(()=>{r.current?.postMessage({type:"PURGE"})},[]);return{masked:y,findings:R,processText:D,getSecureValue:k,purge:x,workerFailed:P}},ae=s.forwardRef(({label:d,type:l="text",placeholder:A,customPatterns:b=[],className:y,style:S,onChange:R,a11yMode:$=!1,onSensitiveCopyAttempt:P,onSensitivePaste:v,onFocus:r,onBlur:w,disabled:D=!1,required:k=!1,maxLength:x,rows:m=3,inputMode:c="text",maxProcessLength:I=1e5,onMaxLengthExceeded:V,onWorkerError:G},W)=>{const{masked:K,findings:Z,processText:z,getSecureValue:ne,purge:re,workerFailed:he}=se(b,I,V,G),ge=$||he,u=Z.length>0,F=d??"Protected field",J=s.useRef(null),le=s.useRef(null),L=s.useRef(null),p=s.useRef(""),_=s.useId(),N=`${_}-warning`,H=`${_}-desc`;s.useImperativeHandle(W,()=>({getSecureValue:ne,purge:()=>{re(),z(""),p.current="";const e=J.current??le.current;e&&(e.value="",L.current&&(L.current.textContent=`
2
- `))}}),[ne,re,z]),s.useEffect(()=>{R?.(K,Z)},[K,Z,R]);const Y=(e,a,n)=>{if(!z(a)){const E=p.current.replace(/[^\n]/g,"x");e.value=E;const g=Math.min(n,p.current.length);e.setSelectionRange(g,g);return}p.current=a;const i=a.replace(/[^\n]/g,"x");e.value=i,e.setSelectionRange(n,n),L.current&&(L.current.textContent=i+`
3
- `)},ie=e=>{const a=e.target,n=a.value,h=a.selectionStart??n.length,i=p.current,E=n.length-i.length;let g;if(E>0){const o=Math.max(0,h-E),T=n.slice(o,h);g=i.slice(0,o)+T+i.slice(o)}else if(E<0){const o=h,T=o-E;g=i.slice(0,o)+i.slice(T)}else{let o=-1,T=-1;for(let C=0;C<n.length;C++)n[C]!=="x"&&n[C]!==`
4
- `&&(o===-1&&(o=C),T=C+1);if(o!==-1){const C=n.slice(o,T);g=i.slice(0,o)+C+i.slice(T)}else g=i}Y(a,g,h)},be=e=>{const a=e.target.value;p.current=a,z(a)},X=e=>{e.preventDefault();const a=e.clipboardData.getData("text/plain"),n=e.target,h=n.selectionStart??0,i=n.selectionEnd??0,E=p.current,g=E.length-(i-h)+a.length;if(g>I){console.warn(`[FieldShield] Paste blocked — result length ${g} would exceed maxProcessLength (${I}). DOM unchanged.`),V?.(g,I);return}const o=E.slice(0,h)+a+E.slice(i),T=h+a.length;if(Y(n,o,T),v&&a){const C=[...Object.entries(j),...b.map(O=>[O.name,O.regex])],de=[];for(const[O,U]of C)try{de.push([O,new RegExp(U,"gi")])}catch{}const q=[];let Q=a;for(const[O,U]of de)U.lastIndex=0,U.test(a)&&(q.push(O),U.lastIndex=0,Q=Q.replace(U,Se=>"█".repeat(Se.length)));q.length>0&&v({timestamp:new Date().toISOString(),fieldLabel:F,findings:[...new Set(q)],masked:Q,eventType:"paste"})===!1&&Y(n,E,h)}},M=e=>{e.preventDefault();const a=e.target,n=a.selectionStart??0,h=a.selectionEnd??p.current.length,i=K.slice(n,h),E=i.includes("█");if(u&&E?(e.clipboardData.setData("text/plain",i),P?.({timestamp:new Date().toISOString(),fieldLabel:F,findings:[...Z],masked:i,eventType:e.type==="cut"?"cut":"copy"})):e.clipboardData.setData("text/plain",p.current.slice(n,h)),e.type==="cut"){const g=p.current.slice(0,n),o=p.current.slice(h);p.current=g+o,z(p.current);const T="x".repeat(p.current.length);a.value=T,a.setSelectionRange(n,n)}};return ge?t.jsxs("div",{className:`fieldshield-container${y?` ${y}`:""}`,style:S,role:"group","aria-labelledby":_,"data-disabled":D||void 0,children:[d&&t.jsx("label",{htmlFor:_,className:"fieldshield-label",children:d}),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:_,ref:J,type:"password",className:"fieldshield-a11y-input",placeholder:A,onChange:be,onCopy:M,onCut:M,onPaste:X,onFocus:r,onBlur:w,disabled:D,required:k,maxLength:x,inputMode:c,spellCheck:!1,autoComplete:"off","aria-required":k,"aria-label":d?void 0:F,"aria-describedby":`${H} ${u?N:""}`.trim(),"aria-invalid":u?"true":"false","aria-errormessage":u?N:void 0}),t.jsx("div",{id:N,role:"status","aria-live":"polite","aria-atomic":"true",className:"fieldshield-findings",children:u&&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:"," "]}),Z.map(e=>t.jsx("span",{className:"fieldshield-tag","aria-label":`pattern: ${e}`,children:e},e))]})})]}):t.jsxs("div",{className:`fieldshield-container${y?` ${y}`:""}`,style:S,role:"group","aria-labelledby":_,"data-disabled":D||void 0,children:[d&&t.jsx("label",{htmlFor:_,className:"fieldshield-label",children:d}),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${u?" fieldshield-mask-unsafe":""}`,"aria-hidden":"true",children:K||t.jsx("span",{className:"fieldshield-placeholder",children:A})}),l==="textarea"&&t.jsx("div",{ref:L,className:"fieldshield-grow","aria-hidden":"true"}),l==="textarea"?t.jsx("textarea",{ref:le,id:_,className:"fieldshield-real-input",placeholder:A,onChange:ie,onPaste:X,onCopy:M,onCut:M,onFocus:r,onBlur:w,disabled:D,required:k,maxLength:x,rows:m,inputMode:c,spellCheck:!1,autoComplete:"off","aria-required":k,"aria-label":u?`${F} — sensitive data detected`:`${F} — protected input`,"aria-describedby":`${H} ${u?N:""}`.trim(),"aria-invalid":u?"true":"false","aria-errormessage":u?N:void 0}):t.jsx("input",{ref:J,id:_,type:"text",className:"fieldshield-real-input",placeholder:A,onChange:ie,onPaste:X,onCopy:M,onCut:M,onFocus:r,onBlur:w,disabled:D,required:k,maxLength:x,inputMode:c,spellCheck:!1,autoComplete:"off","aria-required":k,"aria-label":u?`${F} — sensitive data detected`:`${F} — protected input`,"aria-describedby":`${H} ${u?N:""}`.trim(),"aria-invalid":u?"true":"false","aria-errormessage":u?N:void 0})]}),t.jsx("div",{id:N,role:"status","aria-live":"polite","aria-atomic":"true",className:"fieldshield-findings",children:u&&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:"," "]}),Z.map(e=>t.jsx("span",{className:"fieldshield-tag","aria-label":`pattern: ${e}`,children:e},e))]})})]})});ae.displayName="FieldShieldInput";const ue=(d={})=>{const{maxEvents:l=20}=d,[A,b]=s.useState([]),y=s.useRef(0),S=s.useCallback(P=>{const v=++y.current,r=new Date().toLocaleTimeString();b(w=>[{...P,id:v,timestamp:r},...w].slice(0,l))},[l]),R=s.useCallback(P=>v=>{const r=P==="paste"?"PASTE_DETECTED":v.eventType==="cut"?"CUT_BLOCKED":"COPY_BLOCKED";S({field:v.fieldLabel,type:r,findings:v.findings,detail:`${v.masked.slice(0,32)}${v.masked.length>32?"…":""}`})},[S]),$=s.useCallback(()=>{b([]),y.current=0},[]);return{events:A,pushEvent:S,makeClipboardHandler:R,clearLog:$}},fe=async d=>{const l=Object.entries(d),A=await Promise.allSettled(l.map(([,b])=>b.current?.getSecureValue()??Promise.resolve("")));return Object.fromEntries(l.map(([b],y)=>{const S=A[y];return S.status==="rejected"?(console.warn(`[FieldShield] collectSecureValues: field "${String(b)}" failed to retrieve value.`,S.reason),[b,""]):[b,S.value]}))},pe=d=>{Object.values(d).forEach(l=>l.current?.purge())};f.FIELDSHIELD_PATTERNS=j,f.FieldShieldInput=ae,f.OPT_IN_PATTERNS=ce,f.collectSecureValues=fe,f.purgeSecureValues=pe,f.useFieldShield=se,f.useSecurityLog=ue,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
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,6 +1,6 @@
1
1
  {
2
2
  "name": "fieldshield",
3
- "version": "1.0.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",
@@ -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}}}})();