kodelyth-ecc 2.24.0 → 2.24.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  All notable changes to Kodelyth ECC are documented here.
4
4
 
5
+ ## v2.24.1 — verify Windows downloads before running them (October 2026)
6
+
7
+ **Security fix.** The Windows installer added in 2.23.0 downloaded a release zip
8
+ over HTTPS and extracted it straight into the user's PATH directory **with no
9
+ integrity check at all**.
10
+
11
+ This repository ships a `supply-chain-auditor` agent whose entire job is catching
12
+ "downloads and executes a binary without verifying it". The code doing it was
13
+ ours. Both upstream projects publish a `checksums.txt` in the same release, in
14
+ standard `sha256sum` format, and nothing was reading it.
15
+
16
+ ### What it does now
17
+
18
+ Fetches `checksums.txt` from the release, finds the line for the asset it just
19
+ downloaded, hashes the file with Node's `crypto`, and compares.
20
+
21
+ - **Mismatch is fatal.** It refuses to install and reports both hashes.
22
+ - **Missing or unlisted warns and proceeds.** The fetch is HTTPS from
23
+ github.com so transport tampering is already hard, and a release that stops
24
+ publishing checksums should not make ECC look broken. The case this control
25
+ exists for is a mismatch, and that stays fatal.
26
+
27
+ Hashing uses Node's `crypto` rather than shelling out, because `certutil`,
28
+ `sha256sum` and `shasum` differ in availability and output format across the
29
+ machines this has to run on.
30
+
31
+ ### Proven, not asserted
32
+
33
+ Checked against the real artifact before a single test was written: the genuine
34
+ RTK Windows asset hashes to its published sha256, and flipping one byte produces
35
+ a mismatch.
36
+
37
+ Nine tests cover a matching checksum, a single changed byte, an unlisted asset,
38
+ a malformed entry, picking the right line out of a full file, uppercase hex, and
39
+ that a filename merely *ending* with the asset name does not satisfy the lookup
40
+ (`evil-asset.zip` must not pass as `asset.zip`).
41
+
42
+ The Windows CI job asserts the gate is exercised on every push — a security
43
+ control nobody runs is decoration. From a real `windows-latest` runner:
44
+
45
+ ```
46
+ [win] checksum verified (sha256 1623e9b45d28…)
47
+ ```
48
+
49
+ **656 tests across 43 files.**
50
+
5
51
  ## v2.24.0 — the update notice now tells you why (October 2026)
6
52
 
7
53
  The CLI has always polled npm for new versions and cached the answer for 24
package/CLAUDE.md CHANGED
@@ -26,7 +26,7 @@ scripts/ → Node.js utilities: MCP server, dashboard, swarm, replay, router
26
26
  bundles/ → 3 power bundles (indie-hacker, red-team, enterprise)
27
27
  actions/ → GitHub Action (CI/CD integration for PR review)
28
28
  docs/ → Feature docs (arena.md, mcp.md, dashboard.md, swarm.md, replay.md, evolve.md, supply-chain.md)
