@inkeep/agents-core 0.75.4 → 0.77.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/auth/auth.js +3 -0
- package/dist/auth/sso-issuer-discovery.d.ts +50 -0
- package/dist/auth/sso-issuer-discovery.js +85 -0
- package/dist/constants/allowed-file-formats.d.ts +3 -1
- package/dist/constants/allowed-file-formats.js +3 -2
- package/dist/constants/index.d.ts +2 -2
- package/dist/constants/schema-validation/defaults.d.ts +1 -1
- package/dist/constants/schema-validation/defaults.js +1 -1
- package/dist/constants/schema-validation/index.d.ts +1 -1
- package/dist/data-access/index.d.ts +2 -2
- package/dist/data-access/index.js +2 -2
- package/dist/data-access/manage/agents.d.ts +26 -26
- package/dist/data-access/manage/artifactComponents.d.ts +2 -2
- package/dist/data-access/manage/contextConfigs.d.ts +12 -12
- package/dist/data-access/manage/dataComponents.d.ts +2 -2
- package/dist/data-access/manage/functionTools.d.ts +6 -6
- package/dist/data-access/manage/skills.d.ts +4 -4
- package/dist/data-access/manage/subAgentExternalAgentRelations.d.ts +12 -12
- package/dist/data-access/manage/subAgentRelations.d.ts +12 -12
- package/dist/data-access/manage/subAgentTeamAgentRelations.d.ts +12 -12
- package/dist/data-access/manage/subAgents.d.ts +6 -6
- package/dist/data-access/manage/tools.d.ts +15 -15
- package/dist/data-access/manage/triggers.d.ts +5 -5
- package/dist/data-access/manage/webhookDestinations.d.ts +6 -4
- package/dist/data-access/manage/webhookDestinations.js +13 -15
- package/dist/data-access/runtime/apiKeys.d.ts +4 -4
- package/dist/data-access/runtime/apps.d.ts +2 -2
- package/dist/data-access/runtime/auth.d.ts +4 -1
- package/dist/data-access/runtime/auth.js +5 -1
- package/dist/data-access/runtime/conversations.d.ts +9 -8
- package/dist/data-access/runtime/conversations.js +8 -1
- package/dist/data-access/runtime/events.d.ts +1 -1
- package/dist/data-access/runtime/feedback.d.ts +2 -2
- package/dist/data-access/runtime/messages.d.ts +6 -6
- package/dist/data-access/runtime/tasks.d.ts +1 -1
- package/dist/db/manage/manage-schema.d.ts +387 -387
- package/dist/db/runtime/runtime-schema.d.ts +405 -405
- package/dist/external-fetch/external-file-downloader.d.ts +14 -0
- package/dist/external-fetch/external-file-downloader.js +146 -0
- package/dist/external-fetch/file-content-security.d.ts +18 -0
- package/dist/external-fetch/file-content-security.js +81 -0
- package/dist/external-fetch/file-security-constants.d.ts +12 -0
- package/dist/external-fetch/file-security-constants.js +16 -0
- package/dist/external-fetch/file-security-errors.d.ts +96 -0
- package/dist/external-fetch/file-security-errors.js +154 -0
- package/dist/external-fetch/file-url-security.d.ts +7 -0
- package/dist/external-fetch/file-url-security.js +75 -0
- package/dist/external-fetch/index.d.ts +6 -0
- package/dist/external-fetch/index.js +7 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/text-attachments/index.d.ts +2 -0
- package/dist/text-attachments/index.js +3 -0
- package/dist/text-attachments/text-document-attachments.d.ts +40 -0
- package/dist/text-attachments/text-document-attachments.js +86 -0
- package/dist/validation/schemas/skills.d.ts +29 -29
- package/dist/validation/schemas.d.ts +352 -352
- package/dist/validation/schemas.js +5 -3
- package/package.json +12 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { LookupAddress } from "node:dns";
|
|
2
|
+
|
|
3
|
+
//#region src/external-fetch/external-file-downloader.d.ts
|
|
4
|
+
type LookupCallback = (error: Error | null, address: string | LookupAddress[], family?: number) => void;
|
|
5
|
+
declare function forwardLookupResult(hostname: string, address: string | LookupAddress[], family: number | undefined, callback: LookupCallback): void;
|
|
6
|
+
declare function downloadExternalFile(url: string, options?: {
|
|
7
|
+
expectedMimeType?: string;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
data: Uint8Array;
|
|
11
|
+
mimeType: string;
|
|
12
|
+
}>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { downloadExternalFile, forwardLookupResult };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { retryWithBackoff } from "../utils/retry.js";
|
|
2
|
+
import { BlockedConnectionToPrivateIpError, BlockedExternalFileExceedingError, BlockedExternalFileLargerThanError, ExternalFileResponseBodyEmptyError, FailedToDownloadError, FileSecurityError, RedirectMissingLocationError, TimedOutDownloadingError, TooManyRedirectsError, UnableToResolveHostError, UnexpectedRedirectStateError } from "./file-security-errors.js";
|
|
3
|
+
import { EXTERNAL_FETCH_TIMEOUT_MS, MAX_EXTERNAL_REDIRECTS, MAX_FILE_BYTES } from "./file-security-constants.js";
|
|
4
|
+
import { resolveDownloadedFileMimeType } from "./file-content-security.js";
|
|
5
|
+
import { isBlockedIpAddress, validateExternalFileUrl, validateUrlResolvesToPublicIp } from "./file-url-security.js";
|
|
6
|
+
import { lookup } from "node:dns";
|
|
7
|
+
import { Agent } from "undici";
|
|
8
|
+
|
|
9
|
+
//#region src/external-fetch/external-file-downloader.ts
|
|
10
|
+
const externalImageDispatcher = new Agent({ connect: { lookup(hostname, options, callback) {
|
|
11
|
+
lookup(hostname, options, (error, address, family) => {
|
|
12
|
+
if (error) {
|
|
13
|
+
callback(new UnableToResolveHostError(hostname, { cause: error }), "", 0);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
forwardLookupResult(hostname, address, family, callback);
|
|
17
|
+
});
|
|
18
|
+
} } });
|
|
19
|
+
const MAX_EXTERNAL_FETCH_ATTEMPTS = 3;
|
|
20
|
+
function forwardLookupResult(hostname, address, family, callback) {
|
|
21
|
+
if (Array.isArray(address)) {
|
|
22
|
+
if (!address[0]) {
|
|
23
|
+
callback(new UnableToResolveHostError(hostname), "", 0);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const blocked = address.find((candidate) => isBlockedIpAddress(candidate.address));
|
|
27
|
+
if (blocked) {
|
|
28
|
+
callback(new BlockedConnectionToPrivateIpError(blocked.address), "", 0);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
callback(null, address);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (isBlockedIpAddress(address)) {
|
|
35
|
+
callback(new BlockedConnectionToPrivateIpError(address), "", 0);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
callback(null, address, family);
|
|
39
|
+
}
|
|
40
|
+
async function downloadExternalFile(url, options) {
|
|
41
|
+
let currentUrl = validateExternalFileUrl(url);
|
|
42
|
+
await validateUrlResolvesToPublicIp(currentUrl);
|
|
43
|
+
for (let redirectCount = 0; redirectCount <= MAX_EXTERNAL_REDIRECTS; redirectCount++) {
|
|
44
|
+
const response = await fetchWithRetry(currentUrl, options?.signal);
|
|
45
|
+
if (isRedirectStatus(response.status)) {
|
|
46
|
+
const location = response.headers.get("location");
|
|
47
|
+
if (!location) throw new RedirectMissingLocationError(toSanitizedUrl(currentUrl));
|
|
48
|
+
if (redirectCount === MAX_EXTERNAL_REDIRECTS) throw new TooManyRedirectsError(toSanitizedUrl(url));
|
|
49
|
+
currentUrl = validateExternalFileUrl(new URL(location, currentUrl).toString());
|
|
50
|
+
await validateUrlResolvesToPublicIp(currentUrl);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (!response.ok) throw new FailedToDownloadError(toSanitizedUrl(currentUrl), `${response.status} ${response.statusText}`);
|
|
54
|
+
const headerContentType = (response.headers.get("content-type") || "").split(";")[0].trim().toLowerCase();
|
|
55
|
+
const contentLength = response.headers.get("content-length");
|
|
56
|
+
if (contentLength && Number.isFinite(Number(contentLength)) && Number(contentLength) > MAX_FILE_BYTES) throw new BlockedExternalFileLargerThanError(MAX_FILE_BYTES, contentLength);
|
|
57
|
+
const data = await readResponseBytesWithLimit(response, MAX_FILE_BYTES);
|
|
58
|
+
return {
|
|
59
|
+
data,
|
|
60
|
+
mimeType: await resolveDownloadedFileMimeType(data, headerContentType, options?.expectedMimeType)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
throw new UnexpectedRedirectStateError(toSanitizedUrl(url));
|
|
64
|
+
}
|
|
65
|
+
async function fetchWithRetry(url, signal) {
|
|
66
|
+
return retryWithBackoff(async () => {
|
|
67
|
+
let response;
|
|
68
|
+
try {
|
|
69
|
+
response = await fetchWithConnectionIpValidation(url, signal);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error instanceof TimedOutDownloadingError || error instanceof FailedToDownloadError) error.status = 502;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
if (isRetryableStatus(response.status)) {
|
|
75
|
+
const err = new FailedToDownloadError(toSanitizedUrl(url), `${response.status} ${response.statusText}`);
|
|
76
|
+
err.status = response.status;
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
return response;
|
|
80
|
+
}, {
|
|
81
|
+
maxAttempts: MAX_EXTERNAL_FETCH_ATTEMPTS,
|
|
82
|
+
maxDelayMs: 2e3,
|
|
83
|
+
label: `file-download ${toSanitizedUrl(url)}`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function isRedirectStatus(status) {
|
|
87
|
+
return status === 301 || status === 302 || status === 303 || status === 307 || status === 308;
|
|
88
|
+
}
|
|
89
|
+
function isRetryableStatus(status) {
|
|
90
|
+
return status === 429 || status >= 500 && status <= 599;
|
|
91
|
+
}
|
|
92
|
+
async function readResponseBytesWithLimit(response, maxBytes) {
|
|
93
|
+
if (!response.body) throw new ExternalFileResponseBodyEmptyError();
|
|
94
|
+
const reader = response.body.getReader();
|
|
95
|
+
const chunks = [];
|
|
96
|
+
let totalBytes = 0;
|
|
97
|
+
try {
|
|
98
|
+
while (true) {
|
|
99
|
+
const { done, value } = await reader.read();
|
|
100
|
+
if (done) break;
|
|
101
|
+
if (!value) continue;
|
|
102
|
+
totalBytes += value.byteLength;
|
|
103
|
+
if (totalBytes > maxBytes) throw new BlockedExternalFileExceedingError(maxBytes);
|
|
104
|
+
chunks.push(value);
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
reader.releaseLock();
|
|
108
|
+
}
|
|
109
|
+
const merged = new Uint8Array(totalBytes);
|
|
110
|
+
let offset = 0;
|
|
111
|
+
for (const chunk of chunks) {
|
|
112
|
+
merged.set(chunk, offset);
|
|
113
|
+
offset += chunk.byteLength;
|
|
114
|
+
}
|
|
115
|
+
return merged;
|
|
116
|
+
}
|
|
117
|
+
async function fetchWithConnectionIpValidation(url, callerSignal) {
|
|
118
|
+
const timeoutSignal = AbortSignal.timeout(EXTERNAL_FETCH_TIMEOUT_MS);
|
|
119
|
+
const signal = callerSignal ? AbortSignal.any([timeoutSignal, callerSignal]) : timeoutSignal;
|
|
120
|
+
try {
|
|
121
|
+
return await fetch(url.toString(), {
|
|
122
|
+
redirect: "manual",
|
|
123
|
+
signal,
|
|
124
|
+
dispatcher: externalImageDispatcher
|
|
125
|
+
});
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const fileSecurityError = extractFileSecurityError(error);
|
|
128
|
+
if (fileSecurityError instanceof BlockedConnectionToPrivateIpError || fileSecurityError instanceof UnableToResolveHostError) throw fileSecurityError;
|
|
129
|
+
if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) throw new TimedOutDownloadingError(toSanitizedUrl(url));
|
|
130
|
+
throw new FailedToDownloadError(toSanitizedUrl(url), void 0, { cause: error });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function extractFileSecurityError(error) {
|
|
134
|
+
if (error instanceof FileSecurityError) return error;
|
|
135
|
+
if (error instanceof Error && error.cause) return extractFileSecurityError(error.cause);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
function toSanitizedUrl(url) {
|
|
139
|
+
const parsed = typeof url === "string" ? new URL(url) : new URL(url.toString());
|
|
140
|
+
parsed.search = "";
|
|
141
|
+
parsed.hash = "";
|
|
142
|
+
return parsed.toString();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
export { downloadExternalFile, forwardLookupResult };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/external-fetch/file-content-security.d.ts
|
|
2
|
+
declare function normalizeInlineImageBytes(file: {
|
|
3
|
+
bytes: string;
|
|
4
|
+
mimeType?: string;
|
|
5
|
+
}): Promise<{
|
|
6
|
+
data: Uint8Array;
|
|
7
|
+
mimeType: string;
|
|
8
|
+
}>;
|
|
9
|
+
declare function normalizeInlineFileBytes(file: {
|
|
10
|
+
bytes: string;
|
|
11
|
+
mimeType?: string;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
data: Uint8Array;
|
|
14
|
+
mimeType: string;
|
|
15
|
+
}>;
|
|
16
|
+
declare function resolveDownloadedFileMimeType(data: Uint8Array, headerContentType: string, expectedMimeType?: string): Promise<string>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { normalizeInlineFileBytes, normalizeInlineImageBytes, resolveDownloadedFileMimeType };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { isOfficeDocumentMimeType } from "../constants/allowed-file-formats.js";
|
|
2
|
+
import { BlockedExternalUnsupportedBytesError, BlockedInlineFileExceedingError, BlockedInlineUnsupportedFileBytesError, InvalidInlineFileMalformedBase64Error } from "./file-security-errors.js";
|
|
3
|
+
import { decodeTextDocumentBytes, isTextDocumentMimeType } from "../text-attachments/text-document-attachments.js";
|
|
4
|
+
import { ALLOWED_EXTERNAL_IMAGE_MIME_TYPES, MAX_FILE_BYTES, TEXT_DOCUMENT_MAX_BYTES } from "./file-security-constants.js";
|
|
5
|
+
import { fileTypeFromBuffer } from "file-type";
|
|
6
|
+
|
|
7
|
+
//#region src/external-fetch/file-content-security.ts
|
|
8
|
+
async function normalizeInlineImageBytes(file) {
|
|
9
|
+
const normalized = await normalizeInlineFileBytes(file);
|
|
10
|
+
if (normalized.mimeType.startsWith("image/")) return normalized;
|
|
11
|
+
throw new BlockedInlineUnsupportedFileBytesError(file.mimeType || "unknown");
|
|
12
|
+
}
|
|
13
|
+
async function normalizeInlineFileBytes(file) {
|
|
14
|
+
const data = decodeBase64Bytes(file.bytes);
|
|
15
|
+
const requestedMimeType = file.mimeType?.split(";")[0]?.trim().toLowerCase();
|
|
16
|
+
validateInlineFileSize(data, requestedMimeType);
|
|
17
|
+
const sniffedMime = await sniffAllowedInlineFileMimeType(data, requestedMimeType);
|
|
18
|
+
if (sniffedMime) return {
|
|
19
|
+
data,
|
|
20
|
+
mimeType: sniffedMime
|
|
21
|
+
};
|
|
22
|
+
throw new BlockedInlineUnsupportedFileBytesError(file.mimeType || "unknown");
|
|
23
|
+
}
|
|
24
|
+
async function resolveDownloadedFileMimeType(data, headerContentType, expectedMimeType) {
|
|
25
|
+
const expected = expectedMimeType?.split(";")[0]?.trim().toLowerCase();
|
|
26
|
+
if (expected === "application/pdf") {
|
|
27
|
+
if (looksLikePdf(data)) return "application/pdf";
|
|
28
|
+
throw new BlockedExternalUnsupportedBytesError(headerContentType || expected || "unknown");
|
|
29
|
+
}
|
|
30
|
+
if (isOfficeDocumentMimeType(expected)) {
|
|
31
|
+
if (looksLikeZip(data)) return expected;
|
|
32
|
+
throw new BlockedExternalUnsupportedBytesError(headerContentType || expected || "unknown");
|
|
33
|
+
}
|
|
34
|
+
const sniffedMime = await sniffAllowedImageMimeType(data);
|
|
35
|
+
if (sniffedMime) return sniffedMime;
|
|
36
|
+
throw new BlockedExternalUnsupportedBytesError(headerContentType || expected || "unknown");
|
|
37
|
+
}
|
|
38
|
+
function validateInlineFileSize(data, requestedMimeType) {
|
|
39
|
+
const maxBytes = isTextDocumentMimeType(requestedMimeType) ? TEXT_DOCUMENT_MAX_BYTES : MAX_FILE_BYTES;
|
|
40
|
+
if (data.length > maxBytes) throw new BlockedInlineFileExceedingError(maxBytes);
|
|
41
|
+
}
|
|
42
|
+
async function sniffAllowedImageMimeType(data) {
|
|
43
|
+
const sniffedMime = (await fileTypeFromBuffer(data))?.mime?.toLowerCase();
|
|
44
|
+
if (sniffedMime && ALLOWED_EXTERNAL_IMAGE_MIME_TYPES.has(sniffedMime)) return sniffedMime;
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
async function sniffAllowedInlineFileMimeType(data, requestedMimeType) {
|
|
48
|
+
if (requestedMimeType === "application/pdf") {
|
|
49
|
+
if (!looksLikePdf(data)) throw new BlockedInlineUnsupportedFileBytesError(requestedMimeType);
|
|
50
|
+
return "application/pdf";
|
|
51
|
+
}
|
|
52
|
+
if (isOfficeDocumentMimeType(requestedMimeType)) {
|
|
53
|
+
if (!looksLikeZip(data)) throw new BlockedInlineUnsupportedFileBytesError(requestedMimeType ?? "unknown");
|
|
54
|
+
return requestedMimeType;
|
|
55
|
+
}
|
|
56
|
+
if (isTextDocumentMimeType(requestedMimeType)) try {
|
|
57
|
+
decodeTextDocumentBytes(data);
|
|
58
|
+
return requestedMimeType;
|
|
59
|
+
} catch {
|
|
60
|
+
throw new BlockedInlineUnsupportedFileBytesError(requestedMimeType);
|
|
61
|
+
}
|
|
62
|
+
return await sniffAllowedImageMimeType(data);
|
|
63
|
+
}
|
|
64
|
+
function decodeBase64Bytes(base64Bytes) {
|
|
65
|
+
const normalized = base64Bytes.replace(/\s+/g, "");
|
|
66
|
+
if (normalized.length === 0 || normalized.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(normalized)) throw new InvalidInlineFileMalformedBase64Error();
|
|
67
|
+
const decoded = Buffer.from(normalized, "base64");
|
|
68
|
+
if (decoded.length === 0 || decoded.toString("base64") !== normalized) throw new InvalidInlineFileMalformedBase64Error();
|
|
69
|
+
return Uint8Array.from(decoded);
|
|
70
|
+
}
|
|
71
|
+
function looksLikePdf(data) {
|
|
72
|
+
if (data.length < 5) return false;
|
|
73
|
+
return data[0] === 37 && data[1] === 80 && data[2] === 68 && data[3] === 70 && data[4] === 45;
|
|
74
|
+
}
|
|
75
|
+
function looksLikeZip(data) {
|
|
76
|
+
if (data.length < 4) return false;
|
|
77
|
+
return data[0] === 80 && data[1] === 75 && data[2] === 3 && data[3] === 4;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
export { normalizeInlineFileBytes, normalizeInlineImageBytes, resolveDownloadedFileMimeType };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AllowedImageMimeType } from "../constants/allowed-file-formats.js";
|
|
2
|
+
|
|
3
|
+
//#region src/external-fetch/file-security-constants.d.ts
|
|
4
|
+
type AllowedExternalImageMimeType = AllowedImageMimeType;
|
|
5
|
+
declare const MAX_FILE_BYTES: number;
|
|
6
|
+
declare const TEXT_DOCUMENT_MAX_BYTES: number;
|
|
7
|
+
declare const EXTERNAL_FETCH_TIMEOUT_MS = 10000;
|
|
8
|
+
declare const MAX_EXTERNAL_REDIRECTS = 3;
|
|
9
|
+
declare const ALLOWED_EXTERNAL_IMAGE_MIME_TYPES: Set<string>;
|
|
10
|
+
declare const ALLOWED_HTTP_PORTS: Set<string>;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { ALLOWED_EXTERNAL_IMAGE_MIME_TYPES, ALLOWED_HTTP_PORTS, AllowedExternalImageMimeType, EXTERNAL_FETCH_TIMEOUT_MS, MAX_EXTERNAL_REDIRECTS, MAX_FILE_BYTES, TEXT_DOCUMENT_MAX_BYTES };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ALLOWED_IMAGE_MIME_TYPES } from "../constants/allowed-file-formats.js";
|
|
2
|
+
|
|
3
|
+
//#region src/external-fetch/file-security-constants.ts
|
|
4
|
+
const MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
5
|
+
const TEXT_DOCUMENT_MAX_BYTES = 256 * 1024;
|
|
6
|
+
const EXTERNAL_FETCH_TIMEOUT_MS = 1e4;
|
|
7
|
+
const MAX_EXTERNAL_REDIRECTS = 3;
|
|
8
|
+
const ALLOWED_EXTERNAL_IMAGE_MIME_TYPES = ALLOWED_IMAGE_MIME_TYPES;
|
|
9
|
+
const ALLOWED_HTTP_PORTS = new Set([
|
|
10
|
+
"",
|
|
11
|
+
"80",
|
|
12
|
+
"443"
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { ALLOWED_EXTERNAL_IMAGE_MIME_TYPES, ALLOWED_HTTP_PORTS, EXTERNAL_FETCH_TIMEOUT_MS, MAX_EXTERNAL_REDIRECTS, MAX_FILE_BYTES, TEXT_DOCUMENT_MAX_BYTES };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//#region src/external-fetch/file-security-errors.d.ts
|
|
2
|
+
declare class FileSecurityError extends Error {
|
|
3
|
+
constructor(message: string, options?: ErrorOptions);
|
|
4
|
+
}
|
|
5
|
+
declare class InvalidExternalFileUrlError extends FileSecurityError {
|
|
6
|
+
constructor(rawUrl: string);
|
|
7
|
+
}
|
|
8
|
+
declare class BlockedUnsupportedSchemeError extends FileSecurityError {
|
|
9
|
+
constructor(protocol: string);
|
|
10
|
+
}
|
|
11
|
+
declare class BlockedDisallowedPortError extends FileSecurityError {
|
|
12
|
+
constructor(port: string);
|
|
13
|
+
}
|
|
14
|
+
declare class BlockedEmbeddedCredentialsError extends FileSecurityError {
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
|
17
|
+
declare class NoIpResolvedError extends FileSecurityError {
|
|
18
|
+
constructor(hostname: string);
|
|
19
|
+
}
|
|
20
|
+
declare class BlockedUrlResolvingToPrivateIpError extends FileSecurityError {
|
|
21
|
+
constructor(ip: string);
|
|
22
|
+
}
|
|
23
|
+
declare class UnableToResolveHostError extends FileSecurityError {
|
|
24
|
+
constructor(hostname: string, options?: ErrorOptions);
|
|
25
|
+
}
|
|
26
|
+
declare class BlockedConnectionToPrivateIpError extends FileSecurityError {
|
|
27
|
+
constructor(address: string);
|
|
28
|
+
}
|
|
29
|
+
declare class RedirectMissingLocationError extends FileSecurityError {
|
|
30
|
+
constructor(url: string);
|
|
31
|
+
}
|
|
32
|
+
declare class TooManyRedirectsError extends FileSecurityError {
|
|
33
|
+
constructor(url: string);
|
|
34
|
+
}
|
|
35
|
+
declare class FailedToDownloadError extends FileSecurityError {
|
|
36
|
+
constructor(url: string, statusText?: string, options?: ErrorOptions);
|
|
37
|
+
}
|
|
38
|
+
declare class BlockedExternalFileLargerThanError extends FileSecurityError {
|
|
39
|
+
constructor(maxBytes: number, contentLength: string);
|
|
40
|
+
}
|
|
41
|
+
declare class BlockedExternalFileExceedingError extends FileSecurityError {
|
|
42
|
+
constructor(maxBytes: number);
|
|
43
|
+
}
|
|
44
|
+
declare class ExternalFileResponseBodyEmptyError extends FileSecurityError {
|
|
45
|
+
constructor();
|
|
46
|
+
}
|
|
47
|
+
declare class UnexpectedRedirectStateError extends FileSecurityError {
|
|
48
|
+
constructor(url: string);
|
|
49
|
+
}
|
|
50
|
+
declare class TimedOutDownloadingError extends FileSecurityError {
|
|
51
|
+
constructor(url: string);
|
|
52
|
+
}
|
|
53
|
+
declare class BlockedInlineFileExceedingError extends FileSecurityError {
|
|
54
|
+
constructor(maxBytes: number);
|
|
55
|
+
}
|
|
56
|
+
declare class BlockedInlineUnsupportedFileBytesError extends FileSecurityError {
|
|
57
|
+
constructor(claimedContentType: string);
|
|
58
|
+
}
|
|
59
|
+
declare class BlockedExternalUnsupportedBytesError extends FileSecurityError {
|
|
60
|
+
constructor(contentType: string);
|
|
61
|
+
}
|
|
62
|
+
declare class InvalidInlineFileMalformedBase64Error extends FileSecurityError {
|
|
63
|
+
constructor();
|
|
64
|
+
}
|
|
65
|
+
declare class TextDocumentAttachmentError extends FileSecurityError {
|
|
66
|
+
constructor(message: string, options?: ErrorOptions);
|
|
67
|
+
}
|
|
68
|
+
declare class InvalidUtf8TextDocumentError extends TextDocumentAttachmentError {
|
|
69
|
+
constructor(options?: ErrorOptions);
|
|
70
|
+
}
|
|
71
|
+
declare class TextDocumentControlCharacterError extends TextDocumentAttachmentError {
|
|
72
|
+
constructor(options?: ErrorOptions);
|
|
73
|
+
}
|
|
74
|
+
declare class UnsupportedTextAttachmentSourceError extends TextDocumentAttachmentError {
|
|
75
|
+
constructor(mimeType: string, options?: ErrorOptions);
|
|
76
|
+
}
|
|
77
|
+
declare class PdfUrlIngestionError extends FileSecurityError {
|
|
78
|
+
readonly sourceUrl: string;
|
|
79
|
+
constructor(sourceUrl: string, options?: ErrorOptions);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Transient / content-format errors that occur on the *external-fetch* path:
|
|
83
|
+
* the server told us "no" (404/5xx/timeout/DNS), we couldn't ingest the
|
|
84
|
+
* response (empty body), or the auto-fetched file's MIME type isn't one the
|
|
85
|
+
* platform handles (gif, svg, bmp, etc.). Callers may drop the affected file
|
|
86
|
+
* and continue rather than failing the whole request — the user didn't
|
|
87
|
+
* directly upload it, the producer auto-extracted a URL on their behalf.
|
|
88
|
+
*
|
|
89
|
+
* Inline (user-uploaded) byte rejections like `BlockedInlineUnsupportedFileBytesError`
|
|
90
|
+
* remain FATAL: when a user explicitly pastes an unsupported file we should
|
|
91
|
+
* surface that to them, not silently drop. Real security guards (private-IP/SSRF,
|
|
92
|
+
* embedded credentials, disallowed schemes and ports) likewise remain fatal.
|
|
93
|
+
*/
|
|
94
|
+
declare function isTransientDownloadError(error: unknown): boolean;
|
|
95
|
+
//#endregion
|
|
96
|
+
export { BlockedConnectionToPrivateIpError, BlockedDisallowedPortError, BlockedEmbeddedCredentialsError, BlockedExternalFileExceedingError, BlockedExternalFileLargerThanError, BlockedExternalUnsupportedBytesError, BlockedInlineFileExceedingError, BlockedInlineUnsupportedFileBytesError, BlockedUnsupportedSchemeError, BlockedUrlResolvingToPrivateIpError, ExternalFileResponseBodyEmptyError, FailedToDownloadError, FileSecurityError, InvalidExternalFileUrlError, InvalidInlineFileMalformedBase64Error, InvalidUtf8TextDocumentError, NoIpResolvedError, PdfUrlIngestionError, RedirectMissingLocationError, TextDocumentAttachmentError, TextDocumentControlCharacterError, TimedOutDownloadingError, TooManyRedirectsError, UnableToResolveHostError, UnexpectedRedirectStateError, UnsupportedTextAttachmentSourceError, isTransientDownloadError };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
//#region src/external-fetch/file-security-errors.ts
|
|
2
|
+
var FileSecurityError = class extends Error {
|
|
3
|
+
constructor(message, options) {
|
|
4
|
+
super(message, options);
|
|
5
|
+
this.name = new.target.name;
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
var InvalidExternalFileUrlError = class extends FileSecurityError {
|
|
9
|
+
constructor(rawUrl) {
|
|
10
|
+
super(`Invalid external file URL: ${rawUrl}`);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var BlockedUnsupportedSchemeError = class extends FileSecurityError {
|
|
14
|
+
constructor(protocol) {
|
|
15
|
+
super(`Blocked external file URL with unsupported scheme: ${protocol}`);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
var BlockedDisallowedPortError = class extends FileSecurityError {
|
|
19
|
+
constructor(port) {
|
|
20
|
+
super(`Blocked external file URL with disallowed port: ${port}`);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var BlockedEmbeddedCredentialsError = class extends FileSecurityError {
|
|
24
|
+
constructor() {
|
|
25
|
+
super("Blocked external file URL with embedded credentials");
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var NoIpResolvedError = class extends FileSecurityError {
|
|
29
|
+
constructor(hostname) {
|
|
30
|
+
super(`No IP addresses resolved for host: ${hostname}`);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var BlockedUrlResolvingToPrivateIpError = class extends FileSecurityError {
|
|
34
|
+
constructor(ip) {
|
|
35
|
+
super(`Blocked external file URL resolving to private or reserved IP: ${ip}`);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var UnableToResolveHostError = class extends FileSecurityError {
|
|
39
|
+
constructor(hostname, options) {
|
|
40
|
+
super(`Unable to resolve external file host: ${hostname}`, options);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var BlockedConnectionToPrivateIpError = class extends FileSecurityError {
|
|
44
|
+
constructor(address) {
|
|
45
|
+
super(`Blocked external file connection to private or reserved IP: ${address}`);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var RedirectMissingLocationError = class extends FileSecurityError {
|
|
49
|
+
constructor(url) {
|
|
50
|
+
super(`Redirect response missing location header: ${url}`);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var TooManyRedirectsError = class extends FileSecurityError {
|
|
54
|
+
constructor(url) {
|
|
55
|
+
super(`Too many redirects while downloading file: ${url}`);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var FailedToDownloadError = class extends FileSecurityError {
|
|
59
|
+
constructor(url, statusText, options) {
|
|
60
|
+
super(statusText ? `Failed to download file from ${url}: ${statusText}` : `Failed to download file from ${url}`, options);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
var BlockedExternalFileLargerThanError = class extends FileSecurityError {
|
|
64
|
+
constructor(maxBytes, contentLength) {
|
|
65
|
+
super(`Blocked external file larger than ${maxBytes} bytes: ${contentLength}`);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var BlockedExternalFileExceedingError = class extends FileSecurityError {
|
|
69
|
+
constructor(maxBytes) {
|
|
70
|
+
super(`Blocked external file exceeding ${maxBytes} bytes`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var ExternalFileResponseBodyEmptyError = class extends FileSecurityError {
|
|
74
|
+
constructor() {
|
|
75
|
+
super("External file response body is empty");
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var UnexpectedRedirectStateError = class extends FileSecurityError {
|
|
79
|
+
constructor(url) {
|
|
80
|
+
super(`Unexpected redirect handling state for URL: ${url}`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
var TimedOutDownloadingError = class extends FileSecurityError {
|
|
84
|
+
constructor(url) {
|
|
85
|
+
super(`Timed out downloading file from ${url}`);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var BlockedInlineFileExceedingError = class extends FileSecurityError {
|
|
89
|
+
constructor(maxBytes) {
|
|
90
|
+
super(`Blocked inline file exceeding ${maxBytes} bytes`);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var BlockedInlineUnsupportedFileBytesError = class extends FileSecurityError {
|
|
94
|
+
constructor(claimedContentType) {
|
|
95
|
+
super(`Blocked inline file with unsupported bytes signature (claimed content-type: ${claimedContentType})`);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var BlockedExternalUnsupportedBytesError = class extends FileSecurityError {
|
|
99
|
+
constructor(contentType) {
|
|
100
|
+
super(`Blocked external file with unsupported bytes signature (content-type: ${contentType})`);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var InvalidInlineFileMalformedBase64Error = class extends FileSecurityError {
|
|
104
|
+
constructor() {
|
|
105
|
+
super("Invalid inline file: malformed base64 payload");
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var TextDocumentAttachmentError = class extends FileSecurityError {
|
|
109
|
+
constructor(message, options) {
|
|
110
|
+
super(message, options);
|
|
111
|
+
this.name = new.target.name;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
var InvalidUtf8TextDocumentError = class extends TextDocumentAttachmentError {
|
|
115
|
+
constructor(options) {
|
|
116
|
+
super("Invalid UTF-8 text document", options);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var TextDocumentControlCharacterError = class extends TextDocumentAttachmentError {
|
|
120
|
+
constructor(options) {
|
|
121
|
+
super("Text document contains disallowed control characters", options);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var UnsupportedTextAttachmentSourceError = class extends TextDocumentAttachmentError {
|
|
125
|
+
constructor(mimeType, options) {
|
|
126
|
+
super(`Unsupported text attachment source for mime type: ${mimeType}`, options);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var PdfUrlIngestionError = class extends FileSecurityError {
|
|
130
|
+
sourceUrl;
|
|
131
|
+
constructor(sourceUrl, options) {
|
|
132
|
+
super(`Failed to ingest PDF URL: ${sourceUrl}`, options);
|
|
133
|
+
this.sourceUrl = sourceUrl;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Transient / content-format errors that occur on the *external-fetch* path:
|
|
138
|
+
* the server told us "no" (404/5xx/timeout/DNS), we couldn't ingest the
|
|
139
|
+
* response (empty body), or the auto-fetched file's MIME type isn't one the
|
|
140
|
+
* platform handles (gif, svg, bmp, etc.). Callers may drop the affected file
|
|
141
|
+
* and continue rather than failing the whole request — the user didn't
|
|
142
|
+
* directly upload it, the producer auto-extracted a URL on their behalf.
|
|
143
|
+
*
|
|
144
|
+
* Inline (user-uploaded) byte rejections like `BlockedInlineUnsupportedFileBytesError`
|
|
145
|
+
* remain FATAL: when a user explicitly pastes an unsupported file we should
|
|
146
|
+
* surface that to them, not silently drop. Real security guards (private-IP/SSRF,
|
|
147
|
+
* embedded credentials, disallowed schemes and ports) likewise remain fatal.
|
|
148
|
+
*/
|
|
149
|
+
function isTransientDownloadError(error) {
|
|
150
|
+
return error instanceof FailedToDownloadError || error instanceof TimedOutDownloadingError || error instanceof UnableToResolveHostError || error instanceof NoIpResolvedError || error instanceof ExternalFileResponseBodyEmptyError || error instanceof BlockedExternalUnsupportedBytesError;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//#endregion
|
|
154
|
+
export { BlockedConnectionToPrivateIpError, BlockedDisallowedPortError, BlockedEmbeddedCredentialsError, BlockedExternalFileExceedingError, BlockedExternalFileLargerThanError, BlockedExternalUnsupportedBytesError, BlockedInlineFileExceedingError, BlockedInlineUnsupportedFileBytesError, BlockedUnsupportedSchemeError, BlockedUrlResolvingToPrivateIpError, ExternalFileResponseBodyEmptyError, FailedToDownloadError, FileSecurityError, InvalidExternalFileUrlError, InvalidInlineFileMalformedBase64Error, InvalidUtf8TextDocumentError, NoIpResolvedError, PdfUrlIngestionError, RedirectMissingLocationError, TextDocumentAttachmentError, TextDocumentControlCharacterError, TimedOutDownloadingError, TooManyRedirectsError, UnableToResolveHostError, UnexpectedRedirectStateError, UnsupportedTextAttachmentSourceError, isTransientDownloadError };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/external-fetch/file-url-security.d.ts
|
|
2
|
+
declare function makeSanitizedSourceUrl(rawUrl: string): string;
|
|
3
|
+
declare function validateExternalFileUrl(rawUrl: string): URL;
|
|
4
|
+
declare function validateUrlResolvesToPublicIp(url: URL): Promise<void>;
|
|
5
|
+
declare function isBlockedIpAddress(ipAddress: string): boolean;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { isBlockedIpAddress, makeSanitizedSourceUrl, validateExternalFileUrl, validateUrlResolvesToPublicIp };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getLogger } from "../utils/logger.js";
|
|
2
|
+
import { BlockedDisallowedPortError, BlockedEmbeddedCredentialsError, BlockedUnsupportedSchemeError, BlockedUrlResolvingToPrivateIpError, FileSecurityError, InvalidExternalFileUrlError, NoIpResolvedError, UnableToResolveHostError } from "./file-security-errors.js";
|
|
3
|
+
import { ALLOWED_HTTP_PORTS } from "./file-security-constants.js";
|
|
4
|
+
import { lookup } from "node:dns/promises";
|
|
5
|
+
import { isIP } from "node:net";
|
|
6
|
+
import ipaddr from "ipaddr.js";
|
|
7
|
+
|
|
8
|
+
//#region src/external-fetch/file-url-security.ts
|
|
9
|
+
const logger = getLogger("file-security");
|
|
10
|
+
function makeSanitizedSourceUrl(rawUrl) {
|
|
11
|
+
const parsed = new URL(rawUrl);
|
|
12
|
+
parsed.search = "";
|
|
13
|
+
parsed.hash = "";
|
|
14
|
+
return parsed.toString();
|
|
15
|
+
}
|
|
16
|
+
function validateExternalFileUrl(rawUrl) {
|
|
17
|
+
let parsed;
|
|
18
|
+
try {
|
|
19
|
+
parsed = new URL(rawUrl);
|
|
20
|
+
} catch {
|
|
21
|
+
throw new InvalidExternalFileUrlError(rawUrl);
|
|
22
|
+
}
|
|
23
|
+
const protocol = parsed.protocol.toLowerCase();
|
|
24
|
+
if (protocol !== "http:" && protocol !== "https:") throw new BlockedUnsupportedSchemeError(protocol);
|
|
25
|
+
if (!ALLOWED_HTTP_PORTS.has(parsed.port)) throw new BlockedDisallowedPortError(parsed.port);
|
|
26
|
+
if (parsed.username || parsed.password) throw new BlockedEmbeddedCredentialsError();
|
|
27
|
+
return parsed;
|
|
28
|
+
}
|
|
29
|
+
async function validateUrlResolvesToPublicIp(url) {
|
|
30
|
+
const hostname = url.hostname;
|
|
31
|
+
let candidateIps;
|
|
32
|
+
try {
|
|
33
|
+
candidateIps = await resolveCandidateIps(hostname);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error instanceof FileSecurityError) throw error;
|
|
36
|
+
throw new UnableToResolveHostError(hostname, { cause: error });
|
|
37
|
+
}
|
|
38
|
+
if (candidateIps.length === 0) throw new NoIpResolvedError(hostname);
|
|
39
|
+
for (const ip of candidateIps) if (isBlockedIpAddress(ip)) {
|
|
40
|
+
logger.warn({
|
|
41
|
+
host: hostname,
|
|
42
|
+
ip
|
|
43
|
+
}, "Blocked external file URL resolving to private IP");
|
|
44
|
+
throw new BlockedUrlResolvingToPrivateIpError(ip);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function resolveCandidateIps(hostname) {
|
|
48
|
+
if (isIP(hostname) !== 0) return [hostname];
|
|
49
|
+
try {
|
|
50
|
+
return (await lookup(hostname, {
|
|
51
|
+
all: true,
|
|
52
|
+
verbatim: true
|
|
53
|
+
})).map((result) => result.address);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
logger.warn({
|
|
56
|
+
host: hostname,
|
|
57
|
+
error
|
|
58
|
+
}, "DNS resolution failed for external file URL");
|
|
59
|
+
throw new UnableToResolveHostError(hostname, { cause: error });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function isBlockedIpAddress(ipAddress) {
|
|
63
|
+
if (ipaddr.IPv4.isValid(ipAddress)) {
|
|
64
|
+
const range = ipaddr.IPv4.parse(ipAddress).range();
|
|
65
|
+
return range === "private" || range === "loopback" || range === "linkLocal" || range === "multicast" || range === "carrierGradeNat" || range === "reserved" || range === "unspecified" || range === "broadcast";
|
|
66
|
+
}
|
|
67
|
+
if (ipaddr.IPv6.isValid(ipAddress)) {
|
|
68
|
+
const range = ipaddr.IPv6.parse(ipAddress).range();
|
|
69
|
+
return range === "uniqueLocal" || range === "loopback" || range === "linkLocal" || range === "multicast" || range === "ipv4Mapped" || range === "rfc6145" || range === "rfc6052" || range === "6to4" || range === "teredo" || range === "reserved" || range === "unspecified";
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
export { isBlockedIpAddress, makeSanitizedSourceUrl, validateExternalFileUrl, validateUrlResolvesToPublicIp };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { downloadExternalFile } from "./external-file-downloader.js";
|
|
2
|
+
import { normalizeInlineFileBytes, normalizeInlineImageBytes, resolveDownloadedFileMimeType } from "./file-content-security.js";
|
|
3
|
+
import { ALLOWED_EXTERNAL_IMAGE_MIME_TYPES, ALLOWED_HTTP_PORTS, AllowedExternalImageMimeType, EXTERNAL_FETCH_TIMEOUT_MS, MAX_EXTERNAL_REDIRECTS, MAX_FILE_BYTES, TEXT_DOCUMENT_MAX_BYTES } from "./file-security-constants.js";
|
|
4
|
+
import { BlockedConnectionToPrivateIpError, BlockedDisallowedPortError, BlockedEmbeddedCredentialsError, BlockedExternalFileExceedingError, BlockedExternalFileLargerThanError, BlockedExternalUnsupportedBytesError, BlockedInlineFileExceedingError, BlockedInlineUnsupportedFileBytesError, BlockedUnsupportedSchemeError, BlockedUrlResolvingToPrivateIpError, ExternalFileResponseBodyEmptyError, FailedToDownloadError, FileSecurityError, InvalidExternalFileUrlError, InvalidInlineFileMalformedBase64Error, InvalidUtf8TextDocumentError, NoIpResolvedError, PdfUrlIngestionError, RedirectMissingLocationError, TextDocumentAttachmentError, TextDocumentControlCharacterError, TimedOutDownloadingError, TooManyRedirectsError, UnableToResolveHostError, UnexpectedRedirectStateError, UnsupportedTextAttachmentSourceError, isTransientDownloadError } from "./file-security-errors.js";
|
|
5
|
+
import { isBlockedIpAddress, makeSanitizedSourceUrl, validateExternalFileUrl, validateUrlResolvesToPublicIp } from "./file-url-security.js";
|
|
6
|
+
export { ALLOWED_EXTERNAL_IMAGE_MIME_TYPES, ALLOWED_HTTP_PORTS, type AllowedExternalImageMimeType, BlockedConnectionToPrivateIpError, BlockedDisallowedPortError, BlockedEmbeddedCredentialsError, BlockedExternalFileExceedingError, BlockedExternalFileLargerThanError, BlockedExternalUnsupportedBytesError, BlockedInlineFileExceedingError, BlockedInlineUnsupportedFileBytesError, BlockedUnsupportedSchemeError, BlockedUrlResolvingToPrivateIpError, EXTERNAL_FETCH_TIMEOUT_MS, ExternalFileResponseBodyEmptyError, FailedToDownloadError, FileSecurityError, InvalidExternalFileUrlError, InvalidInlineFileMalformedBase64Error, InvalidUtf8TextDocumentError, MAX_EXTERNAL_REDIRECTS, MAX_FILE_BYTES, NoIpResolvedError, PdfUrlIngestionError, RedirectMissingLocationError, TEXT_DOCUMENT_MAX_BYTES, TextDocumentAttachmentError, TextDocumentControlCharacterError, TimedOutDownloadingError, TooManyRedirectsError, UnableToResolveHostError, UnexpectedRedirectStateError, UnsupportedTextAttachmentSourceError, downloadExternalFile, isBlockedIpAddress, isTransientDownloadError, makeSanitizedSourceUrl, normalizeInlineFileBytes, normalizeInlineImageBytes, resolveDownloadedFileMimeType, validateExternalFileUrl, validateUrlResolvesToPublicIp };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BlockedConnectionToPrivateIpError, BlockedDisallowedPortError, BlockedEmbeddedCredentialsError, BlockedExternalFileExceedingError, BlockedExternalFileLargerThanError, BlockedExternalUnsupportedBytesError, BlockedInlineFileExceedingError, BlockedInlineUnsupportedFileBytesError, BlockedUnsupportedSchemeError, BlockedUrlResolvingToPrivateIpError, ExternalFileResponseBodyEmptyError, FailedToDownloadError, FileSecurityError, InvalidExternalFileUrlError, InvalidInlineFileMalformedBase64Error, InvalidUtf8TextDocumentError, NoIpResolvedError, PdfUrlIngestionError, RedirectMissingLocationError, TextDocumentAttachmentError, TextDocumentControlCharacterError, TimedOutDownloadingError, TooManyRedirectsError, UnableToResolveHostError, UnexpectedRedirectStateError, UnsupportedTextAttachmentSourceError, isTransientDownloadError } from "./file-security-errors.js";
|
|
2
|
+
import { ALLOWED_EXTERNAL_IMAGE_MIME_TYPES, ALLOWED_HTTP_PORTS, EXTERNAL_FETCH_TIMEOUT_MS, MAX_EXTERNAL_REDIRECTS, MAX_FILE_BYTES, TEXT_DOCUMENT_MAX_BYTES } from "./file-security-constants.js";
|
|
3
|
+
import { normalizeInlineFileBytes, normalizeInlineImageBytes, resolveDownloadedFileMimeType } from "./file-content-security.js";
|
|
4
|
+
import { isBlockedIpAddress, makeSanitizedSourceUrl, validateExternalFileUrl, validateUrlResolvesToPublicIp } from "./file-url-security.js";
|
|
5
|
+
import { downloadExternalFile } from "./external-file-downloader.js";
|
|
6
|
+
|
|
7
|
+
export { ALLOWED_EXTERNAL_IMAGE_MIME_TYPES, ALLOWED_HTTP_PORTS, BlockedConnectionToPrivateIpError, BlockedDisallowedPortError, BlockedEmbeddedCredentialsError, BlockedExternalFileExceedingError, BlockedExternalFileLargerThanError, BlockedExternalUnsupportedBytesError, BlockedInlineFileExceedingError, BlockedInlineUnsupportedFileBytesError, BlockedUnsupportedSchemeError, BlockedUrlResolvingToPrivateIpError, EXTERNAL_FETCH_TIMEOUT_MS, ExternalFileResponseBodyEmptyError, FailedToDownloadError, FileSecurityError, InvalidExternalFileUrlError, InvalidInlineFileMalformedBase64Error, InvalidUtf8TextDocumentError, MAX_EXTERNAL_REDIRECTS, MAX_FILE_BYTES, NoIpResolvedError, PdfUrlIngestionError, RedirectMissingLocationError, TEXT_DOCUMENT_MAX_BYTES, TextDocumentAttachmentError, TextDocumentControlCharacterError, TimedOutDownloadingError, TooManyRedirectsError, UnableToResolveHostError, UnexpectedRedirectStateError, UnsupportedTextAttachmentSourceError, downloadExternalFile, isBlockedIpAddress, isTransientDownloadError, makeSanitizedSourceUrl, normalizeInlineFileBytes, normalizeInlineImageBytes, resolveDownloadedFileMimeType, validateExternalFileUrl, validateUrlResolvesToPublicIp };
|