kodelyth-ecc 2.4.2 → 2.4.4
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 +57 -0
- package/VERSION +1 -1
- package/hooks/hooks.json +2 -2
- package/hooks/safety/prompt-injection-guard.js +7 -2
- package/package.json +1 -1
- package/scripts/memory/store.js +34 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,63 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Kodelyth ECC are documented here.
|
|
4
4
|
|
|
5
|
+
## v2.4.4 — Real-user audit: prompt-injection guard was a silent no-op (July 2026)
|
|
6
|
+
|
|
7
|
+
Phase 1 of the real-user audit — firing every hook with realistic input instead of fixtures. Found a second "installed but dummy" feature.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Prompt-injection guard defaulted to `MODE=off`** — so on every fresh install the hook was registered, fired on each prompt, and did **absolutely nothing** unless the user knew to set `KODELYTH_PI_GUARD=warn`. A safety feature nobody knew to turn on is a safety feature that doesn't exist.
|
|
12
|
+
- **Fix**: default is now `warn` — the guard scans every prompt + tool response and surfaces injection findings to stderr (visible in transcript), but **never blocks** (exit 0 always). Measured **0 false positives** across 8 realistic legit prompts containing trigger-adjacent words ("ignore", "override", "system prompt", "bypass", "reveal"). Set `KODELYTH_PI_GUARD=off` to silence, `=block` to hard-block critical patterns (still opt-in, since block can halt on false positives).
|
|
13
|
+
|
|
14
|
+
### Verified (real-user audit, this round)
|
|
15
|
+
|
|
16
|
+
Fired all 8 hooks with realistic Claude Code payloads:
|
|
17
|
+
|
|
18
|
+
| Hook | Result |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `inject-start` | ✓ outputs memory context block |
|
|
21
|
+
| `read-lessons` | ✓ exit 0 |
|
|
22
|
+
| `auto-recall` | ✓ recalls 3 real memories (post-2.4.3 fix) |
|
|
23
|
+
| `capture-stop` | ✓ exit 0 |
|
|
24
|
+
| `capture-correction` | ✓ exit 0 |
|
|
25
|
+
| `auto-resolve` | ✓ exit 0 |
|
|
26
|
+
| `prompt-injection-guard` | ✗→✓ was silent no-op, now warns by default |
|
|
27
|
+
| `token-budget` | ✓ exit 0 |
|
|
28
|
+
|
|
29
|
+
Also verified on real data: `route` (correctly classified a production outage as hard→opus), `evolve analyze` (scanned 195 reuse + 3 miss signals → 5 proposals).
|
|
30
|
+
|
|
31
|
+
### Tests
|
|
32
|
+
|
|
33
|
+
- Updated `mode=off` test to set the env var explicitly (default is no longer off)
|
|
34
|
+
- Added 2 tests: default-no-env warns on injection + stays silent on legit prompts
|
|
35
|
+
- Full suite: 0 failures across 25 files
|
|
36
|
+
|
|
37
|
+
## v2.4.3 — Memory recall crash fix (the "still dummy" bug) (July 2026)
|
|
38
|
+
|
|
39
|
+
Found by testing ECC as a real user actually experiences it, not just in the test harness. **Memory recall was silently crashing on every prompt** for any user whose `~/.kodelythecc/memory/index.json` was written by an older/foreign BM25 schema.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **`scripts/memory/store.js` — recall threw `Cannot read properties of undefined (reading '<token>')` on every UserPromptSubmit** when the on-disk `index.json` used the schema `{k1, b, corpusStats, index, documents, docFreq}` (from a prior BM25 implementation) instead of the current `{tokens, docCount, avgDocLength, totalLength}`. `search()` read `index.tokens[token]` where `index.tokens` was undefined.
|
|
44
|
+
- **Root cause was a two-headed bug**:
|
|
45
|
+
1. `loadIndex()` blindly returned whatever JSON was on disk, with no schema validation
|
|
46
|
+
2. The canonical `rebuildIndex()` correctly rebuilt + saved a valid index but **returned `{count}` instead of the index object**
|
|
47
|
+
- **Fix**: `loadIndex()` now validates the schema via `isValidIndexSchema()` and, on any mismatch/corruption, rebuilds from the append-only `memories.jsonl` (the source of truth). `rebuildIndex()` now returns the rebuilt index (with a `count` property preserved for its other caller). Recall self-heals on first call — no crash, no manual `index.json` deletion needed.
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- **Regression test** in `tests/memory/store.test.js` — writes a foreign-schema `index.json`, asserts `recall()` does not throw and returns results, and asserts the on-disk index is rebuilt to the valid schema. This class of bug can no longer ship silently.
|
|
52
|
+
|
|
53
|
+
### Impact
|
|
54
|
+
|
|
55
|
+
- Every real user with a stale index had **zero working memory recall** — the hook fired, errored, and returned nothing. Auto-recall now works end-to-end.
|
|
56
|
+
- Verified live: corrupted the index with the foreign schema, ran both `store.recall()` and the actual `hooks/memory/auto-recall.js` hook — both self-heal and return real Stripe/CORS memories.
|
|
57
|
+
|
|
58
|
+
### Why this matters
|
|
59
|
+
|
|
60
|
+
This is exactly the "is it real or is it dummy" gap. Files were installed, hooks were registered, rules loaded — but one core feature crashed on real input while passing every existing test. The lesson: test the installed experience with real data, not just unit fixtures.
|
|
61
|
+
|
|
5
62
|
## v2.4.2 — SEO-massive documentation refresh (July 2026)
|
|
6
63
|
|
|
7
64
|
Full documentation rewrite for website-ready SEO. Every doc now ships with YAML frontmatter (title, description, keywords, Open Graph, Twitter card, canonical URL, last_updated, version, category). Six new feature docs cover subsystems shipped since v2.0 that were undocumented until now.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.4.
|
|
1
|
+
2.4.4
|
package/hooks/hooks.json
CHANGED
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
"timeout": 3
|
|
164
164
|
}
|
|
165
165
|
],
|
|
166
|
-
"description": "Kodelyth Safety: scan user prompts for jailbreak / instruction-override patterns.
|
|
166
|
+
"description": "Kodelyth Safety: scan user prompts for jailbreak / instruction-override patterns. Scans by default in warn mode (logs, never blocks). KODELYTH_PI_GUARD=off to silence, =block to hard-block critical.",
|
|
167
167
|
"id": "kodelyth:user-prompt:prompt-injection-guard"
|
|
168
168
|
}
|
|
169
169
|
],
|
|
@@ -387,7 +387,7 @@
|
|
|
387
387
|
"timeout": 3
|
|
388
388
|
}
|
|
389
389
|
],
|
|
390
|
-
"description": "Kodelyth Safety: scan Read/WebFetch/MCP tool responses for indirect prompt-injection.
|
|
390
|
+
"description": "Kodelyth Safety: scan Read/WebFetch/MCP tool responses for indirect prompt-injection. Scans by default in warn mode (logs, never blocks). KODELYTH_PI_GUARD=off to silence, =block to hard-block critical.",
|
|
391
391
|
"id": "kodelyth:post-tool:prompt-injection-guard"
|
|
392
392
|
}
|
|
393
393
|
],
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// injection from external content)
|
|
11
11
|
//
|
|
12
12
|
// Modes (env var KODELYTH_PI_GUARD):
|
|
13
|
-
// off — never run
|
|
13
|
+
// off — never run
|
|
14
14
|
// warn — exit 0 always; print findings to stderr (visible in transcript)
|
|
15
15
|
// block — exit 2 (block) on critical; warn on high; pass on medium
|
|
16
16
|
//
|
|
@@ -28,7 +28,12 @@ const path = require('path');
|
|
|
28
28
|
|
|
29
29
|
const { scan, maxSeverity } = require('./lib/patterns');
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
// Default is 'warn' (v2.4.4+): the guard scans and surfaces findings to stderr
|
|
32
|
+
// but NEVER blocks — exit 0 always. This makes the safety feature actually run
|
|
33
|
+
// on a fresh install instead of being a silent no-op. Measured 0 false positives
|
|
34
|
+
// on realistic legit prompts. Set KODELYTH_PI_GUARD=off to silence entirely, or
|
|
35
|
+
// =block to hard-block on critical patterns (opt-in, can halt on false positives).
|
|
36
|
+
const MODE = String(process.env.KODELYTH_PI_GUARD || 'warn').toLowerCase();
|
|
32
37
|
const MAX_INPUT = Number(process.env.KODELYTH_PI_GUARD_MAX_INPUT || 20000);
|
|
33
38
|
const LOG_PATH = process.env.KODELYTH_PI_GUARD_LOG || null;
|
|
34
39
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kodelyth-ecc",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.4",
|
|
4
4
|
"description": "Production-grade AI coding toolkit — 70 agents (incl. devil-mode adversarial crew), 194 skills, 97 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 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",
|
package/scripts/memory/store.js
CHANGED
|
@@ -72,15 +72,37 @@ function newMemoryId() {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// ── Index ────────────────────────────────────────────────────────────────────
|
|
75
|
+
function emptyIndex() {
|
|
76
|
+
return { tokens: {}, docCount: 0, avgDocLength: 0, totalLength: 0 };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// A valid index for the current schema MUST have a `tokens` object and a
|
|
80
|
+
// numeric `docCount`. Older/foreign schemas (e.g. `{index, documents, docFreq}`
|
|
81
|
+
// from a prior BM25 implementation) are unrecognised — we rebuild from the
|
|
82
|
+
// append-only memories.jsonl rather than crash on `index.tokens[token]`.
|
|
83
|
+
function isValidIndexSchema(idx) {
|
|
84
|
+
return !!idx
|
|
85
|
+
&& typeof idx === 'object'
|
|
86
|
+
&& idx.tokens && typeof idx.tokens === 'object'
|
|
87
|
+
&& typeof idx.docCount === 'number';
|
|
88
|
+
}
|
|
89
|
+
|
|
75
90
|
function loadIndex() {
|
|
76
91
|
if (!fs.existsSync(PATHS.index)) {
|
|
77
|
-
return
|
|
92
|
+
return rebuildIndex();
|
|
78
93
|
}
|
|
94
|
+
let parsed;
|
|
79
95
|
try {
|
|
80
|
-
|
|
96
|
+
parsed = JSON.parse(fs.readFileSync(PATHS.index, 'utf8'));
|
|
81
97
|
} catch {
|
|
82
|
-
return
|
|
98
|
+
return rebuildIndex();
|
|
99
|
+
}
|
|
100
|
+
if (!isValidIndexSchema(parsed)) {
|
|
101
|
+
// Stale or foreign schema on disk — rebuild from the source of truth
|
|
102
|
+
// (the canonical rebuildIndex defined below rebuilds AND persists a valid index).
|
|
103
|
+
return rebuildIndex();
|
|
83
104
|
}
|
|
105
|
+
return parsed;
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
function saveIndex(index) {
|
|
@@ -360,14 +382,19 @@ function autoResolveOnEdit(filePath, projectRoot = null) {
|
|
|
360
382
|
return resolved;
|
|
361
383
|
}
|
|
362
384
|
|
|
385
|
+
// Rebuild the inverted index from memories.jsonl, persist it, and RETURN the
|
|
386
|
+
// index object (with a `count` property for callers that want the memory total).
|
|
387
|
+
// Returning the index lets loadIndex() self-heal a stale/foreign on-disk schema
|
|
388
|
+
// in a single pass instead of returning a bare {count} that breaks search().
|
|
363
389
|
function rebuildIndex() {
|
|
364
390
|
const memories = readMemories();
|
|
365
|
-
let index =
|
|
391
|
+
let index = emptyIndex();
|
|
366
392
|
for (const m of memories) {
|
|
367
|
-
index = indexMemory(index, m);
|
|
393
|
+
if (m && m.id) index = indexMemory(index, m);
|
|
368
394
|
}
|
|
369
|
-
saveIndex(index);
|
|
370
|
-
|
|
395
|
+
try { saveIndex(index); } catch { /* read-only fs — keep in-memory index */ }
|
|
396
|
+
index.count = memories.length;
|
|
397
|
+
return index;
|
|
371
398
|
}
|
|
372
399
|
|
|
373
400
|
function stats() {
|