29
- tests/ → 647 passing tests across 42 test files
29
+ tests/ → 656 passing tests across 43 test files
30
30
  ```
31
31
 
32
32
  ## Running Tests
package/README.md CHANGED
@@ -44,7 +44,7 @@ Google Antigravity, OpenCode, Cline, Roo Code, Aider, Kimi and Gemini CLI.
44
44
  [![npm downloads](https://img.shields.io/npm/dm/kodelyth-ecc.svg)](https://www.npmjs.com/package/kodelyth-ecc)
45
45
  [![GitHub Stars](https://img.shields.io/github/stars/sifxprime/kodelyth-ecc?style=social)](https://github.com/sifxprime/kodelyth-ecc/stargazers)
46
46
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
47
- ![Tests](https://img.shields.io/badge/tests-647%20passing-brightgreen)
47
+ ![Tests](https://img.shields.io/badge/tests-656%20passing-brightgreen)
48
48
  ![Claude Code](https://img.shields.io/badge/Claude%20Code-Ready-8A2BE2)
49
49
  ![Windsurf](https://img.shields.io/badge/Windsurf-Ready-06b6d4)
50
50
  ![Antigravity](https://img.shields.io/badge/Antigravity-Ready-00ADD8)
@@ -118,7 +118,7 @@ Most "AI agent kits" are folders of markdown files you have to remember the name
118
118
  | Quality hooks | ✅ 44 | Some | ❌ | ❌ |
119
119
  | IDE platforms | **11** (Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, Roo Code, Aider, Kimi, Gemini CLI) | 1-2 | 1 | Varies |
120
120
  | Telemetry | ❌ none | Varies | ❌ | Varies |
121
- | Test coverage | ✅ 647 tests | ❌ | ❌ | ❌ |
121
+ | Test coverage | ✅ 656 tests | ❌ | ❌ | ❌ |
122
122
  | Distributed via | `npx`, curl, clone | Manual | Manual | Manual |
123
123
 
124
124
  ---
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.24.0
1
+ 2.24.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kodelyth-ecc",
3
- "version": "2.24.0",
3
+ "version": "2.24.1",
4
4
  "description": "Production-grade AI coding toolkit — 70 agents (incl. devil-mode adversarial crew), 196 skills, 103 commands, parallel multi-agent commands, semantic intent routing, self-learning memory, and a built-in MCP server (16 tools / 6 prompts / 377 resources) that bridges to Claude Desktop, LangGraph, AutoGen, CrewAI, and OpenAI Agents SDK. Works on macOS, Linux and Windows with Claude Code, Windsurf, Cursor, Codex, Antigravity, OpenCode, Cline, RooCode, Aider, Kimi, and Gemini CLI.",
5
5
  "author": "Kodelyth <github.com/sifxprime>",
6
6
  "license": "MIT",
@@ -26,6 +26,7 @@
26
26
  const fs = require('fs');
27
27
  const os = require('os');
28
28
  const path = require('path');
29
+ const crypto = require('crypto');
29
30
  const { execFileSync, spawnSync } = require('child_process');
30
31
 
31
32
  /** Where ECC puts binaries it installs on Windows. User-writable, no admin. */
@@ -81,6 +82,29 @@ function installFromRelease({ repo, asset, binName, log = () => {} }) {
81
82
  return { installed: false, reason: `downloaded file is empty — ${url}` };
82
83
  }
83
84
 
85
+ // Verify the download before extracting, let alone running it.
86
+ //
87
+ // ECC ships a supply-chain-auditor agent whose whole job is catching
88
+ // "downloads and executes a binary with no integrity check" — and the first
89
+ // version of this file did exactly that. Both projects publish a
90
+ // checksums.txt in the same release, so there is no excuse.
91
+ const verdict = verifyChecksum({ repo, asset, file: zip, log });
92
+ if (verdict.status === 'mismatch') {
93
+ return {
94
+ installed: false,
95
+ reason: `checksum mismatch for ${asset} — expected ${verdict.expected}, got ${verdict.actual}. Refusing to install.`,
96
+ };
97
+ }
98
+ if (verdict.status === 'unavailable') {
99
+ // Warn rather than fail. The fetch is HTTPS from github.com, so transport
100
+ // tampering is already hard, and a release that simply stops publishing
101
+ // checksums should not make ECC look broken. A MISMATCH is still fatal —
102
+ // that is the case this check exists for.
103
+ log(`[win] WARNING: no checksum available for ${asset} (${verdict.reason}) — proceeding on HTTPS alone`);
104
+ } else {
105
+ log(`[win] checksum verified (sha256 ${verdict.expected.slice(0, 12)}…)`);
106
+ }
107
+
84
108
  log('[win] extracting…');
85
109
  const ex = spawnSync('tar', ['-xf', zip, '-C', dir], { stdio: 'inherit' });
86
110
  if (ex.status !== 0) {
@@ -154,4 +178,42 @@ function setxHint(dir) {
154
178
  return `setx PATH "%PATH%;${dir}"`;
155
179
  }
156
180
 
157
- module.exports = { installFromRelease, installDir, isOnUserPath, setxHint, findBinary };
181
+
182
+ /**
183
+ * Check a downloaded asset against the release's checksums.txt.
184
+ *
185
+ * Both upstreams publish the standard sha256sum format:
186
+ * <64 hex> <filename>
187
+ *
188
+ * Returns { status: 'ok' | 'mismatch' | 'unavailable' }. Hashing is done with
189
+ * Node's crypto rather than shelling out, because certutil, sha256sum and
190
+ * shasum all differ in availability and output format across the machines this
191
+ * has to run on.
192
+ */
193
+ function verifyChecksum({ repo, asset, file, log = () => {}, checksumText }) {
194
+ // checksumText is injectable so the parsing and comparison can be tested
195
+ // without reaching the network. Production callers never pass it.
196
+ let text = checksumText;
197
+ if (text === undefined) {
198
+ const url = `https://github.com/${repo}/releases/latest/download/checksums.txt`;
199
+ try {
200
+ text = execFileSync('curl', ['-fsSL', url], { encoding: 'utf8', timeout: 20000 });
201
+ } catch {
202
+ return { status: 'unavailable', reason: 'checksums.txt could not be fetched' };
203
+ }
204
+ }
205
+
206
+ const line = text.split('\n').find((l) => l.trim().endsWith(` ${asset}`) || l.trim().endsWith(` ${asset}`));
207
+ if (!line) return { status: 'unavailable', reason: `${asset} not listed in checksums.txt` };
208
+
209
+ const expected = (line.trim().split(/\s+/)[0] || '').toLowerCase();
210
+ if (!/^[0-9a-f]{64}$/.test(expected)) {
211
+ return { status: 'unavailable', reason: 'malformed checksum entry' };
212
+ }
213
+
214
+ const actual = crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
215
+ if (actual !== expected) return { status: 'mismatch', expected, actual };
216
+ return { status: 'ok', expected };
217
+ }
218
+
219
+ module.exports = { installFromRelease, installDir, isOnUserPath, setxHint, findBinary, verifyChecksum };