@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 +8 -28
- package/dist/spamwarden.js +79 -55
- package/dist/spamwarden.min.js +2 -2
- package/package.json +1 -1
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.
|
|
29
|
+
### 1. Drop-in Protection (Heuristic Auto-Blocking)
|
|
30
30
|
|
|
31
|
-
Add this script to your page. It will automatically find your
|
|
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=
|
|
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 `
|
|
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.
|
|
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:
|
|
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.
|
|
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)
|
package/dist/spamwarden.js
CHANGED
|
@@ -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(
|
|
75
|
+
_bind: function(target) {
|
|
83
76
|
let hasPasted = false;
|
|
84
77
|
|
|
85
|
-
const bindForm = () => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
118
|
-
|
|
127
|
+
if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); }
|
|
128
|
+
else { init(); }
|
|
129
|
+
},
|
|
119
130
|
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
124
|
-
else { alert("Submission Blocked: Spam detected."); }
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
}
|
|
150
|
+
candidates.forEach(c => this._bind(c.form));
|
|
128
151
|
};
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const sdFlag = parts[
|
|
423
|
-
const siemEndpointRaw = parts[
|
|
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
|
-
//
|
|
437
|
-
SpamWarden.
|
|
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."); }
|