@arcjet/redact 1.0.0-alpha.30 → 1.0.0-alpha.32
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/package.json +7 -8
- package/index.ts +0 -231
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcjet/redact",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.32",
|
|
4
4
|
"description": "Arcjet sensitive information redaction library",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://arcjet.com",
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
"README.md",
|
|
30
30
|
"*.js",
|
|
31
31
|
"*.d.ts",
|
|
32
|
-
"*.ts",
|
|
33
32
|
"!*.config.js"
|
|
34
33
|
],
|
|
35
34
|
"scripts": {
|
|
@@ -40,17 +39,17 @@
|
|
|
40
39
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest"
|
|
41
40
|
},
|
|
42
41
|
"dependencies": {
|
|
43
|
-
"@arcjet/redact-wasm": "1.0.0-alpha.
|
|
42
|
+
"@arcjet/redact-wasm": "1.0.0-alpha.32"
|
|
44
43
|
},
|
|
45
44
|
"devDependencies": {
|
|
46
|
-
"@arcjet/eslint-config": "1.0.0-alpha.
|
|
47
|
-
"@arcjet/rollup-config": "1.0.0-alpha.
|
|
48
|
-
"@arcjet/tsconfig": "1.0.0-alpha.
|
|
45
|
+
"@arcjet/eslint-config": "1.0.0-alpha.32",
|
|
46
|
+
"@arcjet/rollup-config": "1.0.0-alpha.32",
|
|
47
|
+
"@arcjet/tsconfig": "1.0.0-alpha.32",
|
|
49
48
|
"@jest/globals": "29.7.0",
|
|
50
|
-
"@rollup/wasm-node": "4.27.
|
|
49
|
+
"@rollup/wasm-node": "4.27.4",
|
|
51
50
|
"@types/node": "18.18.0",
|
|
52
51
|
"jest": "29.7.0",
|
|
53
|
-
"typescript": "5.
|
|
52
|
+
"typescript": "5.7.2"
|
|
54
53
|
},
|
|
55
54
|
"publishConfig": {
|
|
56
55
|
"access": "public",
|
package/index.ts
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import { initializeWasm } from "@arcjet/redact-wasm";
|
|
2
|
-
import type { SensitiveInfoEntity } from "@arcjet/redact-wasm";
|
|
3
|
-
|
|
4
|
-
export type ArcjetSensitiveInfoType =
|
|
5
|
-
| "email"
|
|
6
|
-
| "phone-number"
|
|
7
|
-
| "ip-address"
|
|
8
|
-
| "credit-card-number";
|
|
9
|
-
|
|
10
|
-
type DetectSensitiveInfoEntities<T> = (
|
|
11
|
-
tokens: string[],
|
|
12
|
-
) => Array<ArcjetSensitiveInfoType | T | undefined>;
|
|
13
|
-
|
|
14
|
-
type ValidEntities<Detect> = Array<
|
|
15
|
-
// Via https://www.reddit.com/r/typescript/comments/17up72w/comment/k958cb0/
|
|
16
|
-
// Conditional types distribute over unions. If you have ((string | undefined)
|
|
17
|
-
// extends undefined ? 1 : 0) it is evaluated separately for each member of
|
|
18
|
-
// the union, then union-ed together again. The result is (string extends
|
|
19
|
-
// undefined ? 1 : 0) | (undefined extends undefined ? 1 : 0) which simplifies
|
|
20
|
-
// to 0 | 1
|
|
21
|
-
undefined extends Detect
|
|
22
|
-
? ArcjetSensitiveInfoType
|
|
23
|
-
: Detect extends DetectSensitiveInfoEntities<infer CustomEntities>
|
|
24
|
-
? ArcjetSensitiveInfoType | CustomEntities
|
|
25
|
-
: never
|
|
26
|
-
>;
|
|
27
|
-
|
|
28
|
-
export type RedactOptions<Detect> = {
|
|
29
|
-
entities?: ValidEntities<Detect>;
|
|
30
|
-
contextWindowSize?: number;
|
|
31
|
-
detect?: Detect;
|
|
32
|
-
replace?: (entity: ValidEntities<Detect>[number]) => string | undefined;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
function userEntitiesToWasm(entity: unknown) {
|
|
36
|
-
if (typeof entity !== "string") {
|
|
37
|
-
throw new Error("redaction entities must be strings");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (entity === "email") {
|
|
41
|
-
return { tag: "email" as const };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (entity === "phone-number") {
|
|
45
|
-
return { tag: "phone-number" as const };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (entity === "ip-address") {
|
|
49
|
-
return { tag: "ip-address" as const };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (entity === "credit-card-number") {
|
|
53
|
-
return { tag: "credit-card-number" as const };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
tag: "custom" as const,
|
|
58
|
-
val: entity,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function wasmEntitiesToString(entity: SensitiveInfoEntity) {
|
|
63
|
-
if (entity.tag === "email") {
|
|
64
|
-
return "email";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (entity.tag === "ip-address") {
|
|
68
|
-
return "ip-address";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (entity.tag === "credit-card-number") {
|
|
72
|
-
return "credit-card-number";
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (entity.tag === "phone-number") {
|
|
76
|
-
return "phone-number";
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return entity.val;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function performReplacementInText(
|
|
83
|
-
text: string,
|
|
84
|
-
replacement: string,
|
|
85
|
-
start: number,
|
|
86
|
-
end: number,
|
|
87
|
-
): string {
|
|
88
|
-
return text.substring(0, start) + replacement + text.substring(end);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/* c8 ignore start */
|
|
92
|
-
// Coverage is ignored on these no-op functions because they are never executed
|
|
93
|
-
// due to the `skipCustomDetect` and `skipCustomReplace` options.
|
|
94
|
-
function noOpDetect(_tokens: string[]): Array<SensitiveInfoEntity | undefined> {
|
|
95
|
-
return [];
|
|
96
|
-
}
|
|
97
|
-
function noOpReplace(_input: SensitiveInfoEntity): string | undefined {
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
/* c8 ignore stop */
|
|
101
|
-
|
|
102
|
-
interface RedactedSensitiveInfoEntity {
|
|
103
|
-
original: string;
|
|
104
|
-
redacted: string;
|
|
105
|
-
start: number;
|
|
106
|
-
end: number;
|
|
107
|
-
identifiedType: string;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function getWasmOptions<
|
|
111
|
-
const Detect extends DetectSensitiveInfoEntities<CustomEntities> | undefined,
|
|
112
|
-
const CustomEntities extends string,
|
|
113
|
-
>(options?: RedactOptions<Detect>) {
|
|
114
|
-
if (typeof options === "object" && options !== null) {
|
|
115
|
-
if (typeof options.entities !== "undefined") {
|
|
116
|
-
if (Array.isArray(options.entities)) {
|
|
117
|
-
if (options.entities.length < 1) {
|
|
118
|
-
throw new Error("no entities configured for redaction");
|
|
119
|
-
}
|
|
120
|
-
} else {
|
|
121
|
-
throw new Error("entities must be an array");
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
entities: options.entities?.map(userEntitiesToWasm),
|
|
127
|
-
contextWindowSize: options.contextWindowSize || 1,
|
|
128
|
-
skipCustomDetect: typeof options.detect !== "function",
|
|
129
|
-
skipCustomRedact: typeof options.replace !== "function",
|
|
130
|
-
};
|
|
131
|
-
} else {
|
|
132
|
-
return {
|
|
133
|
-
entities: undefined,
|
|
134
|
-
contextWindowSize: 1,
|
|
135
|
-
skipCustomDetect: true,
|
|
136
|
-
skipCustomRedact: true,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async function callRedactWasm<
|
|
142
|
-
const Detect extends DetectSensitiveInfoEntities<CustomEntities> | undefined,
|
|
143
|
-
const CustomEntities extends string,
|
|
144
|
-
>(
|
|
145
|
-
candidate: string,
|
|
146
|
-
options?: RedactOptions<Detect>,
|
|
147
|
-
): Promise<RedactedSensitiveInfoEntity[]> {
|
|
148
|
-
let convertedDetect = noOpDetect;
|
|
149
|
-
if (typeof options?.detect === "function") {
|
|
150
|
-
const detect = options.detect;
|
|
151
|
-
convertedDetect = (tokens: string[]) => {
|
|
152
|
-
return detect(tokens)
|
|
153
|
-
.filter((e) => typeof e !== "undefined")
|
|
154
|
-
.map((e) => userEntitiesToWasm(e));
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
let convertedReplace = noOpReplace;
|
|
159
|
-
if (typeof options?.replace === "function") {
|
|
160
|
-
const replace = options.replace;
|
|
161
|
-
convertedReplace = (identifiedType: SensitiveInfoEntity) => {
|
|
162
|
-
return replace(
|
|
163
|
-
// @ts-ignore because we know this is coming from Wasm
|
|
164
|
-
wasmEntitiesToString(identifiedType),
|
|
165
|
-
);
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const wasm = await initializeWasm(convertedDetect, convertedReplace);
|
|
170
|
-
|
|
171
|
-
if (typeof wasm !== "undefined") {
|
|
172
|
-
const config = getWasmOptions(options);
|
|
173
|
-
|
|
174
|
-
return wasm.redact(candidate, config).map((e) => {
|
|
175
|
-
return {
|
|
176
|
-
...e,
|
|
177
|
-
identifiedType: wasmEntitiesToString(e.identifiedType),
|
|
178
|
-
};
|
|
179
|
-
});
|
|
180
|
-
} else {
|
|
181
|
-
throw new Error(
|
|
182
|
-
"redact failed to run because Wasm is not supported in this environment",
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
type Unredact = (input: string) => string;
|
|
188
|
-
|
|
189
|
-
export async function redact<
|
|
190
|
-
const Detect extends DetectSensitiveInfoEntities<CustomEntities> | undefined,
|
|
191
|
-
const CustomEntities extends string,
|
|
192
|
-
>(
|
|
193
|
-
candidate: string,
|
|
194
|
-
options?: RedactOptions<Detect>,
|
|
195
|
-
): Promise<[string, Unredact]> {
|
|
196
|
-
const redactions = await callRedactWasm(candidate, options);
|
|
197
|
-
|
|
198
|
-
// Need to apply the redactions in reverse order so that the offsets aren't changed
|
|
199
|
-
// when we redact with strings that are longer/shorter than the original.
|
|
200
|
-
redactions.reverse();
|
|
201
|
-
|
|
202
|
-
for (const redaction of redactions) {
|
|
203
|
-
candidate = performReplacementInText(
|
|
204
|
-
candidate,
|
|
205
|
-
redaction.redacted,
|
|
206
|
-
redaction.start,
|
|
207
|
-
redaction.end,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function unredact(input: string): string {
|
|
212
|
-
for (const redaction of redactions) {
|
|
213
|
-
let position;
|
|
214
|
-
while (position !== -1) {
|
|
215
|
-
position = input.indexOf(redaction.redacted);
|
|
216
|
-
if (position !== -1) {
|
|
217
|
-
input = performReplacementInText(
|
|
218
|
-
input,
|
|
219
|
-
redaction.original,
|
|
220
|
-
position,
|
|
221
|
-
position + redaction.redacted.length,
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return input;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return [candidate, unredact];
|
|
231
|
-
}
|