fix: 修正凭据名称避免 DSH 重启失败

This commit is contained in:
cneicy committed 2026-10-05 03:58:46 +08:00
1 parent 108e957c64
commit 7e2ec28b80
4 files changed
+57 -6

No files matched your search

+42 -4
View File
@@ -5,6 +5,12 @@ import { mkdtemp, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join, dirname, basename } from 'node:path';
// Match DSH credentials credentialKey/parseCredentialKey admission rules.
// Validate at every service boundary AND on reload, unlike the original permissive mock.
function validateCredentialName(key) {
assert.match(key, /^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/);
}
const PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jVv8AAAAASUVORK5CYII=', 'base64');
test('native host integration using only a local image API', async t => {
@@ -47,9 +53,9 @@ test('native host integration using only a local image API', async t => {
} } },
tools: { register(tool) { tools.set(tool.name, tool); return () => tools.delete(tool.name); } },
credentials: {
async readRecord(key) { return records.get(key); },
async modifyRecord(key, change) { const value = await change(records.get(key)); records.set(key, value); return value; },
async deleteRecord(key) { records.delete(key); },
async readRecord(key) { validateCredentialName(key); return records.get(key); },
async modifyRecord(key, change) { validateCredentialName(key); const value = await change(records.get(key)); records.set(key, value); return value; },
async deleteRecord(key) { validateCredentialName(key); records.delete(key); },
},
attachments: { async saveImage(image) {
assert.deepEqual(Buffer.from(image.data), PNG);
@@ -90,7 +96,7 @@ test('native host integration using only a local image API', async t => {
assert.ok(!JSON.stringify(value).includes(key));
assert.equal(records.size, 1);
const [credentialName, credential] = [...records][0];
assert.match(credentialName, /^dsh-imagegen:[0-9a-f]{64}$/);
assert.match(credentialName, /^imagegen-local\/endpoint-[0-9a-f]{64}$/);
assert.deepEqual(credential, { kind: 'api-key', key });
const disk = await readFile(join(home, 'imagegen', 'settings.json'), 'utf8');
assert.deepEqual(JSON.parse(disk), settings);
@@ -100,6 +106,18 @@ test('native host integration using only a local image API', async t => {
assert.ok(!JSON.stringify(status).includes(key));
assert.ok(!(await (await call('/api/imagegen/settings')).text()).includes(key));
});
await t.test('saved credentials survive strict reload and host reinitialization', async () => {
const persisted = JSON.stringify({ version: 1, records: Object.fromEntries(records) });
records.clear();
for (const [address, value] of Object.entries(JSON.parse(persisted).records)) {
validateCredentialName(address);
records.set(address, value);
}
listeners.get('dispose')();
routes.clear(); tools.clear();
await host.apply(ctx);
assert.equal((await tools.get('imagegen_status').execute()).configured, true);
});
await t.test('lists models using the local authenticated API', async () => {
const response = await call('/api/imagegen/models', 'POST');
assert.equal(response.status, 200);
@@ -157,6 +175,26 @@ test('native host integration using only a local image API', async t => {
assert.deepEqual(await readFile(value.images[0].path), PNG);
assert.ok(!JSON.stringify(value).includes(key));
});
await t.test('all digest prefixes are valid and clearing only removes the selected endpoint', async () => {
const originalRecords = new Map(records);
const prefixes = new Set();
for (let i = 0; prefixes.size < 16 && i < 512; i++) {
const endpoint = { ...settings, baseUrl: `https://example.com/v1/provider-${i}` };
const response = await call('/api/imagegen/settings', 'POST', { ...endpoint, apiKey: 'test-only-temporary-key' });
assert.equal(response.status, 200);
assert.equal((await response.json()).configured, true);
const address = [...records.keys()].find(item => !originalRecords.has(item));
validateCredentialName(address);
prefixes.add(address.split('endpoint-')[1][0]);
assert.equal((await (await call('/api/imagegen/settings', 'POST', endpoint)).json()).configured, true);
const cleared = await call('/api/imagegen/settings', 'POST', { ...endpoint, clearKey: true });
assert.equal(cleared.status, 200);
assert.equal((await cleared.json()).configured, false);
assert.deepEqual(records, originalRecords);
}
assert.equal(prefixes.size, 16, 'exercise every hexadecimal leading character, including 0–9');
await call('/api/imagegen/settings', 'POST', settings);
});
await t.test('file traversal, absolute paths and missing files return 404', async () => {
for (const filename of ['../settings.json', '..\\settings.json', join(home, 'imagegen', 'settings.json'), '/etc/passwd', '', '123-00000000-0000-0000-0000-000000000000.png']) {
assert.equal((await call(`/api/imagegen/file?name=${encodeURIComponent(filename)}`)).status, 404);