71 lines
2.4 KiB
TypeScript
71 lines
2.4 KiB
TypeScript
const REDACTED_URL_VALUE = "REDACTED";
|
|
|
|
const SENSITIVE_URL_FIELD_PATTERN =
|
|
/^(?:code|state|nonce|key|[A-Za-z0-9_-]*(?:api[-_]?key|access[-_]?token|auth(?:[-_]?token)?|token|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)[A-Za-z0-9_-]*)$/i;
|
|
|
|
export const REMOTE_URL_SECRET_CONFIG_PATH = "remote.url";
|
|
|
|
export interface SplitRemoteUrlCredential {
|
|
publicUrl: string;
|
|
secretUrl: string | null;
|
|
secretParameterNames: string[];
|
|
}
|
|
|
|
/**
|
|
* Separate credential-bearing query values from the URL that is safe to store
|
|
* on a tool connection. The complete URL is written to the encrypted vault;
|
|
* only its public origin, path, and non-sensitive query configuration remain
|
|
* in connection config and API responses.
|
|
*/
|
|
export function splitRemoteUrlCredential(value: string): SplitRemoteUrlCredential {
|
|
const input = value.trim();
|
|
const url = new URL(input);
|
|
const hasUserInfo = url.username.length > 0 || url.password.length > 0;
|
|
if (hasUserInfo) {
|
|
url.username = "";
|
|
url.password = "";
|
|
}
|
|
const secretParameterNames: string[] = [];
|
|
for (const key of [...url.searchParams.keys()]) {
|
|
if (!SENSITIVE_URL_FIELD_PATTERN.test(key)) continue;
|
|
secretParameterNames.push(key);
|
|
url.searchParams.delete(key);
|
|
}
|
|
return {
|
|
publicUrl: url.toString(),
|
|
secretUrl: hasUserInfo || secretParameterNames.length > 0 ? input : null,
|
|
secretParameterNames,
|
|
};
|
|
}
|
|
|
|
/** Preserve useful endpoint context in logs without retaining credentials. */
|
|
export function redactRemoteUrlCredential(value: string): string {
|
|
const input = value.trim();
|
|
try {
|
|
const url = new URL(input);
|
|
if (url.username || url.password) {
|
|
url.username = REDACTED_URL_VALUE;
|
|
url.password = "";
|
|
}
|
|
for (const key of [...url.searchParams.keys()]) {
|
|
if (SENSITIVE_URL_FIELD_PATTERN.test(key)) url.searchParams.set(key, REDACTED_URL_VALUE);
|
|
}
|
|
return url.toString();
|
|
} catch {
|
|
return "configured remote MCP endpoint";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A vault value may be replaced independently of connection config. Keep that
|
|
* replacement constrained to the public endpoint the operator originally
|
|
* reviewed; only credential-bearing query values may differ.
|
|
*/
|
|
export function remoteUrlCredentialMatchesPublicUrl(publicValue: string, secretValue: string): boolean {
|
|
try {
|
|
return splitRemoteUrlCredential(secretValue).publicUrl === new URL(publicValue).toString();
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|