@arcjet/skills 1.14.1 → 1.15.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/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region src/version.ts
2
2
  /** Package version. Updated by the release process. */
3
- const VERSION = "1.14.1";
3
+ const VERSION = "1.15.0";
4
4
  //#endregion
5
5
  export { VERSION };
@@ -38,12 +38,13 @@ result is `{ detected, billing? }` (`text_units` when present). Check
38
38
 
39
39
  ## Data loss prevention
40
40
 
41
- Detection runs locally. The default WebAssembly backend finds exactly four
42
- structured types: card numbers, email addresses, phone numbers, and IP
43
- addresses. Names, addresses, and government or financial identifiers need
44
- `@arcjet/sensitive-info-rampart` (`rampart()`), which needs a server runtime
45
- with filesystem access. Listing a Rampart-only entity without that backend
46
- never matches.
41
+ Detection runs on-device with `@arcjet/sensitive-info-rampart`
42
+ (`backend: rampart()`), which finds names, addresses, government and
43
+ financial identifiers, card numbers, email addresses, phone numbers, and IP
44
+ addresses. It needs a server runtime with filesystem access (Node.js, Bun, or
45
+ Deno), so its bundled loader can't run on Cloudflare Workers or edge
46
+ runtimes. Always pass the backend; a Rampart entity listed without it never
47
+ matches. The rule doesn't inherit the client's backend.
47
48
 
48
49
  **Rules:** `sensitiveInfo` (request-based) and `localDetectSensitiveInfo`
49
50
  (Guard).
package/docs/guard.md CHANGED
@@ -44,21 +44,24 @@ you build yourself with `validateGuardLabel` — a slug the service will not
44
44
  match reads as `ALLOW` with `hasFailedOpen()` false.
45
45
 
46
46
  ```ts
47
- const decision = await arcjet.guard({
48
- label: "tools.get-weather",
49
- rules: [/* ... */],
50
- metadata: { user: { id: userId } },
51
- });
52
-
53
- if (decision.conclusion === "DENY") {
54
- const rateLimited = toolCallLimit.deniedResult(decision);
55
- if (rateLimited) {
56
- throw new Error(`rate limited – retry after unix ${rateLimited.resetAtUnixSeconds}`);
47
+ async function getWeather(userId: string) {
48
+ const decision = await arcjet.guard({
49
+ label: "tools.get-weather",
50
+ rules: [/* ... */],
51
+ metadata: { user: { id: userId } },
52
+ });
53
+
54
+ if (decision.conclusion === "DENY") {
55
+ const rateLimited = toolCallLimit.deniedResult(decision);
56
+ if (rateLimited) {
57
+ throw new Error(`rate limited – retry after unix ${rateLimited.resetAtUnixSeconds}`);
58
+ }
59
+ if (decision.reason === "PROMPT_INJECTION") {
60
+ throw new Error("input flagged as prompt injection");
61
+ }
62
+ return { error: decision.reason };
57
63
  }
58
- if (decision.reason === "PROMPT_INJECTION") {
59
- throw new Error("input flagged as prompt injection");
60
- }
61
- return { error: decision.reason };
64
+ // ... run the tool
62
65
  }
63
66
  ```
64
67
 
@@ -86,10 +89,14 @@ never change the conclusion. Prefer `hasFailedOpen()` over deprecated
86
89
  Pass `correlationId` to join a guard decision with a request or agent
87
90
  trace. It is a dedicated field, not metadata.
88
91
 
89
- `localDetectSensitiveInfo()` defaults to WASM and matches four types:
90
- card, email, phone, IP. Names, addresses, and government / financial
91
- identifiers need `@arcjet/sensitive-info-rampart` (`backend: rampart()`).
92
- Listing a Rampart-only entity without that backend never matches.
92
+ Use `localDetectSensitiveInfo({ backend: rampart() })` from
93
+ `@arcjet/sensitive-info-rampart` for PII: it detects names, addresses,
94
+ government / financial identifiers, card, email, phone, and IP on-device.
95
+ Always pass the backend; a Rampart entity listed without it never matches.
96
+ The rule doesn't inherit `launchArcjet({ sensitiveInfoBackend })`, so share
97
+ one Rampart instance between the client and the rule. Rampart's bundled loader
98
+ needs a filesystem; on Cloudflare Workers or other edge runtimes, pass
99
+ `rampart()` a `classify` function that runs the model on your runtime.
93
100
  `moderateContent()` is Guard-only.
