@arcjet/analyze 1.0.0-beta.1 → 1.0.0-beta.11

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.
Files changed (4) hide show
  1. package/README.md +53 -19
  2. package/index.d.ts +75 -9
  3. package/index.js +112 -36
  4. package/package.json +23 -20
package/README.md CHANGED
@@ -22,37 +22,71 @@ against common attacks.
22
22
 
23
23
  This is the [Arcjet][arcjet] local analysis engine.
24
24
 
25
- ## Installation
25
+ - [npm package (`@arcjet/analyze`)](https://www.npmjs.com/package/@arcjet/analyze)
26
+ - [GitHub source code (`analyze/` in `arcjet/arcjet-js`)](https://github.com/arcjet/arcjet-js/tree/main/analyze)
26
27
 
27
- ```shell
28
- npm install -S @arcjet/analyze
29
- ```
28
+ ## What is this?
30
29
 
31
- ## Example
30
+ This package provides functionality to analyze requests.
31
+ The work is done in WebAssembly but is called here from JavaScript.
32
+ The functionality is wrapped up into rules in our core package
33
+ ([`arcjet`][github-arcjet-arcjet]),
34
+ in turn exposed from our adapters (such as `@arcjet/next`).
32
35
 
33
- ```ts
34
- import { generateFingerprint, isValidEmail } from "@arcjet/analyze";
36
+ <!-- TODO(@wooorm-arcjet): link `adapters` above when the main repo is up to date. -->
37
+
38
+ The WebAssembly files are in
39
+ [`@arcjet/analyze-wasm`][github-arcjet-analyze-wasm].
40
+ They are separate because we need to change the import structure for each
41
+ runtime that we support in the bindings.
42
+ Separate packages lets us not duplicate code while providing a combined
43
+ higher-level API for calling our core functionality.
44
+
45
+ ## When should I use this?
35
46
 
36
- const fingerprint = generateFingerprint("127.0.0.1");
37
- console.log("fingerprint: ", fingerprint);
47
+ This is an internal Arcjet package not designed for public use.
48
+ See our [_Get started_ guide][arcjet-get-started] for how to use Arcjet in your
49
+ application.
38
50
 
39
- const valid = isValidEmail("hello@example.com");
40
- console.log("is email valid?", valid);
51
+ ## Install
52
+
53
+ This package is ESM only.
54
+ Install with npm in Node.js:
55
+
56
+ ```sh
57
+ npm install @arcjet/analyze
41
58
  ```
42
59
 
43
- ## Implementation
60
+ ## Use
44
61
 
45
- This package uses the Wasm bindings provided by `@arcjet/analyze-wasm` to
46
- call various functions that are exported by our wasm bindings.
62
+ ```js
63
+ import { generateFingerprint, isValidEmail } from "@arcjet/analyze";
47
64
 
48
- We chose to put this logic in a separate package because we need to change the
49
- import structure for each runtime that we support in the wasm bindings. Moving
50
- this to a separate package allows us not to have to duplicate code while providing
51
- a combined higher-level api for calling our core functionality in Wasm.
65
+ const fingerprint = await generateFingerprint(
66
+ { characteristics: [] },
67
+ { ip: "127.0.0.1" },
68
+ );
69
+ console.log(fingerprint);
70
+ // => "fp::2::0d219da6100b99f95cf639b77e088c6df3c096aa5fd61dec5287c5cf94d5e545"
71
+
72
+ const result = await isValidEmail({}, "hello@example.com", {
73
+ tag: "allow-email-validation-config",
74
+ val: {
75
+ allowDomainLiteral: false,
76
+ allow: [],
77
+ requireTopLevelDomain: true,
78
+ },
79
+ });
80
+ console.log(result);
81
+ // => { blocked: [], validity: "valid" }
82
+ ```
52
83
 
53
84
  ## License
54
85
 
55
- Licensed under the [Apache License, Version 2.0][apache-license].
86
+ [Apache License, Version 2.0][apache-license] © [Arcjet Labs, Inc.][arcjet]
56
87
 
57
88
  [arcjet]: https://arcjet.com
89
+ [arcjet-get-started]: https://docs.arcjet.com/get-started
58
90
  [apache-license]: http://www.apache.org/licenses/LICENSE-2.0
91
+ [github-arcjet-analyze-wasm]: https://github.com/arcjet/arcjet-js/tree/main/analyze-wasm
92
+ [github-arcjet-arcjet]: https://github.com/arcjet/arcjet-js/tree/main/arcjet
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BotConfig, BotResult, DetectedSensitiveInfoEntity, DetectSensitiveInfoFunction, EmailValidationConfig, EmailValidationResult, SensitiveInfoEntities, SensitiveInfoEntity, SensitiveInfoResult } from "@arcjet/analyze-wasm";
1
+ import type { BotConfig, BotResult, DetectedSensitiveInfoEntity, DetectSensitiveInfoFunction, EmailValidationConfig, EmailValidationResult, FilterResult, SensitiveInfoEntities, SensitiveInfoEntity, SensitiveInfoResult } from "@arcjet/analyze-wasm";
2
2
  import type { ArcjetLogger } from "@arcjet/protocol";
3
3
  interface AnalyzeContext {
4
4
  log: ArcjetLogger;
@@ -15,15 +15,81 @@ type AnalyzeRequest = {
15
15
  query?: string;
16
16
  extra?: Record<string, string>;
17
17
  };
18
- export { type EmailValidationConfig, type BotConfig, type SensitiveInfoEntity, type DetectedSensitiveInfoEntity, };
18
+ export { type EmailValidationConfig, type BotConfig, type FilterResult, type SensitiveInfoEntity, type DetectedSensitiveInfoEntity, };
19
19
  /**
20
- * Generate a fingerprint for the client. This is used to identify the client
21
- * across multiple requests.
22
- * @param context - The Arcjet Analyze context.
23
- * @param request - The request to fingerprint.
24
- * @returns A SHA-256 string fingerprint.
20
+ * Generate a fingerprint.
21
+ *
22
+ * Fingerprints can be used to identify the client across multiple requests.
23
+ *
24
+ * This considers different things on the `request` based on the passed
25
+ * `context.characteristics`.
26
+ *
27
+ * See [*Fingerprints* on
28
+ * `docs.arcjet.com`](https://docs.arcjet.com/fingerprints/) for more info.
29
+ *
30
+ * @param context
31
+ * Context.
32
+ * @param request
33
+ * Request.
34
+ * @returns
35
+ * Promise for a SHA-256 fingerprint.
25
36
  */
26
37
  export declare function generateFingerprint(context: AnalyzeContext, request: AnalyzeRequest): Promise<string>;
27
- export declare function isValidEmail(context: AnalyzeContext, candidate: string, options: EmailValidationConfig): Promise<EmailValidationResult>;
38
+ /**
39
+ * Check whether an email is valid.
40
+ *
41
+ * @param context
42
+ * Context.
43
+ * @param value
44
+ * Value.
45
+ * @param options
46
+ * Configuration.
47
+ * @returns
48
+ * Promise for a result.
49
+ */
50
+ export declare function isValidEmail(context: AnalyzeContext, value: string, options: EmailValidationConfig): Promise<EmailValidationResult>;
51
+ /**
52
+ * Detect whether a request is by a bot.
53
+ *
54
+ * @param context
55
+ * Context.
56
+ * @param request
57
+ * Request.
58
+ * @param options
59
+ * Configuration.
60
+ * @returns
61
+ * Promise for a result.
62
+ */
28
63
  export declare function detectBot(context: AnalyzeContext, request: AnalyzeRequest, options: BotConfig): Promise<BotResult>;
29
- export declare function detectSensitiveInfo(context: AnalyzeContext, candidate: string, entities: SensitiveInfoEntities, contextWindowSize: number, detect?: DetectSensitiveInfoFunction): Promise<SensitiveInfoResult>;
64
+ /**
65
+ * Detect sensitive info in a value.
66
+ *
67
+ * @param context
68
+ * Context.
69
+ * @param value
70
+ * Value.
71
+ * @param entities
72
+ * Strategy to use for detecting sensitive info;
73
+ * either by denying everything and allowing certain tags or by allowing
74
+ * everything and denying certain tags.
75
+ * @param contextWindowSize
76
+ * Number of tokens to pass to `detect`.
77
+ * @param detect
78
+ * Function to detect sensitive info (optional).
79
+ * @returns
80
+ * Promise for a result.
81
+ */
82
+ export declare function detectSensitiveInfo(context: AnalyzeContext, value: string, entities: SensitiveInfoEntities, contextWindowSize: number, detect?: DetectSensitiveInfoFunction): Promise<SensitiveInfoResult>;
83
+ /**
84
+ * Check if a filter matches a request.
85
+ *
86
+ * @param context
87
+ * Arcjet context.
88
+ * @param request
89
+ * Request.
90
+ * @param expressions
91
+ * Filter expressions.
92
+ * @returns
93
+ * Promise to whether the filter matches the request.
94
+ */
95
+ export declare function matchFilters(context: AnalyzeContext, request: AnalyzeRequest, expressions: ReadonlyArray<string>, allowIfMatch: boolean): Promise<FilterResult>;
package/index.js CHANGED
@@ -7,14 +7,20 @@ const FREE_EMAIL_PROVIDERS = [
7
7
  "aol.com",
8
8
  "hotmail.co.uk",
9
9
  ];
10
- function noOpDetect() {
10
+ function noOpSensitiveInfoDetect() {
11
+ return [];
12
+ }
13
+ function noOpBotsDetect() {
11
14
  return [];
12
15
  }
13
16
  function createCoreImports(detect) {
14
17
  if (typeof detect !== "function") {
15
- detect = noOpDetect;
18
+ detect = noOpSensitiveInfoDetect;
16
19
  }
17
20
  return {
21
+ "arcjet:js-req/bot-identifier": {
22
+ detect: noOpBotsDetect,
23
+ },
18
24
  "arcjet:js-req/email-validator-overrides": {
19
25
  isFreeEmail(domain) {
20
26
  if (FREE_EMAIL_PROVIDERS.includes(domain)) {
@@ -32,9 +38,11 @@ function createCoreImports(detect) {
32
38
  return "unknown";
33
39
  },
34
40
  },
41
+ // TODO(@wooorm-arcjet): figure out a test case for this with the default `detect`.
35
42
  "arcjet:js-req/sensitive-information-identifier": {
36
43
  detect,
37
44
  },
45
+ // TODO(@wooorm-arcjet): figure out a test case for this that calls `verify`.
38
46
  "arcjet:js-req/verify-bot": {
39
47
  verify() {
40
48
  return "unverifiable";
@@ -43,11 +51,22 @@ function createCoreImports(detect) {
43
51
  };
44
52
  }
45
53
  /**
46
- * Generate a fingerprint for the client. This is used to identify the client
47
- * across multiple requests.
48
- * @param context - The Arcjet Analyze context.
49
- * @param request - The request to fingerprint.
50
- * @returns A SHA-256 string fingerprint.
54
+ * Generate a fingerprint.
55
+ *
56
+ * Fingerprints can be used to identify the client across multiple requests.
57
+ *
58
+ * This considers different things on the `request` based on the passed
59
+ * `context.characteristics`.
60
+ *
61
+ * See [*Fingerprints* on
62
+ * `docs.arcjet.com`](https://docs.arcjet.com/fingerprints/) for more info.
63
+ *
64
+ * @param context
65
+ * Context.
66
+ * @param request
67
+ * Request.
68
+ * @returns
69
+ * Promise for a SHA-256 fingerprint.
51
70
  */
52
71
  async function generateFingerprint(context, request) {
53
72
  const { log } = context;
@@ -55,62 +74,119 @@ async function generateFingerprint(context, request) {
55
74
  const analyze = await initializeWasm(coreImports);
56
75
  if (typeof analyze !== "undefined") {
57
76
  return analyze.generateFingerprint(JSON.stringify(request), context.characteristics);
77
+ // Ignore the `else` branch as we test in places that have WebAssembly.
78
+ /* node:coverage ignore next 4 */
58
79
  }
59
- else {
60
- log.debug("WebAssembly is not supported in this runtime");
61
- }
80
+ log.debug("WebAssembly is not supported in this runtime");
62
81
  return "";
63
82
  }
64
- async function isValidEmail(context, candidate, options) {
83
+ /**
84
+ * Check whether an email is valid.
85
+ *
86
+ * @param context
87
+ * Context.
88
+ * @param value
89
+ * Value.
90
+ * @param options
91
+ * Configuration.
92
+ * @returns
93
+ * Promise for a result.
94
+ */
95
+ async function isValidEmail(context, value, options) {
65
96
  const { log } = context;
66
97
  const coreImports = createCoreImports();
67
98
  const analyze = await initializeWasm(coreImports);
68
99
  if (typeof analyze !== "undefined") {
69
- return analyze.isValidEmail(candidate, options);
70
- }
71
- else {
72
- log.debug("WebAssembly is not supported in this runtime");
73
- // Skip the local evaluation of the rule if WASM is not available
74
- return {
75
- validity: "valid",
76
- blocked: [],
77
- };
100
+ return analyze.isValidEmail(value, options);
101
+ // Ignore the `else` branch as we test in places that have WebAssembly.
102
+ /* node:coverage ignore next 4 */
78
103
  }
104
+ log.debug("WebAssembly is not supported in this runtime");
105
+ return { blocked: [], validity: "valid" };
79
106
  }
107
+ /**
108
+ * Detect whether a request is by a bot.
109
+ *
110
+ * @param context
111
+ * Context.
112
+ * @param request
113
+ * Request.
114
+ * @param options
115
+ * Configuration.
116
+ * @returns
117
+ * Promise for a result.
118
+ */
80
119
  async function detectBot(context, request, options) {
81
120
  const { log } = context;
82
121
  const coreImports = createCoreImports();
83
122
  const analyze = await initializeWasm(coreImports);
84
123
  if (typeof analyze !== "undefined") {
85
124
  return analyze.detectBot(JSON.stringify(request), options);
125
+ // Ignore the `else` branch as we test in places that have WebAssembly.
126
+ /* node:coverage ignore next 4 */
86
127
  }
87
- else {
88
- log.debug("WebAssembly is not supported in this runtime");
89
- // Skip the local evaluation of the rule if Wasm is not available
90
- return {
91
- allowed: [],
92
- denied: [],
93
- spoofed: false,
94
- verified: false,
95
- };
96
- }
128
+ log.debug("WebAssembly is not supported in this runtime");
129
+ return { allowed: [], denied: [], spoofed: false, verified: false };
97
130
  }
98
- async function detectSensitiveInfo(context, candidate, entities, contextWindowSize, detect) {
131
+ /**
132
+ * Detect sensitive info in a value.
133
+ *
134
+ * @param context
135
+ * Context.
136
+ * @param value
137
+ * Value.
138
+ * @param entities
139
+ * Strategy to use for detecting sensitive info;
140
+ * either by denying everything and allowing certain tags or by allowing
141
+ * everything and denying certain tags.
142
+ * @param contextWindowSize
143
+ * Number of tokens to pass to `detect`.
144
+ * @param detect
145
+ * Function to detect sensitive info (optional).
146
+ * @returns
147
+ * Promise for a result.
148
+ */
149
+ async function detectSensitiveInfo(context, value, entities, contextWindowSize, detect) {
99
150
  const { log } = context;
100
151
  const coreImports = createCoreImports(detect);
101
152
  const analyze = await initializeWasm(coreImports);
102
153
  if (typeof analyze !== "undefined") {
103
154
  const skipCustomDetect = typeof detect !== "function";
104
- return analyze.detectSensitiveInfo(candidate, {
155
+ return analyze.detectSensitiveInfo(value, {
105
156
  entities,
106
157
  contextWindowSize,
107
158
  skipCustomDetect,
108
159
  });
160
+ // Ignore the `else` branch as we test in places that have WebAssembly.
161
+ /* node:coverage ignore next 4 */
109
162
  }
110
- else {
111
- log.debug("WebAssembly is not supported in this runtime");
112
- throw new Error("SENSITIVE_INFO rule failed to run because Wasm is not supported in this environment.");
163
+ log.debug("WebAssembly is not supported in this runtime");
164
+ throw new Error("SENSITIVE_INFO rule failed to run because Wasm is not supported in this environment.");
165
+ }
166
+ /**
167
+ * Check if a filter matches a request.
168
+ *
169
+ * @param context
170
+ * Arcjet context.
171
+ * @param request
172
+ * Request.
173
+ * @param expressions
174
+ * Filter expressions.
175
+ * @returns
176
+ * Promise to whether the filter matches the request.
177
+ */
178
+ async function matchFilters(context, request, expressions, allowIfMatch) {
179
+ const coreImports = createCoreImports();
180
+ const analyze = await initializeWasm(coreImports);
181
+ if (typeof analyze !== "undefined") {
182
+ return analyze.matchFilters(JSON.stringify(request),
183
+ // @ts-expect-error: WebAssembly does not support readonly values.
184
+ expressions, allowIfMatch);
185
+ // Ignore the `else` branch as we test in places that have WebAssembly.
186
+ /* node:coverage ignore next 4 */
113
187
  }
188
+ context.log.debug("WebAssembly is not supported in this runtime");
189
+ throw new Error("FILTER rule failed to run because Wasm is not supported in this environment.");
114
190
  }
115
191
 
116
- export { detectBot, detectSensitiveInfo, generateFingerprint, isValidEmail };
192
+ export { detectBot, detectSensitiveInfo, generateFingerprint, isValidEmail, matchFilters };
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@arcjet/analyze",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.11",
4
4
  "description": "Arcjet local analysis engine",
5
+ "keywords": [
6
+ "analyze",
7
+ "arcjet",
8
+ "attack",
9
+ "limit",
10
+ "protect",
11
+ "verify"
12
+ ],
5
13
  "license": "Apache-2.0",
6
14
  "homepage": "https://arcjet.com",
7
15
  "repository": {
@@ -25,34 +33,29 @@
25
33
  "main": "./index.js",
26
34
  "types": "./index.d.ts",
27
35
  "files": [
28
- "LICENSE",
29
- "README.md",
30
- "_virtual/",
31
- "wasm/",
32
- "*.js",
33
- "*.d.ts",
34
- "!*.config.js"
36
+ "index.d.ts",
37
+ "index.js"
35
38
  ],
36
39
  "scripts": {
37
- "prepublishOnly": "npm run build",
38
40
  "build": "rollup --config rollup.config.js",
39
41
  "lint": "eslint .",
40
- "pretest": "npm run build",
41
- "test": "node --test --experimental-test-coverage"
42
+ "prepublishOnly": "npm run build",
43
+ "test-api": "node --test",
44
+ "test-coverage": "node --experimental-test-coverage --test",
45
+ "test": "npm run build && npm run lint && npm run test-coverage"
42
46
  },
43
47
  "dependencies": {
44
- "@arcjet/analyze-wasm": "1.0.0-beta.1",
45
- "@arcjet/protocol": "1.0.0-beta.1"
48
+ "@arcjet/analyze-wasm": "1.0.0-beta.11",
49
+ "@arcjet/protocol": "1.0.0-beta.11"
46
50
  },
47
51
  "devDependencies": {
48
- "@arcjet/eslint-config": "1.0.0-beta.1",
49
- "@arcjet/rollup-config": "1.0.0-beta.1",
50
- "@arcjet/tsconfig": "1.0.0-beta.1",
52
+ "@arcjet/eslint-config": "1.0.0-beta.11",
53
+ "@arcjet/rollup-config": "1.0.0-beta.11",
51
54
  "@bytecodealliance/jco": "1.5.0",
52
- "@rollup/wasm-node": "4.30.1",
53
- "@types/node": "18.18.0",
54
- "expect": "29.7.0",
55
- "typescript": "5.7.3"
55
+ "@rollup/wasm-node": "4.50.0",
56
+ "@types/node": "24.3.0",
57
+ "eslint": "9.34.0",
58
+ "typescript": "5.9.2"
56
59
  },
57
60
  "publishConfig": {
58
61
  "access": "public",