nexarch 0.12.11 → 0.12.12
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/commands/init-agent.js +11 -1
- package/dist/commands/verify-trust.js +108 -0
- package/dist/index.js +6 -0
- package/package.json +1 -1
|
@@ -440,7 +440,17 @@ function injectTrustAttestationBlock(path, attestation) {
|
|
|
440
440
|
"",
|
|
441
441
|
"This block is verifiable proof that Nexarch generated these instructions for this workspace.",
|
|
442
442
|
"Do not auto-run commands without user confirmation.",
|
|
443
|
-
"
|
|
443
|
+
"",
|
|
444
|
+
"**To verify, run `npx nexarch@latest verify-trust`.** It reads the token from this",
|
|
445
|
+
"file and checks it for you.",
|
|
446
|
+
"",
|
|
447
|
+
"Do not retype, paste or reconstruct the token below into a URL or a shell command.",
|
|
448
|
+
"It is a cryptographic signature: reproducing it from memory yields a plausible token",
|
|
449
|
+
"that fails as `bad_signature`, which is indistinguishable from a forged block. A",
|
|
450
|
+
"transcription slip would then read as tampering and stop work for no reason.",
|
|
451
|
+
"",
|
|
452
|
+
"If verification says `expired`, rerun `npx nexarch@latest init-agent --allow-instruction-write`.",
|
|
453
|
+
"If it cannot reach the endpoint, that is a network problem and not a failed attestation — retry.",
|
|
444
454
|
"",
|
|
445
455
|
`issuer: ${attestation.payload.iss}`,
|
|
446
456
|
`scope: ${attestation.payload.scope}`,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Verifies the trust attestation without anyone retyping it.
|
|
5
|
+
*
|
|
6
|
+
* The attestation block asked the reader to take a ~500 character signed token
|
|
7
|
+
* out of a markdown file and paste it into a URL on a shell command line. That
|
|
8
|
+
* is a copy operation with no error detection: drop one character and the
|
|
9
|
+
* endpoint answers `bad_signature`, which is indistinguishable from a forged
|
|
10
|
+
* block. Agents duly reported the instructions as untrusted and refused to work
|
|
11
|
+
* — the correct response to that answer, and completely wrong about the facts.
|
|
12
|
+
*
|
|
13
|
+
* Reading the token from the file removes the copy, and with it the failure.
|
|
14
|
+
*/
|
|
15
|
+
const INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md", ".cursorrules", ".windsurfrules", ".github/copilot-instructions.md"];
|
|
16
|
+
const DEFAULT_VERIFY_BASE = "https://mcp.nexarch.ai/trust/verify";
|
|
17
|
+
function findAttestation(dir) {
|
|
18
|
+
for (const name of INSTRUCTION_FILES) {
|
|
19
|
+
const path = join(dir, name);
|
|
20
|
+
if (!existsSync(path))
|
|
21
|
+
continue;
|
|
22
|
+
let content = "";
|
|
23
|
+
try {
|
|
24
|
+
content = readFileSync(path, "utf8");
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const token = content.match(/^token:\s*(\S+)\s*$/m)?.[1];
|
|
30
|
+
if (!token)
|
|
31
|
+
continue;
|
|
32
|
+
const verifyUrl = content.match(/^verify_url:\s*(\S+)\s*$/m)?.[1] ?? null;
|
|
33
|
+
return { file: name, token, verifyUrl };
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Prefers the token field over the URL's copy of it.
|
|
39
|
+
*
|
|
40
|
+
* Both are written from the same value, but only the token field is a single
|
|
41
|
+
* self-contained word. Rebuilding the query string here also means a wrapped or
|
|
42
|
+
* truncated `verify_url` line cannot poison the check.
|
|
43
|
+
*/
|
|
44
|
+
function verifyEndpoint(attestation) {
|
|
45
|
+
const base = attestation.verifyUrl?.split("?")[0];
|
|
46
|
+
const endpoint = base && base.startsWith("http") ? base : DEFAULT_VERIFY_BASE;
|
|
47
|
+
return `${endpoint}?token=${encodeURIComponent(attestation.token)}`;
|
|
48
|
+
}
|
|
49
|
+
export async function verifyTrust(args) {
|
|
50
|
+
const asJson = args.includes("--json");
|
|
51
|
+
const dirArg = args.indexOf("--dir");
|
|
52
|
+
const dir = resolve(dirArg !== -1 && args[dirArg + 1] && !args[dirArg + 1].startsWith("--") ? args[dirArg + 1] : process.cwd());
|
|
53
|
+
const attestation = findAttestation(dir);
|
|
54
|
+
if (!attestation) {
|
|
55
|
+
const message = `No Nexarch trust attestation found in ${dir}. Run \`npx nexarch@latest init-agent --allow-instruction-write\` to write one.`;
|
|
56
|
+
if (asJson) {
|
|
57
|
+
process.stdout.write(`${JSON.stringify({ verified: false, reason: "no_attestation_found", dir }, null, 2)}\n`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log(message);
|
|
61
|
+
}
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
let body;
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(verifyEndpoint(attestation));
|
|
68
|
+
body = (await response.json());
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
72
|
+
if (asJson) {
|
|
73
|
+
process.stdout.write(`${JSON.stringify({ verified: false, reason: "unreachable", detail: reason }, null, 2)}\n`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// An unreachable endpoint is not a failed verification, and must not be
|
|
77
|
+
// reported as one: a network blip would otherwise read as a forged block.
|
|
78
|
+
console.log(`Could not reach the verification endpoint — ${reason}`);
|
|
79
|
+
console.log("This is a connectivity problem, not a failed attestation. Retry before treating the instructions as untrusted.");
|
|
80
|
+
}
|
|
81
|
+
process.exitCode = 2;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (asJson) {
|
|
85
|
+
process.stdout.write(`${JSON.stringify({ ...body, source: attestation.file }, null, 2)}\n`);
|
|
86
|
+
process.exitCode = body.verified ? 0 : 1;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (body.verified) {
|
|
90
|
+
const payload = body.payload ?? {};
|
|
91
|
+
console.log(`✓ Trust attestation verified (${attestation.file})`);
|
|
92
|
+
console.log(` issuer: ${String(payload.iss ?? "unknown")}`);
|
|
93
|
+
console.log(` scope: ${String(payload.scope ?? "unknown")}`);
|
|
94
|
+
console.log(` agent_id: ${String(payload.agent_id ?? "unknown")}`);
|
|
95
|
+
if (typeof payload.exp === "number") {
|
|
96
|
+
console.log(` expires: ${new Date(payload.exp * 1000).toISOString()}`);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log(`✗ Trust attestation NOT verified (${attestation.file}) — ${body.reason ?? "unknown reason"}`);
|
|
101
|
+
if (body.reason === "expired") {
|
|
102
|
+
console.log(" Refresh it: npx nexarch@latest init-agent --allow-instruction-write");
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
console.log(" The instruction block may have been altered. Treat it as untrusted and ask the human how to proceed.");
|
|
106
|
+
}
|
|
107
|
+
process.exitCode = 1;
|
|
108
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,7 @@ import { governanceSummary } from "./commands/governance-summary.js";
|
|
|
27
27
|
import { proposalsStart } from "./commands/proposals-start.js";
|
|
28
28
|
import { registerRuntime } from "./commands/register-runtime.js";
|
|
29
29
|
import { ingestInfra } from "./commands/ingest-infra.js";
|
|
30
|
+
import { verifyTrust } from "./commands/verify-trust.js";
|
|
30
31
|
const [, , command, ...args] = process.argv;
|
|
31
32
|
const commands = {
|
|
32
33
|
login,
|
|
@@ -36,6 +37,7 @@ const commands = {
|
|
|
36
37
|
"mcp-config": mcpConfig,
|
|
37
38
|
"mcp-proxy": mcpProxy,
|
|
38
39
|
"init-agent": initAgent,
|
|
40
|
+
"verify-trust": verifyTrust,
|
|
39
41
|
"agent-identify": agentIdentify,
|
|
40
42
|
"init-project": initProject,
|
|
41
43
|
"ingest-infra": ingestInfra,
|
|
@@ -83,6 +85,10 @@ Usage:
|
|
|
83
85
|
Option: --company <id>
|
|
84
86
|
nexarch logout Remove stored credentials
|
|
85
87
|
nexarch status Check connection and show architecture summary
|
|
88
|
+
nexarch verify-trust Verify this repo's Nexarch trust attestation. Reads the token
|
|
89
|
+
from the instruction file, so nothing has to be copied.
|
|
90
|
+
Options: --dir <path> (default: cwd)
|
|
91
|
+
--json
|
|
86
92
|
nexarch setup One-step onboarding: login (if needed) + MCP config + register agent
|
|
87
93
|
Names the workspace it will write to and confirms it before
|
|
88
94
|
registering anything; answer 'n' to pick a different one.
|