@arcjet/skills 1.14.0 → 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 +1 -1
- package/docs/choose-protections.md +7 -6
- package/docs/guard.md +28 -20
- package/docs/protect.md +25 -2
- package/package.json +4 -4
- package/skills/choose-protections/SKILL.md +6 -5
- package/skills/cli/SKILL.md +1 -1
- package/skills/guard/SKILL.md +26 -19
- package/skills/mcp/SKILL.md +1 -1
- package/skills/protect/SKILL.md +11 -2
package/dist/version.js
CHANGED
|
@@ -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
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
never
|
|
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
|
@@ -36,28 +36,32 @@ Declare rules at module scope so `.deniedResult(decision)` and
|
|
|
36
36
|
Call `guard()` where you already know the operation. Hardcode the `label`.
|
|
37
37
|
Do not interpolate names in a generic dispatcher.
|
|
38
38
|
|
|
39
|
-
Hardcode labels as slugs:
|
|
40
|
-
(`tools.get-weather`). Start and end with a letter or digit; max 256 bytes.
|
|
39
|
+
Hardcode labels as slugs: ASCII letters of either case, digits, `-`, `.`, and
|
|
40
|
+
`_` (`tools.get-weather`). Start and end with a letter or digit; max 256 bytes.
|
|
41
|
+
Labels match case-sensitively.
|
|
41
42
|
Prefer dash/dot in new labels. Do not interpolate user input. Check a label
|
|
42
43
|
you build yourself with `validateGuardLabel` — a slug the service will not
|
|
43
44
|
match reads as `ALLOW` with `hasFailedOpen()` false.
|
|
44
45
|
|
|
45
46
|
```ts
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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 };
|
|
56
63
|
}
|
|
57
|
-
|
|
58
|
-
throw new Error("input flagged as prompt injection");
|
|
59
|
-
}
|
|
60
|
-
return { error: decision.reason };
|
|
64
|
+
// ... run the tool
|
|
61
65
|
}
|
|
62
66
|
```
|
|
63
67
|
|
|
@@ -85,10 +89,14 @@ never change the conclusion. Prefer `hasFailedOpen()` over deprecated
|
|
|
85
89
|
Pass `correlationId` to join a guard decision with a request or agent
|
|
86
90
|
trace. It is a dedicated field, not metadata.
|
|
87
91
|
|
|
88
|
-
`localDetectSensitiveInfo(
|
|
89
|
-
|
|
90
|
-
identifiers
|
|
91
|
-
|
|
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.
|
|
92
100
|
`moderateContent()` is Guard-only.
|
|
93
101
|
|
|
94
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
|
|
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.
|
|
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.
|
|
60
|
-
"@types/node": "22.20.
|
|
61
|
-
"tsdown": "0.23.
|
|
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.
|
|
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
|
-
|
|
|
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
|
-
|
|
39
|
-
entities never match without
|
|
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
|
|
package/skills/cli/SKILL.md
CHANGED
package/skills/guard/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ metadata:
|
|
|
7
7
|
author: arcjet
|
|
8
8
|
type: core
|
|
9
9
|
library: "@arcjet/skills"
|
|
10
|
-
library_version: "1.
|
|
10
|
+
library_version: "1.15.0" # x-release-please-version
|
|
11
11
|
sources:
|
|
12
12
|
- docs/guard.md
|
|
13
13
|
---
|
|
@@ -43,27 +43,31 @@ Declare rules at module scope so `.deniedResult(decision)` works.
|
|
|
43
43
|
|
|
44
44
|
Hardcode the `label`. Do not interpolate in a generic dispatcher.
|
|
45
45
|
|
|
46
|
-
Hardcode labels as slugs:
|
|
47
|
-
(`tools.get-weather`). Start and end with a letter or digit; max 256 bytes.
|
|
46
|
+
Hardcode labels as slugs: ASCII letters of either case, digits, `-`, `.`, and
|
|
47
|
+
`_` (`tools.get-weather`). Start and end with a letter or digit; max 256 bytes.
|
|
48
|
+
Labels match case-sensitively.
|
|
48
49
|
Prefer dash/dot in new labels. Check a label you build yourself with
|
|
49
50
|
`validateGuardLabel`.
|
|
50
51
|
|
|
51
52
|
```ts
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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 };
|
|
62
66
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
//
|
|
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
|
|
67
71
|
}
|
|
68
72
|
```
|
|
69
73
|
|
|
@@ -72,8 +76,11 @@ the conclusion. Rate-limit rules need `key` and `bucket`. Nested `metadata`
|
|
|
72
76
|
is Console-only; no secrets or PII. `decision.reason` is a flat string on
|
|
73
77
|
DENY and `undefined` on ALLOW.
|
|
74
78
|
|
|
75
|
-
`localDetectSensitiveInfo(
|
|
76
|
-
|
|
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.
|
|
77
84
|
`moderateContent()` is Guard-only.
|
|
78
85
|
|
|
79
86
|
`capture()` is visibility, never a deny. `flush()` on shutdown.
|
package/skills/mcp/SKILL.md
CHANGED
package/skills/protect/SKILL.md
CHANGED
|
@@ -7,7 +7,7 @@ metadata:
|
|
|
7
7
|
author: arcjet
|
|
8
8
|
type: core
|
|
9
9
|
library: "@arcjet/skills"
|
|
10
|
-
library_version: "1.
|
|
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
|
|
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
|
|