Correct registry validation and contain metadata reads (#1500)

This commit is contained in:
2026-09-10 16:40:16 -05:00
parent b3fa221060
commit 6335342873
12 changed files with 470 additions and 289 deletions
@@ -1 +1 @@
{"accountVersion":"1","id":"homelab-openai","name":"Homelab OpenAI","provider":"openai-codex","type":"oauth","createdAt":"2026-09-10T00:00:00Z"}
{"accountVersion":1,"id":"homelab-openai","name":"Homelab OpenAI","provider":"openai-codex","type":"oauth","createdAt":"2026-09-10T00:00:00Z"}
@@ -1 +1 @@
{"providerVersion":"1","id":"ollama-remote","kind":"custom-endpoint","allowInsecureTransport":true,"harnesses":{"pi":{"api":"openai-completions","baseUrl":"http://ollama.internal:11434/v1","models":["qwen2.5-coder:7b"]}},"credentialTypes":["none"]}
{"providerVersion":1,"id":"ollama-remote","kind":"custom-endpoint","allowInsecureTransport":true,"harnesses":{"pi":{"api":"openai-completions","baseUrl":"http://ollama.internal:11434/v1","models":["qwen2.5-coder:7b"]}},"credentialTypes":["none"]}
@@ -1 +1 @@
{"providerVersion":"1","id":"openai-codex","kind":"native","harnesses":{"pi":{"providerId":"openai-codex"}},"credentialTypes":["oauth","api_key"]}
{"providerVersion":1,"id":"openai-codex","kind":"native","harnesses":{"pi":{"providerId":"openai-codex"}},"credentialTypes":["oauth","api_key"]}
@@ -1 +1 @@
{"settingsVersion":"1","id":"research-default","allowedAccounts":["openai-codex/homelab-openai"],"providers":["openai-codex","ollama-remote"],"defaultAccounts":{"openai-codex":"openai-codex/homelab-openai"},"models":{"ollama-remote":["qwen2.5-coder:7b"]}}
{"settingsVersion":1,"id":"research-default","allowedAccounts":["openai-codex/homelab-openai"],"providers":["openai-codex","ollama-remote"],"defaultAccounts":{"openai-codex":"openai-codex/homelab-openai"},"models":{"ollama-remote":["qwen2.5-coder:7b"]}}
+1 -1
View File
@@ -1 +1 @@
{"harnessVersion":"1","id":"pi","executable":"pi","adapter":"pi","compatibleRange":">=0.85.1 <0.86.0","executionMode":"container","materializers":["auth-json","models-json"]}
{"harnessVersion":1,"id":"pi","executable":"pi","adapter":"pi","compatibleRange":">=0.85.1 <0.86.0","executionMode":"container","materializers":["auth-json","models-json"]}
+35 -19
View File
@@ -1,6 +1,6 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, cpSync, chmodSync, readdirSync, lstatSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, dirname, resolve } from "node:path";
import { spawnSync } from "node:child_process";
@@ -15,12 +15,23 @@ function makeRoot() {
return root;
}
function privateTree(root) {
chmodSync(root, 0o700);
for (const name of readdirSync(root)) {
const p = join(root, name);
if (lstatSync(p).isDirectory()) privateTree(p);
else chmodSync(p, 0o600);
}
}
function writeTree(root, files) {
for (const name of ['auth/providers', 'auth/accounts', 'auth/settings', 'harnesses'])
mkdirSync(join(root, name), { recursive: true });
for (const [name, content] of Object.entries(files)) {
const path = join(root, name);
mkdirSync(dirname(path), { recursive: true });
writeFileSync(path, typeof content === "string" ? content : JSON.stringify(content));
}
privateTree(root);
}
function runCli(root, command = "validate") {
@@ -28,17 +39,21 @@ function runCli(root, command = "validate") {
}
const validTree = () => ({
"auth/providers/openai-codex.json": { providerVersion: "1", id: "openai-codex", kind: "native", harnesses: { pi: { providerId: "openai-codex" } }, credentialTypes: ["oauth", "api_key"] },
"auth/accounts/openai-codex/homelab/account.json": { accountVersion: "1", id: "homelab", name: "Homelab", provider: "openai-codex", type: "oauth", createdAt: "2026-09-10T00:00:00Z" },
"auth/settings/research.json": { settingsVersion: "1", id: "research", allowedAccounts: ["openai-codex/homelab"], providers: ["openai-codex"], defaultAccounts: { "openai-codex": "openai-codex/homelab" } },
"harnesses/pi.json": { harnessVersion: "1", id: "pi", executable: "pi", adapter: "pi", compatibleRange: ">=0.85.1 <0.86.0", executionMode: "container", materializers: ["auth-json"] },
"auth/providers/openai-codex.json": { providerVersion: 1, id: "openai-codex", kind: "native", harnesses: { pi: { providerId: "openai-codex" } }, credentialTypes: ["oauth", "api_key"] },
"auth/accounts/openai-codex/homelab/account.json": { accountVersion: 1, id: "homelab", name: "Homelab", provider: "openai-codex", type: "oauth", createdAt: "2026-09-10T00:00:00Z" },
"auth/settings/research.json": { settingsVersion: 1, id: "research", allowedAccounts: ["openai-codex/homelab"], providers: ["openai-codex"], defaultAccounts: { "openai-codex": "openai-codex/homelab" } },
"harnesses/pi.json": { harnessVersion: 1, id: "pi", executable: "pi", adapter: "pi", compatibleRange: ">=0.85.1 <0.86.0", executionMode: "container", materializers: ["auth-json"] },
});
test("valid fixture tree validates and lists without secrets", () => {
const r = runCli(validFixture);
const root = makeRoot();
try {
cpSync(validFixture, root, { recursive: true });
privateTree(root);
const r = runCli(root);
assert.equal(r.status, 0, r.stderr);
assert.match(r.stdout, /^valid\n$/);
const l = runCli(validFixture, "list");
const l = runCli(root, "list");
assert.equal(l.status, 0, l.stderr);
const listed = JSON.parse(l.stdout);
assert.equal(listed.providers.length, 2);
@@ -46,20 +61,21 @@ test("valid fixture tree validates and lists without secrets", () => {
assert.equal(listed.profiles.length, 1);
assert.ok(!JSON.stringify(listed).includes("credential"));
assert.ok(!JSON.stringify(listed).includes("token"));
} finally { rmSync(root, { recursive: true, force: true }); }
});
test("unknown-field refuses", () => {
const errors = validateProvider({ providerVersion: "1", id: "p", kind: "native", harnesses: {}, credentialTypes: ["none"], extra: 1 });
const errors = validateProvider({ providerVersion: 1, id: "p", kind: "native", harnesses: {}, credentialTypes: ["none"], extra: 1 });
assert.equal(errors[0].code, "unknown-field");
});
test("invalid-id refuses uppercase and traversal shapes", () => {
assert.ok(validateProvider({ providerVersion: "1", id: "BadID", kind: "native", harnesses: {}, credentialTypes: ["none"] }).some((e) => e.code === "invalid-id"));
assert.ok(validateAccount({ accountVersion: "1", id: "../escape", name: "x", provider: "p", type: "oauth", createdAt: "2026-09-10T00:00:00Z" }).some((e) => e.code === "invalid-id"));
assert.ok(validateProvider({ providerVersion: 1, id: "BadID", kind: "native", harnesses: {}, credentialTypes: ["none"] }).some((e) => e.code === "invalid-id"));
assert.ok(validateAccount({ accountVersion: 1, id: "../escape", name: "x", provider: "p", type: "oauth", createdAt: "2026-09-10T00:00:00Z" }).some((e) => e.code === "invalid-id"));
});
test("plain-http baseUrl requires allowInsecureTransport", () => {
const base = { providerVersion: "1", id: "o", kind: "custom-endpoint", harnesses: { pi: { api: "openai-completions", baseUrl: "http://h:11434/v1", models: ["m"] } }, credentialTypes: ["none"] };
const base = { providerVersion: 1, id: "o", kind: "custom-endpoint", harnesses: { pi: { api: "openai-completions", baseUrl: "http://h:11434/v1", models: ["m"] } }, credentialTypes: ["none"] };
assert.ok(validateProvider(base).some((e) => e.code === "insecure-transport-not-allowed"));
assert.equal(validateProvider({ ...base, allowInsecureTransport: true }).length, 0);
const https = JSON.parse(JSON.stringify(base));
@@ -68,30 +84,30 @@ test("plain-http baseUrl requires allowInsecureTransport", () => {
});
test("native provider rejects allowInsecureTransport", () => {
assert.ok(validateProvider({ providerVersion: "1", id: "p", kind: "native", harnesses: {}, credentialTypes: ["none"], allowInsecureTransport: true }).some((e) => e.code === "insecure-transport-unsupported-for-native"));
assert.ok(validateProvider({ providerVersion: 1, id: "p", kind: "native", harnesses: {}, credentialTypes: ["none"], allowInsecureTransport: true }).some((e) => e.code === "insecure-transport-unsupported-for-native"));
});
test("unsupported credential type and kind refuse", () => {
assert.ok(validateAccount({ accountVersion: "1", id: "a", name: "x", provider: "p", type: "basic", createdAt: "2026-09-10T00:00:00Z" }).some((e) => e.code === "unsupported-credential-type"));
assert.ok(validateProvider({ providerVersion: "1", id: "p", kind: "weird", harnesses: {}, credentialTypes: ["none"] }).some((e) => e.code === "unsupported-kind"));
assert.ok(validateAccount({ accountVersion: 1, id: "a", name: "x", provider: "p", type: "basic", createdAt: "2026-09-10T00:00:00Z" }).some((e) => e.code === "unsupported-credential-type"));
assert.ok(validateProvider({ providerVersion: 1, id: "p", kind: "weird", harnesses: {}, credentialTypes: ["none"] }).some((e) => e.code === "unsupported-kind"));
});
test("account provider-path mismatch refuses", () => {
assert.ok(validateAccount({ accountVersion: "1", id: "a", name: "x", provider: "other", type: "oauth", createdAt: "2026-09-10T00:00:00Z" }, "right").some((e) => e.code === "provider-path-mismatch"));
assert.ok(validateAccount({ accountVersion: 1, id: "a", name: "x", provider: "other", type: "oauth", createdAt: "2026-09-10T00:00:00Z" }, "right").some((e) => e.code === "provider-path-mismatch"));
});
test("profile account refs must be provider/account shaped", () => {
const errors = validateSettingsProfile({ settingsVersion: "1", id: "s", allowedAccounts: ["justone"], providers: [], defaultAccounts: {} });
const errors = validateSettingsProfile({ settingsVersion: 1, id: "s", allowedAccounts: ["justone"], providers: [], defaultAccounts: {} });
assert.ok(errors.some((e) => e.code === "invalid-account-ref-list"));
});
test("seat selection accepts fork pin field, validates account refs", () => {
assert.equal(validateSeatSelection({ selectionVersion: "1", profile: "research", accounts: { "openai-codex": "openai-codex/homelab" }, updatedAt: "2026-09-10T00:00:00Z", pinnedFromSession: "abc123" }).length, 0);
assert.ok(validateSeatSelection({ selectionVersion: "1", profile: "research", accounts: { "openai-codex": "nope" } }).some((e) => e.code === "invalid-account-ref"));
assert.equal(validateSeatSelection({ selectionVersion: 1, profile: "research", accounts: { "openai-codex": "openai-codex/homelab" }, updatedAt: "2026-09-10T00:00:00Z", pinnedFromSession: "abc123" }).length, 0);
assert.ok(validateSeatSelection({ selectionVersion: 1, profile: "research", accounts: { "openai-codex": "nope" } }).some((e) => e.code === "invalid-account-ref"));
});
test("harness manifest id must equal executable (gate 1)", () => {
assert.ok(validateHarnessManifest({ harnessVersion: "1", id: "pi", executable: "claude", adapter: "x", compatibleRange: ">=1", executionMode: "container", materializers: ["m"] }).some((e) => e.code === "id-executable-mismatch"));
assert.ok(validateHarnessManifest({ harnessVersion: 1, id: "pi", executable: "claude", adapter: "x", compatibleRange: ">=1", executionMode: "container", materializers: ["m"] }).some((e) => e.code === "id-executable-mismatch"));
});
test("CLI validate: duplicate provider id across files refuses", () => {
+158
View File
@@ -0,0 +1,158 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, cpSync, readdirSync, lstatSync, chmodSync, readFileSync,
writeFileSync, rmSync, mkdirSync, symlinkSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { tmpdir } from 'node:os';
import { spawnSync } from 'node:child_process';
import { loadRegistry } from '../src/registry.mjs';
import { validateProvider, validateAccount, validateSettingsProfile,
validateSeatSelection, validateHarnessManifest } from '../src/records.mjs';
const fixture = resolve(import.meta.dirname, 'fixtures/valid');
const cli = resolve(import.meta.dirname, '../src/cli/main.mjs');
const providerFile = 'auth/providers/openai-codex.json';
const accountDir = 'auth/accounts/openai-codex/homelab-openai';
const profileFile = 'auth/settings/research-default.json';
const marker = 'FIXTURE_PRIVATE_MARKER_1500';
function secure(dir) {
chmodSync(dir, 0o700);
for (const name of readdirSync(dir)) {
const p = join(dir, name);
if (lstatSync(p).isDirectory()) secure(p); else chmodSync(p, 0o600);
}
}
function setup(t) {
const base = mkdtempSync(join(tmpdir(), 'registry-safety-'));
t.after(() => rmSync(base, { recursive: true, force: true }));
const root = join(base, 'registry'); cpSync(fixture, root, { recursive: true }); secure(root);
return { root, base };
}
function edit(root, name, mutate) {
const p = join(root, name); const data = JSON.parse(readFileSync(p, 'utf8'));
mutate(data); writeFileSync(p, JSON.stringify(data), { mode: 0o600 });
}
function run(root, cmd = 'validate') {
return spawnSync(process.execPath, [cli, cmd, '--registry-root', root], { encoding: 'utf8', timeout: 5000 });
}
function refuses(root, code) {
for (const cmd of ['validate', 'list']) {
const r = run(root, cmd);
assert.equal(r.status, 1, r.stderr);
assert.equal(r.stdout, '');
assert.match(r.stderr, new RegExp(code));
assert.ok(!r.stderr.includes(marker));
assert.ok(!r.stderr.includes(' at '));
}
}
test('D1 missing, empty and structurally empty roots refuse, no list projection', t => {
const { root, base } = setup(t);
refuses(join(base, 'absent'), 'missing-path');
mkdirSync(join(base, 'empty'), { mode: 0o700 }); refuses(join(base, 'empty'), 'missing-path');
for (const p of ['auth/providers', 'auth/accounts', 'auth/settings', 'harnesses']) {
rmSync(join(root, p), { recursive: true }); mkdirSync(join(root, p), { mode: 0o700 });
}
refuses(root, 'empty-registry');
});
for (const path of ['auth', 'auth/providers', 'auth/accounts', 'auth/settings', 'harnesses'])
test(`D1 required directory ${path} cannot be absent`, t => {
const { root } = setup(t); rmSync(join(root, path), { recursive: true }); refuses(root, 'missing-path');
});
test('D1 root file and unreadable metadata refuse', t => {
const { root, base } = setup(t);
const f = join(base, 'not-dir'); writeFileSync(f, '{}', { mode: 0o600 }); refuses(f, 'not-a-directory');
chmodSync(join(root, providerFile), 0o000); refuses(root, 'insecure-permissions');
});
for (const path of [providerFile, accountDir, 'auth/providers', 'auth'])
test(`D2 no symlink traversal at ${path}`, t => {
const { root, base } = setup(t), outside = join(base, 'outside');
cpSync(join(root, path), outside, { recursive: true });
rmSync(join(root, path), { recursive: true }); symlinkSync(outside, join(root, path));
refuses(root, 'symlink-forbidden');
});
test('D2 root and ancestor symlinks and lexical traversal refuse', t => {
const { root, base } = setup(t);
symlinkSync(root, join(base, 'alias')); refuses(join(base, 'alias'), 'symlink-forbidden');
symlinkSync(base, join(base, 'ancestor')); refuses(join(base, 'ancestor/registry'), 'symlink-forbidden');
refuses(`${root}/../registry`, 'invalid-root');
});
for (const path of ['', 'auth', providerFile, `${accountDir}/account.json`])
test(`private filesystem modes enforced for ${path || 'root'}`, t => {
const { root } = setup(t); chmodSync(join(root, path), path.endsWith('.json') ? 0o644 : 0o755);
refuses(root, 'insecure-permissions');
});
test('D3 numeric version 1 only across all record kinds', () => {
const pairs = [
[validateProvider, JSON.parse(readFileSync(join(fixture, providerFile))), 'providerVersion'],
[validateAccount, JSON.parse(readFileSync(join(fixture, accountDir, 'account.json'))), 'accountVersion'],
[validateSettingsProfile, JSON.parse(readFileSync(join(fixture, profileFile))), 'settingsVersion'],
[validateHarnessManifest, JSON.parse(readFileSync(join(fixture, 'harnesses/pi.json'))), 'harnessVersion'],
[validateSeatSelection, { selectionVersion: 1, profile: 'research-default' }, 'selectionVersion'],
];
for (const [validate, record, key] of pairs) {
assert.equal(validate(record).length, 0);
for (const value of [2, '1', 'banana-schema', null, true])
assert.ok(validate({ ...record, [key]: value }).some(e => e.code === 'unsupported-version'));
}
});
test('D4 nested unknown keys and missing per-kind required fields refuse', t => {
const { root } = setup(t);
edit(root, providerFile, p => { p.harnesses.pi[marker] = marker; }); refuses(root, 'unknown-field');
const p = { providerVersion: 1, id: 'remote', kind: 'custom-endpoint', credentialTypes: ['none'], harnesses: { claude: { baseUrl: 'https://example.test', models: ['m'] } } };
assert.ok(validateProvider(p).some(e => e.code === 'missing-field'));
assert.ok(validateProvider({ ...p, harnesses: {} }).some(e => e.code === 'empty-harnesses'));
});
test('D5 unenrolled default refuses even when account exists', t => {
const { root } = setup(t);
edit(root, profileFile, p => { p.allowedAccounts = ['openai-codex/other']; }); refuses(root, 'default-account-not-enrolled');
});
test('D6 provider/account credential type must match', t => {
const { root } = setup(t); edit(root, providerFile, p => { p.credentialTypes = ['api_key']; });
refuses(root, 'credential-type-not-supported');
});
test('D7 every harness endpoint enforces HTTP opt-in and shape', () => {
for (const h of ['pi', 'claude', 'codex']) {
const p = { providerVersion: 1, id: 'remote', kind: 'custom-endpoint', credentialTypes: ['none'],
harnesses: { [h]: { api: 'openai-completions', baseUrl: 'http://example.test/v1', models: ['qwen:7b'] } } };
assert.ok(validateProvider(p).some(e => e.code === 'insecure-transport-not-allowed'));
assert.equal(validateProvider({ ...p, allowInsecureTransport: true }).length, 0);
p.harnesses[h][marker] = marker;
assert.ok(validateProvider(p).some(e => e.code === 'unknown-field'));
}
});
test('D8 URLs reject embedded credentials and unsupported protocols without echo', t => {
const { root } = setup(t);
for (const url of [`https://user:${marker}@example.test/v1`, 'file:///tmp/test', 'ftp://example.test']) {
edit(root, 'auth/providers/ollama-remote.json', p => { p.harnesses.pi.baseUrl = url; });
refuses(root, 'url-credentials-forbidden|invalid-url');
}
});
test('D9 malformed JSON diagnostics contain no content excerpt', t => {
const { root } = setup(t); writeFileSync(join(root, providerFile), `${marker} not json`);
refuses(root, '^registry: invalid-json');
});
test('D10 missing metadata is missing-path, not invalid-json', t => {
const { root } = setup(t); rmSync(join(root, accountDir, 'account.json'));
refuses(root, '^registry: missing-path');
});
test('D10 library returns no partial entries on any invalid record', async t => {
const { root } = setup(t); edit(root, 'harnesses/pi.json', p => { p[marker] = marker; });
const result = await loadRegistry(root); assert.equal(result.errors.length, 1);
for (const values of Object.values(result.entries)) assert.equal(Object.keys(values).length, 0);
refuses(root, 'unknown-field');
});
test('null/scalar/array metadata refuses without stack or echo', t => {
const { root } = setup(t);
for (const value of [null, marker, [], 17]) {
writeFileSync(join(root, providerFile), JSON.stringify(value)); refuses(root, 'not-an-object');
}
});
test('credential sibling is never opened, even when an unreadable symlink', t => {
const { root, base } = setup(t); const secret = join(base, 'secret');
writeFileSync(secret, marker, { mode: 0o000 });
symlinkSync(secret, join(root, accountDir, 'credential.json'));
const r = run(root, 'list'); assert.equal(r.status, 0, r.stderr); assert.ok(!r.stdout.includes(marker));
});
test('oversized metadata refuses before parsing', t => {
const { root } = setup(t); writeFileSync(join(root, providerFile), ' '.repeat(1024 * 1024 + 1));
refuses(root, 'record-too-large');
});