@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.
- package/README.md +53 -19
- package/index.d.ts +75 -9
- package/index.js +112 -36
- 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
|
-
|
|
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
|
-
|
|
28
|
-
npm install -S @arcjet/analyze
|
|
29
|
-
```
|
|
28
|
+
## What is this?
|
|
30
29
|
|
|
31
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
##
|
|
60
|
+
## Use
|
|
44
61
|
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
```js
|
|
63
|
+
import { generateFingerprint, isValidEmail } from "@arcjet/analyze";
|
|
47
64
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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.
|
|
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
|
-
"
|
|
29
|
-
"
|
|
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
|
-
"
|
|
41
|
-
"test": "node --test
|
|
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.
|
|
45
|
-
"@arcjet/protocol": "1.0.0-beta.
|
|
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.
|
|
49
|
-
"@arcjet/rollup-config": "1.0.0-beta.
|
|
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.
|
|
53
|
-
"@types/node": "
|
|
54
|
-
"
|
|
55
|
-
"typescript": "5.
|
|
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",
|