@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.
- package/ARCHITECTURE.md +166 -0
- package/LICENSE +202 -0
- package/NEXTJS.md +177 -0
- package/NOTICE +2 -0
- package/README.md +323 -0
- package/RELEASE.md +75 -0
- package/account.mjs +33 -0
- package/admission.mjs +87 -0
- package/browser-evidence.mjs +17 -0
- package/browser.d.mts +2 -0
- package/browser.mjs +11 -0
- package/budget.mjs +105 -0
- package/concurrency.mjs +72 -0
- package/fetch.d.mts +140 -0
- package/fetch.mjs +165 -0
- package/observation.mjs +68 -0
- package/package.json +76 -0
- package/quota.mjs +78 -0
- package/reporting.mjs +43 -0
- package/rules.mjs +46 -0
- package/telemetry.mjs +21 -0
- package/transport.mjs +20 -0
- package/usage.mjs +17 -0
package/ARCHITECTURE.md
ADDED
|
@@ -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