From 05532e0a1593b796a42df721675fe19b776edee4 Mon Sep 17 00:00:00 2001 From: Victor <70475442+vsolano9@users.noreply.github.com> Date: Sun, 23 Aug 2026 16:17:40 +0200 Subject: [PATCH] fix(rule): separate URI and metadata secret signals Keep sensitive resource URIs critical while preventing generic words in names or descriptions from creating false critical findings. Preserve unambiguous secret-file metadata references as medium-confidence findings and cover all three boundaries. Closes AgentPostmortem/MCP-audit#9 --- src/rules/secrets.ts | 33 +++++++++++++++++++++++++----- test/rules.test.ts | 48 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/rules/secrets.ts b/src/rules/secrets.ts index ed7b2f4..fcf3e2f 100644 --- a/src/rules/secrets.ts +++ b/src/rules/secrets.ts @@ -2,6 +2,14 @@ import type { Finding, Rule } from "../types.js"; import { containsAny, SECRET_PATH_PATTERNS } from "./helpers.js"; const PATH_ARG_NAMES = ["path", "file", "filename", "filepath", "dir", "directory", "location"]; +const UNAMBIGUOUS_SECRET_FILE_PATTERNS = [ + ".env", + "id_rsa", + "id_ed25519", + ".pem", + "/etc/passwd", + "/etc/shadow", +]; /** * MCP030 - A resource points at secret material or a sensitive system path. @@ -12,24 +20,39 @@ export const resourceExposesSecrets: Rule = { id: "MCP030", title: "Resource exposes secrets or sensitive paths", description: - "Resources should never surface credential files, private keys, or sensitive system paths.", + "Sensitive resource URIs are critical; unambiguous secret-file references in resource metadata are lower-confidence findings.", severity: "critical", category: "secrets", evaluate(target, ctx): Finding[] { const findings: Finding[] = []; for (const res of target.resources) { - const haystack = `${res.uri} ${res.name ?? ""} ${res.description ?? ""}`; - const hit = containsAny(haystack, SECRET_PATH_PATTERNS); - if (hit) { + const uriHit = containsAny(res.uri, SECRET_PATH_PATTERNS); + if (uriHit) { findings.push( ctx.report({ title: "Resource surfaces sensitive material", - message: `Resource "${res.uri}" references "${hit}", which commonly holds secrets or system credentials.`, + message: `Resource "${res.uri}" references "${uriHit}", which commonly holds secrets or system credentials.`, remediation: "Remove the resource or restrict it to non-sensitive content. Never expose credential files or system paths over MCP.", location: res.uri, }), ); + continue; + } + + const metadata = `${res.name ?? ""} ${res.description ?? ""}`; + const metadataHit = containsAny(metadata, UNAMBIGUOUS_SECRET_FILE_PATTERNS); + if (metadataHit) { + findings.push( + ctx.report({ + severity: "medium", + title: "Resource metadata references sensitive material", + message: `Metadata for resource "${res.uri}" references "${metadataHit}", which may identify secret material.`, + remediation: + "Verify that the resource does not expose the referenced file, and remove or restrict it if it contains sensitive material.", + location: res.uri, + }), + ); } } return findings; diff --git a/test/rules.test.ts b/test/rules.test.ts index 7f1cde8..a9ec1af 100644 --- a/test/rules.test.ts +++ b/test/rules.test.ts @@ -85,6 +85,54 @@ describe("MCP002 exec detection", () => { }); }); +describe("MCP030 resource secret detection", () => { + function findingsFor(resources: AuditTarget["resources"]) { + return audit(makeTarget({ resources })).findings.filter((f) => f.ruleId === "MCP030"); + } + + it("does not treat generic credentials or secrets wording as critical evidence", () => { + const findings = findingsFor([ + { + uri: "docs://project-notes", + name: "Credential safety notes", + description: "Project notes. Contains no credentials or secrets.", + }, + ]); + + expect(findings).toEqual([]); + }); + + it("reports an unambiguous metadata filename reference below critical severity", () => { + const findings = findingsFor([ + { + uri: "docs://deployment-guide", + description: "Includes a copy of the production .env file.", + }, + ]); + + expect(findings).toHaveLength(1); + expect(findings[0]).toMatchObject({ + severity: "medium", + location: "docs://deployment-guide", + }); + }); + + it("keeps sensitive resource URIs critical", () => { + const findings = findingsFor([ + { + uri: "file:///home/app/.env", + description: "Configuration reference", + }, + ]); + + expect(findings).toHaveLength(1); + expect(findings[0]).toMatchObject({ + severity: "critical", + location: "file:///home/app/.env", + }); + }); +}); + describe("MCP040 http auth", () => { it("flags http transport without auth", () => { const target = makeTarget({