diff --git a/CHANGELOG.md b/CHANGELOG.md index 907e297..35c097c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ All notable changes to this project are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed + +- Reject non-array `tools` fields in static manifests before auditing, with an + error identifying the source file. + ## [0.1.1] - 2026-08-06 ### Changed diff --git a/src/static/manifest.ts b/src/static/manifest.ts index f846c5f..ed2f136 100644 --- a/src/static/manifest.ts +++ b/src/static/manifest.ts @@ -52,6 +52,10 @@ export function normalize(parsed: unknown, source: string): AuditTarget { ? { tools: parsed as ToolSpec[] } : ((parsed as ManifestFile) ?? {}); + if (manifest.tools != null && !Array.isArray(manifest.tools)) { + throw new Error(`Invalid manifest ${source}: tools must be an array.`); + } + const serverInfo = manifest.serverInfo ?? manifest.server ?? {}; const declaredHttp = manifest.transport === "http"; diff --git a/test/static.test.ts b/test/static.test.ts index 18b593d..3507ca9 100644 --- a/test/static.test.ts +++ b/test/static.test.ts @@ -36,6 +36,22 @@ describe("loadManifest", () => { expect(target.transport).toBe("static"); }); + it.each(["MCP001", { name: "read_file" }, 42, true])( + "rejects a non-array tools field: %j", + (tools) => { + expect(() => normalize({ tools }, "invalid-tools.json")).toThrow( + "Invalid manifest invalid-tools.json: tools must be an array.", + ); + }, + ); + + it.each([{}, { tools: null }, { tools: [] }])( + "keeps an absent or empty tool list valid: %j", + (manifest) => { + expect(normalize(manifest, "empty.json").tools).toEqual([]); + }, + ); + it("rejects a missing manifest file", async () => { await expect(loadManifest(resolve(root, "does-not-exist.json"))).rejects.toBeTruthy(); });