@ingram-tech/bot-protection 0.1.2 → 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.
package/README.md CHANGED
@@ -59,6 +59,16 @@ export async function POST(request: Request) {
59
59
  `verifyHuman` also accepts a plain object (`{ formData: { ...fields } }`) and a
60
60
  `timing` window (`{ minMs, maxMs }`). Pass `botid: false` to skip layer 3.
61
61
 
62
+ The honeypot field defaults to a name browsers and password managers won't
63
+ autofill (filling it would falsely flag real users). If that default collides
64
+ with a real field in your form, override it on both sides — they must match:
65
+
66
+ ```tsx
67
+ <HoneypotField token={token} field="subject_trap" />
68
+ // ...and on the server:
69
+ await verifyHuman({ formData, honeypotField: "subject_trap" });
70
+ ```
71
+
62
72
  ## Vercel BotID wiring (per app, layer 3 only)
63
73
 
64
74
  ```ts
package/dist/fields.d.ts CHANGED
@@ -2,8 +2,17 @@
2
2
  * Shared field names used by the honeypot component and the server verifier.
3
3
  * Kept in one place so the client and server never drift.
4
4
  */
5
- /** Hidden field real users never fill; bots auto-complete it. */
6
- export declare const HONEYPOT_FIELD = "company_url";
5
+ /**
6
+ * Default hidden field real users never fill; bots auto-complete it.
7
+ *
8
+ * Deliberately *not* a name browsers or password managers autofill — anything
9
+ * containing company / url / email / name / phone / address / organization /
10
+ * website gets filled for real users, which trips the trap and silently drops
11
+ * legitimate submissions. If this default would collide with a real field in
12
+ * your form (e.g. a genuine "subject"), override it: pass the same name as
13
+ * `field` to `<HoneypotField/>` and `honeypotField` to `verifyHuman()`.
14
+ */
15
+ export declare const HONEYPOT_FIELD = "contact_detail";
7
16
  /** Hidden field carrying the signed timing token. */
8
17
  export declare const TOKEN_FIELD = "_bp_token";
9
18
  //# sourceMappingURL=fields.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,iEAAiE;AACjE,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAE5C,qDAAqD;AACrD,eAAO,MAAM,WAAW,cAAc,CAAC"}
1
+ {"version":3,"file":"fields.d.ts","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,qDAAqD;AACrD,eAAO,MAAM,WAAW,cAAc,CAAC"}
package/dist/fields.js CHANGED
@@ -2,8 +2,17 @@
2
2
  * Shared field names used by the honeypot component and the server verifier.
3
3
  * Kept in one place so the client and server never drift.
4
4
  */
5
- /** Hidden field real users never fill; bots auto-complete it. */
6
- export const HONEYPOT_FIELD = "company_url";
5
+ /**
6
+ * Default hidden field real users never fill; bots auto-complete it.
7
+ *
8
+ * Deliberately *not* a name browsers or password managers autofill — anything
9
+ * containing company / url / email / name / phone / address / organization /
10
+ * website gets filled for real users, which trips the trap and silently drops
11
+ * legitimate submissions. If this default would collide with a real field in
12
+ * your form (e.g. a genuine "subject"), override it: pass the same name as
13
+ * `field` to `<HoneypotField/>` and `honeypotField` to `verifyHuman()`.
14
+ */
15
+ export const HONEYPOT_FIELD = "contact_detail";
7
16
  /** Hidden field carrying the signed timing token. */
8
17
  export const TOKEN_FIELD = "_bp_token";
9
18
  //# sourceMappingURL=fields.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fields.js","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,iEAAiE;AACjE,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAC;AAE5C,qDAAqD;AACrD,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC"}
1
+ {"version":3,"file":"fields.js","sourceRoot":"","sources":["../src/fields.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAE/C,qDAAqD;AACrD,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC"}
@@ -1,4 +1,5 @@
1
- export declare const HoneypotField: ({ token }: {
1
+ export declare const HoneypotField: ({ token, field, }: {
2
2
  token: string;
3
+ field?: string;
3
4
  }) => import("react/jsx-runtime").JSX.Element;
4
5
  //# sourceMappingURL=honeypot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"honeypot.d.ts","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":"AAuBA,eAAO,MAAM,aAAa,GAAI,WAAW;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,4CAczD,CAAC"}
1
+ {"version":3,"file":"honeypot.d.ts","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":"AA2BA,eAAO,MAAM,aAAa,GAAI,mBAG3B;IACF,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CACf,4CAmBA,CAAC"}
package/dist/honeypot.js CHANGED
@@ -9,6 +9,10 @@ import { HONEYPOT_FIELD, TOKEN_FIELD } from "./fields";
9
9
  * Real users never see or fill the honeypot (off-screen, aria-hidden,
10
10
  * tabIndex -1, autocomplete off); bots that fill every field give themselves
11
11
  * away.
12
+ *
13
+ * Pass `field` to use a custom honeypot name — it must match the
14
+ * `honeypotField` you pass to `verifyHuman()`, or the trap reads the wrong
15
+ * field and fails open.
12
16
  */
13
17
  const hidden = {
14
18
  position: "absolute",
@@ -18,5 +22,5 @@ const hidden = {
18
22
  height: "1px",
19
23
  overflow: "hidden",
20
24
  };
21
- export const HoneypotField = ({ token }) => (_jsxs("div", { "aria-hidden": "true", style: hidden, children: [_jsxs("label", { children: ["Leave this field empty", _jsx("input", { type: "text", name: HONEYPOT_FIELD, tabIndex: -1, autoComplete: "off", defaultValue: "" })] }), _jsx("input", { type: "hidden", name: TOKEN_FIELD, defaultValue: token })] }));
25
+ export const HoneypotField = ({ token, field = HONEYPOT_FIELD, }) => (_jsxs("div", { "aria-hidden": "true", style: hidden, children: [_jsxs("label", { children: ["Leave this field empty", _jsx("input", { type: "text", name: field, tabIndex: -1, autoComplete: "off", defaultValue: "", "data-1p-ignore": true, "data-lpignore": "true", "data-form-type": "other" })] }), _jsx("input", { type: "hidden", name: TOKEN_FIELD, defaultValue: token })] }));
22
26
  //# sourceMappingURL=honeypot.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"honeypot.js","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;;;;;;GASG;AAEH,MAAM,MAAM,GAAkB;IAC7B,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,KAAK;IACb,QAAQ,EAAE,QAAQ;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAE,KAAK,EAAqB,EAAE,EAAE,CAAC,CAC9D,8BAAiB,MAAM,EAAC,KAAK,EAAE,MAAM,aACpC,sDAEC,gBACC,IAAI,EAAC,MAAM,EACX,IAAI,EAAE,cAAc,EACpB,QAAQ,EAAE,CAAC,CAAC,EACZ,YAAY,EAAC,KAAK,EAClB,YAAY,EAAC,EAAE,GACd,IACK,EACR,gBAAO,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,GAAI,IAC1D,CACN,CAAC"}
1
+ {"version":3,"file":"honeypot.js","sourceRoot":"","sources":["../src/honeypot.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,GAAkB;IAC7B,QAAQ,EAAE,UAAU;IACpB,IAAI,EAAE,SAAS;IACf,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,KAAK;IACb,QAAQ,EAAE,QAAQ;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAC7B,KAAK,EACL,KAAK,GAAG,cAAc,GAItB,EAAE,EAAE,CAAC,CACL,8BAAiB,MAAM,EAAC,KAAK,EAAE,MAAM,aACpC,sDAEC,gBACC,IAAI,EAAC,MAAM,EACX,IAAI,EAAE,KAAK,EACX,QAAQ,EAAE,CAAC,CAAC,EACZ,YAAY,EAAC,KAAK,EAClB,YAAY,EAAC,EAAE,2CAID,MAAM,oBACL,OAAO,GACrB,IACK,EACR,gBAAO,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,GAAI,IAC1D,CACN,CAAC"}
package/dist/verify.d.ts CHANGED
@@ -11,6 +11,11 @@ export interface VerifyOptions {
11
11
  timing?: TokenCheck;
12
12
  /** Run the Vercel BotID layer (default true). Set false to skip. */
13
13
  botid?: boolean;
14
+ /**
15
+ * Honeypot field name to check. Must match the `field` rendered by
16
+ * `<HoneypotField/>`. Defaults to {@link HONEYPOT_FIELD}.
17
+ */
18
+ honeypotField?: string;
14
19
  }
15
20
  /**
16
21
  * Run all configured layers in order (cheapest first):
@@ -1 +1 @@
1
- {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC7B,0DAA0D;IAC1D,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAUD;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAU,SAAS,aAAa,KAAG,OAAO,CAAC,YAAY,CAqB9E,CAAC"}
1
+ {"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAmB,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC7B,0DAA0D;IAC1D,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,0CAA0C;IAC1C,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAUD;;;;;;;;;GASG;AACH,eAAO,MAAM,WAAW,GAAU,SAAS,aAAa,KAAG,OAAO,CAAC,YAAY,CAsB9E,CAAC"}
package/dist/verify.js CHANGED
@@ -19,7 +19,8 @@ const getField = (formData, name) => {
19
19
  * what tripped them.
20
20
  */
21
21
  export const verifyHuman = async (options) => {
22
- if (getField(options.formData, HONEYPOT_FIELD).trim() !== "") {
22
+ const honeypotField = options.honeypotField ?? HONEYPOT_FIELD;
23
+ if (getField(options.formData, honeypotField).trim() !== "") {
23
24
  return { ok: false, reason: "honeypot" };
24
25
  }
25
26
  // Timing layer only applies when a secret is configured; otherwise it's
@@ -1 +1 @@
1
- {"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAmB,eAAe,EAAE,MAAM,SAAS,CAAC;AAiB3D,MAAM,QAAQ,GAAG,CAAC,QAAmC,EAAE,IAAY,EAAU,EAAE;IAC9E,MAAM,KAAK,GACV,QAAQ,YAAY,QAAQ;QAC3B,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QACpB,CAAC,CAAE,QAAoC,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/C,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,OAAsB,EAAyB,EAAE;IAClF,IAAI,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC9D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC1C,CAAC;IAED,wEAAwE;IACxE,+EAA+E;IAC/E,IAAI,YAAY,EAAE,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,eAAe,CAC5B,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,EACvC,OAAO,CAAC,MAAM,CACd,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC;IAC7B,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QAC7B,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ,EAAE,CAAC;QACnC,IAAI,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAClD,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC"}
1
+ {"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAmB,eAAe,EAAE,MAAM,SAAS,CAAC;AAsB3D,MAAM,QAAQ,GAAG,CAAC,QAAmC,EAAE,IAAY,EAAU,EAAE;IAC9E,MAAM,KAAK,GACV,QAAQ,YAAY,QAAQ;QAC3B,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;QACpB,CAAC,CAAE,QAAoC,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/C,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,OAAsB,EAAyB,EAAE;IAClF,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,cAAc,CAAC;IAC9D,IAAI,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC1C,CAAC;IAED,wEAAwE;IACxE,+EAA+E;IAC/E,IAAI,YAAY,EAAE,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,eAAe,CAC5B,QAAQ,CAAC,OAAO,CAAC,QAAQ,EAAE,WAAW,CAAC,EACvC,OAAO,CAAC,MAAM,CACd,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC;IAC7B,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;QAC7B,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ,EAAE,CAAC;QACnC,IAAI,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAClD,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingram-tech/bot-protection",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Invisible, layered bot protection for forms — honeypot + signed timing token + Vercel BotID.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  "test": "vitest run"
36
36
  },
37
37
  "devDependencies": {
38
- "@ingram-tech/typescript-config": "0.1.0",
38
+ "@ingram-tech/typescript-config": "workspace:*",
39
39
  "@types/node": "^20.0.0",
40
40
  "@types/react": "^19.0.0",
41
41
  "react": "^19.0.0",