diff --git a/.agents/skills/verify-tooltrust-scanner/SKILL.md b/.agents/skills/verify-tooltrust-scanner/SKILL.md index 389bd1d3..d6f0b484 100644 --- a/.agents/skills/verify-tooltrust-scanner/SKILL.md +++ b/.agents/skills/verify-tooltrust-scanner/SKILL.md @@ -81,3 +81,7 @@ If launch itself fails, invoke the same cleanup command and preserve build error `.github/workflows/verification.yml` runs `all`, `gaps` and `osv` for every pull request (including stacked bases) and pushes to main/master/dev. Each drive validates fingerprints first. Cleanup and evidence upload run even after failures; evidence is retained for 14 days. Build steps may download Go dependencies; runtime OSV requests use only the owned local fixture. No npm packages are downloaded and no user configuration is modified. Keep this skill and its feature map aligned with changes using `/maintain-verification-skill`. + +## Releases + +Use [features/release.md](features/release.md) before every version bump/tag/npm publication. `--feature release` runs packaging/version checks using an owned local binary; the separate `--published` recipe tests real release downloads and checksums. Node.js 22+ and npm are required. These tests use isolated caches and never publish packages themselves. diff --git a/.agents/skills/verify-tooltrust-scanner/features/README.md b/.agents/skills/verify-tooltrust-scanner/features/README.md index a8d6b763..12351ddc 100644 --- a/.agents/skills/verify-tooltrust-scanner/features/README.md +++ b/.agents/skills/verify-tooltrust-scanner/features/README.md @@ -16,6 +16,7 @@ Launch and doctor using the parent SKILL.md. Use a fresh, owned run directory an | mcp | [Use ToolTrust from an MCP client](mcp.md) | initialize, list, raw JSON scan, rules catalog | | gate | [Scan before adding a server](gate.md) | dry-run, policy block, forced project-config install | | osv | [Vulnerability service failures](osv.md) | Local HTTP success/error/timeout, partial failure, CLI and MCP propagation | +| release | [Release packaging](release.md) | Version alignment, packed npm install, MCP/error/cache behavior; published assets tested separately | ## Proof and gaps diff --git a/.agents/skills/verify-tooltrust-scanner/features/release.md b/.agents/skills/verify-tooltrust-scanner/features/release.md new file mode 100644 index 00000000..ff091532 --- /dev/null +++ b/.agents/skills/verify-tooltrust-scanner/features/release.md @@ -0,0 +1,7 @@ +# Release verification + +Before tagging, run `verify.py drive --run "$TT_VERIFY_RUN" --feature release` after launch. Node.js 22+, npm and Go are required. It checks VERSION, npm package version/binary pin and server.json, packs the actual npm tarball, enforces its file allowlist, installs it with scripts disabled into owned scratch state, and tests the packaged wrapper with the release-version binary. Assert initialization/tool listing, empty scan errors, OSV 503 errors, cache reuse and a failing exit for an invalid cached executable. The preflight seeds a freshly built binary and does not claim a real release download. + +After GitHub release assets exist, run `node scripts/verify-npm-release.js --published --run /absolute/fresh/owned/path`. This tests a cold download of the exact pinned release, checks both MCP/CLI SHA256 against platform checksum assets, verifies binary versions and CLI empty-input failure, then repeats wrapper/cache/error assertions. Release CI runs it on Linux, macOS and Windows. It needs GitHub network access. Evidence stays under the owned run's evidence directory; caches/install files are isolated by TOOLTRUST_CACHE_DIR and npm config/cache flags. + +Only publish npm after the exact release asset tests pass. Verify npm registry metadata/tarball after publication. Update Homebrew version/checksum only once the tag archive exists, and validate the formula PR. Release notes must explain changed exit semantics. Registry publication to the MCP catalog, real Claude installation and signing/provenance guarantees are NOT established by these checks. diff --git a/.agents/skills/verify-tooltrust-scanner/scripts/verify.py b/.agents/skills/verify-tooltrust-scanner/scripts/verify.py index 6ddf10ab..8823371b 100755 --- a/.agents/skills/verify-tooltrust-scanner/scripts/verify.py +++ b/.agents/skills/verify-tooltrust-scanner/scripts/verify.py @@ -35,7 +35,7 @@ def source_digest(): ).decode().split("\0") h = hashlib.sha256() for name in sorted(set(names)): - if name.endswith(".go") or name in ("go.mod", "go.sum", "VERSION"): + if name.endswith(".go") or name in ("go.mod", "go.sum", "VERSION", "npm/package.json", "npm/bin/run.js", "server.json", "scripts/verify-npm-release.js"): path = ROOT / name h.update(name.encode()) h.update(path.read_bytes() if path.exists() else b"DELETED") @@ -414,6 +414,12 @@ def do_POST(self): worker.join(timeout=5) save(self.evidence / "osv.http-requests.json", requests) + def release(self): + # npm uses owned config/cache; Go downloads are allowed during the build. + self.command("release-preflight", [shutil.which("node"), ROOT / "scripts/verify-npm-release.js", + "--run", self.runtime / "release", "--evidence", self.evidence / "release"], + env=os.environ.copy(), cwd=ROOT, timeout=300) + def cleanup(self): self.require_owned() artifacts = sorted(str(p.relative_to(self.evidence)) for p in self.evidence.rglob("*") if p.is_file()) @@ -429,7 +435,7 @@ def main(): p = argparse.ArgumentParser(description=__doc__) p.add_argument("action", choices=["launch", "doctor", "drive", "cleanup"]) p.add_argument("--run", required=True) - p.add_argument("--feature", choices=["static", "live", "source", "mcp", "gate", "all", "gaps", "osv"], default="all") + p.add_argument("--feature", choices=["static", "live", "source", "mcp", "gate", "all", "gaps", "osv", "release"], default="all") args = p.parse_args() run = Run(args.run) if args.action != "drive": diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1f9117a5..d66d9ec3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,6 +48,11 @@ jobs: go-version: "1.26.9" cache: true - run: go test -race -count=1 ./... + - uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Verify release versions and packed npm wrapper + run: node scripts/verify-npm-release.js --run "$RUNNER_TEMP/release-preflight" # ── 2. Cross-compile binaries for all target platforms ──────────────────── build: @@ -126,6 +131,7 @@ jobs: needs: [build] steps: + - uses: actions/checkout@v4 - name: Download all binary artifacts uses: actions/download-artifact@v4 with: @@ -140,6 +146,7 @@ jobs: uses: softprops/action-gh-release@v2 with: generate_release_notes: true + body_path: docs/releases/${{ github.ref_name }}.md files: | dist/tooltrust-scanner_* dist/tooltrust-mcp_* @@ -260,17 +267,21 @@ jobs: # Send a basic initialize request and check if it replies without crashing echo '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0.0"}}}' | $BIN_PATH + - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 - - - name: Test NPM Wrapper + node-version: 22 + - name: Verify exact packaged wrapper and released assets shell: bash - run: | - # Use npx to download and run the wrapper. We must use tooltrust-mcp - # Note: Since the release was just created, the binary is already available on GitHub. - npx -y tooltrust-mcp@latest --help + run: node scripts/verify-npm-release.js --published --run "$RUNNER_TEMP/release-distribution" + - name: Upload distribution evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: distribution-${{ matrix.goos }}-${{ matrix.goarch }} + path: ${{ runner.temp }}/release-distribution/evidence/ + retention-days: 14 # ── 6. Update Homebrew Formula ───────────────────────────────────────────── update-formula: diff --git a/.github/workflows/verification.yml b/.github/workflows/verification.yml index 315bcd41..6e007037 100644 --- a/.github/workflows/verification.yml +++ b/.github/workflows/verification.yml @@ -28,6 +28,9 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.12' + - uses: actions/setup-node@v4 + with: + node-version: 22 - name: Select owned run directory run: echo "TT_VERIFY_RUN=$RUNNER_TEMP/tooltrust-verification" >> "$GITHUB_ENV" - name: Build owned binaries and record fingerprints @@ -42,6 +45,9 @@ jobs: - name: Verify vulnerability service failure handling if: ${{ always() && steps.launch.outcome == 'success' }} run: python3 "$TT_VERIFY_SCRIPT" drive --run "$TT_VERIFY_RUN" --feature osv + - name: Verify release packaging and versions + if: ${{ always() && steps.launch.outcome == 'success' }} + run: python3 "$TT_VERIFY_SCRIPT" drive --run "$TT_VERIFY_RUN" --feature release - name: Clean owned runtime and retain evidence if: ${{ always() }} run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 009c3598..676edafa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). --- +## [0.3.20] - 2026-10-12 + +### Added +- Detect Rust MCP server presence for rmcp ServerHandler implementations and handwritten serde_json tools/list handlers. Ignore comments and string examples. This is presence detection, not complete Rust schema extraction. +- Run CLI/MCP user-path verification in CI, including pagination, empty discovery, OSV failures and release packaging checks. +- Refresh reviewed malicious-package intelligence accumulated since v0.3.19. + +### Fixed +- Empty tool manifests and live discovery now fail explicitly instead of receiving a clean A. Gate cannot bypass missing discovery with --force. +- Failed OSV lookups, invalid responses, timeouts and partial dependency lookup failures now report scan incomplete. MCP returns tool errors and gate refuses installation. +- Update the Go toolchain to 1.26.9 and golang.org/x/text for security fixes. +- Preserve npm wrapper failure exits when a cached binary cannot start, and support an isolated cache via TOOLTRUST_CACHE_DIR. +- Align npm tooltrust-mcp 1.1.13 and registry metadata with scanner v0.3.20; the previous npm wrapper still selected v0.3.17. + +### Upgrade notes +- Scripts must handle nonzero exit when discovery is empty or OSV is unavailable. No success report is produced for these incomplete scans; a pre-existing output file is not evidence of a successful new run. +- TOOLTRUST_OSV_URL can select a trusted OSV-compatible endpoint (HTTPS, or numeric loopback HTTP for testing). Dependency names and versions are sent to that endpoint. +- MCP HTTP transport, full Rust schema extraction and Cargo dependency scanning remain outside this release. + ## [0.3.19] - 2026-06-20 ### Added diff --git a/VERSION b/VERSION index 9ec8136c..f9a4b5f9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.19 +0.3.20 diff --git a/docs/releases/v0.3.20.md b/docs/releases/v0.3.20.md new file mode 100644 index 00000000..f19b92a4 --- /dev/null +++ b/docs/releases/v0.3.20.md @@ -0,0 +1,18 @@ +# ToolTrust Scanner v0.3.20 / npm tooltrust-mcp 1.1.13 + +### Added +- Detect Rust MCP server presence for rmcp ServerHandler implementations and handwritten serde_json tools/list handlers. Ignore comments and string examples. This is presence detection, not complete Rust schema extraction. +- Run CLI/MCP user-path verification in CI, including pagination, empty discovery, OSV failures and release packaging checks. +- Refresh reviewed malicious-package intelligence accumulated since v0.3.19. + +### Fixed +- Empty tool manifests and live discovery now fail explicitly instead of receiving a clean A. Gate cannot bypass missing discovery with --force. +- Failed OSV lookups, invalid responses, timeouts and partial dependency lookup failures now report scan incomplete. MCP returns tool errors and gate refuses installation. +- Update the Go toolchain to 1.26.9 and golang.org/x/text for security fixes. +- Preserve npm wrapper failure exits when a cached binary cannot start, and support an isolated cache via TOOLTRUST_CACHE_DIR. +- Align npm tooltrust-mcp 1.1.13 and registry metadata with scanner v0.3.20; the previous npm wrapper still selected v0.3.17. + +### Upgrade notes +- Scripts must handle nonzero exit when discovery is empty or OSV is unavailable. No success report is produced for these incomplete scans; a pre-existing output file is not evidence of a successful new run. +- TOOLTRUST_OSV_URL can select a trusted OSV-compatible endpoint (HTTPS, or numeric loopback HTTP for testing). Dependency names and versions are sent to that endpoint. +- MCP HTTP transport, full Rust schema extraction and Cargo dependency scanning remain outside this release. diff --git a/npm/README.md b/npm/README.md index 8f90efe6..c7e0a37a 100644 --- a/npm/README.md +++ b/npm/README.md @@ -87,3 +87,7 @@ Flagged Tools: - **Glama:** https://glama.ai/mcp/servers/AgentSafe-AI/tooltrust-scanner - **Directory:** https://www.tooltrust.dev/ - **Rules:** https://github.com/AgentSafe-AI/tooltrust-scanner/blob/main/docs/RULES.md + +## 1.1.13 + +Uses scanner v0.3.20, including Rust MCP presence detection and explicit failures for empty discovery or unavailable vulnerability checks. See the [release notes](https://github.com/AgentSafe-AI/tooltrust-scanner/releases/tag/v0.3.20). Set `TOOLTRUST_CACHE_DIR` to choose an isolated binary cache; by default it remains `~/.tooltrust-mcp/bin/`. diff --git a/npm/bin/run.js b/npm/bin/run.js index ca066473..eec6c614 100755 --- a/npm/bin/run.js +++ b/npm/bin/run.js @@ -61,9 +61,10 @@ function runBinary(binPath, args) { const child = spawn(binPath, args, { stdio: 'inherit' }); child.on('error', (err) => { console.error(`Failed to start subprocess: ${err}`); + process.exitCode = 1; }); - child.on('exit', (code) => { - process.exit(code || 0); + child.on('exit', (code, signal) => { + process.exit(code === null || signal ? 1 : code); }); } @@ -73,7 +74,7 @@ async function main() { const binaryVersion = pkg.tooltrustBinaryVersion || 'latest'; const cacheBinName = `${binaryVersion}_${releaseBinName}`; const downloadUrl = `https://github.com/AgentSafe-AI/tooltrust-scanner/releases/download/${binaryVersion}/${releaseBinName}`; - const cacheDir = path.join(os.homedir(), '.tooltrust-mcp', 'bin'); + const cacheDir = process.env.TOOLTRUST_CACHE_DIR || path.join(os.homedir(), '.tooltrust-mcp', 'bin'); const binPath = path.join(cacheDir, cacheBinName); // --version is the only flag handled by the wrapper (shows npm package version). @@ -90,7 +91,7 @@ async function main() { await downloadFile(downloadUrl, binPath); } catch (e) { console.error(`Error downloading binary: ${e.message}`); - console.error("Failed to download binary. If you are behind a proxy, try setting NODE_TLS_REJECT_UNAUTHORIZED=0 or download the binary manually from GitHub Releases."); + console.error("Failed to download binary. Check your network/proxy settings or download the binary manually from GitHub Releases."); process.exit(1); } } diff --git a/npm/package.json b/npm/package.json index b32d1588..411af0a7 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "tooltrust-mcp", - "version": "1.1.12", + "version": "1.1.13", "description": "MCP server that scans other MCP servers for prompt injection, data exfiltration, and privilege escalation. Add to your .mcp.json and let your AI agent audit its own tools.", "keywords": [ "mcp", @@ -21,5 +21,8 @@ "tooltrust-mcp": "bin/run.js" }, "mcpName": "io.github.AgentSafe-AI/tooltrust-scanner", - "tooltrustBinaryVersion": "v0.3.17" + "tooltrustBinaryVersion": "v0.3.20", + "files": [ + "bin/run.js" + ] } diff --git a/scripts/verify-npm-release.js b/scripts/verify-npm-release.js new file mode 100644 index 00000000..0a4b2e48 --- /dev/null +++ b/scripts/verify-npm-release.js @@ -0,0 +1,131 @@ +#!/usr/bin/env node +// Verifies the packed wrapper, not the source directory or npm@latest. +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const crypto = require('node:crypto'); +const https = require('node:https'); +const http = require('node:http'); +const { spawn } = require('node:child_process'); +const root = path.resolve(__dirname, '..'); +const argv = process.argv.slice(2); +const value = flag => argv[argv.indexOf(flag) + 1]; +assert(argv.includes('--run'), '--run must name a fresh owned directory'); +const runDir = path.resolve(value('--run')); +assert(!fs.existsSync(runDir), 'Never overwrite an existing verification run'); +fs.mkdirSync(runDir, { recursive: true }); +const evidence = argv.includes('--evidence') ? path.resolve(value('--evidence')) : path.join(runDir, 'evidence'); +fs.mkdirSync(evidence, { recursive: true }); +const published = argv.includes('--published'); +const transcript = []; +const env = { ...process.env, TOOLTRUST_CACHE_DIR: path.join(runDir, 'bin') }; +// npm operations must not use the user's credentials or cache. +for (const name of Object.keys(env)) if (/token|password|secret|npm_config/i.test(name)) delete env[name]; +for (const name of ['userconfig', 'globalconfig']) { + const file = path.join(runDir, name); + fs.writeFileSync(file, ''); + env['npm_config_' + name] = file; +} +env.npm_config_cache = path.join(runDir, 'npm-cache'); +const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; +async function command(label, executable, args, input, extraEnv = {}) { + return await new Promise((resolve, reject) => { + const child = spawn(executable, args, { cwd: root, env: { ...env, ...extraEnv }, shell: executable === 'npm.cmd', stdio: ['pipe', 'pipe', 'pipe'] }); + const entry = { label, executable, args, stdout: '', stderr: '' }; + child.stdout.on('data', d => entry.stdout += d); + child.stderr.on('data', d => entry.stderr += d); + child.on('error', reject); + const timer = setTimeout(() => child.kill(), 180000); + child.on('close', (code, signal) => { + clearTimeout(timer); entry.code = code; entry.signal = signal; transcript.push(entry); + fs.writeFileSync(path.join(evidence, 'release.commands.json'), JSON.stringify(transcript, null, 2)); + resolve(entry); + }); + child.stdin.on('error', () => {}); + child.stdin.end(input || ''); + }); +} +function ok(result) { assert.equal(result.code, 0, result.label + ': ' + result.stderr); return result; } +function sha(file) { return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'); } +async function download(url, redirects = 0) { + assert(redirects < 6, 'Too many redirects'); + return await new Promise((resolve, reject) => { + const request = https.get(url, response => { + if ([301, 302, 307, 308].includes(response.statusCode)) { + response.resume(); download(new URL(response.headers.location, url).href, redirects + 1).then(resolve, reject); return; + } + if (response.statusCode !== 200) { response.resume(); reject(new Error('Download status ' + response.statusCode)); return; } + const chunks = []; response.on('data', d => chunks.push(d)); response.on('end', () => resolve(Buffer.concat(chunks))); response.on('error', reject); + }); + request.setTimeout(60000, () => request.destroy(new Error('Download timed out'))); request.on('error', reject); + }); +} +async function main() { + const version = fs.readFileSync(path.join(root, 'VERSION'), 'utf8').trim(); + const pkg = JSON.parse(fs.readFileSync(path.join(root, 'npm/package.json'))); + const registry = JSON.parse(fs.readFileSync(path.join(root, 'server.json'))); + assert.equal(pkg.tooltrustBinaryVersion, 'v' + version); + assert.equal(registry.version, pkg.version); + assert.equal(registry.packages[0].version, pkg.version); + if (process.env.GITHUB_REF_TYPE === 'tag') assert.equal(process.env.GITHUB_REF_NAME, 'v' + version); + const os = { darwin: 'darwin', linux: 'linux', win32: 'windows' }[process.platform]; + const arch = { x64: 'amd64', arm64: 'arm64' }[process.arch]; + assert(os && arch, 'Unsupported verification platform'); + const suffix = os === 'windows' ? '.exe' : ''; + const binaryName = 'tooltrust-mcp_' + os + '_' + arch + suffix; + const binary = path.join(env.TOOLTRUST_CACHE_DIR, 'v' + version + '_' + binaryName); + const pack = JSON.parse(ok(await command('npm-pack', npm, ['pack', './npm', '--ignore-scripts', '--json', '--pack-destination', runDir])).stdout)[0]; + assert.deepEqual(pack.files.map(f => f.path).sort(), ['README.md', 'bin/run.js', 'package.json']); + const tarball = path.join(runDir, pack.filename); + ok(await command('npm-install-packed', npm, ['install', tarball, '--prefix', path.join(runDir, 'install'), '--ignore-scripts', '--no-audit', '--no-fund', '--package-lock=false', '--offline'])); + const wrapper = path.join(runDir, 'install/node_modules/tooltrust-mcp/bin/run.js'); + assert.equal(ok(await command('npm-version', process.execPath, [wrapper, '--version'])).stdout.trim(), pkg.version); + assert(!fs.existsSync(binary), '--version must not download a binary'); + fs.mkdirSync(env.TOOLTRUST_CACHE_DIR, { recursive: true }); + if (!published) ok(await command('build-versioned-mcp', 'go', ['build', '-ldflags', '-X main.version=v' + version, '-o', binary, './cmd/tooltrust-mcp'])); + const reqs = [ + { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'release-verification', version: '1' } } }, + { jsonrpc: '2.0', method: 'notifications/initialized' }, + { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }, + { jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'tooltrust_scanner_scan', arguments: { tools_json: '{"tools":[]}' } } }, + ].map(x => JSON.stringify(x)).join('\n') + '\n'; + const first = ok(await command('wrapper-rpc', process.execPath, [wrapper], reqs)); + const replies = new Map(first.stdout.trim().split('\n').map(JSON.parse).filter(x => x.id).map(x => [x.id, x])); + assert(replies.get(2).result.tools.some(t => t.name === 'tooltrust_scanner_scan')); + assert.equal(replies.get(3).result.isError, true); + assert(JSON.stringify(replies.get(3)).includes('scan incomplete')); + assert(ok(await command('binary-version', binary, ['--version'])).stdout.includes('v' + version)); + const digest = sha(binary); + const warm = ok(await command('wrapper-warm-cache', process.execPath, [wrapper], reqs)); + assert(!warm.stderr.includes('Downloading')); assert.equal(sha(binary), digest); + if (published) { + assert(first.stderr.includes('Downloading'), 'Cold cache must download the pinned release'); + const base = 'https://github.com/AgentSafe-AI/tooltrust-scanner/releases/download/v' + version + '/'; + const checksums = (await download(base + 'checksums_' + os + '_' + arch + '.txt')).toString(); + assert(checksums.split('\n').some(line => line.trim().split(/\s+/)[0] === digest && line.trim().split(/\s+/)[1] === binaryName), 'Downloaded MCP binary checksum mismatch'); + const cliName = 'tooltrust-scanner_' + os + '_' + arch + suffix; + const cli = path.join(runDir, cliName); fs.writeFileSync(cli, await download(base + cliName)); fs.chmodSync(cli, 0o755); + assert(checksums.split('\n').some(line => line.trim().split(/\s+/)[0] === sha(cli) && line.trim().split(/\s+/)[1] === cliName), 'CLI checksum mismatch'); + assert(ok(await command('released-cli-version', cli, ['version'])).stdout.includes('v' + version)); + const empty = path.join(runDir, 'empty.json'); fs.writeFileSync(empty, '{"tools":[]}'); + assert.notEqual((await command('released-cli-empty', cli, ['scan', '--input', empty])).code, 0); + } + // An invalid cached executable must not silently exit successfully. + fs.renameSync(binary, binary + '.saved'); fs.mkdirSync(binary); + assert.notEqual((await command('wrapper-spawn-error', process.execPath, [wrapper, '--help'])).code, 0); + fs.rmdirSync(binary); fs.renameSync(binary + '.saved', binary); + // Drive an actual OSV 503 through the packaged MCP wrapper. + const service = http.createServer((req, res) => { req.resume(); res.writeHead(503); res.end('unavailable'); }); + await new Promise(resolve => service.listen(0, '127.0.0.1', resolve)); + try { + const input = JSON.parse(reqs.trim().split('\n').at(-1)); + input.params.arguments.tools_json = JSON.stringify({ tools: [{ name: 'release_lookup', description: 'Read catalog', inputSchema: { type: 'object' }, metadata: { dependencies: [{ name: 'release-fixture', version: '1.0.0', ecosystem: 'npm' }] } }] }); + const rpc = reqs.trim().split('\n').slice(0, 2).join('\n') + '\n' + JSON.stringify(input) + '\n'; + const failed = ok(await command('wrapper-osv-unavailable', process.execPath, [wrapper], rpc, { TOOLTRUST_OSV_URL: 'http://127.0.0.1:' + service.address().port })); + const reply = failed.stdout.trim().split('\n').map(JSON.parse).find(x => x.id === 3); + assert(reply.result.isError && JSON.stringify(reply).includes('scan incomplete')); + } finally { await new Promise(resolve => service.close(resolve)); } + fs.writeFileSync(path.join(evidence, 'release.summary.json'), JSON.stringify({ status: 'PASS', scanner: version, npm: pkg.version, published, binary_sha256: digest, tarball_sha256: sha(tarball), tarball, checks: ['version alignment', 'package allowlist', 'packed install', 'MCP initialization/listing', 'empty scan', 'OSV failure', 'warm cache', 'spawn failure', ...(published ? ['cold download', 'asset checksums', 'released CLI'] : [])] }, null, 2)); + console.log('Release verification PASS (' + (published ? 'published assets' : 'local binary') + ')'); +} +main().catch(error => { fs.writeFileSync(path.join(evidence, 'release.failure.txt'), error.stack); console.error(error); process.exitCode = 1; }); diff --git a/server.json b/server.json index 9e2a8cb1..c5990fd1 100644 --- a/server.json +++ b/server.json @@ -7,12 +7,12 @@ "url": "https://github.com/AgentSafe-AI/tooltrust-scanner", "source": "github" }, - "version": "1.1.5", + "version": "1.1.13", "packages": [ { "registryType": "npm", "identifier": "tooltrust-mcp", - "version": "1.1.5", + "version": "1.1.13", "transport": { "type": "stdio" }