function isSensitiveUrlPath(path: string): boolean { return isSensitiveUrlConfigPath(path);
}
function hasSensitiveUrlHintPath(hints: ConfigUiHints | undefined, paths: string[]): boolean { if (!hints) { returnfalse;
} return paths.some((path) => hasSensitiveUrlHintTag(hints[path]));
}
function isObjectRecord(value: unknown): value is Record<string, unknown> { returntypeof value === "object" && value !== null && !Array.isArray(value);
}
function collectSensitiveStrings(value: unknown, values: string[]): void { if (typeof value === "string") { if (!isEnvVarPlaceholder(value)) {
values.push(value);
} return;
} if (Array.isArray(value)) { for (const item of value) {
collectSensitiveStrings(item, values);
} return;
} if (isObjectRecord(value)) { const obj = value; // SecretRef objects include structural fields like source/provider that are // not secret material and may appear widely in config text. if (isSecretRefShape(obj)) { if (!isEnvVarPlaceholder(obj.id)) {
values.push(obj.id);
} return;
} for (const item of Object.values(obj)) {
collectSensitiveStrings(item, values);
}
}
}
function isSecretRefWithProvider(
value: Record<string, unknown>,
): value is Record<string, unknown> & { source: string; provider: string; id: string } { return isSecretRefShape(value) && typeof value.provider === "string";
}
// ConfigUiHints' keys look like this: // - path.subpath.key (nested objects) // - path.subpath[].key (object in array in object) // - path.*.key (object in record in object) // records are handled by the lookup, but arrays need two entries in // the Set, as their first lookup is done before the code knows it's // an array. function buildRedactionLookup(hints: ConfigUiHints): Set<string> {
let result = new Set<string>();
for (const [path, hint] of Object.entries(hints)) { if (!hint.sensitive) { continue;
}
const parts = path.split(".");
let joinedPath = parts.shift() ?? "";
result.add(joinedPath); if (joinedPath.endsWith("[]")) {
result.add(joinedPath.slice(0, -2));
}
for (const part of parts) { if (part.endsWith("[]")) {
result.add(`${joinedPath}.${part.slice(0, -2)}`);
} // hey, greptile, notice how this is *NOT* in an else block?
joinedPath = `${joinedPath}.${part}`;
result.add(joinedPath);
}
} if (result.size !== 0) {
result.add("");
} return result;
}
export function redactConfigSnapshot(
snapshot: ConfigFileSnapshot,
uiHints?: ConfigUiHints,
): ConfigFileSnapshot { if (!snapshot.valid) { // This is bad. We could try to redact the raw string using known key names, // but then we would not be able to restore them, and would trash the user's // credentials. Less than ideal---we should never delete important data. // On the other hand, we cannot hand out "raw" if we're not sure we have // properly redacted all sensitive data. Handing out a partially or, worse, // unredacted config string would be bad. // Therefore, the only safe route is to reject handling out broken configs. const redactedConfig = {} as ConfigFileSnapshot["config"]; const redactedResolved = {} as ConfigFileSnapshot["resolved"]; return {
...snapshot,
sourceConfig: redactedResolved,
runtimeConfig: redactedConfig,
config: redactedConfig,
raw: null,
parsed: null,
resolved: redactedResolved,
};
} // else: snapshot.config must be valid and populated, as that is what // readConfigFileSnapshot() does when it creates the snapshot.
class RedactionError extends Error { public readonly key: string; public readonly humanReadableMessage: string;
constructor(key: string, humanReadableMessage?: string) { super("internal error class---should never escape"); this.key = key; this.humanReadableMessage =
humanReadableMessage ??
`Sentinel value "${REDACTED_SENTINEL}" in key ${key} is not valid as real data`; this.name = "RedactionError";
}
}
function restoreOriginalValueOrThrow(params: {
key: string;
path: string;
original: Record<string, unknown>;
}): unknown { if (params.key in params.original) { return params.original[params.key];
} if (!suppressRestoreWarnings) {
log.warn(`Cannot un-redact config key ${params.path} as it doesn't have any value`);
} thrownew RedactionError(params.path);
}
function assertNoRedactedSentinel(value: unknown, path: string): void { if (typeof value === "string" && value === REDACTED_SENTINEL) { const pathLabel = path || "<root>"; thrownew RedactionError(
pathLabel,
`Reserved redaction sentinel "${REDACTED_SENTINEL}" is not valid config data (${pathLabel}).`,
);
} if (Array.isArray(value)) { for (let index = 0; index < value.length; index += 1) { const nextPath = path ? `${path}[${index}]` : `[${index}]`;
assertNoRedactedSentinel(value[index], nextPath);
} return;
} if (isObjectRecord(value)) { for (const [key, item] of Object.entries(value)) {
assertNoRedactedSentinel(item, path ? `${path}.${key}` : key);
}
}
}
const originalObj = toObjectRecord(params.original); if (!isSecretRefWithProvider(originalObj)) { if (isSecretRefShape(originalObj)) { thrownew RedactionError(
params.path,
`SecretRef at ${params.path} requires a provider field to restore the redacted id automatically (original ref lacks provider).`,
);
} thrownew RedactionError(
params.path,
`SecretRef at ${params.path} contains a redacted id placeholder with no matching original value.`,
);
}
if (!isSecretRefWithProvider(incomingObj)) { thrownew RedactionError(
params.path,
`SecretRef at ${params.path} must include source, provider, and id when redacted placeholders are present.`,
);
}
if (incomingObj.source !== originalObj.source || incomingObj.provider !== originalObj.provider) { thrownew RedactionError(
params.path,
`SecretRef at ${params.path} changed source/provider while id is redacted. Provide an explicit id when changing source/provider.`,
);
}
const arrayContext = toRestoreArrayContext(incoming, prefix); if (arrayContext) { // Note: If the user removed an item in the middle of the array, // we have no way of knowing which one. In this case, the last // element(s) get(s) chopped off. Not good, so please don't put // sensitive string array in the config... const { incoming: incomingArray, path } = arrayContext; return restoreGuessingArray(incomingArray, original, path, hints);
} const orig = toObjectRecord(original); const result: Record<string, unknown> = {}; for (const [key, value] of Object.entries(toObjectRecord(incoming))) { const path = prefix ? `${prefix}.${key}` : key; const wildcardPath = prefix ? `${prefix}.*` : "*";
result[key] = restoreRedactedEntryGuessing({
key,
value,
path,
wildcardPath,
original: orig,
hints,
});
} return result;
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.18 Sekunden
(vorverarbeitet am 2026-09-27)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.