94
101
 
95
102
  ## Framework wrappers
package/docs/protect.md CHANGED
@@ -66,8 +66,8 @@ Install the adapter with the project's package manager. Do not hand-edit
66
66
  ### Frameworks that are not a shared client file
67
67
 
68
68
  - **Astro** — register the integration in `astro.config.mjs` and import
69
- `arcjet:client`. There is no `lib/arcjet.ts` and no `withRule()`.
70
- `@arcjet/astro` validates with Zod `.strict()`, so leftover
69
+ `arcjet:client`. There is no `lib/arcjet.ts`; call `aj.withRule()` on
70
+ `arcjet:client` to add a rule to one route. `@arcjet/astro` validates with Zod `.strict()`, so leftover
71
71
  `detectPromptInjection({ threshold })` throws at startup.
72
72
  - **Nuxt** — register the `@arcjet/nuxt` module. The key lives in
73
73
  `nuxt.config.ts`. Import from `#arcjet`. Each `arcjet()` call is its own
@@ -131,6 +131,29 @@ Rules that need extra input at `protect()` time: `tokenBucket` needs
131
131
  `sensitiveInfo` needs `{ sensitiveInfoValue }`, `detectPromptInjection`
132
132
  needs `{ detectPromptInjectionMessage }`.
133
133
 
134
+ Configure `sensitiveInfo` with `backend: rampart()` from
135
+ `@arcjet/sensitive-info-rampart`, which detects PII on-device. Rampart runs on
136
+ Node.js, Bun, and Deno; it can't run on Cloudflare Workers or edge runtimes.
137
+ On Next.js, add it, `@huggingface/transformers`, and `onnxruntime-node` to
138
+ `serverExternalPackages` and keep the route on the Node.js runtime (the
139
+ default). On Astro, add the rule in the route with `aj.withRule()`, not in
140
+ `astro.config.mjs`, and import `sensitiveInfo` from the `arcjet` core package:
141
+
142
+ ```ts
143
+ // src/pages/api/chat.ts
144
+ import { rampart } from "@arcjet/sensitive-info-rampart";
145
+ import { sensitiveInfo } from "arcjet";
146
+ import aj from "arcjet:client";
147
+
148
+ const routeAj = aj.withRule(
149
+ sensitiveInfo({
150
+ mode: "LIVE",
151
+ deny: ["CREDIT_CARD_NUMBER", "EMAIL", "PHONE_NUMBER"],
152
+ backend: rampart(),
153
+ }),
154
+ );
155
+ ```
156
+
134
157
  Pass `correlationId` when the decision must join another request, guard
135
158
  call, or agent trace. It does not affect fingerprinting.
136
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcjet/skills",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "description": "Versioned Agent Skills for the Arcjet JavaScript SDK, shipped with TanStack Intent",
5
5
  "keywords": [
6
6
  "agent-skills",
@@ -56,9 +56,9 @@
56
56
  "test": "npm run build && npm run test-coverage && npm run validate-skills && node ../.github/scripts/check-skill-source-stale.mjs"
57
57
  },
58
58
  "devDependencies": {
59
- "@tanstack/intent": "0.4.0",
60
- "@types/node": "22.20.4",
61
- "tsdown": "0.23.0",
59
+ "@tanstack/intent": "0.5.5",
60
+ "@types/node": "22.20.5",
61
+ "tsdown": "0.23.1",
62
62
  "typescript": "7.0.2"
63
63
  },
