From 4459bfc89f51a3e4e68569debf6c5db4bfc88b02 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 17 Jul 2026 21:35:35 +0000 Subject: [PATCH 1/3] test: strengthen coding-standards guardrails for security and multi-scope pathBuilders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add automated enforcement for docs/coding-standards.md §4 and §9: - security.test.ts: secret read-only metadata, RateLimiter, response redaction - multi-scope-pathbuilders.test.ts: PathBuilderConfig contract for scope-encoding pathBuilders - docs-consistency.test.ts: lock 11-tool model, registerTool, pathBuilder docs, AGENTS.md alignment - docs/coding-standards.md: document multi-scope pathBuilder rules Co-authored-by: Rohan Gupta --- docs/coding-standards.md | 8 ++ .../coding-standards/docs-consistency.test.ts | 27 +++++ .../multi-scope-pathbuilders.test.ts | 112 ++++++++++++++++++ tests/coding-standards/security.test.ts | 74 ++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 tests/coding-standards/multi-scope-pathbuilders.test.ts create mode 100644 tests/coding-standards/security.test.ts diff --git a/docs/coding-standards.md b/docs/coding-standards.md index d7e33b543..5afce5eb6 100644 --- a/docs/coding-standards.md +++ b/docs/coding-standards.md @@ -67,6 +67,14 @@ Every `ResourceDefinition` declares a `scope`: `"project"`, `"org"`, or `"accoun The `Registry.dispatch()` method handles scope injection automatically based on the definition. Do NOT manually inject `accountIdentifier`, `orgIdentifier`, or `projectIdentifier` in toolset specs. +**Multi-scope resources** (`supportedScopes: ["account", "org", "project"]`) with custom `pathBuilder` functions that embed scope in the URL path (not only query params) must: + +- Accept `PathBuilderConfig` as the second argument: `(input, config) => ...` +- Honor `config.HARNESS_ORG` / `config.HARNESS_PROJECT` when callers omit `org_id` / `project_id`, matching list-operation defaults +- Respect explicit `resource_scope` and clear unused `org_id` / `project_id` from query params when targeting account scope + +Enforced by `tests/coding-standards/multi-scope-pathbuilders.test.ts`. + ### 5. Identifier Fields and Deep Links Every `ResourceDefinition` must declare: diff --git a/tests/coding-standards/docs-consistency.test.ts b/tests/coding-standards/docs-consistency.test.ts index 0989e711e..dfd46d9a5 100644 --- a/tests/coding-standards/docs-consistency.test.ts +++ b/tests/coding-standards/docs-consistency.test.ts @@ -45,4 +45,31 @@ describe("Coding standards — documentation consistency", () => { it("docs/coding-standards.md forbids new harness-*.ts handler files", () => { expect(content).toMatch(/Do NOT add new `harness-\*\.ts` handler files/); }); + + it("docs/coding-standards.md documents multi-scope pathBuilder contract", () => { + expect(content).toContain("multi-scope-pathbuilders.test.ts"); + expect(content).toMatch(/PathBuilderConfig/); + expect(content).toMatch(/supportedScopes:\s*\["account",\s*"org",\s*"project"\]/); + }); + + it("docs/coding-standards.md documents registerTool (not deprecated server.tool)", () => { + expect(content).toMatch(/server\.registerTool\(\)/); + expect(content).not.toMatch(/10 consolidated tool handlers/); + }); +}); + +describe("Coding standards — AGENTS.md consistency", () => { + const agentsContent = readFileSync(join(REPO_ROOT, "AGENTS.md"), "utf8"); + + it("AGENTS.md documents 11 consolidated MCP tools including harness_schema", () => { + expect(agentsContent).toMatch(/11 consolidated tools/); + for (const tool of REQUIRED_TOOLS) { + expect(agentsContent, `missing ${tool} in AGENTS.md`).toContain(tool); + } + }); + + it("AGENTS.md references Zod v4 and standards:check", () => { + expect(agentsContent).toMatch(/Zod v4/); + expect(agentsContent).toContain("pnpm standards:check"); + }); }); diff --git a/tests/coding-standards/multi-scope-pathbuilders.test.ts b/tests/coding-standards/multi-scope-pathbuilders.test.ts new file mode 100644 index 000000000..1d9216076 --- /dev/null +++ b/tests/coding-standards/multi-scope-pathbuilders.test.ts @@ -0,0 +1,112 @@ +/** + * Multi-scope pathBuilder contract — docs/coding-standards.md §4. + * + * Custom pathBuilders that encode account/org/project in the URL path must accept + * PathBuilderConfig and honor HARNESS_ORG / HARNESS_PROJECT defaults. Otherwise + * list → get/update flows can target a different scope than the caller listed. + */ +import { describe, it, expect } from "vitest"; +import { readFileSync, readdirSync, statSync } from "node:fs"; +import { join, relative } from "node:path"; + +const REPO_ROOT = join(import.meta.dirname, "../.."); +const TOOLSET_DIR = join(REPO_ROOT, "src/registry/toolsets"); + +const TOOLSET_HELPER_FILES = new Set([ + "src/registry/toolsets/chaos-descriptions.ts", + "src/registry/toolsets/scopes.ts", +]); + +/** Heuristics for pathBuilders that embed scope in the path (not query params). */ +const SCOPE_ENCODING_PATTERNS = [ + /let\s+scope\s*=/, + /scope\s*\+=/, + /HARNESS_ORG/, + /HARNESS_PROJECT/, + /templateV1BasePathFromScope/, + /effectiveOrgId/, + /shouldUseProject/, + /shouldUseOrg/, + /\/v1\/entities\/\$\{/, +]; + +function walkTsFiles(dir: string): string[] { + const results: string[] = []; + for (const entry of readdirSync(dir)) { + const full = join(dir, entry); + const stat = statSync(full); + if (stat.isDirectory()) { + results.push(...walkTsFiles(full)); + } else if (entry.endsWith(".ts")) { + results.push(full); + } + } + return results; +} + +function rel(path: string): string { + return relative(REPO_ROOT, path).replace(/\\/g, "/"); +} + +function encodesScopeInPath(snippet: string): boolean { + return SCOPE_ENCODING_PATTERNS.some((pattern) => pattern.test(snippet)); +} + +describe("Coding standards — multi-scope pathBuilders", () => { + it("named pathBuilder helpers that encode scope accept PathBuilderConfig", () => { + const violations: string[] = []; + + for (const file of walkTsFiles(TOOLSET_DIR)) { + const fileRel = rel(file); + if (TOOLSET_HELPER_FILES.has(fileRel)) continue; + + const content = readFileSync(file, "utf8"); + const pathBuilderRefs = [...content.matchAll(/pathBuilder:\s*(\w+)/g)].map((m) => m[1]!); + + for (const fnName of pathBuilderRefs) { + const sigRe = new RegExp(`const\\s+${fnName}\\s*=\\s*\\(([^)]*)\\)`); + const sigMatch = sigRe.exec(content); + if (!sigMatch) continue; + + const params = sigMatch[1]!; + const fnSnippet = content.slice(sigMatch.index!, sigMatch.index! + 1200); + if (encodesScopeInPath(fnSnippet) && !/\bconfig\b/.test(params)) { + violations.push( + `${fileRel}: ${fnName} encodes scope in the path but lacks PathBuilderConfig — pass (input, config) and honor HARNESS_ORG/HARNESS_PROJECT`, + ); + } + } + } + + expect(violations, violations.join("\n")).toEqual([]); + }); + + it("inline pathBuilders on multi-scope resources that encode scope accept config", () => { + const violations: string[] = []; + + for (const file of walkTsFiles(TOOLSET_DIR)) { + const fileRel = rel(file); + if (TOOLSET_HELPER_FILES.has(fileRel)) continue; + + const content = readFileSync(file, "utf8"); + if (!contentHasMultiScopeResource(content)) continue; + + for (const match of content.matchAll(/pathBuilder:\s*\(([^)]*)\)\s*=>/g)) { + const params = match[1]!; + const start = match.index ?? 0; + const snippet = content.slice(start, start + 1200); + if (encodesScopeInPath(snippet) && !/\bconfig\b/.test(params)) { + violations.push( + `${fileRel}: inline pathBuilder encodes scope but params are (${params}) — use (input, config)`, + ); + } + } + } + + expect(violations, violations.join("\n")).toEqual([]); + }); +}); + +function contentHasMultiScopeResource(content: string): boolean { + return /supportedScopes:\s*\[[^\]]*,/.test(content); +} diff --git a/tests/coding-standards/security.test.ts b/tests/coding-standards/security.test.ts new file mode 100644 index 000000000..78582fd23 --- /dev/null +++ b/tests/coding-standards/security.test.ts @@ -0,0 +1,74 @@ +/** + * Safety and security rules from docs/coding-standards.md §9. + * + * Complements registry-metadata write-handler checks with secret exposure, + * client-side rate limiting, and response redaction guardrails. + */ +import { describe, it, expect } from "vitest"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { Registry } from "../../src/registry/index.js"; + +const REPO_ROOT = join(import.meta.dirname, "../.."); + +const MINIMAL_CONFIG = { + HARNESS_API_KEY: "pat.testaccount.testtoken.testsecret", + HARNESS_BASE_URL: "https://app.harness.io", +} as const; + +/** Schema/body field names that must never appear in secret toolset specs. */ +const FORBIDDEN_SECRET_VALUE_FIELDS = [ + "secretValue", + "secretText", + "decryptedValue", + "plaintext", +] as const; + +describe("Coding standards — safety and security (§9)", () => { + const registry = new Registry(MINIMAL_CONFIG); + + it("secret resource is read-only metadata (list + get only)", () => { + const def = registry.getResource("secret"); + const ops = Object.keys(def.operations); + + expect(ops.sort()).toEqual(["get", "list"]); + expect(def.executeActions ?? {}).toEqual({}); + }); + + it("secret resource description states values are never exposed", () => { + const def = registry.getResource("secret"); + expect(def.description.toLowerCase()).toMatch(/never|not returned|metadata/); + }); + + it("HarnessClient enforces client-side rate limiting via RateLimiter", () => { + const content = readFileSync(join(REPO_ROOT, "src/client/harness-client.ts"), "utf8"); + expect(content).toMatch(/import\s*\{[^}]*RateLimiter[^}]*\}\s*from/); + expect(content).toMatch(/new\s+RateLimiter\s*\(/); + }); + + it("HarnessClient redacts sensitive fields in logged API bodies by default", () => { + const content = readFileSync(join(REPO_ROOT, "src/client/harness-client.ts"), "utf8"); + expect(content).toContain("redactJsonString"); + expect(content).toMatch(/logUnsafeBodies\s*\?\s*[^:]+:\s*redactJsonString/); + }); + + it("redact utility covers common credential key patterns", () => { + const content = readFileSync(join(REPO_ROOT, "src/utils/redact.ts"), "utf8"); + expect(content).toMatch(/api[_-]?key|access[_-]?token|password|secret/i); + expect(content).toContain("redactSensitiveFields"); + }); + + it("secrets toolset does not define body fields that expose secret values", () => { + const content = readFileSync( + join(REPO_ROOT, "src/registry/toolsets/secrets.ts"), + "utf8", + ); + + for (const key of FORBIDDEN_SECRET_VALUE_FIELDS) { + expect( + content, + `secrets toolset must not define exposed value field "${key}"`, + ).not.toMatch(new RegExp(`\\b${key}\\s*:`)); + } + }); +}); From 6ddc8f3f43bab9a7fe7a04b6986c17ffceb05f21 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 19 Jul 2026 01:38:36 +0000 Subject: [PATCH 2/3] docs: record coding-standards compliance review Co-authored-by: Rohan Gupta --- tasks/todo.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tasks/todo.md b/tasks/todo.md index ca802b154..5b5d38f60 100644 --- a/tasks/todo.md +++ b/tasks/todo.md @@ -1,5 +1,22 @@ # Harness MCP Server — Task Tracking +## Coding Standards Compliance Review (2026-07-19) +- [x] Audit codebase against docs/coding-standards.md +- [x] Run pnpm standards:check and full test suite +- [x] Cherry-pick strengthened guardrails (security + multi-scope pathBuilders) +- [x] Commit, push, and open PR + +### Plan +- Compare user-provided standards snapshot against canonical docs/coding-standards.md (11 tools incl. harness_schema, Zod v4, registerTool). +- Rely on existing tests/coding-standards/*.test.ts guardrails; add missing §4/§9 enforcement from prior branch. +- Verify no runtime code changes needed — standards compliance is already enforced in CI. + +### Review +- Baseline `pnpm standards:check` passed (80 tests) before adding strengthened guardrails. +- Cherry-picked `e92468da`: security.test.ts (§9 secret metadata, RateLimiter, redaction), multi-scope-pathbuilders.test.ts (§4 PathBuilderConfig), docs-consistency extensions (registerTool, pathBuilder docs, AGENTS.md alignment), and multi-scope pathBuilder docs in coding-standards.md. +- User-provided standards snapshot is outdated vs canonical: 10 tools (actual 11 incl. harness_schema), Zod v3 (actual v4), server.tool() (actual registerTool()). +- Verification passed: `pnpm standards:check` (92 tests), `pnpm typecheck`, `pnpm build`, `pnpm test` (119 files / 2565 tests). + ## Version Bump 3.2.12 (2026-07-19) - [x] Update package and bundle manifest versions to 3.2.12 - [x] Update the release metadata version test From a600cc0ff12ab7ec6d68710d9495dcea2fe31baf Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 21 Jul 2026 15:37:00 +0000 Subject: [PATCH 3/3] docs: update coding-standards review verification counts Co-authored-by: Rohan Gupta --- tasks/todo.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/todo.md b/tasks/todo.md index 5b5d38f60..483caf3b2 100644 --- a/tasks/todo.md +++ b/tasks/todo.md @@ -15,7 +15,7 @@ - Baseline `pnpm standards:check` passed (80 tests) before adding strengthened guardrails. - Cherry-picked `e92468da`: security.test.ts (§9 secret metadata, RateLimiter, redaction), multi-scope-pathbuilders.test.ts (§4 PathBuilderConfig), docs-consistency extensions (registerTool, pathBuilder docs, AGENTS.md alignment), and multi-scope pathBuilder docs in coding-standards.md. - User-provided standards snapshot is outdated vs canonical: 10 tools (actual 11 incl. harness_schema), Zod v3 (actual v4), server.tool() (actual registerTool()). -- Verification passed: `pnpm standards:check` (92 tests), `pnpm typecheck`, `pnpm build`, `pnpm test` (119 files / 2565 tests). +- Verification passed: `pnpm standards:check` (12 files / 92 tests), `pnpm typecheck`, `pnpm build`, `pnpm test` (121 files / 2619 tests). ## Version Bump 3.2.12 (2026-07-19) - [x] Update package and bundle manifest versions to 3.2.12