cabloy 5.1.219 → 5.1.221
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/.cabloy-version +1 -1
- package/.github/workflows/agent-governance.yml +2 -2
- package/.github/workflows/create-cabloy.yml +3 -3
- package/.github/workflows/docs-pages.yml +2 -2
- package/.github/workflows/playwright-e2e.yml +4 -4
- package/.github/workflows/vona-cov-pg.yml +2 -2
- package/.github/workflows/vona-test-crud.yml +2 -2
- package/.github/workflows/vona-test-mysql.yml +2 -2
- package/.github/workflows/vona-test-pg.yml +2 -2
- package/.github/workflows/vona-test-sqlite3.yml +2 -2
- package/.github/workflows/vona-tsc.yml +2 -2
- package/.github/workflows/zova-ui.yml +2 -2
- package/CHANGELOG.md +23 -0
- package/README.md +1 -1
- package/package.json +4 -3
- package/repo-docs/frontend/openapi-sdk-guide.md +2 -0
- package/repo-docs/fullstack/quickstart.md +3 -3
- package/repo-docs/reference/repo-scripts.md +16 -0
- package/scripts/generateOpenapiIsolated.test.ts +25 -0
- package/scripts/generateOpenapiIsolated.ts +46 -12
- package/scripts/runIsolatedTest.test.ts +165 -45
- package/scripts/runIsolatedTest.ts +111 -24
- package/scripts/strictGenerationDiagnostics.test.ts +1009 -0
- package/scripts/strictGenerationDiagnostics.ts +61 -0
- package/scripts/strictGenerationStartup.test.ts +212 -0
- package/vona/package.original.json +3 -2
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +14 -4
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateEntryFiles.ts +6 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateVonaMeta.ts +2 -10
- package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/validateGenerationPaths.ts +27 -0
- package/vona/packages-cli/cli-set-api/test/binDevLifecycle.test.ts +1 -1
- package/vona/packages-cli/cli-set-api/test/strictGenerationPrivacy.test.ts +439 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-core/package.json +1 -1
- package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +4 -0
- package/vona/packages-vona/vona-core/src/lib/core/asyncLocalStorage.ts +84 -12
- package/vona/packages-vona/vona-core/src/lib/core/index.ts +14 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/appLogger.ts +19 -3
- package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.test.ts +346 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.ts +138 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/index.ts +1 -0
- package/vona/packages-vona/vona-core/src/lib/core/logger/utils.ts +25 -5
- package/vona/packages-vona/vona-core/src/lib/core/requestBoundary.ts +89 -0
- package/vona/packages-vona/vona-core/src/lib/core/testFreshness.test.ts +379 -0
- package/vona/packages-vona/vona-core/src/lib/core/testFreshness.ts +232 -0
- package/vona/packages-vona/vona-core/src/lib/core/testStrictMode.ts +9 -0
- package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +4 -3
- package/vona/packages-vona/vona-core/src/lib/framework/start.ts +2 -0
- package/vona/packages-vona/vona-mock/package.json +1 -1
- package/vona/pnpm-lock.yaml +691 -356
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +111 -60
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/service/executor.ts +6 -6
- package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +13 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/lib/jwtClientLogging.test.ts +182 -0
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +2 -8
- package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/aopMethod.log.ts +4 -1
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/bean.logger.ts +9 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +27 -14
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.test.ts +207 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.ts +30 -0
- package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +33 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/index.ts +11 -0
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/lib/protectedSqliteRegistry.ts +356 -0
- package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +6 -0
- package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/bean.telemetry.ts +8 -1
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/middlewareSystem.trace.ts +9 -14
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/lib/spanDiagnostics.ts +70 -0
- package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/service/telemetry.ts +37 -23
- package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +31 -4
- package/vona/src/suite-vendor/a-vona/package.json +2 -2
- package/zova/package.original.json +3 -2
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.bin.buildRest.ts +11 -101
- package/zova/packages-cli/cli-set-front/src/lib/common/restDependencies.ts +78 -0
- package/zova/packages-cli/cli-set-front/src/lib/common/restDependencyVersions.ts +252 -0
- package/zova/packages-cli/cli-set-front/test/restDependencies.test.ts +568 -0
- package/zova/pnpm-lock.yaml +442 -564
|
@@ -5,9 +5,10 @@ import type {
|
|
|
5
5
|
} from 'vona-module-a-aspect';
|
|
6
6
|
|
|
7
7
|
import StdSerializers from 'pino-std-serializers';
|
|
8
|
-
import { BeanBase } from 'vona';
|
|
8
|
+
import { BeanBase, getRequestBoundary } from 'vona';
|
|
9
9
|
import { MiddlewareSystem } from 'vona-module-a-aspect';
|
|
10
10
|
|
|
11
|
+
import { redactHttpLogHeaders, redactHttpLogUrl } from '../lib/redactHttpLog.ts';
|
|
11
12
|
import { redactIsolatedReadyHeader } from '../lib/redactIsolatedReadyHeader.ts';
|
|
12
13
|
|
|
13
14
|
export interface IMiddlewareSystemOptionsHttpLog extends IDecoratorMiddlewareSystemOptions {}
|
|
@@ -15,28 +16,40 @@ export interface IMiddlewareSystemOptionsHttpLog extends IDecoratorMiddlewareSys
|
|
|
15
16
|
@MiddlewareSystem<IMiddlewareSystemOptionsHttpLog>()
|
|
16
17
|
export class MiddlewareSystemHttpLog extends BeanBase implements IMiddlewareSystemExecute {
|
|
17
18
|
async execute(_options: IMiddlewareSystemOptionsHttpLog, next: Next) {
|
|
19
|
+
if (getRequestBoundary()) return next();
|
|
18
20
|
const ctx = this.ctx;
|
|
19
21
|
// start
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
const serialized = StdSerializers.req(ctx.req);
|
|
23
|
+
// Do not retain serializer query/params/raw extensions or request bodies in logs.
|
|
24
|
+
const req = {
|
|
25
|
+
method: serialized.method,
|
|
26
|
+
url: redactHttpLogUrl(serialized.url),
|
|
27
|
+
headers: redactHttpLogHeaders(
|
|
28
|
+
redactIsolatedReadyHeader(
|
|
29
|
+
serialized.headers,
|
|
30
|
+
ctx.path,
|
|
31
|
+
this.app.meta.env.VONA_TEST_RUN_ID,
|
|
32
|
+
['x-vona-test-challenge', 'x-vona-test-stop-nonce', 'x-vona-test-stop-authorization'],
|
|
33
|
+
),
|
|
34
|
+
),
|
|
35
|
+
remoteAddress: serialized.remoteAddress,
|
|
36
|
+
remotePort: serialized.remotePort,
|
|
37
|
+
};
|
|
27
38
|
this.$loggerChild('req').http(() => JSON.stringify(req));
|
|
28
39
|
const profiler = this.$loggerChild('res').startTimer();
|
|
29
40
|
// next
|
|
30
41
|
await next();
|
|
31
42
|
// end
|
|
32
|
-
const headers =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
const headers = redactHttpLogHeaders(
|
|
44
|
+
redactIsolatedReadyHeader(
|
|
45
|
+
ctx.res.getHeaders(),
|
|
46
|
+
ctx.path,
|
|
47
|
+
this.app.meta.env.VONA_TEST_RUN_ID,
|
|
48
|
+
['x-vona-test-proof', 'x-vona-test-stop-proof'],
|
|
49
|
+
),
|
|
37
50
|
);
|
|
38
51
|
const res = {
|
|
39
|
-
url: ctx.req.url,
|
|
52
|
+
url: redactHttpLogUrl(ctx.req.url),
|
|
40
53
|
statusCode: ctx.res.statusCode,
|
|
41
54
|
headers,
|
|
42
55
|
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { registerHooks } from 'node:module';
|
|
4
|
+
import { test } from 'node:test';
|
|
5
|
+
import ts from 'typescript';
|
|
6
|
+
|
|
7
|
+
import { redactHttpLogHeaders, redactHttpLogUrl } from './redactHttpLog.ts';
|
|
8
|
+
|
|
9
|
+
const previousApp = Object.getOwnPropertyDescriptor(globalThis, '__app__');
|
|
10
|
+
const vonaUrl = new URL('../../../../../../../', import.meta.url);
|
|
11
|
+
const middlewareUrl = new URL(
|
|
12
|
+
'../bean/middlewareSystem.httpLog.ts?fixture=httpLogging',
|
|
13
|
+
import.meta.url,
|
|
14
|
+
).href;
|
|
15
|
+
const formatterUrl = new URL('packages-vona/vona-core/src/lib/core/logger/utils.ts', vonaUrl).href;
|
|
16
|
+
const checkUrl = new URL('packages-utils/utils/src/check.ts', vonaUrl).href;
|
|
17
|
+
const fixture = (source: string) => `data:text/javascript,${encodeURIComponent(source)}`;
|
|
18
|
+
const hooks = registerHooks({
|
|
19
|
+
resolve(specifier, context, nextResolve) {
|
|
20
|
+
if (context.parentURL === middlewareUrl) {
|
|
21
|
+
if (specifier === 'vona') {
|
|
22
|
+
return {
|
|
23
|
+
url: fixture('export class BeanBase {} export function getRequestBoundary() {}'),
|
|
24
|
+
shortCircuit: true,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (specifier === 'vona-module-a-aspect') {
|
|
28
|
+
return {
|
|
29
|
+
url: fixture('export function MiddlewareSystem() { return () => {}; }'),
|
|
30
|
+
shortCircuit: true,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (context.parentURL === formatterUrl) {
|
|
35
|
+
if (specifier === '@cabloy/utils') return { url: checkUrl, shortCircuit: true };
|
|
36
|
+
if (specifier === '../../framework/useApp.ts') {
|
|
37
|
+
return {
|
|
38
|
+
url: fixture(
|
|
39
|
+
'export function useApp() { throw new Error("unexpected application access"); }',
|
|
40
|
+
),
|
|
41
|
+
shortCircuit: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return nextResolve(specifier, context);
|
|
46
|
+
},
|
|
47
|
+
load(url, context, nextLoad) {
|
|
48
|
+
if (url === middlewareUrl) {
|
|
49
|
+
return {
|
|
50
|
+
format: 'module',
|
|
51
|
+
source: ts.transpileModule(readFileSync(new URL(url), 'utf8'), {
|
|
52
|
+
compilerOptions: {
|
|
53
|
+
experimentalDecorators: true,
|
|
54
|
+
target: ts.ScriptTarget.ESNext,
|
|
55
|
+
module: ts.ModuleKind.ESNext,
|
|
56
|
+
},
|
|
57
|
+
}).outputText,
|
|
58
|
+
shortCircuit: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return nextLoad(url, context);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const { MiddlewareSystemHttpLog, formatLoggerFilter } = await (async () => {
|
|
65
|
+
try {
|
|
66
|
+
return { ...(await import(middlewareUrl)), ...(await import(formatterUrl)) };
|
|
67
|
+
} finally {
|
|
68
|
+
hooks.deregister();
|
|
69
|
+
}
|
|
70
|
+
})();
|
|
71
|
+
|
|
72
|
+
const secret = 'synthetic-http-secret-sentinel';
|
|
73
|
+
|
|
74
|
+
test('header copies hide standard, mixed-case and custom credentials', () => {
|
|
75
|
+
const headers = Object.freeze({
|
|
76
|
+
'Authorization': secret,
|
|
77
|
+
'Proxy-Authorization': secret,
|
|
78
|
+
'Cookie': secret,
|
|
79
|
+
'Set-Cookie': Object.freeze([secret, secret]),
|
|
80
|
+
'X-Custom-Credential': secret,
|
|
81
|
+
'Location': `/callback?code=${secret}`,
|
|
82
|
+
'Referer': `/callback?state=${secret}`,
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
'content-length': '42',
|
|
85
|
+
'accept': 'application/json',
|
|
86
|
+
});
|
|
87
|
+
const logged = redactHttpLogHeaders(headers);
|
|
88
|
+
assert.equal(JSON.stringify(logged).includes(secret), false);
|
|
89
|
+
assert.equal(logged['Content-Type'], 'application/json');
|
|
90
|
+
assert.equal(logged['content-length'], '42');
|
|
91
|
+
assert.equal(logged.accept, 'application/json');
|
|
92
|
+
assert.equal(headers.Authorization, secret);
|
|
93
|
+
assert.deepEqual(headers['Set-Cookie'], [secret, secret]);
|
|
94
|
+
assert.notEqual(logged, headers);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('URL projections omit all query/fragment data and absolute-URL userinfo', () => {
|
|
98
|
+
for (const key of [
|
|
99
|
+
'access_token',
|
|
100
|
+
'refresh_token',
|
|
101
|
+
'code',
|
|
102
|
+
'state',
|
|
103
|
+
'x-vona-passport-code',
|
|
104
|
+
'custom',
|
|
105
|
+
]) {
|
|
106
|
+
const url = `/callback?${key}=${secret}&${key}=${secret}#${secret}`;
|
|
107
|
+
assert.equal(redactHttpLogUrl(url), '/callback?[REDACTED]');
|
|
108
|
+
}
|
|
109
|
+
assert.equal(redactHttpLogUrl(`/callback#${secret}`), '/callback?[REDACTED]');
|
|
110
|
+
assert.equal(
|
|
111
|
+
redactHttpLogUrl(`https://${secret}:${secret}@example.invalid/callback?x=${secret}`),
|
|
112
|
+
'https://example.invalid/callback?[REDACTED]',
|
|
113
|
+
);
|
|
114
|
+
assert.equal(
|
|
115
|
+
redactHttpLogUrl(`//${secret}@example.invalid/callback`),
|
|
116
|
+
'//example.invalid/callback',
|
|
117
|
+
);
|
|
118
|
+
assert.equal(redactHttpLogUrl('https://['), '[REDACTED]');
|
|
119
|
+
assert.equal(redactHttpLogUrl(`data:${secret}`), '[REDACTED]');
|
|
120
|
+
assert.equal(redactHttpLogUrl({ path: secret }), undefined);
|
|
121
|
+
assert.equal(redactHttpLogUrl('/ordinary/path'), '/ordinary/path');
|
|
122
|
+
assert.equal(redactHttpLogUrl('*'), '*');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
for (const failure of [false, true]) {
|
|
126
|
+
test(`actual middleware strict-http projection, downstream ${failure ? 'error' : 'success'}`, async () => {
|
|
127
|
+
const output: string[] = [];
|
|
128
|
+
const filter = formatLoggerFilter({ level: 'http', child: undefined, strict: true });
|
|
129
|
+
const capture = (message: any, extra = {}) => {
|
|
130
|
+
const info = filter.transform({ level: 'http', message, ...extra }, filter.options);
|
|
131
|
+
assert.ok(info);
|
|
132
|
+
output.push(JSON.stringify(info));
|
|
133
|
+
};
|
|
134
|
+
const headers = Object.freeze({
|
|
135
|
+
authorization: secret,
|
|
136
|
+
cookie: secret,
|
|
137
|
+
accept: 'application/json',
|
|
138
|
+
});
|
|
139
|
+
const responseHeaders = Object.freeze({
|
|
140
|
+
'set-cookie': [secret],
|
|
141
|
+
'content-type': 'application/json',
|
|
142
|
+
});
|
|
143
|
+
const req = Object.freeze({
|
|
144
|
+
method: 'POST',
|
|
145
|
+
url: `/callback?custom=${secret}`,
|
|
146
|
+
originalUrl: `/callback?x-vona-passport-code=${secret}`,
|
|
147
|
+
query: { custom: secret },
|
|
148
|
+
params: { token: secret },
|
|
149
|
+
body: { token: secret },
|
|
150
|
+
headers,
|
|
151
|
+
socket: { remoteAddress: '127.0.0.1', remotePort: 12345 },
|
|
152
|
+
});
|
|
153
|
+
const ctx = {
|
|
154
|
+
req,
|
|
155
|
+
path: '/callback',
|
|
156
|
+
res: { statusCode: 204, getHeaders: () => responseHeaders },
|
|
157
|
+
};
|
|
158
|
+
const fake = {
|
|
159
|
+
ctx,
|
|
160
|
+
app: { meta: { env: {} } },
|
|
161
|
+
$loggerChild: (name: string) => {
|
|
162
|
+
assert.ok(name === 'req' || name === 'res');
|
|
163
|
+
return {
|
|
164
|
+
http: (message: any) => capture(message),
|
|
165
|
+
startTimer: () => ({
|
|
166
|
+
done: (info: any) => {
|
|
167
|
+
assert.equal(info.level, 'http');
|
|
168
|
+
capture(info.message, { durationMs: 7 });
|
|
169
|
+
},
|
|
170
|
+
}),
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
let calls = 0;
|
|
175
|
+
const error = new Error(secret);
|
|
176
|
+
const execution = MiddlewareSystemHttpLog.prototype.execute.call(fake as any, {}, async () => {
|
|
177
|
+
calls++;
|
|
178
|
+
assert.equal(ctx.req, req);
|
|
179
|
+
assert.equal(ctx.req.headers.authorization, secret);
|
|
180
|
+
assert.equal(ctx.req.body.token, secret);
|
|
181
|
+
if (failure) throw error;
|
|
182
|
+
});
|
|
183
|
+
if (failure) {
|
|
184
|
+
await assert.rejects(execution, value => value === error);
|
|
185
|
+
} else {
|
|
186
|
+
await execution;
|
|
187
|
+
}
|
|
188
|
+
assert.equal(calls, 1);
|
|
189
|
+
assert.equal(output.length, failure ? 1 : 2);
|
|
190
|
+
assert.equal(output.join('\n').includes(secret), false);
|
|
191
|
+
const request = JSON.parse(JSON.parse(output[0]!).message);
|
|
192
|
+
assert.equal(request.method, 'POST');
|
|
193
|
+
assert.equal(request.url, '/callback?[REDACTED]');
|
|
194
|
+
assert.equal(request.query, undefined);
|
|
195
|
+
assert.equal(request.params, undefined);
|
|
196
|
+
assert.equal(request.body, undefined);
|
|
197
|
+
assert.equal(request.raw, undefined);
|
|
198
|
+
if (!failure) {
|
|
199
|
+
const response = JSON.parse(output[1]!);
|
|
200
|
+
assert.equal(response.durationMs, 7);
|
|
201
|
+
assert.equal(JSON.parse(response.message).statusCode, 204);
|
|
202
|
+
}
|
|
203
|
+
assert.equal(req.url, `/callback?custom=${secret}`);
|
|
204
|
+
assert.deepEqual(responseHeaders['set-cookie'], [secret]);
|
|
205
|
+
assert.deepEqual(Object.getOwnPropertyDescriptor(globalThis, '__app__'), previousApp);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const safeHeaders = new Set(['accept', 'content-type', 'content-length']);
|
|
2
|
+
|
|
3
|
+
export function redactHttpLogHeaders(headers: Record<string, unknown>): Record<string, unknown> {
|
|
4
|
+
// Allowlist diagnostic metadata so custom credential headers are covered too.
|
|
5
|
+
return Object.fromEntries(
|
|
6
|
+
Object.entries(headers).map(([key, value]) => [
|
|
7
|
+
key,
|
|
8
|
+
safeHeaders.has(key.toLowerCase()) ? value : '[REDACTED]',
|
|
9
|
+
]),
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function redactHttpLogUrl(value: unknown): string | undefined {
|
|
14
|
+
if (typeof value !== 'string') return undefined;
|
|
15
|
+
const boundary = value.search(/[?#]/);
|
|
16
|
+
let path = boundary < 0 ? value : value.slice(0, boundary);
|
|
17
|
+
if (/^[a-z][a-z\d+.-]*:\/\//i.test(path) || path.startsWith('//')) {
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(path, 'http://redacted.invalid');
|
|
20
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') return '[REDACTED]';
|
|
21
|
+
path = `${path.startsWith('//') ? '//' : `${url.protocol}//`}${url.host}${url.pathname}`;
|
|
22
|
+
} catch {
|
|
23
|
+
return '[REDACTED]';
|
|
24
|
+
}
|
|
25
|
+
} else if (!path.startsWith('/') && path !== '*') {
|
|
26
|
+
return '[REDACTED]';
|
|
27
|
+
}
|
|
28
|
+
// Query names can be configured by authentication clients; omit the whole query/fragment.
|
|
29
|
+
return `${path}${boundary < 0 ? '' : '?[REDACTED]'}`;
|
|
30
|
+
}
|
|
@@ -8,11 +8,18 @@ const middlewareUrl = new URL(
|
|
|
8
8
|
'../src/bean/middlewareSystem.httpLog.ts?fixture=httpLog',
|
|
9
9
|
import.meta.url,
|
|
10
10
|
).href;
|
|
11
|
+
const boundaryFixture = `data:text/javascript,${encodeURIComponent(`
|
|
12
|
+
export class BeanBase {}
|
|
13
|
+
let boundary;
|
|
14
|
+
export function getRequestBoundary() { return boundary; }
|
|
15
|
+
export function setBoundary(value) { boundary = value; }
|
|
16
|
+
`)}`;
|
|
17
|
+
const { setBoundary } = await import(boundaryFixture);
|
|
11
18
|
const hooks = registerHooks({
|
|
12
19
|
resolve(specifier, context, nextResolve) {
|
|
13
20
|
if (context.parentURL === middlewareUrl) {
|
|
21
|
+
if (specifier === 'vona') return { url: boundaryFixture, shortCircuit: true };
|
|
14
22
|
const source = {
|
|
15
|
-
'vona': 'export class BeanBase {}',
|
|
16
23
|
'vona-module-a-aspect': 'export function MiddlewareSystem() { return () => {}; }',
|
|
17
24
|
'pino-std-serializers':
|
|
18
25
|
'export default { req: req => ({ headers: req.headers, url: req.url }) };',
|
|
@@ -42,6 +49,31 @@ const hooks = registerHooks({
|
|
|
42
49
|
const { MiddlewareSystemHttpLog } = await import(middlewareUrl);
|
|
43
50
|
hooks.deregister();
|
|
44
51
|
|
|
52
|
+
test('HTTP logger skips context and diagnostic access inside a request boundary', async () => {
|
|
53
|
+
const middleware = Object.create(MiddlewareSystemHttpLog.prototype);
|
|
54
|
+
for (const key of ['app', 'ctx', '$loggerChild']) {
|
|
55
|
+
Object.defineProperty(middleware, key, {
|
|
56
|
+
get() {
|
|
57
|
+
assert.fail('protected diagnostic access');
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
let calls = 0;
|
|
62
|
+
setBoundary({ diagnostics: 'suppress' });
|
|
63
|
+
try {
|
|
64
|
+
assert.equal(
|
|
65
|
+
await middleware.execute({}, async () => {
|
|
66
|
+
calls++;
|
|
67
|
+
return 23;
|
|
68
|
+
}),
|
|
69
|
+
23,
|
|
70
|
+
);
|
|
71
|
+
assert.equal(calls, 1);
|
|
72
|
+
} finally {
|
|
73
|
+
setBoundary(undefined);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
45
77
|
for (const path of ['/health/ready', '/health/test-stop']) {
|
|
46
78
|
test(`HTTP logger redacts request authorization and response proof at ${path} without mutating headers`, async () => {
|
|
47
79
|
const headers = {
|
|
@@ -1 +1,12 @@
|
|
|
1
1
|
export * from './.metadata/index.ts';
|
|
2
|
+
export { createProtectedSqliteRegistry } from './lib/protectedSqliteRegistry.ts';
|
|
3
|
+
export type {
|
|
4
|
+
ProtectedSqliteRegistry,
|
|
5
|
+
ProtectedSqliteRegistryCloseResult,
|
|
6
|
+
ProtectedSqliteRegistryLookupOutcome,
|
|
7
|
+
ProtectedSqliteRegistryLookupResult,
|
|
8
|
+
ProtectedSqliteRegistryOptions,
|
|
9
|
+
ProtectedSqliteRegistryPrepareResult,
|
|
10
|
+
ProtectedSqliteRegistryRow,
|
|
11
|
+
ProtectedSqliteRegistrySnapshot,
|
|
12
|
+
} from './lib/protectedSqliteRegistry.ts';
|