64
64
  "engines": {
@@ -7,7 +7,7 @@ metadata:
7
7
  author: arcjet
8
8
  type: core
9
9
  library: "@arcjet/skills"
10
- library_version: "1.14.1" # x-release-please-version
10
+ library_version: "1.15.0" # x-release-please-version
11
11
  sources:
12
12
  - docs/choose-protections.md
13
13
  ---
@@ -23,8 +23,7 @@ or `@arcjet/skills#guard` to implement them.
23
23
  | SQLi / XSS / common probes | `shield` (`mode: "LIVE"`) | Request only |
24
24
  | Cost / abuse quotas | `tokenBucket`, `fixedWindow`, `slidingWindow` | Request and Guard |
25
25
  | Jailbreaks, instruction overrides | `detectPromptInjection` (binary; no `threshold`) | Request and Guard |
26
- | Card / email / phone / IP in text | `sensitiveInfo` / `localDetectSensitiveInfo` | Request and Guard |
27
- | Names, addresses, gov IDs | same rules + `@arcjet/sensitive-info-rampart` | Server runtime |
26
+ | PII in text (names, addresses, gov IDs, card, email, phone, IP) | `sensitiveInfo` / `localDetectSensitiveInfo` + `backend: rampart()` | Request and Guard (server runtime) |
28
27
  | Unsafe / abusive text | `moderateContent` | Guard only |
29
28
  | Signup spam, disposable email | `validateEmail` + `protectSignup` | Request only |
30
29
  | VPN / Tor / country / IP lists | `filter` | Request only |
@@ -35,8 +34,10 @@ Sliding window: no boundary burst. Request rate limits default to IP; set
35
34
  `characteristics: ["userId"]` for a user key. Guard rate limits always need
36
35
  `key` and `bucket`.
37
36
 
38
- Default sensitive-info backend matches four structured types. Rampart-only
39
- entities never match without `backend: rampart()`.
37
+ Sensitive-info rules use `@arcjet/sensitive-info-rampart`. Always pass
38
+ `backend: rampart()` on the rule; Rampart entities never match without it.
39
+ Rampart needs Node.js, Bun, or Deno – not Cloudflare Workers or edge
40
+ runtimes.
40
41
 
41
42
  `capture()` records that an action happened. It is not a protection rule.
42
43
 
@@ -7,7 +7,7 @@ metadata:
7
7
  author: arcjet
8
8
  type: core
9
9
  library: "@arcjet/skills"
10
- library_version: "1.14.1" # x-release-please-version
10
+ library_version: "1.15.0" # x-release-please-version
11
11
  sources:
12
12
  - docs/cli.md
13
13
  ---
@@ -7,7 +7,7 @@ metadata:
7
7
  author: arcjet
8
8
  type: core
9
9
  library: "@arcjet/skills"
10
- library_version: "1.14.1" # x-release-please-version
10
+ library_version: "1.15.0" # x-release-please-version
11
11
  sources:
12
12
  - docs/guard.md
13
13
  ---
@@ -50,21 +50,24 @@ Prefer dash/dot in new labels. Check a label you build yourself with
50
50
  `validateGuardLabel`.
51
51
 
52
52
  ```ts
53
- const decision = await arcjet.guard({
54
- label: "tools.get-weather",
55
- rules: [/* ... */],
56
- metadata: { user: { id: userId } },
57
- });
58
-
59
- if (decision.conclusion === "DENY") {
60
- const rateLimited = toolCallLimit.deniedResult(decision);
61
- if (rateLimited) {
62
- return { error: `rate limited, retry after ${rateLimited.resetAtUnixSeconds}` };
53
+ async function getWeather(userId: string) {
54
+ const decision = await arcjet.guard({
55
+ label: "tools.get-weather",
56
+ rules: [/* ... */],
57
+ metadata: { user: { id: userId } },
58
+ });
59
+
60
+ if (decision.conclusion === "DENY") {
61
+ const rateLimited = toolCallLimit.deniedResult(decision);
62
+ if (rateLimited) {
63
+ return { error: `rate limited, retry after ${rateLimited.resetAtUnixSeconds}` };
64
+ }
65
+ return { error: decision.reason };
63
66
  }
64
- return { error: decision.reason };
65
- }
66
- if (decision.hasFailedOpen()) {
67
- // ALLOW only because a rule could not run — deny here if the site is sensitive
67
+ if (decision.hasFailedOpen()) {
68
+ // ALLOW only because a rule could not run — deny here if the site is sensitive
69
+ }
70
+ // ... run the tool
68
71
  }
69
72
  ```
70
73
 
@@ -73,8 +76,11 @@ the conclusion. Rate-limit rules need `key` and `bucket`. Nested `metadata`
73
76
  is Console-only; no secrets or PII. `decision.reason` is a flat string on
74
77
  DENY and `undefined` on ALLOW.
75
78
 
76
- `localDetectSensitiveInfo()` default backend matches card / email / phone /
77
- IP only. Names and government IDs need `backend: rampart()`.
79
+ Use `localDetectSensitiveInfo({ backend: rampart() })` from
80
+ `@arcjet/sensitive-info-rampart` for PII (names, addresses, government IDs,
81
+ card, email, phone, IP). Always pass the backend on the rule – it doesn't
82
+ inherit the client's `sensitiveInfoBackend`, so share one instance. On
83
+ Cloudflare Workers or edge runtimes, pass `rampart()` a `classify` function.
78
84
  `moderateContent()` is Guard-only.
79
85
 
80
86
  `capture()` is visibility, never a deny. `flush()` on shutdown.
@@ -7,7 +7,7 @@ metadata:
7
7
  author: arcjet
8
8
  type: core
9
9
  library: "@arcjet/skills"
10
- library_version: "1.14.1" # x-release-please-version
10
+ library_version: "1.15.0" # x-release-please-version
11
11
  sources:
12
12
  - docs/mcp.md
13
13
  ---
@@ -7,7 +7,7 @@ metadata:
7
7
  author: arcjet
8
8
  type: core
9
9
  library: "@arcjet/skills"
10
- library_version: "1.14.1" # x-release-please-version
10
+ library_version: "1.15.0" # x-release-please-version
11
11
  sources:
12
12
  - docs/protect.md
13
13
  ---
@@ -55,7 +55,8 @@ Omitted `mode` is `DRY_RUN`. `detectBot` requires exactly one of `allow` or
55
55
  One client. `withRule()` clones share the decision cache. A second
56
56
  `arcjet()` constructor does not.
57
57
 
58
- Astro is an integration (`arcjet:client`), not a shared file. Nuxt is a
58
+ Astro is an integration (`arcjet:client`), not a shared file; add a
59
+ per-route rule with `aj.withRule()` on `arcjet:client`. Nuxt is a
59
60
  module (`#arcjet`). NestJS is `ArcjetModule` + `@InjectArcjet()`. Bun and
60
61
  Deno wrap fetch with `aj.handler()` for IP detection, then still call
61
62
  `protect()`. Hono on Node passes `c.env.incoming`.
@@ -81,6 +82,14 @@ Branch only when the status differs: rate limit → 429; email / PII /
81
82
  prompt injection → 400; everything else → 403. `isErrored()` is fail-open —
82
83
  log and allow.
83
84
 
85
+ Configure `sensitiveInfo` with `backend: rampart()` from
86
+ `@arcjet/sensitive-info-rampart` (on-device PII detection). It runs on
87
+ Node.js, Bun, and Deno – not Cloudflare Workers or edge runtimes. On Next.js,
88
+ list it, `@huggingface/transformers`, and `onnxruntime-node` in
89
+ `serverExternalPackages` and keep the route on the Node.js runtime. On Astro,
90
+ add the rule in the route with `aj.withRule()` (import `sensitiveInfo` from
91
+ `arcjet`), not in `astro.config.mjs`.
92
+
84
93
  Pass a trusted `userId` on the rule that needs it. Pass `correlationId`
85
94
  when the decision must join another request or guard call.
86
95