@slates/provider 1.0.0-rc.21 → 1.0.0-rc.22
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/index.cjs +103 -77
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.module.js +103 -77
- package/package.json +2 -2
- package/src/action/directUpload.test.ts +3 -1
- package/src/action/directUpload.ts +8 -4
- package/src/action/getFileUrlTool.ts +14 -4
- package/src/auth/redact.test.ts +121 -1
- package/src/auth/redact.ts +30 -6
package/dist/index.cjs
CHANGED
|
@@ -1051,6 +1051,91 @@ var SlateActionBuilder = class {
|
|
|
1051
1051
|
// src/action/getFileUrlTool.ts
|
|
1052
1052
|
var import_zod = require("zod");
|
|
1053
1053
|
|
|
1054
|
+
// src/auth/redact.ts
|
|
1055
|
+
var AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX = "$$MT$secret$authConfig$";
|
|
1056
|
+
var AuthConfigSecretRedactor = class {
|
|
1057
|
+
secretToPlaceholder = /* @__PURE__ */ new Map();
|
|
1058
|
+
constructor(authConfig) {
|
|
1059
|
+
if (authConfig) this.buildSecretMap(authConfig);
|
|
1060
|
+
}
|
|
1061
|
+
redact(value) {
|
|
1062
|
+
return this.transform(value, (stringValue) => {
|
|
1063
|
+
return this.secretToPlaceholder.get(stringValue) ?? stringValue;
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
redactEmbedded(value) {
|
|
1067
|
+
if (this.secretToPlaceholder.size === 0) return value;
|
|
1068
|
+
let pattern = new RegExp(
|
|
1069
|
+
[...this.secretToPlaceholder.keys()].sort((a, b) => b.length - a.length).map((secret) => secret.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"),
|
|
1070
|
+
"g"
|
|
1071
|
+
);
|
|
1072
|
+
return this.transform(value, (stringValue) => {
|
|
1073
|
+
let exactPlaceholder = this.secretToPlaceholder.get(stringValue);
|
|
1074
|
+
if (exactPlaceholder) return exactPlaceholder;
|
|
1075
|
+
return stringValue.replace(
|
|
1076
|
+
pattern,
|
|
1077
|
+
(secret) => `${this.secretToPlaceholder.get(secret)}$$`
|
|
1078
|
+
);
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
buildSecretMap(value, path = []) {
|
|
1082
|
+
if (typeof value === "string") {
|
|
1083
|
+
if (value.length === 0) return;
|
|
1084
|
+
this.secretToPlaceholder.set(
|
|
1085
|
+
value,
|
|
1086
|
+
`${AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX}${path.join(".")}`
|
|
1087
|
+
);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (Array.isArray(value)) {
|
|
1091
|
+
value.forEach((item, index) => {
|
|
1092
|
+
this.buildSecretMap(item, [...path, String(index)]);
|
|
1093
|
+
});
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (this.isPlainObject(value)) {
|
|
1097
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
1098
|
+
this.buildSecretMap(nestedValue, [...path, key]);
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
transform(value, transformString) {
|
|
1103
|
+
if (typeof value === "string") {
|
|
1104
|
+
return transformString(value);
|
|
1105
|
+
}
|
|
1106
|
+
if (Array.isArray(value)) {
|
|
1107
|
+
return value.map((item) => this.transform(item, transformString));
|
|
1108
|
+
}
|
|
1109
|
+
if (this.isPlainObject(value)) {
|
|
1110
|
+
let result = {};
|
|
1111
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
1112
|
+
result[key] = this.transform(nestedValue, transformString);
|
|
1113
|
+
});
|
|
1114
|
+
return result;
|
|
1115
|
+
}
|
|
1116
|
+
return value;
|
|
1117
|
+
}
|
|
1118
|
+
isPlainObject(value) {
|
|
1119
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
var redactUrlAttachmentSecrets = (attachments, authConfig) => {
|
|
1123
|
+
if (!authConfig || !attachments) return attachments;
|
|
1124
|
+
let redactor = new AuthConfigSecretRedactor(authConfig);
|
|
1125
|
+
return attachments.map((attachment) => {
|
|
1126
|
+
if (attachment.content.type !== "url") return attachment;
|
|
1127
|
+
if (!attachment.content.headers && !attachment.content.query) return attachment;
|
|
1128
|
+
return {
|
|
1129
|
+
...attachment,
|
|
1130
|
+
content: {
|
|
1131
|
+
...attachment.content,
|
|
1132
|
+
headers: attachment.content.headers ? redactor.redactEmbedded(attachment.content.headers) : void 0,
|
|
1133
|
+
query: attachment.content.query ? redactor.redactEmbedded(attachment.content.query) : void 0
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
});
|
|
1137
|
+
};
|
|
1138
|
+
|
|
1054
1139
|
// src/action/tool.ts
|
|
1055
1140
|
var SlateTool = class _SlateTool extends SlateAction {
|
|
1056
1141
|
#handleInvocation;
|
|
@@ -1089,10 +1174,18 @@ var getFileUrlTool = (spec2, handleGetFileUrl) => tool(spec2, {
|
|
|
1089
1174
|
key: GET_FILE_URL_TOOL_ID,
|
|
1090
1175
|
name: "Get File URL",
|
|
1091
1176
|
description: "Reserved: refreshes an expiring attachment download URL. Hub-internal only."
|
|
1092
|
-
}).input(getFileUrlToolInputSchema).output(getFileUrlToolOutputSchema).handleInvocation(async (ctx) =>
|
|
1093
|
-
output
|
|
1094
|
-
|
|
1095
|
-
|
|
1177
|
+
}).input(getFileUrlToolInputSchema).output(getFileUrlToolOutputSchema).handleInvocation(async (ctx) => {
|
|
1178
|
+
let output = await handleGetFileUrl(ctx);
|
|
1179
|
+
let redactor = new AuthConfigSecretRedactor(ctx._getAuthConfigForRedaction());
|
|
1180
|
+
return {
|
|
1181
|
+
output: {
|
|
1182
|
+
...output,
|
|
1183
|
+
...output.headers ? { headers: redactor.redactEmbedded(output.headers) } : {},
|
|
1184
|
+
...output.query ? { query: redactor.redactEmbedded(output.query) } : {}
|
|
1185
|
+
},
|
|
1186
|
+
message: "ok"
|
|
1187
|
+
};
|
|
1188
|
+
}).build();
|
|
1096
1189
|
|
|
1097
1190
|
// src/action/publicTool.ts
|
|
1098
1191
|
var SlatePublicTool = class {
|
|
@@ -1215,7 +1308,9 @@ var bufferAttachmentBody = async (body, maxSizeBytes) => {
|
|
|
1215
1308
|
if (body.byteLength > maxSizeBytes) {
|
|
1216
1309
|
throw new Error(`Attachment exceeds the maximum size of ${maxSizeBytes} bytes`);
|
|
1217
1310
|
}
|
|
1218
|
-
|
|
1311
|
+
let buffered2 = new ArrayBuffer(body.byteLength);
|
|
1312
|
+
new Uint8Array(buffered2).set(body);
|
|
1313
|
+
return buffered2;
|
|
1219
1314
|
}
|
|
1220
1315
|
let reader = body.getReader();
|
|
1221
1316
|
let chunks = [];
|
|
@@ -1231,10 +1326,11 @@ var bufferAttachmentBody = async (body, maxSizeBytes) => {
|
|
|
1231
1326
|
}
|
|
1232
1327
|
chunks.push(value);
|
|
1233
1328
|
}
|
|
1234
|
-
let buffered = new
|
|
1329
|
+
let buffered = new ArrayBuffer(sizeBytes);
|
|
1330
|
+
let bufferedView = new Uint8Array(buffered);
|
|
1235
1331
|
let offset = 0;
|
|
1236
1332
|
for (let chunk of chunks) {
|
|
1237
|
-
|
|
1333
|
+
bufferedView.set(chunk, offset);
|
|
1238
1334
|
offset += chunk.byteLength;
|
|
1239
1335
|
}
|
|
1240
1336
|
return buffered;
|
|
@@ -1555,76 +1651,6 @@ var normalizeOAuthTokenResponse = (data, options = {}) => {
|
|
|
1555
1651
|
};
|
|
1556
1652
|
};
|
|
1557
1653
|
|
|
1558
|
-
// src/auth/redact.ts
|
|
1559
|
-
var AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX = "$$MT$secret$authConfig$";
|
|
1560
|
-
var AuthConfigSecretRedactor = class {
|
|
1561
|
-
secretToPlaceholder = /* @__PURE__ */ new Map();
|
|
1562
|
-
constructor(authConfig) {
|
|
1563
|
-
if (authConfig) this.buildSecretMap(authConfig);
|
|
1564
|
-
}
|
|
1565
|
-
redact(value) {
|
|
1566
|
-
return this.transform(value, (stringValue) => {
|
|
1567
|
-
return this.secretToPlaceholder.get(stringValue) ?? stringValue;
|
|
1568
|
-
});
|
|
1569
|
-
}
|
|
1570
|
-
buildSecretMap(value, path = []) {
|
|
1571
|
-
if (typeof value === "string") {
|
|
1572
|
-
if (value.length === 0) return;
|
|
1573
|
-
this.secretToPlaceholder.set(
|
|
1574
|
-
value,
|
|
1575
|
-
`${AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX}${path.join(".")}`
|
|
1576
|
-
);
|
|
1577
|
-
return;
|
|
1578
|
-
}
|
|
1579
|
-
if (Array.isArray(value)) {
|
|
1580
|
-
value.forEach((item, index) => {
|
|
1581
|
-
this.buildSecretMap(item, [...path, String(index)]);
|
|
1582
|
-
});
|
|
1583
|
-
return;
|
|
1584
|
-
}
|
|
1585
|
-
if (this.isPlainObject(value)) {
|
|
1586
|
-
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
1587
|
-
this.buildSecretMap(nestedValue, [...path, key]);
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
transform(value, transformString) {
|
|
1592
|
-
if (typeof value === "string") {
|
|
1593
|
-
return transformString(value);
|
|
1594
|
-
}
|
|
1595
|
-
if (Array.isArray(value)) {
|
|
1596
|
-
return value.map((item) => this.transform(item, transformString));
|
|
1597
|
-
}
|
|
1598
|
-
if (this.isPlainObject(value)) {
|
|
1599
|
-
let result = {};
|
|
1600
|
-
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
1601
|
-
result[key] = this.transform(nestedValue, transformString);
|
|
1602
|
-
});
|
|
1603
|
-
return result;
|
|
1604
|
-
}
|
|
1605
|
-
return value;
|
|
1606
|
-
}
|
|
1607
|
-
isPlainObject(value) {
|
|
1608
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1609
|
-
}
|
|
1610
|
-
};
|
|
1611
|
-
var redactUrlAttachmentSecrets = (attachments, authConfig) => {
|
|
1612
|
-
if (!authConfig || !attachments) return attachments;
|
|
1613
|
-
let redactor = new AuthConfigSecretRedactor(authConfig);
|
|
1614
|
-
return attachments.map((attachment) => {
|
|
1615
|
-
if (attachment.content.type !== "url") return attachment;
|
|
1616
|
-
if (!attachment.content.headers && !attachment.content.query) return attachment;
|
|
1617
|
-
return {
|
|
1618
|
-
...attachment,
|
|
1619
|
-
content: {
|
|
1620
|
-
...attachment.content,
|
|
1621
|
-
headers: attachment.content.headers ? redactor.redact(attachment.content.headers) : void 0,
|
|
1622
|
-
query: attachment.content.query ? redactor.redact(attachment.content.query) : void 0
|
|
1623
|
-
}
|
|
1624
|
-
};
|
|
1625
|
-
});
|
|
1626
|
-
};
|
|
1627
|
-
|
|
1628
1654
|
// src/axios/index.ts
|
|
1629
1655
|
var import_axios3 = __toESM(require("axios"), 1);
|
|
1630
1656
|
|
package/dist/index.d.cts
CHANGED
|
@@ -42,15 +42,15 @@ declare let createTextAttachment: (content: string, mimeType?: string, attachmen
|
|
|
42
42
|
*/
|
|
43
43
|
declare let AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX: string;
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* like the http trace redactor in '../axios/trace'.
|
|
45
|
+
* Maps auth-config string leaves to `$$MT$secret$authConfig$<path>` placeholders.
|
|
46
|
+
* `redact` preserves whole-value substitution for HTTP traces; `redactEmbedded` also
|
|
47
|
+
* replaces embedded values in attachment credentials, terminating those placeholders with `$$`.
|
|
49
48
|
*/
|
|
50
49
|
declare class AuthConfigSecretRedactor {
|
|
51
50
|
private readonly secretToPlaceholder;
|
|
52
51
|
constructor(authConfig: Record<string, unknown> | undefined);
|
|
53
52
|
redact<T>(value: T): T;
|
|
53
|
+
redactEmbedded<T>(value: T): T;
|
|
54
54
|
private buildSecretMap;
|
|
55
55
|
private transform;
|
|
56
56
|
private isPlainObject;
|
package/dist/index.d.ts
CHANGED
|
@@ -42,15 +42,15 @@ declare let createTextAttachment: (content: string, mimeType?: string, attachmen
|
|
|
42
42
|
*/
|
|
43
43
|
declare let AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX: string;
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* like the http trace redactor in '../axios/trace'.
|
|
45
|
+
* Maps auth-config string leaves to `$$MT$secret$authConfig$<path>` placeholders.
|
|
46
|
+
* `redact` preserves whole-value substitution for HTTP traces; `redactEmbedded` also
|
|
47
|
+
* replaces embedded values in attachment credentials, terminating those placeholders with `$$`.
|
|
49
48
|
*/
|
|
50
49
|
declare class AuthConfigSecretRedactor {
|
|
51
50
|
private readonly secretToPlaceholder;
|
|
52
51
|
constructor(authConfig: Record<string, unknown> | undefined);
|
|
53
52
|
redact<T>(value: T): T;
|
|
53
|
+
redactEmbedded<T>(value: T): T;
|
|
54
54
|
private buildSecretMap;
|
|
55
55
|
private transform;
|
|
56
56
|
private isPlainObject;
|
package/dist/index.module.js
CHANGED
|
@@ -942,6 +942,91 @@ var SlateActionBuilder = class {
|
|
|
942
942
|
// src/action/getFileUrlTool.ts
|
|
943
943
|
import { z } from "zod";
|
|
944
944
|
|
|
945
|
+
// src/auth/redact.ts
|
|
946
|
+
var AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX = "$$MT$secret$authConfig$";
|
|
947
|
+
var AuthConfigSecretRedactor = class {
|
|
948
|
+
secretToPlaceholder = /* @__PURE__ */ new Map();
|
|
949
|
+
constructor(authConfig) {
|
|
950
|
+
if (authConfig) this.buildSecretMap(authConfig);
|
|
951
|
+
}
|
|
952
|
+
redact(value) {
|
|
953
|
+
return this.transform(value, (stringValue) => {
|
|
954
|
+
return this.secretToPlaceholder.get(stringValue) ?? stringValue;
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
redactEmbedded(value) {
|
|
958
|
+
if (this.secretToPlaceholder.size === 0) return value;
|
|
959
|
+
let pattern = new RegExp(
|
|
960
|
+
[...this.secretToPlaceholder.keys()].sort((a, b) => b.length - a.length).map((secret) => secret.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"),
|
|
961
|
+
"g"
|
|
962
|
+
);
|
|
963
|
+
return this.transform(value, (stringValue) => {
|
|
964
|
+
let exactPlaceholder = this.secretToPlaceholder.get(stringValue);
|
|
965
|
+
if (exactPlaceholder) return exactPlaceholder;
|
|
966
|
+
return stringValue.replace(
|
|
967
|
+
pattern,
|
|
968
|
+
(secret) => `${this.secretToPlaceholder.get(secret)}$$`
|
|
969
|
+
);
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
buildSecretMap(value, path = []) {
|
|
973
|
+
if (typeof value === "string") {
|
|
974
|
+
if (value.length === 0) return;
|
|
975
|
+
this.secretToPlaceholder.set(
|
|
976
|
+
value,
|
|
977
|
+
`${AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX}${path.join(".")}`
|
|
978
|
+
);
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
if (Array.isArray(value)) {
|
|
982
|
+
value.forEach((item, index) => {
|
|
983
|
+
this.buildSecretMap(item, [...path, String(index)]);
|
|
984
|
+
});
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (this.isPlainObject(value)) {
|
|
988
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
989
|
+
this.buildSecretMap(nestedValue, [...path, key]);
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
transform(value, transformString) {
|
|
994
|
+
if (typeof value === "string") {
|
|
995
|
+
return transformString(value);
|
|
996
|
+
}
|
|
997
|
+
if (Array.isArray(value)) {
|
|
998
|
+
return value.map((item) => this.transform(item, transformString));
|
|
999
|
+
}
|
|
1000
|
+
if (this.isPlainObject(value)) {
|
|
1001
|
+
let result = {};
|
|
1002
|
+
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
1003
|
+
result[key] = this.transform(nestedValue, transformString);
|
|
1004
|
+
});
|
|
1005
|
+
return result;
|
|
1006
|
+
}
|
|
1007
|
+
return value;
|
|
1008
|
+
}
|
|
1009
|
+
isPlainObject(value) {
|
|
1010
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
var redactUrlAttachmentSecrets = (attachments, authConfig) => {
|
|
1014
|
+
if (!authConfig || !attachments) return attachments;
|
|
1015
|
+
let redactor = new AuthConfigSecretRedactor(authConfig);
|
|
1016
|
+
return attachments.map((attachment) => {
|
|
1017
|
+
if (attachment.content.type !== "url") return attachment;
|
|
1018
|
+
if (!attachment.content.headers && !attachment.content.query) return attachment;
|
|
1019
|
+
return {
|
|
1020
|
+
...attachment,
|
|
1021
|
+
content: {
|
|
1022
|
+
...attachment.content,
|
|
1023
|
+
headers: attachment.content.headers ? redactor.redactEmbedded(attachment.content.headers) : void 0,
|
|
1024
|
+
query: attachment.content.query ? redactor.redactEmbedded(attachment.content.query) : void 0
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
});
|
|
1028
|
+
};
|
|
1029
|
+
|
|
945
1030
|
// src/action/tool.ts
|
|
946
1031
|
var SlateTool = class _SlateTool extends SlateAction {
|
|
947
1032
|
#handleInvocation;
|
|
@@ -980,10 +1065,18 @@ var getFileUrlTool = (spec2, handleGetFileUrl) => tool(spec2, {
|
|
|
980
1065
|
key: GET_FILE_URL_TOOL_ID,
|
|
981
1066
|
name: "Get File URL",
|
|
982
1067
|
description: "Reserved: refreshes an expiring attachment download URL. Hub-internal only."
|
|
983
|
-
}).input(getFileUrlToolInputSchema).output(getFileUrlToolOutputSchema).handleInvocation(async (ctx) =>
|
|
984
|
-
output
|
|
985
|
-
|
|
986
|
-
|
|
1068
|
+
}).input(getFileUrlToolInputSchema).output(getFileUrlToolOutputSchema).handleInvocation(async (ctx) => {
|
|
1069
|
+
let output = await handleGetFileUrl(ctx);
|
|
1070
|
+
let redactor = new AuthConfigSecretRedactor(ctx._getAuthConfigForRedaction());
|
|
1071
|
+
return {
|
|
1072
|
+
output: {
|
|
1073
|
+
...output,
|
|
1074
|
+
...output.headers ? { headers: redactor.redactEmbedded(output.headers) } : {},
|
|
1075
|
+
...output.query ? { query: redactor.redactEmbedded(output.query) } : {}
|
|
1076
|
+
},
|
|
1077
|
+
message: "ok"
|
|
1078
|
+
};
|
|
1079
|
+
}).build();
|
|
987
1080
|
|
|
988
1081
|
// src/action/publicTool.ts
|
|
989
1082
|
var SlatePublicTool = class {
|
|
@@ -1106,7 +1199,9 @@ var bufferAttachmentBody = async (body, maxSizeBytes) => {
|
|
|
1106
1199
|
if (body.byteLength > maxSizeBytes) {
|
|
1107
1200
|
throw new Error(`Attachment exceeds the maximum size of ${maxSizeBytes} bytes`);
|
|
1108
1201
|
}
|
|
1109
|
-
|
|
1202
|
+
let buffered2 = new ArrayBuffer(body.byteLength);
|
|
1203
|
+
new Uint8Array(buffered2).set(body);
|
|
1204
|
+
return buffered2;
|
|
1110
1205
|
}
|
|
1111
1206
|
let reader = body.getReader();
|
|
1112
1207
|
let chunks = [];
|
|
@@ -1122,10 +1217,11 @@ var bufferAttachmentBody = async (body, maxSizeBytes) => {
|
|
|
1122
1217
|
}
|
|
1123
1218
|
chunks.push(value);
|
|
1124
1219
|
}
|
|
1125
|
-
let buffered = new
|
|
1220
|
+
let buffered = new ArrayBuffer(sizeBytes);
|
|
1221
|
+
let bufferedView = new Uint8Array(buffered);
|
|
1126
1222
|
let offset = 0;
|
|
1127
1223
|
for (let chunk of chunks) {
|
|
1128
|
-
|
|
1224
|
+
bufferedView.set(chunk, offset);
|
|
1129
1225
|
offset += chunk.byteLength;
|
|
1130
1226
|
}
|
|
1131
1227
|
return buffered;
|
|
@@ -1446,76 +1542,6 @@ var normalizeOAuthTokenResponse = (data, options = {}) => {
|
|
|
1446
1542
|
};
|
|
1447
1543
|
};
|
|
1448
1544
|
|
|
1449
|
-
// src/auth/redact.ts
|
|
1450
|
-
var AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX = "$$MT$secret$authConfig$";
|
|
1451
|
-
var AuthConfigSecretRedactor = class {
|
|
1452
|
-
secretToPlaceholder = /* @__PURE__ */ new Map();
|
|
1453
|
-
constructor(authConfig) {
|
|
1454
|
-
if (authConfig) this.buildSecretMap(authConfig);
|
|
1455
|
-
}
|
|
1456
|
-
redact(value) {
|
|
1457
|
-
return this.transform(value, (stringValue) => {
|
|
1458
|
-
return this.secretToPlaceholder.get(stringValue) ?? stringValue;
|
|
1459
|
-
});
|
|
1460
|
-
}
|
|
1461
|
-
buildSecretMap(value, path = []) {
|
|
1462
|
-
if (typeof value === "string") {
|
|
1463
|
-
if (value.length === 0) return;
|
|
1464
|
-
this.secretToPlaceholder.set(
|
|
1465
|
-
value,
|
|
1466
|
-
`${AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX}${path.join(".")}`
|
|
1467
|
-
);
|
|
1468
|
-
return;
|
|
1469
|
-
}
|
|
1470
|
-
if (Array.isArray(value)) {
|
|
1471
|
-
value.forEach((item, index) => {
|
|
1472
|
-
this.buildSecretMap(item, [...path, String(index)]);
|
|
1473
|
-
});
|
|
1474
|
-
return;
|
|
1475
|
-
}
|
|
1476
|
-
if (this.isPlainObject(value)) {
|
|
1477
|
-
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
1478
|
-
this.buildSecretMap(nestedValue, [...path, key]);
|
|
1479
|
-
});
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
transform(value, transformString) {
|
|
1483
|
-
if (typeof value === "string") {
|
|
1484
|
-
return transformString(value);
|
|
1485
|
-
}
|
|
1486
|
-
if (Array.isArray(value)) {
|
|
1487
|
-
return value.map((item) => this.transform(item, transformString));
|
|
1488
|
-
}
|
|
1489
|
-
if (this.isPlainObject(value)) {
|
|
1490
|
-
let result = {};
|
|
1491
|
-
Object.entries(value).forEach(([key, nestedValue]) => {
|
|
1492
|
-
result[key] = this.transform(nestedValue, transformString);
|
|
1493
|
-
});
|
|
1494
|
-
return result;
|
|
1495
|
-
}
|
|
1496
|
-
return value;
|
|
1497
|
-
}
|
|
1498
|
-
isPlainObject(value) {
|
|
1499
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1500
|
-
}
|
|
1501
|
-
};
|
|
1502
|
-
var redactUrlAttachmentSecrets = (attachments, authConfig) => {
|
|
1503
|
-
if (!authConfig || !attachments) return attachments;
|
|
1504
|
-
let redactor = new AuthConfigSecretRedactor(authConfig);
|
|
1505
|
-
return attachments.map((attachment) => {
|
|
1506
|
-
if (attachment.content.type !== "url") return attachment;
|
|
1507
|
-
if (!attachment.content.headers && !attachment.content.query) return attachment;
|
|
1508
|
-
return {
|
|
1509
|
-
...attachment,
|
|
1510
|
-
content: {
|
|
1511
|
-
...attachment.content,
|
|
1512
|
-
headers: attachment.content.headers ? redactor.redact(attachment.content.headers) : void 0,
|
|
1513
|
-
query: attachment.content.query ? redactor.redact(attachment.content.query) : void 0
|
|
1514
|
-
}
|
|
1515
|
-
};
|
|
1516
|
-
});
|
|
1517
|
-
};
|
|
1518
|
-
|
|
1519
1545
|
// src/axios/index.ts
|
|
1520
1546
|
import baseAxios, { getAdapter, isAxiosError as isAxiosError2 } from "axios";
|
|
1521
1547
|
|
package/package.json
CHANGED
|
@@ -50,7 +50,9 @@ describe('uploadAttachmentDirect', () => {
|
|
|
50
50
|
'content-length': '5'
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
|
-
expect(upload[1]!.body).toEqual(
|
|
53
|
+
expect(new Uint8Array(upload[1]!.body as ArrayBuffer)).toEqual(
|
|
54
|
+
new Uint8Array([1, 2, 3, 4, 5])
|
|
55
|
+
);
|
|
54
56
|
expect(upload[1]).not.toHaveProperty('duplex');
|
|
55
57
|
});
|
|
56
58
|
|
|
@@ -16,12 +16,15 @@ export interface SlateDirectUploadInput {
|
|
|
16
16
|
let bufferAttachmentBody = async (
|
|
17
17
|
body: SlateDirectUploadInput['body'],
|
|
18
18
|
maxSizeBytes: number
|
|
19
|
-
) => {
|
|
19
|
+
): Promise<ArrayBuffer> => {
|
|
20
20
|
if (body instanceof Uint8Array) {
|
|
21
21
|
if (body.byteLength > maxSizeBytes) {
|
|
22
22
|
throw new Error(`Attachment exceeds the maximum size of ${maxSizeBytes} bytes`);
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
|
|
25
|
+
let buffered = new ArrayBuffer(body.byteLength);
|
|
26
|
+
new Uint8Array(buffered).set(body);
|
|
27
|
+
return buffered;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
let reader = body.getReader();
|
|
@@ -41,10 +44,11 @@ let bufferAttachmentBody = async (
|
|
|
41
44
|
chunks.push(value);
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
let buffered = new
|
|
47
|
+
let buffered = new ArrayBuffer(sizeBytes);
|
|
48
|
+
let bufferedView = new Uint8Array(buffered);
|
|
45
49
|
let offset = 0;
|
|
46
50
|
for (let chunk of chunks) {
|
|
47
|
-
|
|
51
|
+
bufferedView.set(chunk, offset);
|
|
48
52
|
offset += chunk.byteLength;
|
|
49
53
|
}
|
|
50
54
|
return buffered;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { AuthConfigSecretRedactor } from '../auth/redact';
|
|
2
3
|
import type { SlateContext } from '../context/context';
|
|
3
4
|
import type { SlateSpecification } from '../specification/specification';
|
|
4
5
|
import { tool } from './tool';
|
|
@@ -34,8 +35,17 @@ export let getFileUrlTool = <ConfigType extends {}, AuthType extends {}>(
|
|
|
34
35
|
})
|
|
35
36
|
.input(getFileUrlToolInputSchema)
|
|
36
37
|
.output(getFileUrlToolOutputSchema)
|
|
37
|
-
.handleInvocation(async ctx =>
|
|
38
|
-
output
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
.handleInvocation(async ctx => {
|
|
39
|
+
let output = await handleGetFileUrl(ctx);
|
|
40
|
+
let redactor = new AuthConfigSecretRedactor(ctx._getAuthConfigForRedaction());
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
output: {
|
|
44
|
+
...output,
|
|
45
|
+
...(output.headers ? { headers: redactor.redactEmbedded(output.headers) } : {}),
|
|
46
|
+
...(output.query ? { query: redactor.redactEmbedded(output.query) } : {})
|
|
47
|
+
},
|
|
48
|
+
message: 'ok'
|
|
49
|
+
};
|
|
50
|
+
})
|
|
41
51
|
.build();
|
package/src/auth/redact.test.ts
CHANGED
|
@@ -49,6 +49,98 @@ describe('AuthConfigSecretRedactor', () => {
|
|
|
49
49
|
});
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
+
describe('AuthConfigSecretRedactor.redactEmbedded', () => {
|
|
53
|
+
it('preserves the legacy placeholder format for whole-value matches', () => {
|
|
54
|
+
let redactor = new AuthConfigSecretRedactor({
|
|
55
|
+
token: 'ABC',
|
|
56
|
+
nested: { tokens: ['XYZ'] }
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(redactor.redactEmbedded({ values: ['ABC', 'XYZ', 'plain', 42, null] })).toEqual({
|
|
60
|
+
values: [
|
|
61
|
+
'$$MT$secret$authConfig$token',
|
|
62
|
+
'$$MT$secret$authConfig$nested.tokens.0',
|
|
63
|
+
'plain',
|
|
64
|
+
42,
|
|
65
|
+
null
|
|
66
|
+
]
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('terminates embedded placeholders before prefixes, suffixes, and adjacent literals', () => {
|
|
71
|
+
let redactor = new AuthConfigSecretRedactor({ token: 'ABC', token2: 'XYZ' });
|
|
72
|
+
|
|
73
|
+
expect(
|
|
74
|
+
redactor.redactEmbedded({
|
|
75
|
+
bearer: 'Bearer ABC',
|
|
76
|
+
suffix: 'ABC2',
|
|
77
|
+
multiple: 'ABC:XYZ:ABC',
|
|
78
|
+
adjacent: 'ABCXYZ'
|
|
79
|
+
})
|
|
80
|
+
).toEqual({
|
|
81
|
+
bearer: 'Bearer $$MT$secret$authConfig$token$$',
|
|
82
|
+
suffix: '$$MT$secret$authConfig$token$$2',
|
|
83
|
+
multiple:
|
|
84
|
+
'$$MT$secret$authConfig$token$$:$$MT$secret$authConfig$token2$$:$$MT$secret$authConfig$token$$',
|
|
85
|
+
adjacent: '$$MT$secret$authConfig$token$$$$MT$secret$authConfig$token2$$'
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('matches overlapping secrets longest-first', () => {
|
|
90
|
+
let redactor = new AuthConfigSecretRedactor({ token: 'ABC', longer: 'ABC2' });
|
|
91
|
+
|
|
92
|
+
expect(redactor.redactEmbedded('ABC2 ABC')).toBe(
|
|
93
|
+
'$$MT$secret$authConfig$longer$$ $$MT$secret$authConfig$token$$'
|
|
94
|
+
);
|
|
95
|
+
expect(redactor.redactEmbedded('ABC2')).toBe('$$MT$secret$authConfig$longer');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('does not scan emitted placeholders again', () => {
|
|
99
|
+
let redactor = new AuthConfigSecretRedactor({ token: 'ABC', other: 'token' });
|
|
100
|
+
|
|
101
|
+
expect(redactor.redactEmbedded('Bearer ABC token')).toBe(
|
|
102
|
+
'Bearer $$MT$secret$authConfig$token$$ $$MT$secret$authConfig$other$$'
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it.each([
|
|
107
|
+
'.',
|
|
108
|
+
'*',
|
|
109
|
+
'+',
|
|
110
|
+
'?',
|
|
111
|
+
'^',
|
|
112
|
+
'$',
|
|
113
|
+
'{',
|
|
114
|
+
'}',
|
|
115
|
+
'(',
|
|
116
|
+
')',
|
|
117
|
+
'|',
|
|
118
|
+
'[',
|
|
119
|
+
']',
|
|
120
|
+
'\\'
|
|
121
|
+
])('treats %s literally in secret values', character => {
|
|
122
|
+
let secret = `${character}secret`;
|
|
123
|
+
let redactor = new AuthConfigSecretRedactor({ token: secret });
|
|
124
|
+
|
|
125
|
+
expect(redactor.redactEmbedded(`prefix ${secret} suffix`)).toBe(
|
|
126
|
+
'prefix $$MT$secret$authConfig$token$$ suffix'
|
|
127
|
+
);
|
|
128
|
+
expect(redactor.redactEmbedded('prefix secret suffix')).toBe('prefix secret suffix');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('ignores empty secrets and leaves unrelated values unchanged', () => {
|
|
132
|
+
let redactor = new AuthConfigSecretRedactor({ empty: '', token: 'ABC' });
|
|
133
|
+
|
|
134
|
+
expect(redactor.redactEmbedded({ empty: '', plain: 'plain', token: 'ABC' })).toEqual({
|
|
135
|
+
empty: '',
|
|
136
|
+
plain: 'plain',
|
|
137
|
+
token: '$$MT$secret$authConfig$token'
|
|
138
|
+
});
|
|
139
|
+
expect(new AuthConfigSecretRedactor({ empty: '' }).redactEmbedded('plain')).toBe('plain');
|
|
140
|
+
expect(new AuthConfigSecretRedactor(undefined).redactEmbedded('plain')).toBe('plain');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
52
144
|
describe('redactUrlAttachmentSecrets', () => {
|
|
53
145
|
let authConfig = { token: '123SECRET', nested: { token2: 'also_secret' } };
|
|
54
146
|
|
|
@@ -69,12 +161,40 @@ describe('redactUrlAttachmentSecrets', () => {
|
|
|
69
161
|
|
|
70
162
|
expect(redactUrlAttachmentSecrets(attachments, authConfig)).toEqual([
|
|
71
163
|
urlAttachment({
|
|
72
|
-
headers: { Authorization: 'Bearer
|
|
164
|
+
headers: { Authorization: 'Bearer $$MT$secret$authConfig$token$$' },
|
|
73
165
|
query: { key: '$$MT$secret$authConfig$nested.token2' }
|
|
74
166
|
})
|
|
75
167
|
]);
|
|
76
168
|
});
|
|
77
169
|
|
|
170
|
+
it('only changes header and query values, preserving metadata and the original attachment', () => {
|
|
171
|
+
let attachment: SlateAttachment = {
|
|
172
|
+
mimeType: 'application/pdf',
|
|
173
|
+
attachmentHash: 'file-hash',
|
|
174
|
+
content: {
|
|
175
|
+
type: 'url',
|
|
176
|
+
url: 'https://example.com/123SECRET/file.pdf',
|
|
177
|
+
headers: { Authorization: 'Bearer 123SECRET' },
|
|
178
|
+
query: { key: 'also_secret-suffix' },
|
|
179
|
+
refreshReference: { id: 'file-1' },
|
|
180
|
+
refreshAt: '2026-09-10T00:00:00Z'
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
let original = structuredClone(attachment);
|
|
184
|
+
|
|
185
|
+
expect(redactUrlAttachmentSecrets([attachment], authConfig)).toEqual([
|
|
186
|
+
{
|
|
187
|
+
...original,
|
|
188
|
+
content: {
|
|
189
|
+
...original.content,
|
|
190
|
+
headers: { Authorization: 'Bearer $$MT$secret$authConfig$token$$' },
|
|
191
|
+
query: { key: '$$MT$secret$authConfig$nested.token2$$-suffix' }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
]);
|
|
195
|
+
expect(attachment).toEqual(original);
|
|
196
|
+
});
|
|
197
|
+
|
|
78
198
|
it('leaves url attachments with no headers/query untouched', () => {
|
|
79
199
|
let attachments = [urlAttachment()];
|
|
80
200
|
expect(redactUrlAttachmentSecrets(attachments, authConfig)).toEqual(attachments);
|
package/src/auth/redact.ts
CHANGED
|
@@ -8,10 +8,9 @@ import type { SlateAttachment } from '../action/attachment';
|
|
|
8
8
|
export let AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX = '$$MT$secret$authConfig$';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* like the http trace redactor in '../axios/trace'.
|
|
11
|
+
* Maps auth-config string leaves to `$$MT$secret$authConfig$<path>` placeholders.
|
|
12
|
+
* `redact` preserves whole-value substitution for HTTP traces; `redactEmbedded` also
|
|
13
|
+
* replaces embedded values in attachment credentials, terminating those placeholders with `$$`.
|
|
15
14
|
*/
|
|
16
15
|
export class AuthConfigSecretRedactor {
|
|
17
16
|
private readonly secretToPlaceholder = new Map<string, string>();
|
|
@@ -26,6 +25,29 @@ export class AuthConfigSecretRedactor {
|
|
|
26
25
|
});
|
|
27
26
|
}
|
|
28
27
|
|
|
28
|
+
public redactEmbedded<T>(value: T): T {
|
|
29
|
+
if (this.secretToPlaceholder.size === 0) return value;
|
|
30
|
+
|
|
31
|
+
let pattern = new RegExp(
|
|
32
|
+
[...this.secretToPlaceholder.keys()]
|
|
33
|
+
.sort((a, b) => b.length - a.length)
|
|
34
|
+
// Escape regex metacharacters so values match literally; $& inserts the matched character.
|
|
35
|
+
.map(secret => secret.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
36
|
+
.join('|'),
|
|
37
|
+
'g'
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return this.transform(value, stringValue => {
|
|
41
|
+
let exactPlaceholder = this.secretToPlaceholder.get(stringValue);
|
|
42
|
+
if (exactPlaceholder) return exactPlaceholder;
|
|
43
|
+
|
|
44
|
+
return stringValue.replace(
|
|
45
|
+
pattern,
|
|
46
|
+
secret => `${this.secretToPlaceholder.get(secret)}$$`
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
private buildSecretMap(value: unknown, path: string[] = []): void {
|
|
30
52
|
if (typeof value === 'string') {
|
|
31
53
|
// Never register "" -- it would replace every empty header/query value with a placeholder.
|
|
@@ -102,9 +124,11 @@ export let redactUrlAttachmentSecrets = (
|
|
|
102
124
|
content: {
|
|
103
125
|
...attachment.content,
|
|
104
126
|
headers: attachment.content.headers
|
|
105
|
-
? redactor.
|
|
127
|
+
? redactor.redactEmbedded(attachment.content.headers)
|
|
106
128
|
: undefined,
|
|
107
|
-
query: attachment.content.query
|
|
129
|
+
query: attachment.content.query
|
|
130
|
+
? redactor.redactEmbedded(attachment.content.query)
|
|
131
|
+
: undefined
|
|
108
132
|
}
|
|
109
133
|
};
|
|
110
134
|
});
|