@redsocs/spam-warden 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,40 +26,20 @@ You can test the spam engine interactively, analyze your forms, and generate aut
26
26
  > **Are you a Thai government agency or public sector website administrator?**
27
27
  > Get your free token configuration and drop-in script to protect your online portals from annoying gambling/loan ads and spam campaigns at [redsocs.com/spam-warden](https://redsocs.com/spam-warden).
28
28
 
29
- ### 1. The "No-Code" Way (Auto-Blocking)
29
+ ### 1. Drop-in Protection (Heuristic Auto-Blocking)
30
30
 
31
- Add this script to your page. It will automatically find your form and block submission if spam is detected.
31
+ Add this script to your page. It will automatically find your most significant forms (using an intelligent heuristic: top 2 forms with >= 2 inputs) and block submission if spam is detected.
32
32
 
33
33
  ```html
34
- <script src="https://cdn.redsocs.com/js/spamwarden.min.js?client=cG9zdHEtZm9ybXxtZXNzYWdlLWlucHV0fDE"></script>
34
+ <script src="https://cdn.redsocs.com/js/spamwarden.min.js?client=MHxzaWVtLnJlZHNvY3MuY29tL3Yx"></script>
35
35
  ```
36
36
 
37
- _Note: The `client` parameter is a Base64 configuration string of `formId|inputId|sdFlag|siemEndpoint` (e.g., `postq-form|message-input|1` encoded)._
37
+ _Note: The `client` parameter is a Base64 configuration string of `sdFlag|siemEndpoint` (e.g., `0|siem.redsocs.com/v1` encoded)._
38
38
 
39
- ### 2. Manual Configuration
40
-
41
- ```html
42
- <script src="dist/spamwarden.min.js"></script>
43
- <script>
44
- spamwarden.configure({
45
- siteToken: "YOUR_TOKEN",
46
- formId: "contact-form",
47
- inputId: "message-field",
48
- autoReport: true,
49
- isTrusted: true, // Required to authorize telemetry reporting
50
- reportSD: true, // Optional: Enable PII/DLP leak telemetry auditing
51
- siemEndpoint: "https://api.yourdomain.com/v1/telemetry", // Optional: Custom secondary SIEM/SOC endpoint
52
- onSpam: (result) => {
53
- alert("Please do not send spam!");
54
- },
55
- });
56
- </script>
57
- ```
58
-
59
- ### 3. API Usage (Node or Browser)
39
+ ### 2. API Usage (Node or Browser)
60
40
 
61
41
  ```javascript
62
- const result = spamwarden.spamcheck("สมัครสมาชิกวันนี้ รับโบนัส ฟรี!");
42
+ const result = spamwarden.spamcheck("[Hello, this is a Thai casino & scam ads — and guess what? Your tax pays for my traffic.]");
63
43
  if (result.isSpam) {
64
44
  console.log("Blocked:", result.reason || "AI match");
65
45
  console.log("Confidence:", result.prob);
@@ -112,7 +92,7 @@ You can spin up a local simulation server to test the DOM auto-blocking behavior
112
92
  ```text
113
93
  🚨 [SIEM RECEIVER] Blocked Payload Received!
114
94
  ================================================
115
- Client Token: cG9zdHEtZm9ybXxtZXNzYWdlLWlucHV0fDF8aHR0cDovL2xvY2FsaG9zdDozMDAwL3YxL3RlbGVtZXRyeQ
95
+ Client Token: MXxodHRwOi8vbG9jYWxob3N0OjMwMDAvdjEvdGVsZW1ldHJ5
116
96
  URL: h_tt_p://localhost:3000/
117
97
  Rule Matched: currency_symbol
118
98
  Confidence: 100%
@@ -125,7 +105,7 @@ You can spin up a local simulation server to test the DOM auto-blocking behavior
125
105
 
126
106
  # About
127
107
 
128
- - **Version:** 1.0.6 (v2 Engine)
108
+ - **Version:** 1.0.7 (v2 Engine)
129
109
  - **Author:** [RedSocs](https://github.com/RedSocs)
130
110
  - **License:** MIT
131
111
  - **Model Origin:** Trained via [RedSocs/spam-labeler](https://github.com/RedSocs/spam-labeler)
@@ -41,7 +41,6 @@ const SpamWarden = {
41
41
 
42
42
  /**
43
43
  * Configure the warden for secondary SIEM/SOC reporting.
44
- * Note: DOM protection (formId/inputId) is handled automatically via the Digital Key.
45
44
  */
46
45
  configure: function(options) {
47
46
  if (options.endpoint !== undefined) this._config.endpoint = options.endpoint;
@@ -53,11 +52,6 @@ const SpamWarden = {
53
52
  this._config.isTrusted = !!options.isTrusted;
54
53
  if (typeof options.onSpam === "function") this._config.onSpam = options.onSpam;
55
54
  if (typeof options.customReporter === "function") this._config.customReporter = options.customReporter;
56
-
57
- // Auto-Bind DOM if formId and inputId are passed manually (synced with README)
58
- if (options.formId && options.inputId) {
59
- this._bind(options.formId, options.inputId);
60
- }
61
55
  },
62
56
 
63
57
  /**
@@ -76,58 +70,88 @@ const SpamWarden = {
76
70
 
77
71
  /**
78
72
  * Internal: Attaches protection to the DOM.
79
-
80
- * Only called via the Base64 configuration parser.
73
+ * Can accept a string ID or a direct Form Element.
81
74
  */
82
- _bind: function(formId, inputId) {
75
+ _bind: function(target) {
83
76
  let hasPasted = false;
84
77
 
85
- const bindForm = () => {
86
- // Priority: ID > Name
87
- const form = document.getElementById(formId) || document.querySelector(`form[name="${formId}"]`);
88
- if (form) {
89
- // Query visible text-bearing elements
90
- const getElements = () => {
91
- return Array.from(form.querySelectorAll('input[type="text"], input:not([type]), textarea'))
92
- .filter(el => {
93
- if (el.type === 'hidden') return false;
94
- try {
95
- const style = window.getComputedStyle(el);
96
- if (style.display === 'none' || style.visibility === 'hidden') return false;
97
- } catch (e) {}
98
- if (el.offsetWidth === 0 && el.offsetHeight === 0) return false;
99
- return true;
100
- });
101
- };
102
-
103
- // Attach paste listeners to all detected visible fields
104
- getElements().forEach(el => {
105
- el.addEventListener("paste", () => {
106
- hasPasted = true;
78
+ const bindForm = (form) => {
79
+ if (!form) return;
80
+
81
+ // Query visible text-bearing elements
82
+ const getElements = () => {
83
+ return Array.from(form.querySelectorAll('input[type="text"], input:not([type]), textarea'))
84
+ .filter(el => {
85
+ if (el.type === 'hidden' || el.type === 'password') return false;
86
+ try {
87
+ const style = window.getComputedStyle(el);
88
+ if (style.display === 'none' || style.visibility === 'hidden') return false;
89
+ } catch (e) {}
90
+ if (el.offsetWidth === 0 && el.offsetHeight === 0) return false;
91
+ return true;
107
92
  });
93
+ };
94
+
95
+ // Attach paste listeners to all detected visible fields
96
+ getElements().forEach(el => {
97
+ el.addEventListener("paste", () => {
98
+ hasPasted = true;
108
99
  });
100
+ });
101
+
102
+ form.addEventListener("submit", (e) => {
103
+ // Re-query during submit to capture dynamically added elements
104
+ const combinedText = getElements()
105
+ .map(el => el.value)
106
+ .filter(val => val.trim() !== '')
107
+ .join('\n');
108
+
109
+ const result = this.spamcheck(combinedText, { pasted: hasPasted });
110
+ result.pasted = hasPasted; // Inject flag before callback
111
+
112
+ if (result.isSpam) {
113
+ e.preventDefault();
114
+ if (this._config.onSpam) { this._config.onSpam(result); }
115
+ else { alert("Submission Blocked: Spam detected."); }
116
+ }
117
+ });
118
+ };
109
119
 
110
- form.addEventListener("submit", (e) => {
111
- // Re-query during submit to capture dynamically added elements
112
- const combinedText = getElements()
113
- .map(el => el.value)
114
- .filter(val => val.trim() !== '')
115
- .join('\n');
120
+ const init = () => {
121
+ const form = (typeof target === "string") ?
122
+ (document.getElementById(target) || document.querySelector(`form[name="${target}"]`)) :
123
+ target;
124
+ bindForm(form);
125
+ };
116
126
 
117
- const result = this.spamcheck(combinedText, { pasted: hasPasted });
118
- result.pasted = hasPasted; // Inject flag before callback
127
+ if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); }
128
+ else { init(); }
129
+ },
119
130
 
120
- if (result.isSpam) {
121
- e.preventDefault();
131
+ /**
132
+ * Heuristic: Auto-discover the top 2 forms on the page that have at least 2 text inputs.
133
+ * This ignores simple search bars or login fields.
134
+ */
135
+ _autoBind: function() {
136
+ const bindAll = () => {
137
+ const forms = Array.from(document.querySelectorAll('form'));
138
+ const candidates = forms.map(form => {
139
+ const inputs = Array.from(form.querySelectorAll('input[type="text"], input:not([type]), textarea'))
140
+ .filter(el => {
141
+ if (el.type === 'hidden' || el.type === 'password') return false;
142
+ return el.offsetWidth > 0 || el.offsetHeight > 0;
143
+ });
144
+ return { form, count: inputs.length };
145
+ })
146
+ .filter(c => c.count >= 2)
147
+ .sort((a, b) => b.count - a.count)
148
+ .slice(0, 2);
122
149
 
123
- if (this._config.onSpam) { this._config.onSpam(result); }
124
- else { alert("Submission Blocked: Spam detected."); }
125
- }
126
- });
127
- }
150
+ candidates.forEach(c => this._bind(c.form));
128
151
  };
129
- if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", bindForm); }
130
- else { bindForm(); }
152
+
153
+ if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", bindAll); }
154
+ else { bindAll(); }
131
155
  },
132
156
 
133
157
  // ── 2. SIEM Internal Reporting & Actor Extraction ────────────────
@@ -416,11 +440,11 @@ if (typeof document !== "undefined" && document.currentScript) {
416
440
 
417
441
  const decodedString = atob(base64Config);
418
442
  const parts = decodedString.split("|");
419
- if (parts.length >= 3) {
420
- const formId = parts[0];
421
- const inputId = parts[1];
422
- const sdFlag = parts[2];
423
- const siemEndpointRaw = parts[3] || null;
443
+
444
+ if (parts.length >= 2) {
445
+ // Simplified Format: sdFlag|siemEndpoint
446
+ const sdFlag = parts[0];
447
+ const siemEndpointRaw = parts[1] || null;
424
448
 
425
449
  if (siemEndpointRaw) {
426
450
  SpamWarden._config.siemEndpoint = siemEndpointRaw.indexOf(",") !== -1 ?
@@ -433,8 +457,8 @@ if (typeof document !== "undefined" && document.currentScript) {
433
457
  SpamWarden._config.autoReport = true;
434
458
  SpamWarden._config.isTrusted = true;
435
459
 
436
- // Force DOM binding only through the key
437
- SpamWarden._bind(formId, inputId);
460
+ // Trigger Heuristic Auto-Binding
461
+ SpamWarden._autoBind();
438
462
  }
439
463
  }
440
464
  } catch (e) { console.error("[SpamWarden] Failed to parse client configuration string."); }