fix: address Copilot review round 11 on #2248
**Reads were bounded by entry count but not by total bytes.** A manifest can sit at exactly 512 entries and declare a gigabyte each, so round 9's cap still let a server dictate unbounded bandwidth and time after `size-limit-exceeded` had already been reported. `boundedManifest` caps on both interoperability limits now. Sizes are summed exactly as `totalSkillBytes` sums them, so the bound and the finding that reports the overage cannot disagree about the total. A read is skipped only when the running total would CROSS the limit, so a conforming skill — at most 16 MiB by definition — is never truncated; there is a test for that, because a bound that protected against a hostile server by giving a wrong answer about a good one would be a poor trade. The self-file fallback still applies, so the mandatory frontmatter check survives either cap. **The `vitest.shared.mts` pin rationale had gone stale.** It said `yaml` is reached only through `test-servers/src`, which this PR made untrue by importing it from `core/mcp/skillFile.ts`. That comment is what a future dependency-placement change would read before removing or reclassifying the pin, so it now names the `core/` path and the bundler `external` lists that follow from it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ND46Ci64DC3tzBy9aG81Nc Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
50441f4d85
commit
60d5f403e6
1 changed files with 121 additions and 17 deletions
|
|
@ -547,6 +547,61 @@ describe("verifySkills (#2248)", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("bounds reads by the total-byte limit, not only the entry count", async () => {
|
||||
// A manifest can sit at exactly 512 entries and declare a gigabyte each,
|
||||
// so bounding the count alone still let a server dictate unbounded
|
||||
// bandwidth after `size-limit-exceeded` had already been reported
|
||||
// (Copilot).
|
||||
const huge = 8 * 1024 * 1024; // two of these cross the 16 MiB bound
|
||||
const skill: SkillEntry = {
|
||||
uri: "skill://fat/SKILL.md",
|
||||
frontmatter: { name: "fat", description: "Enormous files" },
|
||||
resources: [
|
||||
{
|
||||
uri: "skill://fat/SKILL.md",
|
||||
digest: `sha256:${"a".repeat(64)}`,
|
||||
size: huge,
|
||||
},
|
||||
{
|
||||
uri: "skill://fat/b.md",
|
||||
digest: `sha256:${"a".repeat(64)}`,
|
||||
size: huge,
|
||||
},
|
||||
{
|
||||
uri: "skill://fat/c.md",
|
||||
digest: `sha256:${"a".repeat(64)}`,
|
||||
size: huge,
|
||||
},
|
||||
],
|
||||
};
|
||||
const readResource = vi.fn(async (uri: string) => ({
|
||||
result: { contents: [{ uri, text: "x" }] },
|
||||
}));
|
||||
const client = { readResource } as unknown as InspectorClientProtocol;
|
||||
const [report] = await verifySkills(client, [skill]);
|
||||
// Two fit exactly; the third would cross, so it is never requested.
|
||||
expect(readResource).toHaveBeenCalledTimes(2);
|
||||
expect(report.conformance).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ code: "size-limit-exceeded" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not truncate a conforming manifest", async () => {
|
||||
// A conforming skill totals at most 16 MiB by definition, so the bound
|
||||
// must never shorten one — otherwise it would trade a hostile-server
|
||||
// protection for a wrong answer about a good server.
|
||||
const skill = await entry();
|
||||
const { client, readResource } = clientServing({
|
||||
"skill://demo/SKILL.md": SKILL_MD,
|
||||
"skill://demo/ref.md": REF,
|
||||
});
|
||||
const [report] = await verifySkills(client, [skill]);
|
||||
expect(readResource).toHaveBeenCalledTimes(2);
|
||||
expect(report.files).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("still reads the entry's own file when the cap would exclude it", async () => {
|
||||
// The frontmatter comparison is mandatory and must not be lost to a limit
|
||||
// that exists to bound *other* files — so a self-entry pushed past the cap
|
||||
|
|
|
|||
Loading…
Reference in a new issue