@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
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam between neutral agent-config discovery and a lexicon's vocabulary.
|
|
3
|
+
*
|
|
4
|
+
* `discover.ts` deliberately knows nothing about chant resources — it reads
|
|
5
|
+
* five harnesses' config dialects and stops. Turning an {@link AgentConfigSite}
|
|
6
|
+
* into resources is a statement about a *target* (fountain's `Agent` and
|
|
7
|
+
* `Environment`, today), and that judgement belongs to the lexicon that owns
|
|
8
|
+
* those types, exactly as `templateParser()` puts CloudFormation's JSON
|
|
9
|
+
* vocabulary in the aws lexicon rather than in core.
|
|
10
|
+
*
|
|
11
|
+
* So a lexicon that can express local agent configuration implements
|
|
12
|
+
* {@link AgentConfigImporter} and returns it from
|
|
13
|
+
* `LexiconPlugin.agentConfigImporter()`. `chant import --agents` loads the
|
|
14
|
+
* plugin, hands it the scan, and pipes the resulting IR through the same
|
|
15
|
+
* `templateGenerator()` every other import path uses.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { TemplateIR } from "../import/parser";
|
|
19
|
+
import type { AgentConfigSite } from "./types";
|
|
20
|
+
|
|
21
|
+
/** A site the importer could not express, and why. */
|
|
22
|
+
export interface SkippedSite {
|
|
23
|
+
siteId: string;
|
|
24
|
+
reason: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The result of re-expressing a scan.
|
|
29
|
+
*
|
|
30
|
+
* The three report arrays exist because this conversion is lossy in ways the
|
|
31
|
+
* user must be told about: a site that couldn't be mapped, a required property
|
|
32
|
+
* that had to be defaulted, and a secret that was rewritten rather than copied
|
|
33
|
+
* are all things a reader would otherwise discover only by diffing the output
|
|
34
|
+
* against their own config.
|
|
35
|
+
*/
|
|
36
|
+
export interface AgentImportOutcome {
|
|
37
|
+
ir: TemplateIR;
|
|
38
|
+
/** Sites that could not be re-expressed in this lexicon's vocabulary. */
|
|
39
|
+
skipped: SkippedSite[];
|
|
40
|
+
/** Site ids where a required property was filled with a default rather than a discovered value. */
|
|
41
|
+
unmappedModel: string[];
|
|
42
|
+
/** Site ids where a literal credential was replaced with an environment reference. */
|
|
43
|
+
redactedSecrets: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Converts discovered agent configuration into a lexicon's resource IR. */
|
|
47
|
+
export interface AgentConfigImporter {
|
|
48
|
+
toTemplateIR(sites: AgentConfigSite[]): AgentImportOutcome;
|
|
49
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent-configuration discovery and checks.
|
|
3
|
+
*
|
|
4
|
+
* Finds the agent configs on a machine (`discover.ts`), judges them
|
|
5
|
+
* (`checks.ts`), and normalizes both onto one vendor-neutral model
|
|
6
|
+
* (`types.ts`). The two consumers are `chant audit --agents`, which reports on
|
|
7
|
+
* what it finds, and `chant import --agents`, which re-expresses it as chant
|
|
8
|
+
* code via a lexicon's IR mapper.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
AgentConfigSite,
|
|
13
|
+
AgentFinding,
|
|
14
|
+
AgentRuntime,
|
|
15
|
+
AgentScanResult,
|
|
16
|
+
AgentScope,
|
|
17
|
+
CommandDecl,
|
|
18
|
+
InstructionFile,
|
|
19
|
+
McpServerDecl,
|
|
20
|
+
McpTransport,
|
|
21
|
+
PermissionConfig,
|
|
22
|
+
PluginDecl,
|
|
23
|
+
SkillDecl,
|
|
24
|
+
SkillOrigin,
|
|
25
|
+
SubagentDecl,
|
|
26
|
+
} from "./types";
|
|
27
|
+
export { AGENT_RUNTIMES, AGENT_SCOPES } from "./types";
|
|
28
|
+
export { scanAgentConfigs, normalizeMcpServers, frontmatter, systemSettingsPaths, unscannedProjectCount, type ScanOptions } from "./discover";
|
|
29
|
+
export { checkAgentConfigs, AGENT_RULE_IDS } from "./checks";
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The normalized model for an agent configuration found on a machine.
|
|
3
|
+
*
|
|
4
|
+
* Five harnesses (claude, codex, gemini, opencode, cursor) each spell the same
|
|
5
|
+
* four ideas differently — standing instructions, tool servers, reusable
|
|
6
|
+
* skills, and ambient settings. `discover.ts` reads each harness's own dialect
|
|
7
|
+
* and lands here; everything downstream (`checks.ts`, the report, the fountain
|
|
8
|
+
* IR mapping) reads only this shape and never a vendor file format again.
|
|
9
|
+
*
|
|
10
|
+
* The `AgentConfigSite` boundary is deliberately "one config that governs one
|
|
11
|
+
* root, for one runtime" rather than "one file". A single Claude Code site is
|
|
12
|
+
* assembled from `CLAUDE.md` + `settings.json` + `settings.local.json` +
|
|
13
|
+
* `mcp.json` + `~/.claude.json` + a `skills/` tree — six files that are only
|
|
14
|
+
* meaningful together, and that the agent itself experiences as one merged
|
|
15
|
+
* configuration. `sources` keeps the receipts so a finding can point at the
|
|
16
|
+
* actual file the reader has to edit.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Which agent harness a config belongs to. The first four are exactly
|
|
21
|
+
* fountain's `Agent.runtime` enum, so they re-express losslessly; `cursor` is
|
|
22
|
+
* discovered and reported but has no fountain runtime to map onto (see
|
|
23
|
+
* `MAPPABLE_RUNTIMES` in the fountain import mapper).
|
|
24
|
+
*/
|
|
25
|
+
export type AgentRuntime = "claude" | "codex" | "gemini" | "opencode" | "cursor";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Blast radius of a config, in the order harnesses merge them (later wins).
|
|
29
|
+
*
|
|
30
|
+
* - `system` — machine-wide policy, typically administrator-installed and not
|
|
31
|
+
* user-editable (Claude Code's `managed-settings.json`).
|
|
32
|
+
* - `user` — the home directory. Applies to *every* project the user opens,
|
|
33
|
+
* which is what makes it the highest-leverage scope to audit.
|
|
34
|
+
* - `project` — checked into (or sitting beside) a repo. Narrowest scope, but
|
|
35
|
+
* the one most likely to arrive from someone else.
|
|
36
|
+
*/
|
|
37
|
+
export type AgentScope = "system" | "user" | "project";
|
|
38
|
+
|
|
39
|
+
export const AGENT_SCOPES: readonly AgentScope[] = ["system", "user", "project"] as const;
|
|
40
|
+
export const AGENT_RUNTIMES: readonly AgentRuntime[] = ["claude", "codex", "gemini", "opencode", "cursor"] as const;
|
|
41
|
+
|
|
42
|
+
/** A standing-instructions file (CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules). */
|
|
43
|
+
export interface InstructionFile {
|
|
44
|
+
/** Absolute path on disk. */
|
|
45
|
+
path: string;
|
|
46
|
+
/** File contents. Read in full — these become fountain's `Agent.system`. */
|
|
47
|
+
content: string;
|
|
48
|
+
bytes: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** How an MCP server is reached. `unknown` when the declaration has neither a command nor a URL. */
|
|
52
|
+
export type McpTransport = "stdio" | "http" | "sse" | "unknown";
|
|
53
|
+
|
|
54
|
+
/** One MCP server declaration, normalized across the several files that can declare one. */
|
|
55
|
+
export interface McpServerDecl {
|
|
56
|
+
name: string;
|
|
57
|
+
transport: McpTransport;
|
|
58
|
+
/** The file this declaration was read from. */
|
|
59
|
+
source: string;
|
|
60
|
+
/** stdio: the executable. */
|
|
61
|
+
command?: string;
|
|
62
|
+
/** stdio: argv after the executable. */
|
|
63
|
+
args?: string[];
|
|
64
|
+
/** http/sse: the endpoint. */
|
|
65
|
+
url?: string;
|
|
66
|
+
/** Environment handed to the server process. Values are kept verbatim — `checks.ts` is what decides whether one looks like a literal credential. */
|
|
67
|
+
env?: Record<string, string>;
|
|
68
|
+
/** Extra keys the harness accepted that this model doesn't name (headers, timeouts, …). */
|
|
69
|
+
extra?: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Where a skill came from. This distinction is the whole point of the skill
|
|
74
|
+
* checks: a `local` skill is text the user can read in their own filesystem,
|
|
75
|
+
* while `plugin`/`marketplace` skills are fetched from a remote and can change
|
|
76
|
+
* under the user without any edit to their machine.
|
|
77
|
+
*/
|
|
78
|
+
export type SkillOrigin = "local" | "plugin" | "marketplace";
|
|
79
|
+
|
|
80
|
+
/** One skill available to the agent. */
|
|
81
|
+
export interface SkillDecl {
|
|
82
|
+
name: string;
|
|
83
|
+
origin: SkillOrigin;
|
|
84
|
+
/** Absolute path to the skill directory or SKILL.md, when it exists on disk. */
|
|
85
|
+
path?: string;
|
|
86
|
+
/** Remote source (`owner/repo`, a URL) for plugin/marketplace skills. */
|
|
87
|
+
source?: string;
|
|
88
|
+
/** Version pin on `source`: a tag, branch, or sha. Absence is what AGT004 flags. */
|
|
89
|
+
ref?: string;
|
|
90
|
+
/** SKILL.md body, when read. Becomes fountain's inline `{name, content}` skill form. */
|
|
91
|
+
content?: string;
|
|
92
|
+
/** The skill's own frontmatter `description`, used for the fountain `description`. */
|
|
93
|
+
description?: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** A subagent / custom agent definition (`.claude/agents/*.md`). */
|
|
97
|
+
export interface SubagentDecl {
|
|
98
|
+
name: string;
|
|
99
|
+
path: string;
|
|
100
|
+
description?: string;
|
|
101
|
+
/** Tool allowlist declared in frontmatter, if any. */
|
|
102
|
+
tools?: string;
|
|
103
|
+
model?: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A slash command definition (`.claude/commands/*.md`, `~/.codex/prompts`). */
|
|
107
|
+
export interface CommandDecl {
|
|
108
|
+
name: string;
|
|
109
|
+
path: string;
|
|
110
|
+
description?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** An installed plugin — a bundle that can inject skills, commands, and MCP servers at once. */
|
|
114
|
+
export interface PluginDecl {
|
|
115
|
+
name: string;
|
|
116
|
+
/** Marketplace or repo the plugin was installed from. */
|
|
117
|
+
marketplace?: string;
|
|
118
|
+
/** Version pin, when the install recorded one. */
|
|
119
|
+
ref?: string;
|
|
120
|
+
enabled: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* True when the plugin tracks a remote the user does not control.
|
|
123
|
+
*
|
|
124
|
+
* The pinning checks turn on this: a plugin vendored from a local directory
|
|
125
|
+
* cannot change underneath the user, so "pin it" is not advice that applies.
|
|
126
|
+
* Harnesses ship bundled plugins from local paths (codex's
|
|
127
|
+
* `source_type = "local"`), and reporting those as unpinned supply chain is
|
|
128
|
+
* noise that trains the reader to skip the rule.
|
|
129
|
+
*/
|
|
130
|
+
remote: boolean;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Tool-permission configuration, normalized from a harness's own settings shape. */
|
|
134
|
+
export interface PermissionConfig {
|
|
135
|
+
allow?: string[];
|
|
136
|
+
deny?: string[];
|
|
137
|
+
ask?: string[];
|
|
138
|
+
defaultMode?: string;
|
|
139
|
+
/** True when the config disables a confirmation prompt outright. */
|
|
140
|
+
bypassesPrompts?: boolean;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* One complete agent configuration governing one root, for one runtime.
|
|
145
|
+
*
|
|
146
|
+
* Assembled from every file at that scope the harness would merge together —
|
|
147
|
+
* see the module docstring for why the file is not the unit.
|
|
148
|
+
*/
|
|
149
|
+
export interface AgentConfigSite {
|
|
150
|
+
/** Stable slug, unique within a scan: `user-claude`, `project-claude-chant`. Becomes the fountain resource name. */
|
|
151
|
+
id: string;
|
|
152
|
+
scope: AgentScope;
|
|
153
|
+
runtime: AgentRuntime;
|
|
154
|
+
/** Directory this configuration governs. For `user` scope, the home directory. */
|
|
155
|
+
root: string;
|
|
156
|
+
/** Every file that contributed, absolute. A site with no sources is never emitted. */
|
|
157
|
+
sources: string[];
|
|
158
|
+
instructions: InstructionFile[];
|
|
159
|
+
mcpServers: McpServerDecl[];
|
|
160
|
+
skills: SkillDecl[];
|
|
161
|
+
subagents: SubagentDecl[];
|
|
162
|
+
commands: CommandDecl[];
|
|
163
|
+
plugins: PluginDecl[];
|
|
164
|
+
/** Environment variables the harness injects into every session. */
|
|
165
|
+
env: Record<string, string>;
|
|
166
|
+
permissions?: PermissionConfig;
|
|
167
|
+
/** Default model pinned by the config, when set. */
|
|
168
|
+
model?: string;
|
|
169
|
+
/**
|
|
170
|
+
* Raw merged settings, kept so a check can interrogate a key this model
|
|
171
|
+
* doesn't name without a schema change. Not used for codegen.
|
|
172
|
+
*/
|
|
173
|
+
settings: Record<string, unknown>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Everything one scan found, plus what it couldn't read. */
|
|
177
|
+
export interface AgentScanResult {
|
|
178
|
+
sites: AgentConfigSite[];
|
|
179
|
+
/** Locations probed and found absent — useful for "is it really not configured, or did I not look?" */
|
|
180
|
+
probed: string[];
|
|
181
|
+
/** Paths that existed but could not be read or parsed, with the reason. */
|
|
182
|
+
unreadable: Array<{ path: string; reason: string }>;
|
|
183
|
+
/**
|
|
184
|
+
* Every MCP declaration each site saw, keyed by site id — including names
|
|
185
|
+
* that lost the first-wins merge and so do not appear in
|
|
186
|
+
* `AgentConfigSite.mcpServers`. Diagnostic data for the shadowing check
|
|
187
|
+
* (AGT007), kept beside the sites rather than on them because it describes
|
|
188
|
+
* the *discovery*, not the configuration being re-expressed.
|
|
189
|
+
*/
|
|
190
|
+
declarations: Record<string, McpServerDecl[]>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** A finding raised by an agent-config check against a discovered site. */
|
|
194
|
+
export interface AgentFinding {
|
|
195
|
+
/** Catalog id, e.g. `AGT001`. */
|
|
196
|
+
checkId: string;
|
|
197
|
+
severity: "error" | "warning" | "info";
|
|
198
|
+
message: string;
|
|
199
|
+
/** The specific file a reader has to open to fix this. */
|
|
200
|
+
file: string;
|
|
201
|
+
/** The site the finding belongs to. */
|
|
202
|
+
siteId: string;
|
|
203
|
+
scope: AgentScope;
|
|
204
|
+
runtime: AgentRuntime;
|
|
205
|
+
/** The named thing within the site (an MCP server name, a skill name). */
|
|
206
|
+
entity?: string;
|
|
207
|
+
}
|
package/src/audit/catalog.ts
CHANGED
|
@@ -127,6 +127,14 @@ export const CF_STATIC_ASSETS: Authority = {
|
|
|
127
127
|
name: "Cloudflare Workers — Static assets",
|
|
128
128
|
url: "https://developers.cloudflare.com/workers/static-assets/",
|
|
129
129
|
};
|
|
130
|
+
export const CWE_HARDCODED_CREDS: Authority = {
|
|
131
|
+
name: "CWE-798 — Use of Hard-coded Credentials",
|
|
132
|
+
url: "https://cwe.mitre.org/data/definitions/798.html",
|
|
133
|
+
};
|
|
134
|
+
export const CWE_CLEARTEXT: Authority = {
|
|
135
|
+
name: "CWE-319 — Cleartext Transmission of Sensitive Information",
|
|
136
|
+
url: "https://cwe.mitre.org/data/definitions/319.html",
|
|
137
|
+
};
|
|
130
138
|
|
|
131
139
|
function meta(
|
|
132
140
|
id: string,
|
|
@@ -149,6 +157,21 @@ const R = "report-only" as const;
|
|
|
149
157
|
const D = "deterministic" as const;
|
|
150
158
|
const G = "guidance" as const;
|
|
151
159
|
|
|
160
|
+
/**
|
|
161
|
+
* An AGT (agent-configuration) rule. Unlike every other family in the catalog
|
|
162
|
+
* these do not run against emitted YAML — they run against the agent config
|
|
163
|
+
* discovered on a machine (`packages/core/src/agents/`), so `yamlBased` is
|
|
164
|
+
* false: feeding a workflow file to the auditor will never produce one.
|
|
165
|
+
*
|
|
166
|
+
* All eight are `guidance`. There is no safe mechanical fix for any of them —
|
|
167
|
+
* pinning someone's MCP server picks a version on their behalf, and narrowing a
|
|
168
|
+
* permission grant requires knowing which commands they actually run.
|
|
169
|
+
*/
|
|
170
|
+
function agentMeta(id: string, tier: Tier, title: string, remediation: string, authority?: Authority[]): RuleMeta {
|
|
171
|
+
const category: Category = authority && authority.length > 0 ? "security" : RULE_CATEGORY[id] ?? "best-practice";
|
|
172
|
+
return { id, tier, fixKind: G, category, title, remediation, authority, yamlBased: false };
|
|
173
|
+
}
|
|
174
|
+
|
|
152
175
|
/**
|
|
153
176
|
* Finding category per rule (#415). Curated: security rules are those that
|
|
154
177
|
* cite an authority or guard an exposure; correctness rules flag structural
|
|
@@ -174,6 +197,17 @@ export const RULE_CATEGORY: Record<string, Category> = {
|
|
|
174
197
|
WRG004: "security",
|
|
175
198
|
WRG005: "security",
|
|
176
199
|
WRG006: "security",
|
|
200
|
+
// AGT — agent configuration (`chant audit --agents`). Core-owned like COR/EXT:
|
|
201
|
+
// these run against the machine's own agent config, not against any one
|
|
202
|
+
// lexicon's emitted output, so no lexicon ships them.
|
|
203
|
+
AGT001: "security",
|
|
204
|
+
AGT002: "security",
|
|
205
|
+
AGT003: "security",
|
|
206
|
+
AGT004: "security",
|
|
207
|
+
AGT005: "security",
|
|
208
|
+
AGT006: "best-practice",
|
|
209
|
+
AGT007: "correctness",
|
|
210
|
+
AGT008: "best-practice",
|
|
177
211
|
};
|
|
178
212
|
|
|
179
213
|
/**
|
|
@@ -181,7 +215,8 @@ export const RULE_CATEGORY: Record<string, Category> = {
|
|
|
181
215
|
* CloudFormation ids (COR/EXT) that aren't owned by a single lexicon. Every
|
|
182
216
|
* per-provider block moved to its lexicon's `auditCatalog()` (#687): WAW→aws,
|
|
183
217
|
* WGC→gcp, AZR→azure, DKRD→docker, WK8/ARGO→k8s, WHM→helm, GHA→github,
|
|
184
|
-
* WGL→gitlab, WFJ→forgejo. `resolveAuditCatalog` merges those over this map
|
|
218
|
+
* WGL→gitlab, WFJ→forgejo. `resolveAuditCatalog` merges those over this map,
|
|
219
|
+
* alongside the lexicon-independent SEC/WRG/AGT families core owns outright.
|
|
185
220
|
*/
|
|
186
221
|
export const RULE_CATALOG: Record<string, RuleMeta> = {
|
|
187
222
|
COR020: meta("COR020", M, G, "Circular resource dependency", "Break the dependency cycle between resources."),
|
|
@@ -211,6 +246,60 @@ export const RULE_CATALOG: Record<string, RuleMeta> = {
|
|
|
211
246
|
WRG004: meta("WRG004", M, G, "Unscoped wildcard route", "Scope the route pattern to the intended zone (e.g. \"example.com/*\") instead of a bare \"*\" or \"*/*\" that matches every zone on the account.", [CF_ROUTES]),
|
|
212
247
|
WRG005: meta("WRG005", M, G, "Non-production environment shares a data store with production", "Give the non-production environment its own KV namespace/R2 bucket/D1 database id instead of reusing production's.", [CF_ENVIRONMENTS]),
|
|
213
248
|
WRG006: meta("WRG006", M, G, "Static assets served from the project root", "Point [site].bucket / [assets].directory at a dedicated public output folder, not the project root, so non-public files (config, source maps, .git) aren't served.", [CF_STATIC_ASSETS]),
|
|
249
|
+
|
|
250
|
+
// ── Agent configuration (`chant audit --agents`) ──────────────────
|
|
251
|
+
AGT001: agentMeta(
|
|
252
|
+
"AGT001",
|
|
253
|
+
M,
|
|
254
|
+
"MCP server runs an unpinned package",
|
|
255
|
+
"Pin the package spec to an exact version (`server@1.2.3`), so a new upstream release can't execute on this machine unreviewed.",
|
|
256
|
+
[SCORECARD_PINNED],
|
|
257
|
+
),
|
|
258
|
+
AGT002: agentMeta(
|
|
259
|
+
"AGT002",
|
|
260
|
+
M,
|
|
261
|
+
"Literal credential in agent config",
|
|
262
|
+
"Replace the value with an environment reference (`${TOKEN}`) and keep the secret in a secret store — agent config files sync, back up, and get shared.",
|
|
263
|
+
[CWE_HARDCODED_CREDS],
|
|
264
|
+
),
|
|
265
|
+
AGT003: agentMeta(
|
|
266
|
+
"AGT003",
|
|
267
|
+
M,
|
|
268
|
+
"MCP server reached over cleartext HTTP",
|
|
269
|
+
"Use an https:// endpoint. Tool arguments and results — including data the agent read locally — otherwise cross the network in the clear.",
|
|
270
|
+
[CWE_CLEARTEXT],
|
|
271
|
+
),
|
|
272
|
+
AGT004: agentMeta(
|
|
273
|
+
"AGT004",
|
|
274
|
+
M,
|
|
275
|
+
"Remote skill or plugin is unpinned",
|
|
276
|
+
"Pin the source to a tag or commit sha, so the instructions the agent follows can't change upstream without a local edit.",
|
|
277
|
+
[SCORECARD_PINNED],
|
|
278
|
+
),
|
|
279
|
+
AGT005: agentMeta(
|
|
280
|
+
"AGT005",
|
|
281
|
+
M,
|
|
282
|
+
"Tool permission granted without constraint",
|
|
283
|
+
"Scope the grant to the specific commands you run (`Bash(git status:*)`), and re-enable the confirmation prompt for dangerous operations.",
|
|
284
|
+
),
|
|
285
|
+
AGT006: agentMeta(
|
|
286
|
+
"AGT006",
|
|
287
|
+
R,
|
|
288
|
+
"User-scope config applies to every project",
|
|
289
|
+
"Move project-specific instructions, MCP servers, and skills to that project's own config so they don't follow you into unrelated repos.",
|
|
290
|
+
),
|
|
291
|
+
AGT007: agentMeta(
|
|
292
|
+
"AGT007",
|
|
293
|
+
R,
|
|
294
|
+
"MCP server declared in multiple files",
|
|
295
|
+
"Delete the shadowed declarations. The harness silently picks one, so the file you read may not be the one that decides what runs.",
|
|
296
|
+
),
|
|
297
|
+
AGT008: agentMeta(
|
|
298
|
+
"AGT008",
|
|
299
|
+
R,
|
|
300
|
+
"Instruction file exceeds the attention budget",
|
|
301
|
+
"Move situational guidance into skills that load on demand, so the always-on instructions stay short enough to be followed reliably.",
|
|
302
|
+
),
|
|
214
303
|
};
|
|
215
304
|
|
|
216
305
|
/**
|
package/src/audit/report.ts
CHANGED
|
@@ -39,6 +39,14 @@ export interface RenderOptions {
|
|
|
39
39
|
notes?: string[];
|
|
40
40
|
/** Resolved audit catalog (core static + active lexicons' contributions, #687). Defaults to core's static catalog. */
|
|
41
41
|
catalog?: Record<string, RuleMeta>;
|
|
42
|
+
/**
|
|
43
|
+
* Markdown heading text and level for the report's own title. `chant audit
|
|
44
|
+
* --agents` embeds this report under its inventory, so it renders as a
|
|
45
|
+
* level-2 "Findings" section rather than a second `# chant audit` H1 in a
|
|
46
|
+
* document that already has one.
|
|
47
|
+
*/
|
|
48
|
+
title?: string;
|
|
49
|
+
headingLevel?: 1 | 2;
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
function escapeCell(s: string): string {
|
|
@@ -96,7 +104,7 @@ export function renderMarkdown(findings: AuditFinding[], opts: RenderOptions = {
|
|
|
96
104
|
const model = buildReportModel(findings, opts);
|
|
97
105
|
const { counts } = model;
|
|
98
106
|
|
|
99
|
-
const lines: string[] = ["# chant audit"];
|
|
107
|
+
const lines: string[] = [`${"#".repeat(opts.headingLevel ?? 1)} ${opts.title ?? "chant audit"}`];
|
|
100
108
|
if (opts.target) lines.push("", `Target: ${opts.target}`);
|
|
101
109
|
lines.push("");
|
|
102
110
|
for (const note of opts.notes ?? []) lines.push(`> Note: ${note}`, "");
|
package/src/audit/rules-doc.ts
CHANGED
|
@@ -23,6 +23,12 @@ const GROUPS: Array<{ heading: string; prefixes: string[]; blurb: string }> = [
|
|
|
23
23
|
{ heading: "fountain (FTN)", prefixes: ["FTN"], blurb: "Run against fountain manifests (`apiVersion: fountain.dev/v1`) — standalone `fountain apply` YAML is parsed back into the entity graph, so the same rules fire on `chant build` and `chant audit`." },
|
|
24
24
|
{ heading: "Secrets & credentials (SEC)", prefixes: ["SEC"], blurb: "Lexicon-independent — scans the raw text of every scanned file for likely credentials, regardless of which audit lexicons are installed. Matched values are always redacted; see [suppressing false positives](/chant/cli/audit/#suppressing-a-secrets-finding)." },
|
|
25
25
|
{ heading: "Wrangler config (WRG)", prefixes: ["WRG"], blurb: "Lexicon-independent, audit-only (#446) — scans `wrangler.toml`, Cloudflare Workers' native deploy config, which the engine cannot otherwise parse (it is not YAML/JSON). No authoring surface: chant does not write Wrangler config, it only reads it for these checks." },
|
|
26
|
+
{
|
|
27
|
+
heading: "Agent configuration (AGT)",
|
|
28
|
+
prefixes: ["AGT"],
|
|
29
|
+
blurb:
|
|
30
|
+
"Run by `chant audit --agents` against the agent configuration on a machine — instruction files, MCP servers, skills, plugins, and permissions at system, user, and project scope. Unlike every other family here, these do not fire on repository YAML.",
|
|
31
|
+
},
|
|
26
32
|
];
|
|
27
33
|
|
|
28
34
|
/**
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Parameters": {
|
|
3
|
+
"EnvName": {
|
|
4
|
+
"Type": "String",
|
|
5
|
+
"Default": "dev"
|
|
6
|
+
},
|
|
7
|
+
"BootstrapVersion": {
|
|
8
|
+
"Type": "AWS::SSM::Parameter::Value<String>",
|
|
9
|
+
"Default": "/cdk-bootstrap/hnb659fds/version",
|
|
10
|
+
"Description": "Version of the CDK Bootstrap resources in this environment"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"Conditions": {
|
|
14
|
+
"IsProd": { "Fn::Equals": [{ "Ref": "EnvName" }, "prod"] }
|
|
15
|
+
},
|
|
16
|
+
"Resources": {
|
|
17
|
+
"ApiQueue4A6E9832": {
|
|
18
|
+
"Type": "AWS::SQS::Queue",
|
|
19
|
+
"Properties": {
|
|
20
|
+
"VisibilityTimeout": 300
|
|
21
|
+
},
|
|
22
|
+
"UpdateReplacePolicy": "Delete",
|
|
23
|
+
"DeletionPolicy": "Delete",
|
|
24
|
+
"Metadata": {
|
|
25
|
+
"aws:cdk:path": "AppStack/Api/Queue/Resource"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"ApiTopic1B2C3D4E": {
|
|
29
|
+
"Type": "AWS::SNS::Topic",
|
|
30
|
+
"Properties": {
|
|
31
|
+
"DisplayName": "api-events"
|
|
32
|
+
},
|
|
33
|
+
"Metadata": {
|
|
34
|
+
"aws:cdk:path": "AppStack/Api/Topic/Resource"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"ApiTopicQueueSubscriptionAA11BB22": {
|
|
38
|
+
"Type": "AWS::SNS::Subscription",
|
|
39
|
+
"Properties": {
|
|
40
|
+
"Protocol": "sqs",
|
|
41
|
+
"TopicArn": { "Ref": "ApiTopic1B2C3D4E" },
|
|
42
|
+
"Endpoint": { "Fn::GetAtt": ["ApiQueue4A6E9832", "Arn"] }
|
|
43
|
+
},
|
|
44
|
+
"Metadata": {
|
|
45
|
+
"aws:cdk:path": "AppStack/Api/Topic/QueueSubscription/Resource"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"HandlerServiceRoleFCDC14AE": {
|
|
49
|
+
"Type": "AWS::IAM::Role",
|
|
50
|
+
"Properties": {
|
|
51
|
+
"AssumeRolePolicyDocument": {
|
|
52
|
+
"Statement": [
|
|
53
|
+
{
|
|
54
|
+
"Action": "sts:AssumeRole",
|
|
55
|
+
"Effect": "Allow",
|
|
56
|
+
"Principal": { "Service": "lambda.amazonaws.com" }
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"Version": "2012-10-17"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"Metadata": {
|
|
63
|
+
"aws:cdk:path": "AppStack/Handler/ServiceRole/Resource"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"HandlerServiceRoleDefaultPolicy4C43A1F9": {
|
|
67
|
+
"Type": "AWS::IAM::Policy",
|
|
68
|
+
"Properties": {
|
|
69
|
+
"PolicyDocument": {
|
|
70
|
+
"Statement": [
|
|
71
|
+
{
|
|
72
|
+
"Action": ["s3:GetObject", "s3:PutObject"],
|
|
73
|
+
"Effect": "Allow",
|
|
74
|
+
"Resource": {
|
|
75
|
+
"Fn::Join": [
|
|
76
|
+
"",
|
|
77
|
+
[
|
|
78
|
+
{ "Fn::ImportValue": "DataStack:ExportsOutputFnGetAttAssets675B7A57ArnE0B2A4A8" },
|
|
79
|
+
"/*"
|
|
80
|
+
]
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"Version": "2012-10-17"
|
|
86
|
+
},
|
|
87
|
+
"PolicyName": "HandlerServiceRoleDefaultPolicy4C43A1F9",
|
|
88
|
+
"Roles": [{ "Ref": "HandlerServiceRoleFCDC14AE" }]
|
|
89
|
+
},
|
|
90
|
+
"Metadata": {
|
|
91
|
+
"aws:cdk:path": "AppStack/Handler/ServiceRole/DefaultPolicy/Resource"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"Handler886CB40B": {
|
|
95
|
+
"Type": "AWS::Lambda::Function",
|
|
96
|
+
"Properties": {
|
|
97
|
+
"Code": {
|
|
98
|
+
"S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" },
|
|
99
|
+
"S3Key": "9f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a0.zip"
|
|
100
|
+
},
|
|
101
|
+
"Environment": {
|
|
102
|
+
"Variables": {
|
|
103
|
+
"QUEUE_URL": { "Ref": "ApiQueue4A6E9832" }
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"Handler": "index.handler",
|
|
107
|
+
"Role": { "Fn::GetAtt": ["HandlerServiceRoleFCDC14AE", "Arn"] },
|
|
108
|
+
"Runtime": "nodejs20.x"
|
|
109
|
+
},
|
|
110
|
+
"DependsOn": ["HandlerServiceRoleDefaultPolicy4C43A1F9", "HandlerServiceRoleFCDC14AE"],
|
|
111
|
+
"Metadata": {
|
|
112
|
+
"aws:cdk:path": "AppStack/Handler/Resource",
|
|
113
|
+
"aws:asset:path": "asset.9f8e7d6c5b4a39281706f5e4d3c2b1a0",
|
|
114
|
+
"aws:asset:is-bundled": false,
|
|
115
|
+
"aws:asset:property": "Code"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"LegacyBucket": {
|
|
119
|
+
"Type": "AWS::S3::Bucket",
|
|
120
|
+
"Condition": "IsProd",
|
|
121
|
+
"Properties": {
|
|
122
|
+
"BucketName": { "Fn::Sub": "legacy-${EnvName}-archive" }
|
|
123
|
+
},
|
|
124
|
+
"UpdateReplacePolicy": "Retain",
|
|
125
|
+
"DeletionPolicy": "Retain",
|
|
126
|
+
"Metadata": {
|
|
127
|
+
"aws:cdk:path": "AppStack/Legacy"
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
"ReportsNestedStackReportsNestedStackResource7B21D4C6": {
|
|
131
|
+
"Type": "AWS::CloudFormation::Stack",
|
|
132
|
+
"Properties": {
|
|
133
|
+
"TemplateURL": {
|
|
134
|
+
"Fn::Join": [
|
|
135
|
+
"",
|
|
136
|
+
[
|
|
137
|
+
"https://s3.",
|
|
138
|
+
{ "Ref": "AWS::Region" },
|
|
139
|
+
".",
|
|
140
|
+
{ "Ref": "AWS::URLSuffix" },
|
|
141
|
+
"/cdk-hnb659fds-assets-",
|
|
142
|
+
{ "Ref": "AWS::AccountId" },
|
|
143
|
+
"-",
|
|
144
|
+
{ "Ref": "AWS::Region" },
|
|
145
|
+
"/1a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f7081.json"
|
|
146
|
+
]
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"UpdateReplacePolicy": "Delete",
|
|
151
|
+
"DeletionPolicy": "Delete",
|
|
152
|
+
"Metadata": {
|
|
153
|
+
"aws:cdk:path": "AppStack/Reports.NestedStack/Reports.NestedStackResource"
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
"CDKMetadata": {
|
|
157
|
+
"Type": "AWS::CDK::Metadata",
|
|
158
|
+
"Properties": {
|
|
159
|
+
"Analytics": "v2:deflate64:H4sIAAAAAAAA/y2K0QqAIAwAv8V3XVEE9V4fEEQfIGtJmDrYVoT4740ed3fY4wr7BgtCvIYuBl+w7gLuoIm2y9lgAWJv6TylOUYVXpLK5x0Ipp98yRz5Uqz+MvzQZlvibDpsMK1SUteYWvwNyxu2AlgAAAA="
|
|
160
|
+
},
|
|
161
|
+
"Metadata": {
|
|
162
|
+
"aws:cdk:path": "AppStack/CDKMetadata/Default"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
"Outputs": {
|
|
167
|
+
"QueueUrl": {
|
|
168
|
+
"Value": { "Ref": "ApiQueue4A6E9832" }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|