@intentius/chant 0.52.1 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/checks.d.ts +35 -0
- package/dist/agents/checks.d.ts.map +1 -0
- package/dist/agents/discover.d.ts +86 -0
- package/dist/agents/discover.d.ts.map +1 -0
- package/dist/agents/importer.d.ts +46 -0
- package/dist/agents/importer.d.ts.map +1 -0
- package/dist/agents/index.d.ts +14 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/types.d.ts +196 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/audit/catalog.d.ts +4 -1
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/report.d.ts +8 -0
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/cdk/advise.d.ts +29 -0
- package/dist/cdk/advise.d.ts.map +1 -0
- package/dist/cdk/assembly.d.ts +38 -0
- package/dist/cdk/assembly.d.ts.map +1 -0
- package/dist/cdk/graph.d.ts +68 -0
- package/dist/cdk/graph.d.ts.map +1 -0
- package/dist/cdk/tier-map.d.ts +44 -0
- package/dist/cdk/tier-map.d.ts.map +1 -0
- package/dist/cdk/types.d.ts +114 -0
- package/dist/cdk/types.d.ts.map +1 -0
- package/dist/cli/commands/audit-agents.d.ts +83 -0
- package/dist/cli/commands/audit-agents.d.ts.map +1 -0
- package/dist/cli/commands/carve-apply.d.ts.map +1 -1
- package/dist/cli/commands/carve-bridge.d.ts.map +1 -1
- package/dist/cli/commands/carve-emit.d.ts.map +1 -1
- package/dist/cli/commands/carve.d.ts +48 -6
- package/dist/cli/commands/carve.d.ts.map +1 -1
- package/dist/cli/commands/import-agents.d.ts +64 -0
- package/dist/cli/commands/import-agents.d.ts.map +1 -0
- package/dist/cli/handlers/carve-emit.d.ts.map +1 -1
- package/dist/cli/handlers/carve.d.ts +5 -4
- package/dist/cli/handlers/carve.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +10 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +15 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/identity.d.ts +196 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/lexicon.d.ts +52 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/terraform/adopt-state.d.ts +17 -63
- package/dist/terraform/adopt-state.d.ts.map +1 -1
- package/dist/terraform/aws-resources.d.ts +1 -1
- package/dist/terraform/bridge.d.ts.map +1 -1
- package/dist/terraform/carve-provider.d.ts +142 -0
- package/dist/terraform/carve-provider.d.ts.map +1 -0
- package/dist/terraform/carve.d.ts +36 -3
- package/dist/terraform/carve.d.ts.map +1 -1
- package/dist/terraform/emit-source.d.ts +25 -0
- package/dist/terraform/emit-source.d.ts.map +1 -0
- package/dist/terraform/graduate.d.ts +11 -1
- package/dist/terraform/graduate.d.ts.map +1 -1
- package/dist/terraform/providers/aws.d.ts +19 -0
- package/dist/terraform/providers/aws.d.ts.map +1 -0
- package/dist/terraform/providers/gcp.d.ts +41 -0
- package/dist/terraform/providers/gcp.d.ts.map +1 -0
- package/dist/terraform/providers/index.d.ts +15 -0
- package/dist/terraform/providers/index.d.ts.map +1 -0
- package/dist/terraform/providers/kubernetes.d.ts +29 -0
- package/dist/terraform/providers/kubernetes.d.ts.map +1 -0
- package/dist/terraform/score.d.ts +70 -4
- package/dist/terraform/score.d.ts.map +1 -1
- package/dist/terraform/tier-map.d.ts +38 -26
- package/dist/terraform/tier-map.d.ts.map +1 -1
- package/dist/terraform/types.d.ts +6 -0
- package/dist/terraform/types.d.ts.map +1 -1
- package/dist/yaml.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/agents/checks.test.ts +228 -0
- package/src/agents/checks.ts +429 -0
- package/src/agents/discover.test.ts +310 -0
- package/src/agents/discover.ts +939 -0
- package/src/agents/importer.ts +49 -0
- package/src/agents/index.ts +29 -0
- package/src/agents/types.ts +207 -0
- package/src/audit/catalog.ts +90 -1
- package/src/audit/report.ts +9 -1
- package/src/audit/rules-doc.ts +6 -0
- package/src/cdk/__fixtures__/cdk.out/AppStack.template.json +171 -0
- package/src/cdk/__fixtures__/cdk.out/DataStack.template.json +90 -0
- package/src/cdk/__fixtures__/cdk.out/cdk.out +1 -0
- package/src/cdk/__fixtures__/cdk.out/manifest.json +30 -0
- package/src/cdk/__fixtures__/cdk.out/tree.json +201 -0
- package/src/cdk/__fixtures__/cdk.out-dummy/LookupStack.template.json +29 -0
- package/src/cdk/__fixtures__/cdk.out-dummy/manifest.json +26 -0
- package/src/cdk/advise.test.ts +208 -0
- package/src/cdk/advise.ts +44 -0
- package/src/cdk/assembly.ts +133 -0
- package/src/cdk/graph.test.ts +206 -0
- package/src/cdk/graph.ts +525 -0
- package/src/cdk/tier-map.ts +71 -0
- package/src/cdk/types.ts +115 -0
- package/src/cli/commands/audit-agents.test.ts +260 -0
- package/src/cli/commands/audit-agents.ts +387 -0
- package/src/cli/commands/carve-apply.ts +20 -4
- package/src/cli/commands/carve-bridge.test.ts +30 -0
- package/src/cli/commands/carve-bridge.ts +24 -2
- package/src/cli/commands/carve-emit-k8s.test.ts +262 -0
- package/src/cli/commands/carve-emit-provider.test.ts +207 -0
- package/src/cli/commands/carve-emit.test.ts +72 -1
- package/src/cli/commands/carve-emit.ts +55 -28
- package/src/cli/commands/carve.ts +139 -36
- package/src/cli/commands/import-agents.test.ts +208 -0
- package/src/cli/commands/import-agents.ts +196 -0
- package/src/cli/handlers/carve-emit.ts +8 -1
- package/src/cli/handlers/carve.ts +8 -7
- package/src/cli/handlers/lifecycle.test.ts +187 -1
- package/src/cli/handlers/lifecycle.ts +125 -1
- package/src/cli/handlers/misc.ts +111 -0
- package/src/cli/main.ts +29 -5
- package/src/cli/registry.ts +15 -0
- package/src/identity.test.ts +199 -0
- package/src/identity.ts +346 -0
- package/src/index.ts +1 -0
- package/src/lexicon.ts +65 -0
- package/src/terraform/__fixtures__/gcp-estate/main.tf +60 -0
- package/src/terraform/adopt-state.test.ts +131 -0
- package/src/terraform/adopt-state.ts +22 -167
- package/src/terraform/aws-resources.test.ts +55 -16
- package/src/terraform/aws-resources.ts +1 -1
- package/src/terraform/bridge.test.ts +12 -0
- package/src/terraform/bridge.ts +4 -1
- package/src/terraform/carve-provider.test.ts +155 -0
- package/src/terraform/carve-provider.ts +237 -0
- package/src/terraform/carve.test.ts +55 -1
- package/src/terraform/carve.ts +0 -0
- package/src/terraform/emit-source.ts +39 -0
- package/src/terraform/graduate.test.ts +37 -0
- package/src/terraform/graduate.ts +55 -7
- package/src/terraform/graph.ts +3 -3
- package/src/terraform/providers/aws.ts +169 -0
- package/src/terraform/providers/gcp.test.ts +228 -0
- package/src/terraform/providers/gcp.ts +329 -0
- package/src/terraform/providers/index.ts +21 -0
- package/src/terraform/providers/kubernetes.ts +224 -0
- package/src/terraform/score.ts +111 -25
- package/src/terraform/tier-map.ts +55 -90
- package/src/terraform/types.ts +6 -0
- package/src/yaml.test.ts +54 -0
- package/src/yaml.ts +24 -3
|
@@ -62,6 +62,12 @@ export interface TfEdge {
|
|
|
62
62
|
* parameter. Empty when the reference came from a non-object block shape.
|
|
63
63
|
*/
|
|
64
64
|
via: string[];
|
|
65
|
+
/**
|
|
66
|
+
* The two ends live in different templates — a CloudFormation
|
|
67
|
+
* `Fn::ImportValue` resolved through the exporting stack's `Outputs` (#1056).
|
|
68
|
+
* Never set on the Terraform path, where a single estate is one namespace.
|
|
69
|
+
*/
|
|
70
|
+
crossStack?: boolean;
|
|
65
71
|
}
|
|
66
72
|
export interface TfGraph {
|
|
67
73
|
nodes: TfNode[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/terraform/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,MAAM;IACrB,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC5B,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,uFAAuF;IACvF,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;;;OAKG;IACH,GAAG,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/terraform/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,MAAM;IACrB,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,UAAU,GAAG,QAAQ,CAAC;IAC5B,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,uFAAuF;IACvF,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB;;;;;OAKG;IACH,GAAG,EAAE,MAAM,EAAE,CAAC;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IACjD,8FAA8F;IAC9F,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IACnC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB"}
|
package/dist/yaml.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA0BH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAgH/D;AAMD,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"yaml.d.ts","sourceRoot":"","sources":["../src/yaml.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA0BH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAgH/D;AAMD,sCAAsC;AACtC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAuBD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASlE;AAiFD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,WAAW,CA8Eb;AA0GD;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,WAAW,CAqGb;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAwBlD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -69,6 +69,11 @@
|
|
|
69
69
|
"types": "./dist/testing.d.ts",
|
|
70
70
|
"default": "./src/testing.ts"
|
|
71
71
|
},
|
|
72
|
+
"./agents": {
|
|
73
|
+
"development": "./src/agents/index.ts",
|
|
74
|
+
"types": "./dist/agents/index.d.ts",
|
|
75
|
+
"default": "./src/agents/index.ts"
|
|
76
|
+
},
|
|
72
77
|
"./*": {
|
|
73
78
|
"development": "./src/*.ts",
|
|
74
79
|
"types": "./dist/*.d.ts",
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks are tested from hand-built sites rather than a scan, so a case can be
|
|
3
|
+
* stated in one object.
|
|
4
|
+
*
|
|
5
|
+
* Several of these encode false positives found by running the tool against a
|
|
6
|
+
* real machine — a SHA256 pin read as a leaked credential, a vendored local
|
|
7
|
+
* plugin read as an unpinned remote, twenty findings for one unpinned
|
|
8
|
+
* marketplace. Those are the cases most likely to regress, because each fix
|
|
9
|
+
* narrowed a heuristic that still has to fire on the real thing.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, test, expect } from "vitest";
|
|
13
|
+
import { checkAgentConfigs } from "./checks";
|
|
14
|
+
import type { AgentConfigSite, AgentScanResult, McpServerDecl } from "./types";
|
|
15
|
+
|
|
16
|
+
function site(overrides: Partial<AgentConfigSite> = {}): AgentConfigSite {
|
|
17
|
+
return {
|
|
18
|
+
id: "user-claude",
|
|
19
|
+
scope: "user",
|
|
20
|
+
runtime: "claude",
|
|
21
|
+
root: "/home/u",
|
|
22
|
+
sources: ["/home/u/.claude/settings.json"],
|
|
23
|
+
instructions: [],
|
|
24
|
+
mcpServers: [],
|
|
25
|
+
skills: [],
|
|
26
|
+
subagents: [],
|
|
27
|
+
commands: [],
|
|
28
|
+
plugins: [],
|
|
29
|
+
env: {},
|
|
30
|
+
settings: {},
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function scanOf(s: AgentConfigSite, declarations: McpServerDecl[] = []): AgentScanResult {
|
|
36
|
+
return { sites: [s], probed: [], unreadable: [], declarations: { [s.id]: declarations } };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ids = (s: AgentConfigSite, decls: McpServerDecl[] = []) => checkAgentConfigs(scanOf(s, decls)).map((f) => f.checkId);
|
|
40
|
+
|
|
41
|
+
function server(overrides: Partial<McpServerDecl> = {}): McpServerDecl {
|
|
42
|
+
return { name: "srv", transport: "stdio", source: "/home/u/.claude/mcp.json", ...overrides };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("AGT001 — unpinned MCP package", () => {
|
|
46
|
+
test("fires on a package runner with a floating spec", () => {
|
|
47
|
+
const s = site({ mcpServers: [server({ command: "npx", args: ["-y", "some-server"] })] });
|
|
48
|
+
expect(ids(s)).toContain("AGT001");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("reads the spec through --from, as uvx uses it", () => {
|
|
52
|
+
const s = site({ mcpServers: [server({ command: "uvx", args: ["--from", "pkg-name", "entry"] })] });
|
|
53
|
+
expect(ids(s)).toContain("AGT001");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("does not fire when the spec is pinned to an exact version", () => {
|
|
57
|
+
const s = site({ mcpServers: [server({ command: "npx", args: ["-y", "some-server@1.2.3"] })] });
|
|
58
|
+
expect(ids(s)).not.toContain("AGT001");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("does not fire for a plain local executable", () => {
|
|
62
|
+
const s = site({ mcpServers: [server({ command: "/usr/local/bin/my-server" })] });
|
|
63
|
+
expect(ids(s)).not.toContain("AGT001");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("does not fire for a remote server, which runs no local code", () => {
|
|
67
|
+
const s = site({ mcpServers: [server({ transport: "http", command: undefined, url: "https://x/mcp" })] });
|
|
68
|
+
expect(ids(s)).not.toContain("AGT001");
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("AGT002 — literal credentials", () => {
|
|
73
|
+
test("fires on a vendor-prefixed token in env", () => {
|
|
74
|
+
const s = site({ mcpServers: [server({ env: { API_KEY: "sk-ant-abcdefghijklmnopqrstuvwxyz012345" } })] });
|
|
75
|
+
expect(ids(s)).toContain("AGT002");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("fires on a bearer token hidden in headers", () => {
|
|
79
|
+
// Regression: `headers` arrives via `extra`, which the first version of the
|
|
80
|
+
// check never walked — the exact place remote MCP servers keep credentials.
|
|
81
|
+
const s = site({
|
|
82
|
+
mcpServers: [server({ transport: "http", command: undefined, url: "https://x/mcp", extra: { headers: { Authorization: "Bearer rnd_wwMfAFG8Xxw0fD6unZ2S" } } })],
|
|
83
|
+
});
|
|
84
|
+
expect(ids(s)).toContain("AGT002");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("does not fire on an env-var reference", () => {
|
|
88
|
+
const s = site({ mcpServers: [server({ env: { API_KEY: "${API_KEY}" } })] });
|
|
89
|
+
expect(ids(s)).not.toContain("AGT002");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("does not fire on a SHA256 digest", () => {
|
|
93
|
+
// Regression: a digest pins trusted code. It matches every "long opaque
|
|
94
|
+
// string" heuristic and is the opposite of a leaked secret.
|
|
95
|
+
const s = site({
|
|
96
|
+
mcpServers: [server({ env: { NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S: "eb55d24cf065cd5311af6f99b16bade2b7a670790ff2db9accf60d012dd55dd1" } })],
|
|
97
|
+
});
|
|
98
|
+
expect(ids(s)).not.toContain("AGT002");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("does not fire on a short config value under a secret-sounding key", () => {
|
|
102
|
+
const s = site({ mcpServers: [server({ env: { TOKEN_ENABLED: "true" } })] });
|
|
103
|
+
expect(ids(s)).not.toContain("AGT002");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("AGT003 — cleartext MCP", () => {
|
|
108
|
+
test("fires on a plain http:// endpoint", () => {
|
|
109
|
+
const s = site({ mcpServers: [server({ transport: "http", command: undefined, url: "http://example.com/mcp" })] });
|
|
110
|
+
expect(ids(s)).toContain("AGT003");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("does not fire on loopback, which never crosses the network", () => {
|
|
114
|
+
const s = site({ mcpServers: [server({ transport: "http", command: undefined, url: "http://localhost:3000/mcp" })] });
|
|
115
|
+
expect(ids(s)).not.toContain("AGT003");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("does not fire on https", () => {
|
|
119
|
+
const s = site({ mcpServers: [server({ transport: "http", command: undefined, url: "https://example.com/mcp" })] });
|
|
120
|
+
expect(ids(s)).not.toContain("AGT003");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("AGT004 — unpinned remote skills and plugins", () => {
|
|
125
|
+
test("reports one finding per source, not per skill", () => {
|
|
126
|
+
// Regression: fifteen skills from one marketplace is one decision to
|
|
127
|
+
// revisit; fifteen findings would bury every other rule.
|
|
128
|
+
const s = site({
|
|
129
|
+
skills: Array.from({ length: 15 }, (_, i) => ({ name: `s${i}`, origin: "marketplace" as const, source: "Q00/ouroboros", path: `/p/s${i}` })),
|
|
130
|
+
});
|
|
131
|
+
const findings = checkAgentConfigs(scanOf(s)).filter((f) => f.checkId === "AGT004");
|
|
132
|
+
expect(findings).toHaveLength(1);
|
|
133
|
+
expect(findings[0].message).toContain("15 skills");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("does not fire on a pinned source", () => {
|
|
137
|
+
const s = site({ skills: [{ name: "s", origin: "marketplace", source: "o/r", ref: "v1.0.0", path: "/p" }] });
|
|
138
|
+
expect(ids(s)).not.toContain("AGT004");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("does not fire on a local skill, which has no upstream to drift", () => {
|
|
142
|
+
const s = site({ skills: [{ name: "s", origin: "local", path: "/p", content: "x" }] });
|
|
143
|
+
expect(ids(s)).not.toContain("AGT004");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("does not fire on a plugin vendored from a local directory", () => {
|
|
147
|
+
// Regression: harnesses ship bundled plugins from local paths; "pin it"
|
|
148
|
+
// is not advice that applies to a directory the user already controls.
|
|
149
|
+
const s = site({ plugins: [{ name: "bundled", marketplace: "/opt/bundled", enabled: true, remote: false }] });
|
|
150
|
+
expect(ids(s)).not.toContain("AGT004");
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("fires on an enabled remote plugin with no ref", () => {
|
|
154
|
+
const s = site({ plugins: [{ name: "p", marketplace: "owner/repo", enabled: true, remote: true }] });
|
|
155
|
+
expect(ids(s)).toContain("AGT004");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("AGT005 — permissions", () => {
|
|
160
|
+
test("fires when the dangerous-operation prompt is disabled", () => {
|
|
161
|
+
const s = site({ permissions: { bypassesPrompts: true } });
|
|
162
|
+
const finding = checkAgentConfigs(scanOf(s)).find((f) => f.checkId === "AGT005");
|
|
163
|
+
expect(finding?.severity).toBe("error");
|
|
164
|
+
expect(finding?.message).toContain("every project");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("fires on a blanket tool grant", () => {
|
|
168
|
+
const s = site({ permissions: { allow: ["Bash", "Bash(*)", "Read(:*)"] } });
|
|
169
|
+
expect(checkAgentConfigs(scanOf(s)).filter((f) => f.checkId === "AGT005")).toHaveLength(3);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("does not fire on a scoped grant", () => {
|
|
173
|
+
const s = site({ permissions: { allow: ["Bash(git status:*)", "Bash(npm test:*)"] } });
|
|
174
|
+
expect(ids(s)).not.toContain("AGT005");
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("AGT006 — user-scope blast radius", () => {
|
|
179
|
+
test("fires for a user-scope config that carries content", () => {
|
|
180
|
+
const s = site({ mcpServers: [server()] });
|
|
181
|
+
expect(ids(s)).toContain("AGT006");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("does not fire at project scope, where the radius is the project", () => {
|
|
185
|
+
const s = site({ scope: "project", mcpServers: [server()] });
|
|
186
|
+
expect(ids(s)).not.toContain("AGT006");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("AGT007 — shadowed declarations", () => {
|
|
191
|
+
test("fires when one server name is declared in two files, naming the winner", () => {
|
|
192
|
+
const winner = server({ name: "dup", source: "/a.json" });
|
|
193
|
+
const s = site({ mcpServers: [winner] });
|
|
194
|
+
const finding = checkAgentConfigs(scanOf(s, [winner, server({ name: "dup", source: "/b.json" })])).find((f) => f.checkId === "AGT007");
|
|
195
|
+
expect(finding?.message).toContain("/a.json");
|
|
196
|
+
expect(finding?.message).toContain("/b.json");
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("does not fire when each server is declared once", () => {
|
|
200
|
+
const only = server({ name: "solo" });
|
|
201
|
+
expect(ids(site({ mcpServers: [only] }), [only])).not.toContain("AGT007");
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe("AGT008 — instruction size", () => {
|
|
206
|
+
test("fires past the attention budget", () => {
|
|
207
|
+
const big = "x".repeat(40 * 1024);
|
|
208
|
+
const s = site({ instructions: [{ path: "/home/u/CLAUDE.md", content: big, bytes: big.length }] });
|
|
209
|
+
expect(ids(s)).toContain("AGT008");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("does not fire on a normal instruction file", () => {
|
|
213
|
+
const s = site({ instructions: [{ path: "/home/u/CLAUDE.md", content: "be brief", bytes: 8 }] });
|
|
214
|
+
expect(ids(s)).not.toContain("AGT008");
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("ordering", () => {
|
|
219
|
+
test("errors sort before warnings and infos", () => {
|
|
220
|
+
const s = site({
|
|
221
|
+
mcpServers: [server({ command: "npx", args: ["-y", "floating"] })],
|
|
222
|
+
instructions: [{ path: "/p", content: "x".repeat(40 * 1024), bytes: 40 * 1024 }],
|
|
223
|
+
plugins: [{ name: "p", marketplace: "o/r", enabled: true, remote: true }],
|
|
224
|
+
});
|
|
225
|
+
const severities = checkAgentConfigs(scanOf(s)).map((f) => f.severity);
|
|
226
|
+
expect(severities).toEqual([...severities].sort((a, b) => ({ error: 0, warning: 1, info: 2 })[a] - ({ error: 0, warning: 1, info: 2 })[b]));
|
|
227
|
+
});
|
|
228
|
+
});
|