Files
yaoweiprcandClaude Sonnet 5.5 8e5e10bb89 feat(insomnia-data): add encrypted field definitions (phase 2)
Add the encrypted() mark and getEncryptedFieldPaths(), which derives the
encrypted field paths from a Zod model schema with an exhaustive walk over
the Zod node kinds (unknown kinds and unaddressable marks throw instead of
being skipped). getEncryptedFieldPathsByType() / getEncryptedFieldPathsForType()
build the path table for every model type.

Mark the sensitive fields of Request (and the schemas that share its
authentication: RequestGroup, WebSocketRequest, SocketIORequest, McpRequest),
CookieJar and GrpcRequest, and add temporary encryptedFields arrays for
ClientCertificate, CloudCredential, GitCredentials, OAuth2Token and
RequestVersion until they move to Zod.

The hook path walker supports arrays of arrays, and the read hook logs
failures after parsing without logging the line. Update the design doc to
match the implementation.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-02 18:00:59 +08:00

101 lines
3.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { forEachEncryptedLeaf } from './paths';
const collect = (doc: unknown, paths: string[]) => {
const seen: { path: string; value: unknown }[] = [];
forEachEncryptedLeaf(doc, paths, ({ container, key, path }) => seen.push({ path, value: container[key] }));
return seen;
};
describe('forEachEncryptedLeaf()', () => {
it('walks object keys', () => {
expect(collect({ passphrase: 'a', other: 'b' }, ['passphrase'])).toEqual([{ path: 'passphrase', value: 'a' }]);
expect(collect({ authentication: { password: 'p', username: 'u' } }, ['authentication.password'])).toEqual([
{ path: 'authentication.password', value: 'p' },
]);
});
it('walks every element of an array with []', () => {
const doc = { cookies: [{ key: 'a', value: '1' }, { key: 'b', value: '2' }, { key: 'c' }] };
expect(collect(doc, ['cookies[].value'])).toEqual([
{ path: 'cookies[0].value', value: '1' },
{ path: 'cookies[1].value', value: '2' },
]);
});
it('supports arrays of strings and nested arrays', () => {
expect(collect({ tokens: ['a', 'b'] }, ['tokens[]'])).toEqual([
{ path: 'tokens[0]', value: 'a' },
{ path: 'tokens[1]', value: 'b' },
]);
expect(collect({ a: [{ b: [{ c: 'x' }, { c: 'y' }] }] }, ['a[].b[].c'])).toEqual([
{ path: 'a[0].b[0].c', value: 'x' },
{ path: 'a[0].b[1].c', value: 'y' },
]);
});
it('lets the visitor replace the value in place', () => {
const doc = { authentication: { password: 'p' }, cookies: [{ value: '1' }] };
forEachEncryptedLeaf(doc, ['authentication.password', 'cookies[].value'], ({ container, key }) => {
container[key] = 'replaced';
});
expect(doc).toEqual({ authentication: { password: 'replaced' }, cookies: [{ value: 'replaced' }] });
});
it('visits null, empty and non-string leaves as they are and leaves the filtering to the caller', () => {
expect(collect({ a: null, b: '', c: 5, d: { x: 1 } }, ['a', 'b', 'c', 'd'])).toEqual([
{ path: 'a', value: null },
{ path: 'b', value: '' },
{ path: 'c', value: 5 },
{ path: 'd', value: { x: 1 } },
]);
});
it('skips shapes that do not match instead of throwing', () => {
const paths = ['authentication.password', 'cookies[].value'];
for (const doc of [
{},
{ authentication: null, cookies: null },
{ authentication: 'str', cookies: 'str' },
{ authentication: [], cookies: {} },
{ authentication: { password: undefined }, cookies: [null, 1, 'x', [], { value: undefined }] },
null,
undefined,
'text',
42,
]) {
expect(() => collect(doc, paths)).not.toThrow();
}
expect(collect({ authentication: [{ password: 'p' }] }, ['authentication.password'])).toEqual([]);
});
it('does not follow inherited properties', () => {
expect(collect({}, ['toString', 'constructor.name'])).toEqual([]);
});
it('goes through arrays of arrays with [][]', () => {
expect(collect({ x: [['a', 'b'], ['c']] }, ['x[][]'])).toEqual([
{ path: 'x[0][0]', value: 'a' },
{ path: 'x[0][1]', value: 'b' },
{ path: 'x[1][0]', value: 'c' },
]);
expect(collect({ x: [[{ s: '1' }], [{ s: '2' }, {}]] }, ['x[][].s'])).toEqual([
{ path: 'x[0][0].s', value: '1' },
{ path: 'x[1][0].s', value: '2' },
]);
});
it('skips elements that are not arrays where [][] expects one', () => {
expect(collect({ x: [['a'], 'b', null, { y: 1 }, 5] }, ['x[][]'])).toEqual([{ path: 'x[0][0]', value: 'a' }]);
});
it('lets the visitor replace values inside nested arrays', () => {
const doc = { x: [['a'], ['b']] };
forEachEncryptedLeaf(doc, ['x[][]'], ({ container, key }) => {
container[key] = 'replaced';
});
expect(doc).toEqual({ x: [['replaced'], ['replaced']] });
});
});