novahiz 0.3.4 → 0.3.5
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/adapters/opencode/novahiz.ts +5 -1
- package/dist/memory.js +17 -1
- package/install/bootstrap.mjs +4 -3
- package/install/install.mjs +4 -3
- package/package.json +1 -1
- package/src/memory.ts +19 -1
|
@@ -409,9 +409,13 @@ const GATE_TOOLS = new Set(
|
|
|
409
409
|
type RunResult = { status: number; stdout: string; stderr: string; spawnError?: string };
|
|
410
410
|
|
|
411
411
|
// C1: timeout prevents a hung CLI from freezing the whole OpenCode process.
|
|
412
|
+
// Raised 10 s -> 30 s (audit 2026-09-25, MEDIUM): gate/CLI runs legitimately
|
|
413
|
+
// exceeded 10 s on 23-24/09 and the cap turned them into 14 spurious
|
|
414
|
+
// fail-closed refusals. Still bounded, so a truly hung CLI cannot freeze
|
|
415
|
+
// OpenCode for more than 30 s.
|
|
412
416
|
// C2: maxBuffer caps output; oversized output is treated as a gate failure,
|
|
413
417
|
// never as truncated-then-allowed.
|
|
414
|
-
const RUN_TIMEOUT_MS =
|
|
418
|
+
const RUN_TIMEOUT_MS = 30_000;
|
|
415
419
|
const RUN_MAX_BUFFER = 1_048_576;
|
|
416
420
|
|
|
417
421
|
function run(args: string[], input?: string): RunResult {
|
package/dist/memory.js
CHANGED
|
@@ -390,6 +390,7 @@ export function listSlots(root) {
|
|
|
390
390
|
return ensureMemoryRoot(dir);
|
|
391
391
|
}
|
|
392
392
|
export function getSlot(id, root) {
|
|
393
|
+
assertSlotId(id);
|
|
393
394
|
const dir = root && root.length > 0 ? root : memoryRoot();
|
|
394
395
|
const index = ensureMemoryRoot(dir);
|
|
395
396
|
const meta = index.slots.find((slot) => slot.id === id);
|
|
@@ -423,6 +424,21 @@ export function rebuildIndex(root) {
|
|
|
423
424
|
slots.sort((a, b) => a.id.localeCompare(b.id));
|
|
424
425
|
return writeIndex(root, { version: 1, updated: nowIso(), slots });
|
|
425
426
|
}
|
|
427
|
+
const SLOT_ID_MAX_LEN = 128;
|
|
428
|
+
// Audit 2026-09-25, LOW: slot ids are index keys, not paths — they must never
|
|
429
|
+
// carry path segments. getSlot/parseSlotInput already refuse unknown ids via
|
|
430
|
+
// E_SLOT; this adds an explicit format check so a crafted id fails on its own
|
|
431
|
+
// terms (defense in depth, and a clearer error than "slot inconnu").
|
|
432
|
+
export function assertSlotId(id) {
|
|
433
|
+
const hasPathChar = [...id].some((ch) => {
|
|
434
|
+
const c = ch.charCodeAt(0);
|
|
435
|
+
return c === 47 || c === 92 || c === 0;
|
|
436
|
+
});
|
|
437
|
+
if (id.length === 0 || id.length > SLOT_ID_MAX_LEN || id.includes("..") || hasPathChar) {
|
|
438
|
+
throw memError("E_SLOT_ID", `Invalid params: slot id illégal: ${JSON.stringify(id)}`);
|
|
439
|
+
}
|
|
440
|
+
return id;
|
|
441
|
+
}
|
|
426
442
|
export function parseSlotInput(args) {
|
|
427
443
|
const title = typeof args?.title === "string" ? args.title : "";
|
|
428
444
|
const content = typeof args?.content === "string" ? args.content : "";
|
|
@@ -435,7 +451,7 @@ export function parseSlotInput(args) {
|
|
|
435
451
|
content,
|
|
436
452
|
description: typeof args?.description === "string" ? args.description : undefined,
|
|
437
453
|
tags: Array.isArray(args?.tags) ? args.tags.map(String) : undefined,
|
|
438
|
-
slotId: typeof args?.slotId === "string" ? args.slotId : undefined,
|
|
454
|
+
slotId: typeof args?.slotId === "string" ? assertSlotId(args.slotId) : undefined,
|
|
439
455
|
root: typeof args?.root === "string" ? args.root : undefined
|
|
440
456
|
};
|
|
441
457
|
}
|
package/install/bootstrap.mjs
CHANGED
|
@@ -87,7 +87,7 @@ function generateOpenCodeJson(configDir, NovahizHome) {
|
|
|
87
87
|
mcp: {
|
|
88
88
|
context7: {
|
|
89
89
|
type: "local",
|
|
90
|
-
command: ["context7-mcp", "--transport", "stdio"],
|
|
90
|
+
command: ["npx", "-y", "@upstash/context7-mcp@4.1.1", "--transport", "stdio"],
|
|
91
91
|
enabled: true,
|
|
92
92
|
},
|
|
93
93
|
narsil: {
|
|
@@ -100,7 +100,7 @@ function generateOpenCodeJson(configDir, NovahizHome) {
|
|
|
100
100
|
// (scheduler-mcp local venv); no npm install, no template entry.
|
|
101
101
|
playwright: {
|
|
102
102
|
type: "local",
|
|
103
|
-
command: ["npx", "@playwright/mcp@
|
|
103
|
+
command: ["npx", "-y", "@playwright/mcp@0.0.82", "--browser=msedge"],
|
|
104
104
|
enabled: true,
|
|
105
105
|
},
|
|
106
106
|
dart: {
|
|
@@ -189,10 +189,11 @@ async function main() {
|
|
|
189
189
|
log("");
|
|
190
190
|
log("Installing MCP servers...");
|
|
191
191
|
const mcpServers = [
|
|
192
|
-
{ pkg: "@upstash/context7-mcp", bin: "context7-mcp" },
|
|
193
192
|
{ pkg: "narsil-mcp", bin: "narsil-mcp" },
|
|
194
193
|
{ pkg: "security-mcp", bin: "security-mcp" },
|
|
195
194
|
];
|
|
195
|
+
// context7 is invoked via `npx -y @upstash/context7-mcp@4.1.1` (pinned in
|
|
196
|
+
// the generated config), so no global shim is installed for it.
|
|
196
197
|
// `cron` has no npm package: the plugin registers it from catalog/providers.json
|
|
197
198
|
// (scheduler-mcp local venv, see docs/PROVIDERS.md). Disabled by default in
|
|
198
199
|
// fresh configs — users opt in explicitly.
|
package/install/install.mjs
CHANGED
|
@@ -296,10 +296,11 @@ async function main() {
|
|
|
296
296
|
|
|
297
297
|
// Install MCP servers globally
|
|
298
298
|
const mcpServers = [
|
|
299
|
-
{ pkg: "@upstash/context7-mcp", bin: "context7-mcp", name: "context7" },
|
|
300
299
|
{ pkg: "narsil-mcp", bin: "narsil-mcp", name: "narsil" },
|
|
301
300
|
{ pkg: "security-mcp", bin: "security-mcp", name: "security" },
|
|
302
301
|
];
|
|
302
|
+
// context7 is invoked via `npx -y @upstash/context7-mcp@4.1.1` (pinned in
|
|
303
|
+
// the generated config), so no global shim is installed for it.
|
|
303
304
|
// `cron` has no npm package and ships disabled (local scheduler clone only);
|
|
304
305
|
// enable it after the local setup documented in docs/PROVIDERS.md.
|
|
305
306
|
|
|
@@ -393,7 +394,7 @@ async function main() {
|
|
|
393
394
|
"mcp": {
|
|
394
395
|
"context7": {
|
|
395
396
|
"type": "local",
|
|
396
|
-
"command": ["context7-mcp", "--transport", "stdio"],
|
|
397
|
+
"command": ["npx", "-y", "@upstash/context7-mcp@4.1.1", "--transport", "stdio"],
|
|
397
398
|
"enabled": true
|
|
398
399
|
},
|
|
399
400
|
"narsil": {
|
|
@@ -406,7 +407,7 @@ async function main() {
|
|
|
406
407
|
// after the local scheduler clone setup (docs/PROVIDERS.md).
|
|
407
408
|
"playwright": {
|
|
408
409
|
"type": "local",
|
|
409
|
-
"command": ["npx", "@playwright/mcp@
|
|
410
|
+
"command": ["npx", "-y", "@playwright/mcp@0.0.82", "--browser=msedge"],
|
|
410
411
|
"enabled": true
|
|
411
412
|
},
|
|
412
413
|
"dart": {
|
package/package.json
CHANGED
package/src/memory.ts
CHANGED
|
@@ -465,6 +465,7 @@ export function listSlots(root?: string): MemoryIndex {
|
|
|
465
465
|
}
|
|
466
466
|
|
|
467
467
|
export function getSlot(id: string, root?: string): SlotFile {
|
|
468
|
+
assertSlotId(id);
|
|
468
469
|
const dir = root && root.length > 0 ? root : memoryRoot();
|
|
469
470
|
const index = ensureMemoryRoot(dir);
|
|
470
471
|
const meta = index.slots.find((slot) => slot.id === id);
|
|
@@ -499,6 +500,23 @@ export function rebuildIndex(root: string): MemoryIndex {
|
|
|
499
500
|
return writeIndex(root, { version: 1, updated: nowIso(), slots });
|
|
500
501
|
}
|
|
501
502
|
|
|
503
|
+
const SLOT_ID_MAX_LEN = 128;
|
|
504
|
+
|
|
505
|
+
// Audit 2026-09-25, LOW: slot ids are index keys, not paths — they must never
|
|
506
|
+
// carry path segments. getSlot/parseSlotInput already refuse unknown ids via
|
|
507
|
+
// E_SLOT; this adds an explicit format check so a crafted id fails on its own
|
|
508
|
+
// terms (defense in depth, and a clearer error than "slot inconnu").
|
|
509
|
+
export function assertSlotId(id: string): string {
|
|
510
|
+
const hasPathChar = [...id].some((ch) => {
|
|
511
|
+
const c = ch.charCodeAt(0);
|
|
512
|
+
return c === 47 || c === 92 || c === 0;
|
|
513
|
+
});
|
|
514
|
+
if (id.length === 0 || id.length > SLOT_ID_MAX_LEN || id.includes("..") || hasPathChar) {
|
|
515
|
+
throw memError("E_SLOT_ID", `Invalid params: slot id illégal: ${JSON.stringify(id)}`);
|
|
516
|
+
}
|
|
517
|
+
return id;
|
|
518
|
+
}
|
|
519
|
+
|
|
502
520
|
export function parseSlotInput(args: Record<string, unknown>): WriteEntryInput {
|
|
503
521
|
const title = typeof args?.title === "string" ? args.title : "";
|
|
504
522
|
const content = typeof args?.content === "string" ? args.content : "";
|
|
@@ -509,7 +527,7 @@ export function parseSlotInput(args: Record<string, unknown>): WriteEntryInput {
|
|
|
509
527
|
content,
|
|
510
528
|
description: typeof args?.description === "string" ? args.description : undefined,
|
|
511
529
|
tags: Array.isArray(args?.tags) ? args.tags.map(String) : undefined,
|
|
512
|
-
slotId: typeof args?.slotId === "string" ? args.slotId : undefined,
|
|
530
|
+
slotId: typeof args?.slotId === "string" ? assertSlotId(args.slotId) : undefined,
|
|
513
531
|
root: typeof args?.root === "string" ? args.root : undefined
|
|
514
532
|
};
|
|
515
533
|
}
|