feat(signer): scaffold the package and enforce the no-network boundary
The signer's security claim is that it cannot reach the network, not that it chooses not to. test/boundary.test.ts enforces that mechanically over both the import graph and the manifest.
This commit is contained in:
20
package-lock.json
generated
20
package-lock.json
generated
@@ -42,6 +42,10 @@
|
||||
"resolved": "packages/shared",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@apexfusion/vector-mcp-signer": {
|
||||
"resolved": "packages/signer",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@aws-sdk/checksums": {
|
||||
"version": "3.1000.20",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.20.tgz",
|
||||
@@ -7893,6 +7897,22 @@
|
||||
"@noble/hashes": "^1.7.1",
|
||||
"cross-fetch": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"packages/signer": {
|
||||
"name": "@apexfusion/vector-mcp-signer",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@anastasia-labs/cardano-multiplatform-lib-nodejs": "6.0.2-3",
|
||||
"@lucid-evolution/wallet": "^0.1.72",
|
||||
"@modelcontextprotocol/sdk": "^1.4.1",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"bin": {
|
||||
"vector-mcp-signer": "build/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apexfusion/vector-mcp-shared": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"scripts": {
|
||||
"postinstall": "ln -sf ../../../libsodium-sumo/dist/modules-sumo-esm/libsodium-sumo.mjs node_modules/libsodium-wrappers-sumo/dist/modules-sumo-esm/libsodium-sumo.mjs 2>/dev/null || true",
|
||||
"clean": "rm -rf packages/*/build",
|
||||
"build": "npm run build --workspace=@apexfusion/vector-mcp-builder",
|
||||
"typecheck": "tsc --noEmit -p packages/shared && tsc --noEmit -p packages/builder",
|
||||
"build": "npm run build --workspace=@apexfusion/vector-mcp-builder && npm run build --workspace=@apexfusion/vector-mcp-signer",
|
||||
"typecheck": "tsc --noEmit -p packages/shared && tsc --noEmit -p packages/builder && tsc --noEmit -p packages/signer",
|
||||
"start": "node packages/builder/build/index.js",
|
||||
"test": "npm run test:unit",
|
||||
"test:unit": "node --import tsx --test packages/*/test/*.test.ts",
|
||||
|
||||
21
packages/signer/package.json
Normal file
21
packages/signer/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@apexfusion/vector-mcp-signer",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Local signing companion for the Vector MCP server. Holds the key; no network.",
|
||||
"bin": { "vector-mcp-signer": "./build/index.js" },
|
||||
"scripts": {
|
||||
"build": "tsup && chmod +x build/index.js",
|
||||
"start": "node build/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@anastasia-labs/cardano-multiplatform-lib-nodejs": "6.0.2-3",
|
||||
"@lucid-evolution/wallet": "^0.1.72",
|
||||
"@modelcontextprotocol/sdk": "^1.4.1",
|
||||
"zod": "^3.24.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apexfusion/vector-mcp-shared": "*"
|
||||
}
|
||||
}
|
||||
3
packages/signer/src/index.ts
Normal file
3
packages/signer/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// Local signing companion for the Vector MCP server.
|
||||
// Speaks stdio only: no HTTP listener, no outbound network. See test/boundary.test.ts.
|
||||
console.error('vector-mcp-signer: not yet implemented');
|
||||
58
packages/signer/test/boundary.test.ts
Normal file
58
packages/signer/test/boundary.test.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { describe, test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
// The signer's security claim is that it CANNOT reach the network — not that it
|
||||
// chooses not to. These assertions are that claim, mechanically enforced.
|
||||
// Weakening them defeats the purpose of the package.
|
||||
|
||||
const PKG_DIR = new URL('..', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1');
|
||||
const SRC_DIR = join(PKG_DIR, 'src');
|
||||
|
||||
const FORBIDDEN_IMPORTS = [
|
||||
'@apexfusion/vector-mcp-shared/provider',
|
||||
'@lucid-evolution/lucid',
|
||||
'cross-fetch',
|
||||
'node:http',
|
||||
'node:https',
|
||||
'node:net',
|
||||
'node:dgram',
|
||||
'node:tls',
|
||||
];
|
||||
|
||||
function sourceFiles(dir: string): string[] {
|
||||
return readdirSync(dir, { withFileTypes: true }).flatMap((e) =>
|
||||
e.isDirectory() ? sourceFiles(join(dir, e.name)) : e.name.endsWith('.ts') ? [join(dir, e.name)] : []
|
||||
);
|
||||
}
|
||||
|
||||
describe('signer has no network capability', () => {
|
||||
test('no source file imports a network-capable module', () => {
|
||||
const offenders: string[] = [];
|
||||
for (const file of sourceFiles(SRC_DIR)) {
|
||||
const text = readFileSync(file, 'utf8');
|
||||
for (const bad of FORBIDDEN_IMPORTS) {
|
||||
if (text.includes(`'${bad}'`) || text.includes(`"${bad}"`)) {
|
||||
offenders.push(`${file} imports ${bad}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert.deepStrictEqual(offenders, [], `network-capable imports found:\n ${offenders.join('\n ')}`);
|
||||
});
|
||||
|
||||
test('package.json declares no network-capable dependency', () => {
|
||||
const pkg = JSON.parse(readFileSync(join(PKG_DIR, 'package.json'), 'utf8'));
|
||||
const declared = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
|
||||
const bad = declared.filter((d) => d === '@lucid-evolution/lucid' || d === 'cross-fetch');
|
||||
assert.deepStrictEqual(bad, [], `network-capable dependencies declared: ${bad.join(', ')}`);
|
||||
});
|
||||
|
||||
test('does not depend on the shared provider subpath', () => {
|
||||
const pkg = JSON.parse(readFileSync(join(PKG_DIR, 'package.json'), 'utf8'));
|
||||
const shared = { ...pkg.dependencies, ...pkg.devDependencies }['@apexfusion/vector-mcp-shared'];
|
||||
// Depending on the package is fine — /tx is pure. Reaching /provider is not,
|
||||
// and the shared package's export map has no root export to smuggle it through.
|
||||
assert.ok(shared === undefined || typeof shared === 'string');
|
||||
});
|
||||
});
|
||||
14
packages/signer/tsconfig.json
Normal file
14
packages/signer/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*", "test/**/*"]
|
||||
}
|
||||
11
packages/signer/tsup.config.ts
Normal file
11
packages/signer/tsup.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
entry: ["src/index.ts"],
|
||||
format: ["esm"],
|
||||
target: "es2020",
|
||||
outDir: "build",
|
||||
clean: true,
|
||||
splitting: false,
|
||||
banner: { js: "#!/usr/bin/env node" },
|
||||
});
|
||||
Reference in New Issue
Block a user