Invisible bot-protection primitives for shared use between public forms (client) and the hub's per-route `verifyHuman` gate (server). Designed as a pure, React-free server entry so the hub can import it without triggering a client-reference boundary. ## Key Components | Export | Type | Description | |---|---|---| | `HONEYPOT_FIELD` | `const string` | Hidden field name bots may fill; real users never interact with it | | `ELAPSED_MS_FIELD` | `const string` | Field name carrying client-measured ms from form mount to submit | | `DEFAULT_MIN_FILL_MS` | `const number` | Minimum acceptable fill time (`700` ms); faster submits are treated as bots | | `HumanitySignals` | `type` | Wire object spread into the POST body by `useHumanitySignals().getSignals()` | | `HumanityVerdict` | `type` | Union result — `{ ok: true }` or `{ ok: false; reason: 'honeypot' \| 'too_fast' }` | | `extractHumanitySignals` | `function` | Tolerant reader that parses raw POST body; never throws, returns `null` for missing/garbage timing | | `evaluateHumanitySignals` | `function` | Single source of truth for the block/allow decision — honeypot fill or sub-threshold timing → reject | | `splitCsvEnv` | `function` | Utility to parse comma-separated env strings into trimmed, non-empty arrays | ## Usage Example ```typescript import { evaluateHumanitySignals, DEFAULT_MIN_FILL_MS, } from 'openframe-oss-lib/humanity-signals' // Server-side route handler (hub's verifyHuman gate) export async function POST(req: Request) { const body = await req.json() const verdict = evaluateHumanitySignals(body, { minFillMs: DEFAULT_MIN_FILL_MS, }) if (!verdict.ok) { // 'honeypot' — hidden field was filled // 'too_fast' — submission arrived before a human could type return new Response(`Blocked: ${verdict.reason}`, { status: 400 }) } // Proceed with legitimate submission... } ``` ```typescript // Client-side form — spread signals into the POST body import { HONEYPOT_FIELD, ELAPSED_MS_FIELD } from 'openframe-oss-lib/humanity-signals' const payload = { email: formData.email, message: formData.message, [HONEYPOT_FIELD]: '', // always empty for real users [ELAPSED_MS_FIELD]: elapsedMs, // measured since form mount } ``` > **Note:** Missing timing values (`elapsedMs: null`) are always allowed — only a present sub-threshold value triggers a `too_fast` block, preventing false positives from clients that omit the field. ## Source [`humanity-signals.ts`](https://github.com/flamingo-stack/openframe-oss-lib/blob/main/openframe-frontend-core/src/utils/humanity-signals.ts) ## `HUMANITY_SIGNAL_KEYS` `[HONEYPOT_FIELD, ELAPSED_MS_FIELD] as const` — the strip list for server handlers that forward form payloads upstream (HubSpot booking, CRM pushes). Strip by THIS array, never hand-typed strings.