@webdecoy/ai-protection 0.1.0-alpha.1

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,166 @@
1
+ # Local rules + cloud detection
2
+
3
+ The SDK makes deterministic application-policy decisions locally and delegates bot
4
+ classification to WebDecoy. No detector weights, threat intelligence, WebAssembly
5
+ engine, prompt inspection or remote executable policies are shipped here.
6
+
7
+ ```text
8
+ Customer authenticates, validates input and loads trusted application context
9
+ → synchronous local rules
10
+ enforced denial/error → decision (no blocking cloud check)
11
+ otherwise → resolve trusted IP → WebDecoy account/config + detection
12
+ → immutable combined decision
13
+ → customer's application or convenience wrapper enforces it
14
+ → response streams unchanged
15
+ → bounded, best-effort central reporting and application observation sink
16
+ ```
17
+
18
+ ## Decision API
19
+
20
+ `createAIProtection(options)` remains callable as
21
+ `protect(request, handler, context?)`. It also exposes:
22
+
23
+ - `check(request, context)` → immutable decision, no automatic normal-outcome report.
24
+ - `report(decision, outcome?)` → best-effort reporting promise; once per decision
25
+ created by that instance. Foreign/already-reported decisions are ignored.
26
+ - `flush()` → waits for currently queued reports up to their individual timeouts.
27
+
28
+ A decision contains `id`, `conclusion` (`allow`/`deny`), a stable `reason`, denial
29
+ `status`, `degraded`, and `checks`. Each check has its source, mode, verdict, reason
30
+ and duration. A remote denial observed in cloud observation mode remains visible
31
+ in `checks` while the overall decision allows. Missing IP/account/detector or a
32
+ failed local rule marks coverage degraded; deliberate remote skipping after a
33
+ local denial does not.
34
+
35
+ `check()` does not invoke a model or grant authentication. Keep auth, origin
36
+ validation, input validation and quotas before it. For direct use, recheck
37
+ `request.signal.throwIfAborted()` immediately before starting inference. The
38
+ wrapper does this automatically and returns the original Response unmodified.
39
+ Cancellation during remote admission throws and emits a best-effort cancellation
40
+ record; it never produces a usable allow decision.
41
+
42
+ ## Trusted context and local rules
43
+
44
+ Context comes from the application's server code. The SDK cannot establish its
45
+ truth: use verified sessions, database entitlements and validated operation names.
46
+ Never spread a request body into this context or accept a browser's plan/user claim.
47
+ Context is passed only to local rules. The SDK neither serializes it to detection
48
+ nor copies it into observation records.
49
+
50
+ ```ts
51
+ const protect = createAIProtection<{canGenerate: boolean}>({
52
+ // ...existing connection and trusted-IP configuration
53
+ rules: [{
54
+ id: 'generation_entitlement',
55
+ mode: 'enforce',
56
+ evaluate: context => ({
57
+ allowed: context.canGenerate,
58
+ reason: 'generation_entitlement_required',
59
+ }),
60
+ }],
61
+ });
62
+ ```
63
+
64
+ Rules must be synchronous, deterministic and cheap; load external state before
65
+ calling protection. A rule returns `{allowed, reason?, status?}`. Denial status
66
+ may be 403 or 429. `id` and `reason` must be stable non-sensitive codes: a lowercase
67
+ letter followed by up to 63 lowercase letters, digits or underscores. Do not put
68
+ user identifiers, prompts or exception messages in codes; these enter reports
69
+ and denial reasons can reach the caller.
70
+
71
+ Rules default to **observe**. Each rule's mode is independent of cloud
72
+ `protectionMode`, plan entitlement and the dashboard's AI mode. Setting cloud
73
+ observation does not disable explicitly enforced customer rules. Customer rules
74
+ are application policies, not paid WebDecoy detection features.
75
+
76
+ All local rules run in declaration order; the first enforced denial determines
77
+ HTTP status/reason. Local allow means continue evaluation, never bypass cloud
78
+ protection. Any enforced local denial skips the blocking detection request; reporting follows asynchronously. Rule throws,
79
+ malformed values and accidental promises are `local_rule_error`; raw exceptions
80
+ are never logged. A rule in enforce mode defaults to `failureMode: 'closed'`
81
+ (503); explicit `open` permits later checks. Observe rules never enforce errors.
82
+
83
+ ## Availability matrix
84
+
85
+ | Condition | Default outcome |
86
+ |---|---|
87
+ | Enforced local rule denies | Deny locally, including during a WebDecoy outage |
88
+ | Enforced local rule fails | 503, unless that rule explicitly opts into fail-open |
89
+ | Observed local rule denies/fails | Continue, expose result in decision |
90
+ | Remote detector unavailable | Allow, expose degraded coverage |
91
+ | Account unavailable/mismatched | Observe and skip remote scoring |
92
+ | Missing trusted IP | Skip remote scoring; successful local checks still apply |
93
+ | Report fails, times out or queue fills | Decision/response unchanged; log a generic warning |
94
+ | Caller aborts | Stop; never start the protected callback |
95
+
96
+ Only the existing verified account configuration is cached (60 seconds; failures
97
+ 5 seconds). We do not cache detector allow verdicts or turn the process-local shadow counter
98
+ into a spend limit. Explicit shared quota, concurrency and budget hooks use the
99
+ server state protocols; they are independent of the detector cache.
100
+ A secure decision cache needs explicit scoping, policy versions and invalidation;
101
+ cached allow results must not accidentally bypass fresh checks or counters.
102
+
103
+ ## Reporting and hosting lifecycle
104
+
105
+ The remote `/sdk/detect` request still creates its existing server-side detection
106
+ record. A separate `POST /api/v1/sdk/ai-abuse/reports` endpoint receives the SDK's
107
+ final decision/outcome, including local-only denials and degraded checks. The
108
+ property-scoped key and matching `X-WebDecoy-Property-ID` assertion bind attribution;
109
+ the payload cannot select a tenant. Unknown fields are rejected. Only request ID,
110
+ timestamp, decision/reason, check IDs/sources/modes/verdicts/durations, degraded
111
+ status, handler invocation/status and action are sent. Prompts, user IDs, context,
112
+ raw IPs and subject hashes are excluded from this reporting payload.
113
+
114
+ Central reporting is enabled by default (`reportToWebDecoy: true`). Set it to
115
+ false to keep application observation without sending these reports. The local
116
+ `onObservation` sink still defaults to JSON stdout and is independent: a failing
117
+ custom sink cannot prevent central delivery, and vice versa. Do not place sensitive
118
+ values in rule IDs/reason codes.
119
+
120
+ Ingest deduplicates by organization/property/request ID: the first accepted report
121
+ wins, including its receipt time. There are no automatic retries. Reports are
122
+ bounded to 32 KiB and 36 checks (32 local rules, quota, concurrency, cloud and browser evidence). The pilot endpoint
123
+ limits traffic to 6000 reports/minute per source IP with a burst of 200; excess
124
+ reports are dropped by this SDK after a generic warning, without affecting chat.
125
+
126
+ The dashboard presents these as **SDK-reported application decisions**, separate
127
+ from server-derived detector evidence. They are not summed into detection counts,
128
+ charged as detections, or treated as proof of blocked inference or savings. The
129
+ same request ID lets users correlate the two sources. Counts use receipt time;
130
+ reports have a seven-day window and hourly retention cleanup. A prolonged outage
131
+ can prevent delivery, so this is not complete audit coverage.
132
+
133
+ `onObservation(event, {signal})` can return a promise. `report()` catches rejection,
134
+ limits outstanding reports (`maxPendingReports`, default 100), and stops waiting
135
+ at `reportingTimeoutMs` (default 1000). Sinks must honor the AbortSignal and avoid
136
+ CPU-heavy synchronous work; JavaScript cannot forcibly stop arbitrary sink code.
137
+ No retries, durable queue or exactly-once delivery is claimed.
138
+
139
+ Use `waitUntil(task)` to attach reporting to the deployment's request lifecycle.
140
+ For a Next.js route:
141
+
142
+ ```ts
143
+ import { after } from 'next/server';
144
+ // In createAIProtection options:
145
+ // waitUntil: task => after(() => task)
146
+ ```
147
+
148
+ This registers delivery with Next.js's supported lifecycle mechanism; the hosting
149
+ adapter must support it and execution remains subject to host duration limits.
150
+ Do not use an untracked fire-and-forget promise as a delivery guarantee. In a
151
+ long-lived Node service, call `flush()` during graceful shutdown. Tests can await
152
+ `report()`/`flush()` directly. Neither mechanism proves stream completion or actual
153
+ provider usage; wrapper records stop at Response creation.
154
+
155
+ Reference: [Next.js after](https://nextjs.org/docs/app/api-reference/functions/after).
156
+
157
+ ## Current controls and usage reporting
158
+
159
+ Shared account quotas run before remote detection. `concurrent` explicitly wraps
160
+ bounded model work with distributed leases. `createAIBudget` reserves/settles each
161
+ provider attempt; it is not an automatic middleware spending cap. Both controls
162
+ default to observe/open; enforce/closed is an explicit state-availability tradeoff.
163
+ Usage events are separate from schema-1 request reports and include numeric rates,
164
+ tokens and call/request/reservation UUIDs. They do not include raw identities or
165
+ model content. See README and RELEASE.md; backend contracts are maintained in the
166
+ private app repository's integrations/ai-abuse documentation.
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/NEXTJS.md ADDED
@@ -0,0 +1,177 @@
1
+ # Next.js / AI SDK pilot integration
2
+
3
+ Status: local, experimental Node server adapter with local rules and cloud detection. Not published to npm or deployed.
4
+ No Next.js dependency in the adapter: it uses standard `Request` / `Response` and
5
+ can be called by other Node frameworks, but only the Next.js example is tested.
6
+
7
+ ## Where it runs
8
+
9
+ ```
10
+ Browser → customer's /api/chat → existing auth / input checks
11
+ → WebDecoy admission (metadata only)
12
+ → customer's model provider → original stream
13
+ ```
14
+
15
+ Install in the application server that owns the model call, not the browser,
16
+ Next.js middleware, or the model provider. Node >=22.22.3 is required; Edge runtime
17
+ is not supported. The application and provider traffic remain on customer
18
+ infrastructure. WebDecoy receives the client IP, URL pathname (no query string),
19
+ method, user agent, header names and accept-language/accept-encoding values.
20
+ Request bodies, session cookies, authorization values and responses are not sent
21
+ to WebDecoy. Opt-in browser evidence forwards only its WebDecoy receipt cookie.
22
+ Avoid sensitive identifiers in route paths. This is a remote metadata decision,
23
+ not proof that a caller is human. Optional browser evidence is a separate opt-in.
24
+
25
+ ## Install the local pilot
26
+
27
+ From this repository:
28
+
29
+ ```sh
30
+ cd ai-protection
31
+ npm pack --pack-destination /tmp
32
+ ```
33
+
34
+ From the customer's application:
35
+
36
+ ```sh
37
+ npm install /tmp/webdecoy-ai-protection-0.1.0-alpha.1.tgz
38
+ ```
39
+
40
+ The tarball contains the adapter, declarations and shared core; it does not
41
+ need this checkout at runtime. The initial npm release is pending. Once published, install the pilot with
42
+ `npm install @webdecoy/ai-protection@alpha`. There is no automatic marketplace installer.
43
+
44
+ 1. In WebDecoy, select an existing property on **AI Abuse Protection**.
45
+ 2. Create a property-scoped key with Write Detections permission.
46
+ 3. Set server-only environment variables: `WEBDECOY_URL` (your ingest origin),
47
+ `WEBDECOY_KEY`, `WEBDECOY_PROPERTY_ID`, and `WEBDECOY_SUBJECT_SECRET` (random,
48
+ at least 32 characters). Never use `NEXT_PUBLIC_` for these.
49
+ 4. Create one protection instance per application scope, outside the route.
50
+ 5. Call it after your authentication, origin/input checks and quotas, immediately
51
+ before the code that invokes the model.
52
+
53
+ ```ts
54
+ // app/api/chat/route.ts — incorporate into your existing authenticated route
55
+ import { createAIProtection } from '@webdecoy/ai-protection';
56
+ import { after } from 'next/server';
57
+
58
+ export const runtime = 'nodejs';
59
+ const protect = createAIProtection({
60
+ webdecoyUrl: process.env.WEBDECOY_URL!,
61
+ webdecoyKey: process.env.WEBDECOY_KEY!,
62
+ propertyId: process.env.WEBDECOY_PROPERTY_ID!,
63
+ subjectSecret: process.env.WEBDECOY_SUBJECT_SECRET!,
64
+ scopeId: 'support-chat',
65
+ route: '/api/chat',
66
+ protectionMode: 'observe',
67
+ waitUntil: task => after(() => task),
68
+ resolveClientIP: trustedClientIP, // Your ingress-specific implementation; see below.
69
+ });
70
+
71
+ // Inside POST, AFTER your existing authentication and input validation:
72
+ // return protect(request, () => {
73
+ // const result = streamText({ model, messages, abortSignal: request.signal });
74
+ // return result.toUIMessageStreamResponse({ consumeSseStream: consumeStream });
75
+ // });
76
+ ```
77
+
78
+ `trustedClientIP` is intentionally application supplied. Use a hosting API that
79
+ vouches for the address, or a header overwritten by a reverse proxy you control
80
+ with direct origin access blocked. Do not simply read arbitrary `X-Forwarded-For`
81
+ or `X-Real-IP` from public requests. The adapter validates a single IPv4/IPv6
82
+ address, not a comma-separated forwarding chain. Missing/invalid addresses allow
83
+ the request without scoring and emit `webdecoy_admission_skipped`; this is degraded
84
+ coverage, not successful protection. Resolver exceptions propagate as application errors. Async resolution has a
85
+ 1000ms default timeout and receives `{signal}` as a second argument; timeout skips
86
+ cloud scoring as degraded coverage. Use `route` for an explicit non-sensitive
87
+ route template. Test spoofed forwarding headers against the deployed ingress before
88
+ switching to enforcement.
89
+
90
+ Keep existing request-size limits at the ingress and application. The adapter
91
+ never reads, clones or buffers the body. It does not add authentication, CORS or CSRF protection. Shared quota, concurrency
92
+ and budget controls require their separate explicit configuration; the basic
93
+ wrapper alone supplies none of those limits.
94
+
95
+ ## Local policies and explicit decisions
96
+
97
+ The callable wrapper accepts a third argument containing trusted server context.
98
+ Configure `rules` to inspect that context locally, or use `protect.check()` and
99
+ `protect.report()` for custom response handling. See [the architecture contract](ARCHITECTURE.md).
100
+ Context stays local. Rule IDs/reasons are stable non-sensitive codes that can enter logs.
101
+
102
+ **Cloud observation does not override explicitly enforced local rules.** Each
103
+ local rule defaults to observe; when explicitly enforced, its denial/error remains
104
+ active during a cloud outage. The example's local `plan_input_limit` rule allows
105
+ up to 4000 input characters for its server-configured free plan, while the route's
106
+ input validation caps all prompts at 8000 characters. `EXAMPLE_PLAN` is a local
107
+ fixture setting; production should load entitlements from authenticated server state.
108
+ A `plan` field in the request body is ignored.
109
+
110
+ The example uses Next.js `after()` to keep best-effort observation delivery tied
111
+ to the request lifecycle. Local-only outcomes and degraded checks are sent to
112
+ WebDecoy's application-decision reporting endpoint and the local sink (stdout by
113
+ default). The dashboard distinguishes SDK reports from server-generated detector
114
+ records; the same request ID correlates them. `reportToWebDecoy: false` opts out
115
+ of central outcome reporting. Deploy the compatible reporting endpoint before
116
+ enabling the pilot; absent/unavailable reporting never changes request decisions.
117
+
118
+ ## Behavior customers should expect
119
+
120
+ - Start with cloud and dashboard observation, and each local rule in observe mode. Review real traffic and false
121
+ positives before enabling both enforcement settings on an entitled plan.
122
+ - Enforced cloud block/challenge: JSON 403 before the protected callback. There is no
123
+ interactive challenge flow; handle `verification_required` in the chat UI.
124
+ - WebDecoy outage/timeouts: allow by default. Unknown account binding always
125
+ observes and skips scoring. Missing trusted IP also skips scoring.
126
+ - Cancellation during admission: throw the request's abort reason, never invoke
127
+ the callback. Forward `request.signal` to the AI SDK for later cancellation.
128
+ - Allowed response: exact original Response, including streaming body and headers.
129
+ No buffering, background stream reader or response rewriting by WebDecoy.
130
+ - Logs record callback invocation and returned HTTP status, **not** stream
131
+ completion, model invocation, tokens saved or provider billing. Existing core
132
+ `upstream_attempted` remains false: this adapter cannot observe model activity.
133
+ - Account policy caches and per-call timeouts are documented in README. Cold remote admission can wait roughly two seconds with defaults, plus up to
134
+ one second of IP resolution. Optional quota, lease acquisition and reservation
135
+ each add their own deadline. See RELEASE.md.
136
+
137
+ Use the AI SDK's documented `consumeSseStream: consumeStream` handling alongside
138
+ `abortSignal` when returning UI streams. Provider cancellation/billing remains
139
+ provider dependent. The adapter cannot promise refunded or unbilled tokens.
140
+
141
+ ## Runnable example and validation
142
+
143
+ `examples/nextjs` contains an authenticated POST endpoint using a deterministic
144
+ local AI SDK model; it cannot incur model-provider costs. Its hardcoded test IP
145
+ is enabled only by `WEBDECOY_LOCAL_FIXTURE=1` and is not production configuration.
146
+
147
+ ```sh
148
+ cd examples/nextjs
149
+ npm ci
150
+ npm run build -- --webpack
151
+ npm test
152
+ ```
153
+
154
+ The test starts a real Next.js production server and a local detector/account
155
+ fixture; no secrets required. It checks authentication/input validation before
156
+ scoring, UI-message SSE response, enforced rejection, and outage fail-open.
157
+ Root `npm test` covers local decisions, reporting failure isolation, cancellation during admission, streaming cancellation,
158
+ unchanged response identity, metadata privacy and missing-IP behavior.
159
+
160
+ For a real pilot, replace the mock model with the application's existing model,
161
+ configure trusted ingress/IP resolution, connect staging ingest/backend and
162
+ verify a stored check appears for the selected property. Real abuse/legitimate
163
+ traffic labels are still needed to measure detection value. These tests validate
164
+ integration mechanics, not detection accuracy or demand.
165
+
166
+ ## Why this integration, and the competitive limit
167
+
168
+ Vercel already offers [BotID for AI endpoints](https://vercel.com/kb/guide/protect-ai-endpoints-with-vercel-botid),
169
+ including browser-side challenges and server checks. This is not evidence of an
170
+ unprotected Vercel market. The hypothesis is existing WebDecoy customers and
171
+ applications wanting the same account/policies on other Node hosting. Portability
172
+ is an integration property, not yet demonstrated differentiation or demand.
173
+
174
+ The integration follows Next.js's [standard Route Handler APIs](https://nextjs.org/docs/app/getting-started/route-handlers)
175
+ and the AI SDK's [abort handling guidance](https://ai-sdk.dev/docs/troubleshooting/stream-abort-handling).
176
+ Versions pinned for this fixture: Next.js 16.3.6, AI SDK 7.0.117, React 19.3.0.
177
+ No compatibility claim for every Next.js/AI SDK version or other hosting platform.
package/NOTICE ADDED
@@ -0,0 +1,2 @@
1
+ WebDecoy AI Protection
2
+ Copyright 2026 WebDecoy