@chdlabs/my-shield 0.1.3 → 0.2.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.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @file src/biometrics/index.js
3
+ * Behavioral Biometrics Engine Exports
4
+ *
5
+ * @author ChdLabs
6
+ * @license MIT
7
+ */
8
+
9
+ export { MiniRocket } from './minirocket.js';
10
+ export { DualRidgeClassifier } from './ridge.js';
@@ -0,0 +1,213 @@
1
+ /**
2
+ * @file src/biometrics/minirocket.js
3
+ * Deterministic MiniRocket 1D Transform Engine
4
+ * High-performance, zero-dependency feature extractor using 84 fixed length-9 kernels
5
+ * with binary/ternary weights {-1, 2} and proportion of positive values (PPV).
6
+ *
7
+ * @author ChdLabs
8
+ * @license MIT
9
+ */
10
+
11
+ const MR_KERNEL_LENGTH = 9;
12
+ const MR_NUM_KERNELS = 84;
13
+
14
+ export class MiniRocket {
15
+ /**
16
+ * @param {number} [windowLength=128] Length of the input kinematic series
17
+ * @param {number} [numFeatures=500] Number of MiniRocket PPV features to extract
18
+ */
19
+ constructor(windowLength = 128, numFeatures = 500) {
20
+ this.windowLength = windowLength;
21
+ this.targetFeatures = numFeatures;
22
+
23
+ this.kernelIndices = this._buildIndices();
24
+ this.dilations = this._buildDilations();
25
+ this.featureMap = this._buildFeatureMap();
26
+ this.thresholds = null;
27
+
28
+ // Pre-allocated scratch buffers to eliminate per-inference GC allocations
29
+ this._ca = new Float64Array(this.windowLength);
30
+ this._cg = new Float64Array(MR_KERNEL_LENGTH * this.windowLength);
31
+ this._c = new Float64Array(this.windowLength);
32
+ }
33
+
34
+ /**
35
+ * Build all C(9, 3) = 84 combinations of 3 positive kernel indices
36
+ * @private
37
+ */
38
+ _buildIndices() {
39
+ const indices = new Int32Array(MR_NUM_KERNELS * 3);
40
+ let p = 0;
41
+ for (let a = 0; a < MR_KERNEL_LENGTH; a++) {
42
+ for (let b = a + 1; b < MR_KERNEL_LENGTH; b++) {
43
+ for (let c = b + 1; c < MR_KERNEL_LENGTH; c++) {
44
+ indices[p * 3 + 0] = a;
45
+ indices[p * 3 + 1] = b;
46
+ indices[p * 3 + 2] = c;
47
+ p++;
48
+ }
49
+ }
50
+ }
51
+ return indices;
52
+ }
53
+
54
+ /**
55
+ * Compute multi-scale dilation schedule
56
+ * @private
57
+ */
58
+ _buildDilations() {
59
+ const maxExp = Math.log2((this.windowLength - 1) / (MR_KERNEL_LENGTH - 1));
60
+ const maxDilations = Math.min(32, Math.floor(this.targetFeatures / MR_NUM_KERNELS));
61
+ const dilations = [];
62
+
63
+ for (let i = 0; i < maxDilations; i++) {
64
+ const exponent = (i === maxDilations - 1) ? maxExp : (i * maxExp) / (maxDilations - 1);
65
+ const d = Math.floor(Math.pow(2, exponent));
66
+ if (!dilations.includes(d) && d >= 1) {
67
+ dilations.push(d);
68
+ }
69
+ }
70
+ return Int32Array.from(dilations);
71
+ }
72
+
73
+ /**
74
+ * Map feature indices to kernel index, dilation, and golden ratio quantile
75
+ * @private
76
+ */
77
+ _buildFeatureMap() {
78
+ const map = [];
79
+ const phi = (Math.sqrt(5) + 1) / 2;
80
+ let count = 0;
81
+
82
+ for (const d of this.dilations) {
83
+ for (let k = 0; k < MR_NUM_KERNELS; k++) {
84
+ if (count >= this.targetFeatures) break;
85
+ map.push({ kernelIdx: k, dilation: d, quantile: (count * phi) % 1.0 });
86
+ count++;
87
+ }
88
+ if (count >= this.targetFeatures) break;
89
+ }
90
+ return map;
91
+ }
92
+
93
+ /**
94
+ * Shared prefix sum computation across all kernels sharing dilation d
95
+ * @private
96
+ */
97
+ _convolveBase(x, d) {
98
+ const L = this.windowLength;
99
+ const padding = Math.floor(((MR_KERNEL_LENGTH - 1) * d) / 2);
100
+ const mid = Math.floor(MR_KERNEL_LENGTH / 2);
101
+
102
+ this._ca.fill(0);
103
+ this._cg.fill(0);
104
+
105
+ for (let i = 0; i < L; i++) {
106
+ this._ca[i] = -x[i];
107
+ this._cg[mid * L + i] = 3.0 * x[i];
108
+ }
109
+
110
+ let end = L - padding;
111
+ for (let g = 0; g < mid; g++) {
112
+ const e = Math.min(end, L);
113
+ if (e > 0) {
114
+ const off = L - e;
115
+ for (let j = 0; j < e; j++) {
116
+ this._ca[off + j] -= x[j];
117
+ this._cg[g * L + off + j] = 3.0 * x[j];
118
+ }
119
+ }
120
+ end += d;
121
+ }
122
+
123
+ let start = d;
124
+ for (let g = mid + 1; g < MR_KERNEL_LENGTH; g++) {
125
+ const m = L - start;
126
+ for (let j = 0; j < m; j++) {
127
+ this._ca[j] -= x[start + j];
128
+ this._cg[g * L + j] = 3.0 * x[start + j];
129
+ }
130
+ start += d;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Evaluate kernel output using precomputed prefix sums and 3 index lookups
136
+ * @private
137
+ */
138
+ _kernelOut(kernelIdx) {
139
+ const L = this.windowLength;
140
+ const i0 = this.kernelIndices[kernelIdx * 3 + 0] * L;
141
+ const i1 = this.kernelIndices[kernelIdx * 3 + 1] * L;
142
+ const i2 = this.kernelIndices[kernelIdx * 3 + 2] * L;
143
+
144
+ for (let i = 0; i < L; i++) {
145
+ this._c[i] = this._ca[i] + this._cg[i0 + i] + this._cg[i1 + i] + this._cg[i2 + i];
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Load pre-computed bias thresholds
151
+ * @param {number[]|Float32Array} thresholdsArray
152
+ */
153
+ loadThresholds(thresholdsArray) {
154
+ this.thresholds = Float32Array.from(thresholdsArray);
155
+ }
156
+
157
+ /**
158
+ * Fit bias thresholds from a collection of training series
159
+ * @param {Array<number[]|Float64Array>} trainingWindows
160
+ */
161
+ fit(trainingWindows) {
162
+ const N = trainingWindows.length;
163
+ if (N === 0) throw new Error('Training windows cannot be empty.');
164
+ this.thresholds = new Float32Array(this.featureMap.length);
165
+
166
+ for (let f = 0; f < this.featureMap.length; f++) {
167
+ const feat = this.featureMap[f];
168
+ this._convolveBase(trainingWindows[f % N], feat.dilation);
169
+ this._kernelOut(feat.kernelIdx);
170
+
171
+ const sorted = Float64Array.from(this._c).sort();
172
+ const qIdx = Math.min(this.windowLength - 1, Math.floor(feat.quantile * this.windowLength));
173
+ this.thresholds[f] = sorted[qIdx];
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Transform a 1D input series into Proportion of Positive Values (PPV) representation
179
+ * @param {number[]|Float64Array} series Equispaced series of length equal to windowLength
180
+ * @returns {Float32Array} PPV feature vector
181
+ */
182
+ transform(series) {
183
+ if (!this.thresholds) {
184
+ throw new Error('MiniRocket requires fitted or loaded thresholds.');
185
+ }
186
+ if (series.length !== this.windowLength) {
187
+ throw new Error(`Input series length (${series.length}) must match windowLength (${this.windowLength}).`);
188
+ }
189
+
190
+ const out = new Float32Array(this.featureMap.length);
191
+ let currentDilation = -1;
192
+
193
+ for (let f = 0; f < this.featureMap.length; f++) {
194
+ const feat = this.featureMap[f];
195
+
196
+ if (feat.dilation !== currentDilation) {
197
+ this._convolveBase(series, feat.dilation);
198
+ currentDilation = feat.dilation;
199
+ }
200
+
201
+ this._kernelOut(feat.kernelIdx);
202
+
203
+ const thresh = this.thresholds[f];
204
+ let pos = 0;
205
+ for (let i = 0; i < this.windowLength; i++) {
206
+ if (this._c[i] > thresh) pos++;
207
+ }
208
+ out[f] = pos / this.windowLength;
209
+ }
210
+
211
+ return out;
212
+ }
213
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * @file src/biometrics/ridge.js
3
+ * Dual Regularized Ridge Regression Solver with Cholesky Decomposition
4
+ * Solves ridge regression in dual space when feature dimension P >> N, recovering
5
+ * primal weights for O(P) single dot-product inference latency.
6
+ *
7
+ * @author ChdLabs
8
+ * @license MIT
9
+ */
10
+
11
+ export class DualRidgeClassifier {
12
+ /**
13
+ * @param {number} [alpha=10.0] L2 regularization strength
14
+ */
15
+ constructor(alpha = 10.0) {
16
+ this.alpha = alpha;
17
+ this.weights = null;
18
+ this.bias = 0.0;
19
+ }
20
+
21
+ /**
22
+ * Load pre-fitted weights and bias
23
+ * @param {number[]|Float32Array} weightsArray
24
+ * @param {number} [bias=0.0]
25
+ */
26
+ loadWeights(weightsArray, bias = 0.0) {
27
+ this.weights = Float32Array.from(weightsArray);
28
+ this.bias = bias;
29
+ }
30
+
31
+ /**
32
+ * Fit Dual Ridge classifier from feature matrix X and labels y
33
+ * @param {Array<Float32Array|number[]>} X Feature matrix (N samples x P features)
34
+ * @param {number[]} y Target labels (-1 for bot, +1 for human)
35
+ */
36
+ fit(X, y) {
37
+ const N = X.length;
38
+ if (N === 0) throw new Error('Training data X cannot be empty.');
39
+ const P = X[0].length;
40
+
41
+ let meanY = 0;
42
+ for (let i = 0; i < N; i++) meanY += y[i];
43
+ meanY /= N;
44
+ this.bias = meanY;
45
+
46
+ // Gram Matrix K = X * X^T + alpha * I
47
+ const K = Array.from({ length: N }, () => new Float64Array(N));
48
+ for (let i = 0; i < N; i++) {
49
+ for (let j = i; j < N; j++) {
50
+ let dot = 0;
51
+ for (let k = 0; k < P; k++) dot += X[i][k] * X[j][k];
52
+ K[i][j] = dot;
53
+ K[j][i] = dot;
54
+ }
55
+ K[i][i] += this.alpha;
56
+ }
57
+
58
+ // Cholesky Solve L * L^T * beta = (y - meanY)
59
+ const yC = y.map(v => v - this.bias);
60
+ const beta = this._choleskySolve(K, yC);
61
+
62
+ // Primal Recovery w = X^T * beta
63
+ this.weights = new Float32Array(P);
64
+ for (let j = 0; j < P; j++) {
65
+ let sum = 0;
66
+ for (let i = 0; i < N; i++) sum += beta[i] * X[i][j];
67
+ this.weights[j] = sum;
68
+ }
69
+
70
+ // Exact intercept b = mean(y) - w^T * mean(X)
71
+ let wMeanX = 0;
72
+ for (let j = 0; j < P; j++) {
73
+ let meanX_j = 0;
74
+ for (let i = 0; i < N; i++) meanX_j += X[i][j];
75
+ meanX_j /= N;
76
+ wMeanX += this.weights[j] * meanX_j;
77
+ }
78
+ this.bias = meanY - wMeanX;
79
+ }
80
+
81
+ /**
82
+ * Predict human classification probability for a feature vector
83
+ * @param {Float32Array|number[]} features
84
+ * @returns {{ score: number, probability: number, isHuman: boolean }}
85
+ */
86
+ predict(features) {
87
+ if (!this.weights) {
88
+ throw new Error('DualRidgeClassifier requires fitted or loaded weights.');
89
+ }
90
+
91
+ let score = this.bias;
92
+ const P = this.weights.length;
93
+ for (let i = 0; i < P; i++) {
94
+ score += this.weights[i] * features[i];
95
+ }
96
+
97
+ // Sigmoid mapping: P(Human) = 1 / (1 + exp(-score))
98
+ const probability = 1 / (1 + Math.exp(-Math.max(-50, Math.min(50, score))));
99
+ return {
100
+ score,
101
+ probability,
102
+ isHuman: score >= 0
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Solve A * x = b via Cholesky decomposition A = L * L^T
108
+ * @private
109
+ */
110
+ _choleskySolve(A, b) {
111
+ const n = b.length;
112
+ const L = Array.from({ length: n }, () => new Float64Array(n));
113
+
114
+ for (let i = 0; i < n; i++) {
115
+ for (let j = 0; j <= i; j++) {
116
+ let sum = 0;
117
+ for (let k = 0; k < j; k++) sum += L[i][k] * L[j][k];
118
+ if (i === j) {
119
+ const val = A[i][i] - sum;
120
+ L[i][j] = Math.sqrt(val > 0 ? val : 1e-9);
121
+ } else {
122
+ L[i][j] = (A[i][j] - sum) / L[j][j];
123
+ }
124
+ }
125
+ }
126
+
127
+ // Forward solve: L * z = b
128
+ const z = new Float64Array(n);
129
+ for (let i = 0; i < n; i++) {
130
+ let sum = 0;
131
+ for (let k = 0; k < i; k++) sum += L[i][k] * z[k];
132
+ z[i] = (b[i] - sum) / L[i][i];
133
+ }
134
+
135
+ // Back substitution: L^T * x = z
136
+ const x = new Float64Array(n);
137
+ for (let i = n - 1; i >= 0; i--) {
138
+ let sum = 0;
139
+ for (let k = i + 1; k < n; k++) sum += L[k][i] * x[k];
140
+ x[i] = (z[i] - sum) / L[i][i];
141
+ }
142
+ return x;
143
+ }
144
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @file src/client/collector.js
3
+ * Browser Client Kinematics Telemetry Collector
4
+ * Captures pointer dynamics and normalizes trajectories to equispaced kinematic series
5
+ * for behavioral biometric classification. Zero external dependencies.
6
+ *
7
+ * @author ChdLabs
8
+ * @license MIT
9
+ */
10
+
11
+ export class MouseTelemetryCollector {
12
+ /**
13
+ * @param {Object} [options={}]
14
+ * @param {number} [options.windowLength=128] Number of equispaced time points
15
+ * @param {number} [options.sampleIntervalMs=10] Equispaced time delta (10ms = 100Hz)
16
+ * @param {number} [options.maxBufferSize=500] Raw event sliding buffer size
17
+ */
18
+ constructor(options = {}) {
19
+ this.windowLength = options.windowLength || 128;
20
+ this.sampleIntervalMs = options.sampleIntervalMs || 10;
21
+ this.maxBufferSize = options.maxBufferSize || 500;
22
+ this.buffer = [];
23
+ this.isListening = false;
24
+ this._onMove = this._onMove.bind(this);
25
+ }
26
+
27
+ /**
28
+ * Start capturing mouse movement events
29
+ */
30
+ start() {
31
+ if (this.isListening || typeof window === 'undefined') return;
32
+ window.addEventListener('mousemove', this._onMove, { passive: true });
33
+ this.isListening = true;
34
+ }
35
+
36
+ /**
37
+ * Stop capturing mouse movement events
38
+ */
39
+ stop() {
40
+ if (!this.isListening || typeof window === 'undefined') return;
41
+ window.removeEventListener('mousemove', this._onMove);
42
+ this.isListening = false;
43
+ }
44
+
45
+ /**
46
+ * Event listener callback
47
+ * @private
48
+ */
49
+ _onMove(e) {
50
+ this.buffer.push({
51
+ x: e.clientX,
52
+ y: e.clientY,
53
+ t: typeof performance !== 'undefined' ? performance.now() : Date.now(),
54
+ trusted: e.isTrusted !== false
55
+ });
56
+
57
+ if (this.buffer.length > this.maxBufferSize) {
58
+ this.buffer.shift();
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Resamples trajectory to fixed length and derives kinematic velocity vector.
64
+ * @returns {{ velocity: number[], isTrusted: boolean, timestamp: number } | null}
65
+ */
66
+ getPayload() {
67
+ const requiredDuration = this.windowLength * this.sampleIntervalMs;
68
+ if (this.buffer.length < 10) return null;
69
+
70
+ const latest = this.buffer[this.buffer.length - 1];
71
+ const threshold = latest.t - requiredDuration;
72
+ const points = this.buffer.filter(p => p.t >= threshold);
73
+
74
+ if (points.length < 8) return null;
75
+
76
+ // Check synthetic dispatch
77
+ const allTrusted = points.every(p => p.trusted === true);
78
+
79
+ const startTime = points[0].t;
80
+ const L = this.windowLength;
81
+ const dt = this.sampleIntervalMs;
82
+
83
+ const resampledX = new Float64Array(L);
84
+ const resampledY = new Float64Array(L);
85
+ let rawIdx = 0;
86
+
87
+ // 1. Equispaced linear interpolation
88
+ for (let i = 0; i < L; i++) {
89
+ const targetT = startTime + i * dt;
90
+ while (rawIdx < points.length - 2 && points[rawIdx + 1].t < targetT) {
91
+ rawIdx++;
92
+ }
93
+ const p0 = points[rawIdx];
94
+ const p1 = points[Math.min(rawIdx + 1, points.length - 1)];
95
+ const segmentDt = p1.t - p0.t;
96
+ const alpha = segmentDt > 0 ? Math.min(Math.max((targetT - p0.t) / segmentDt, 0), 1) : 0;
97
+
98
+ resampledX[i] = p0.x + alpha * (p1.x - p0.x);
99
+ resampledY[i] = p0.y + alpha * (p1.y - p0.y);
100
+ }
101
+
102
+ // 2. Velocity Derivation
103
+ const velocity = new Float64Array(L);
104
+ for (let i = 1; i < L; i++) {
105
+ const dx = resampledX[i] - resampledX[i - 1];
106
+ const dy = resampledY[i] - resampledY[i - 1];
107
+ velocity[i] = Math.hypot(dx, dy) / dt;
108
+ }
109
+ velocity[0] = velocity[1];
110
+
111
+ return {
112
+ velocity: Array.from(velocity),
113
+ isTrusted: allTrusted,
114
+ timestamp: Date.now()
115
+ };
116
+ }
117
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @file src/client/index.js
3
+ * Browser Client Suite for @chdlabs/my-shield
4
+ * Exports behavioral biometrics kinematics collector and DOM tripwire decoy.
5
+ *
6
+ * @author ChdLabs
7
+ * @license MIT
8
+ */
9
+
10
+ export { MouseTelemetryCollector } from './collector.js';
11
+
12
+ /**
13
+ * Injects invisible DOM tripwire decoy forms and links to trap automated scrapers
14
+ */
15
+ export function injectTripwire() {
16
+ if (typeof document === 'undefined') return;
17
+
18
+ const trapContainer = document.createElement('div');
19
+ trapContainer.setAttribute('aria-hidden', 'true');
20
+ trapContainer.style.position = 'absolute';
21
+ trapContainer.style.left = '-9999px';
22
+ trapContainer.style.top = '-9999px';
23
+ trapContainer.style.width = '1px';
24
+ trapContainer.style.height = '1px';
25
+ trapContainer.style.overflow = 'hidden';
26
+ trapContainer.style.opacity = '0';
27
+ trapContainer.style.pointerEvents = 'none';
28
+
29
+ // Decoy form
30
+ const form = document.createElement('form');
31
+ form.setAttribute('method', 'POST');
32
+ form.setAttribute('action', '/api/__shield_trap');
33
+
34
+ const inputUser = document.createElement('input');
35
+ inputUser.setAttribute('type', 'text');
36
+ inputUser.setAttribute('name', 'admin_override');
37
+ inputUser.setAttribute('tabindex', '-1');
38
+ inputUser.setAttribute('autocomplete', 'off');
39
+
40
+ const inputPass = document.createElement('input');
41
+ inputPass.setAttribute('type', 'password');
42
+ inputPass.setAttribute('name', 'debug_pass');
43
+ inputPass.setAttribute('tabindex', '-1');
44
+
45
+ form.appendChild(inputUser);
46
+ form.appendChild(inputPass);
47
+ trapContainer.appendChild(form);
48
+
49
+ // Decoy link
50
+ const link = document.createElement('a');
51
+ link.setAttribute('href', '/api/__shield_trap');
52
+ link.setAttribute('rel', 'nofollow');
53
+ link.setAttribute('tabindex', '-1');
54
+ link.textContent = 'Admin Console';
55
+ trapContainer.appendChild(link);
56
+
57
+ document.body.appendChild(trapContainer);
58
+ }
59
+
60
+ // Auto-inject if document is available and ready
61
+ if (typeof document !== 'undefined') {
62
+ if (document.readyState === 'loading') {
63
+ document.addEventListener('DOMContentLoaded', injectTripwire);
64
+ } else {
65
+ injectTripwire();
66
+ }
67
+ }
package/src/index.d.ts CHANGED
@@ -47,12 +47,39 @@ export interface AdminOptions {
47
47
  enabled?: boolean;
48
48
  }
49
49
 
50
+ export interface BehavioralGuardOptions {
51
+ threshold?: number;
52
+ headerName?: string;
53
+ replayWindowMs?: number;
54
+ allowFallbackOnError?: boolean;
55
+ autoBlockIP?: boolean;
56
+ onBotDetected?: (req: any, res: any, meta: any) => void;
57
+ }
58
+
59
+ export interface BiometricsOptions {
60
+ modelWeights?: {
61
+ thresholds: number[];
62
+ weights: number[];
63
+ bias: number;
64
+ windowLength?: number;
65
+ numFeatures?: number;
66
+ } | null;
67
+ }
68
+
69
+ export interface BehavioralVerificationResult {
70
+ score: number;
71
+ probability: number;
72
+ isHuman: boolean;
73
+ latencyMs: number;
74
+ }
75
+
50
76
  export interface ShieldOptions {
51
77
  whitelist?: string[];
52
78
  bloom?: BloomOptions;
53
79
  lru?: LRUOptions;
54
80
  honeypot?: HoneypotOptions;
55
81
  intelligence?: IntelligenceOptions;
82
+ biometrics?: BiometricsOptions;
56
83
  admin?: AdminOptions;
57
84
  blockStatusCode?: number;
58
85
  logger?: {
@@ -82,6 +109,8 @@ export class Shield extends EventEmitter {
82
109
  adminRouter(options?: AdminRouterOptions): Router;
83
110
  costThrottle(options?: CostThrottleOptions): RequestHandler;
84
111
  keyLeakDetector(): RequestHandler;
112
+ behavioralGuard(options?: BehavioralGuardOptions): RequestHandler;
113
+ verifyBiometrics(velocity: number[] | Float64Array): BehavioralVerificationResult;
85
114
 
86
115
  blockIP(ip: string, reason?: string, metadata?: Record<string, any>, customTtlMs?: number | null): void;
87
116
  unblockIP(ip: string): void;
@@ -99,5 +128,36 @@ export class Shield extends EventEmitter {
99
128
  shutdown(): Promise<void>;
100
129
  }
101
130
 
131
+ export class MiniRocket {
132
+ windowLength: number;
133
+ targetFeatures: number;
134
+ thresholds: Float32Array | null;
135
+ constructor(windowLength?: number, numFeatures?: number);
136
+ loadThresholds(thresholdsArray: number[] | Float32Array): void;
137
+ fit(trainingWindows: Array<number[] | Float64Array>): void;
138
+ transform(series: number[] | Float64Array): Float32Array;
139
+ }
140
+
141
+ export class DualRidgeClassifier {
142
+ alpha: number;
143
+ weights: Float32Array | null;
144
+ bias: number;
145
+ constructor(alpha?: number);
146
+ loadWeights(weightsArray: number[] | Float32Array, bias?: number): void;
147
+ fit(X: Array<Float32Array | number[]>, y: number[]): void;
148
+ predict(features: Float32Array | number[]): { score: number; probability: number; isHuman: boolean };
149
+ }
150
+
151
+ export class MouseTelemetryCollector {
152
+ windowLength: number;
153
+ sampleIntervalMs: number;
154
+ maxBufferSize: number;
155
+ constructor(options?: { windowLength?: number; sampleIntervalMs?: number; maxBufferSize?: number });
156
+ start(): void;
157
+ stop(): void;
158
+ getPayload(): { velocity: number[]; isTrusted: boolean; timestamp: number } | null;
159
+ }
160
+
161
+ export function injectTripwire(): void;
102
162
  export function createShield(options?: ShieldOptions): Shield;
103
163
  export default createShield;
package/src/index.js CHANGED
@@ -17,6 +17,9 @@ import { NotFoundCollector } from './middleware/notFoundCollector.js';
17
17
  import { ShieldGuard } from './middleware/shieldGuard.js';
18
18
  import { CostThrottle } from './middleware/costThrottle.js';
19
19
  import { KeyLeakDetector } from './middleware/keyLeakDetector.js';
20
+ import { BehavioralShieldService } from './middleware/behavioralGuard.js';
21
+ import { MiniRocket } from './biometrics/minirocket.js';
22
+ import { DualRidgeClassifier } from './biometrics/ridge.js';
20
23
  import { createAdminRouter } from './admin/adminRouter.js';
21
24
 
22
25
  export class Shield extends EventEmitter {
@@ -122,7 +125,15 @@ export class Shield extends EventEmitter {
122
125
  logger: this.logger
123
126
  });
124
127
 
125
- // 8. Admin Dashboard Configuration (Default: false)
128
+ // 8. Behavioral Biometrics Subsystem (MiniRocket + Dual Ridge)
129
+ const biometricsOpts = options.biometrics || {};
130
+ this.behavioralShieldService = new BehavioralShieldService({
131
+ modelWeights: biometricsOpts.modelWeights || null,
132
+ shield: this,
133
+ logger: this.logger
134
+ });
135
+
136
+ // 9. Admin Dashboard Configuration (Default: false)
126
137
  const adminOpts = options.admin || {};
127
138
  this.adminEnabled = Boolean(adminOpts.enabled);
128
139
  }
@@ -171,6 +182,23 @@ export class Shield extends EventEmitter {
171
182
  return this.keyLeakDetectorInstance.middleware();
172
183
  }
173
184
 
185
+ /**
186
+ * Route-level Behavioral Biometrics Express Middleware
187
+ * Validates mouse kinematics against MiniRocket model
188
+ * @param {Object} [opts]
189
+ */
190
+ behavioralGuard(opts) {
191
+ return this.behavioralShieldService.middleware(opts);
192
+ }
193
+
194
+ /**
195
+ * Verify kinematics velocity series against MiniRocket model
196
+ * @param {number[]|Float64Array} velocityArray
197
+ */
198
+ verifyBiometrics(velocityArray) {
199
+ return this.behavioralShieldService.verify(velocityArray);
200
+ }
201
+
174
202
  /**
175
203
  * Block an IP address immediately
176
204
  * @param {string} ip
@@ -318,4 +346,8 @@ export function createShield(options = {}) {
318
346
  return new Shield(options);
319
347
  }
320
348
 
349
+ export { MiniRocket } from './biometrics/minirocket.js';
350
+ export { DualRidgeClassifier } from './biometrics/ridge.js';
351
+ export { BehavioralShieldService } from './middleware/behavioralGuard.js';
352
+
321
353
  export default createShield;