candor-ts 0.36.1 → 0.36.2
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/README.md +23 -0
- package/package.json +1 -1
- package/scan.mjs +41 -1
package/README.md
CHANGED
|
@@ -249,3 +249,26 @@ node scan.mjs <dir | file.ts | tsconfig.json> --out .candor/report # scan a pr
|
|
|
249
249
|
The pure cores are factored into importable modules — `query-core.mjs` (the §3.1 queries),
|
|
250
250
|
`policy.mjs` (the §6.2 DSL + literal matchers), and `scan-core.mjs` (the classifier + the SQL/
|
|
251
251
|
command/host extractors) — so they're unit-tested directly; the TS-compiler-driven walk stays in `scan.mjs`.
|
|
252
|
+
|
|
253
|
+
## Agents: install candor as an MCP server
|
|
254
|
+
|
|
255
|
+
`candor-ts` ships candor's read-only query surface as an MCP server, so an agent asks *"what is the
|
|
256
|
+
blast radius of changing this?"* or *"what reaches the network?"* and gets a deterministic answer from a
|
|
257
|
+
precomputed report instead of grepping for it.
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
npx -y candor-ts --mcp # the server, over stdio
|
|
261
|
+
candor mcp install # or let the umbrella write/merge .mcp.json for you
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Registration instructions come from **the package you already installed** — `candor mcp --help` prints
|
|
265
|
+
the `.mcp.json` snippet and the `claude mcp add` line, and `candor mcp install` writes them. There is no
|
|
266
|
+
remote file to fetch, tamper with, or auto-execute, and nothing here tells an agent to run anything it
|
|
267
|
+
did not already choose to install.
|
|
268
|
+
|
|
269
|
+
Discovery is passive: the manifest is [`server.json`](server.json), published to the official
|
|
270
|
+
[MCP Registry](https://registry.modelcontextprotocol.io) so clients and directories can find candor
|
|
271
|
+
without being handed a script. The registry verifies namespace ownership through the marker below.
|
|
272
|
+
|
|
273
|
+
mcp-name: io.github.tombaldwin/candor
|
|
274
|
+
|
package/package.json
CHANGED
package/scan.mjs
CHANGED
|
@@ -108,6 +108,27 @@ if (process.argv.includes("--version") || process.argv.includes("-V")) {
|
|
|
108
108
|
process.exit(0);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// --mcp: a MODE, in the same place and for the same reason as --version above — it hands the process
|
|
112
|
+
// to the MCP server so the ONE bin the MCP Registry can invoke (`npx -y candor-ts`) is able to serve.
|
|
113
|
+
//
|
|
114
|
+
// WHY A FLAG AND NOT A BARE `mcp` SUBCOMMAND, which is what ebman does. This script takes its scan
|
|
115
|
+
// target POSITIONALLY, so `candor-ts mcp` would be ambiguous with a directory literally named `mcp` —
|
|
116
|
+
// and a registry-invoked server runs in whatever repo the client happens to be in, where that
|
|
117
|
+
// directory may well exist. A flag cannot collide with a path. The ambiguity would be rare and silent,
|
|
118
|
+
// which is the combination worth designing out rather than documenting.
|
|
119
|
+
//
|
|
120
|
+
// spawnSync rather than `await import("./mcp.mjs")`: an import would RUN the server and then fall
|
|
121
|
+
// through into this file's own argument walk when it returned. Handing the child our stdio and
|
|
122
|
+
// exiting on its status keeps the two programs separate, which is what the transport needs anyway.
|
|
123
|
+
if (process.argv.includes("--mcp")) {
|
|
124
|
+
const { spawnSync } = await import("node:child_process");
|
|
125
|
+
const { fileURLToPath } = await import("node:url");
|
|
126
|
+
const server = fileURLToPath(new URL("./mcp.mjs", import.meta.url));
|
|
127
|
+
const rest = process.argv.slice(2).filter((a) => a !== "--mcp");
|
|
128
|
+
const r = spawnSync(process.execPath, [server, ...rest], { stdio: "inherit" });
|
|
129
|
+
process.exit(r.status ?? 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
111
132
|
// -h / --help: a print-and-exit MODE (like --version), handled before the arg walk so `-h` (a single
|
|
112
133
|
// dash) is never mistaken for the scan target by the positional fallthrough below.
|
|
113
134
|
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
@@ -7143,8 +7164,27 @@ function visitCalls(node) {
|
|
|
7143
7164
|
// a connected socket). `post/put/patch/delete/head/options` cover the axios/got/undici tier whose
|
|
7144
7165
|
// URL is the call arg (sweep [18]); `dgram.send(buf,port,host)` is added module-aware below (UDP
|
|
7145
7166
|
// has no connect, so send carries the destination — sweep [12]).
|
|
7167
|
+
// SOUNDNESS R410 — THE RESOLVER FAMILY WAS MISSING, and its absence is a GATE BYPASS, not a
|
|
7168
|
+
// missed disclosure. `dns.resolve` classifies Net (see the κ table below), so a resolver call
|
|
7169
|
+
// carries the effect while contributing NO host; with this list not naming it, nothing marked
|
|
7170
|
+
// the surface incomplete and a benign sibling `fetch("https://api.stripe.com")` certified a
|
|
7171
|
+
// caller-controlled DNS target — `allow Net api.stripe.com` exit 0, measured, with the
|
|
7172
|
+
// sibling-free control correctly caught by AS-EFF-008. Written as the WHOLE family rather than
|
|
7173
|
+
// the spelling in hand (R346): every node `dns` resolver, the `dns/promises` twins (same names)
|
|
7174
|
+
// and the `Resolver` class methods, which share these member names.
|
|
7175
|
+
//
|
|
7176
|
+
// NOTE THE SHAPE PROBLEM THIS DOES NOT FIX. This set is an INCLUSION list, so forgetting a
|
|
7177
|
+
// member UNDER-reports — the opposite of `FS_USE_VERBS`/`EXEC_USE_VERBS` below, where
|
|
7178
|
+
// forgetting over-charges and is safe. That asymmetry is the defect class itself: java's Net
|
|
7179
|
+
// is sound precisely because it uses the general rule (any Net call contributing no visible
|
|
7180
|
+
// host leaves the surface incomplete) rather than a list. The durable repair is to INVERT this
|
|
7181
|
+
// into a use-verb denylist beside the other two; that is a wider change with its own
|
|
7182
|
+
// over-charge bill to price, so it is filed rather than smuggled in here.
|
|
7146
7183
|
const NET_ESTABLISHING = new Set(["request", "get", "post", "put", "patch", "delete", "head",
|
|
7147
|
-
"options", "connect", "createConnection", "fetch"
|
|
7184
|
+
"options", "connect", "createConnection", "fetch",
|
|
7185
|
+
"lookup", "lookupService", "reverse", "resolve", "resolve4", "resolve6", "resolveAny",
|
|
7186
|
+
"resolveCname", "resolveCaa", "resolveMx", "resolveNaptr", "resolveNs", "resolvePtr",
|
|
7187
|
+
"resolveSoa", "resolveSrv", "resolveTxt"]);
|
|
7148
7188
|
// Fs/Exec USE-verbs whose LOCATOR was fixed earlier, not an arg of THIS call — so a missing literal
|
|
7149
7189
|
// here is the legitimate split-construct/use shape, never the masking signal (the establishing-
|
|
7150
7190
|
// allowlist discipline, generalized from Net to all 4 effects; sweep [11]). Fs: the fd/FileHandle
|