@jagilber-org/index-server 1.27.2 → 1.28.0

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/CONTRIBUTING.md +3 -3
  3. package/dist/dashboard/client/admin.html +28 -28
  4. package/dist/dashboard/client/js/admin.feedback.js +1 -1
  5. package/dist/dashboard/client/js/admin.instructions.js +1 -1
  6. package/dist/dashboard/security/SecurityMonitor.js +2 -2
  7. package/dist/dashboard/server/AdminPanelState.js +5 -1
  8. package/dist/dashboard/server/ApiRoutes.js +2 -1
  9. package/dist/dashboard/server/MetricsCollector.js +3 -2
  10. package/dist/dashboard/server/WebSocketManager.js +2 -2
  11. package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
  12. package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +1 -1
  13. package/dist/dashboard/server/routes/api.usage.routes.js +5 -1
  14. package/dist/dashboard/server/routes/instructions.routes.js +142 -12
  15. package/dist/dashboard/server/routes/scripts.routes.js +1 -1
  16. package/dist/dashboard/server/routes/sqlite.routes.js +74 -0
  17. package/dist/models/instruction.d.ts +1 -1
  18. package/dist/schemas/index.d.ts +1 -1
  19. package/dist/schemas/index.js +1 -1
  20. package/dist/services/auditLog.d.ts +1 -1
  21. package/dist/services/auditLog.js +1 -1
  22. package/dist/services/feedbackStorage.js +1 -1
  23. package/dist/services/handlers/instructions.add.js +36 -3
  24. package/dist/services/handlers.feedback.js +1 -1
  25. package/dist/services/handlers.instructionSchema.js +4 -4
  26. package/dist/services/handlers.search.js +3 -3
  27. package/dist/services/instructionRecordValidation.d.ts +3 -0
  28. package/dist/services/instructionRecordValidation.js +64 -4
  29. package/dist/services/seedBootstrap.js +2 -2
  30. package/dist/services/toolRegistry.js +8 -8
  31. package/dist/services/toolRegistry.zod.js +3 -3
  32. package/dist/versioning/schemaVersion.d.ts +1 -1
  33. package/dist/versioning/schemaVersion.js +47 -2
  34. package/package.json +44 -40
  35. package/schemas/index-server.code-schema.json +1 -1
  36. package/schemas/instruction.schema.json +3 -3
  37. package/schemas/json-schema/instruction-content-type.schema.json +1 -1
  38. package/schemas/json-schema/instruction-instruction-entry.schema.json +1 -1
  39. package/scripts/README.md +48 -0
  40. package/scripts/{generate-certs.mjs → build/generate-certs.mjs} +1 -1
  41. package/scripts/{setup-wizard.mjs → build/setup-wizard.mjs} +1 -1
  42. package/scripts/{setup-hooks.cjs → hooks/setup-hooks.cjs} +3 -3
  43. package/server.json +2 -2
  44. /package/scripts/{copy-dashboard-assets.mjs → build/copy-dashboard-assets.mjs} +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,40 @@ The format is based on Keep a Changelog and this project adheres to Semantic Ver
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ### Added
10
+
11
+ - **Release workflow**: added `scripts/Invoke-ReleaseWorkflow.ps1` as the canonical release/publish front door. It loads `.env`, resolves tag/remote/clean-room defaults, runs release preflight checks, optionally pushes and verifies the internal branch/tags, waits for internal GitHub Actions checks, prepares the clean-room mirror copy, and either prints or explicitly invokes the human-only public mirror delivery path.
12
+
13
+ ### Changed
14
+
15
+ - **Publish scripts**: restored root-level entrypoints for `New-CleanRoomCopy.ps1`, `Publish-ToMirror.ps1`, and `publish-direct-to-remote.cjs` after the scripts reorganization so documented/template paths continue to work without introducing alternate release workflow names.
16
+ - **Public publish/security gates**: clean-room publish now keeps ambient exact env-value leak detection active, GitHub Action scanner uploads fail visibly instead of being hidden, ggshield quota exhaustion fails closed, and internal release branch/tag verification compares exact local/remote SHAs.
17
+ - **Tool classification docs**: clarified that registry `stable` vs `mutation` is a visibility/gating contract, so `feedback_submit` remains stable/core while still persisting feedback and audit entries.
18
+ - **Instruction content type migration**: bumped instruction schema version to v5 and added a compatibility path mapping legacy `contentType: "chat-session"` records and writes to `workflow`, preserving workflow/runbook semantics instead of falling back to `instruction`.
19
+
20
+ ## [1.28.0] - 2026-05-04
21
+
22
+ ### Added
23
+
24
+ - **Integrity probes**: pre-push hook runs 12-probe mutation integrity test and 4-client concurrent SQLite write test before every push (`scripts/hooks/pre-push-integrity.ps1`). Skip with `SKIP_INTEGRITY_PREPUSH=1`.
25
+ - **Adhoc diagnostics**: disk-state monitor (`scripts/diagnostics/adhoc-disk-state-monitor.mjs`), mutation integrity probe (`scripts/diagnostics/adhoc-mutation-integrity.mjs`), and concurrent write test (`scripts/perf/adhoc-concurrent-integrity.mjs`) for ad-hoc and CI use.
26
+ - **SQLite validation endpoint**: `/sqlite/validate` dashboard route and client script (`scripts/diagnostics/sqlite-validate.ps1`) for on-demand WAL integrity checks.
27
+ - **Enum validation in migration**: `migrateInstructionRecord()` now validates all 6 enum fields (audience, requirement, contentType, status, priorityTier, classification) with legacy coercion maps aligned with the loader, falling back to safe defaults for truly unknown values.
28
+ - **Negative enum tests**: 19 unit tests covering input-surface rejection and migration correction of invalid enum values.
29
+
30
+ ### Fixed
31
+
32
+ - **Lax mode defaults** (`instructions.add`): `contentType` now defaults to `"instruction"` in lax mode so agents omitting it no longer fail silently (#312).
33
+ - **Enum coercion ordering**: legacy enum values (e.g., `audience:"developers"`, `requirement:"SHOULD"`) are now coerced before validation instead of being rejected at the input surface (#312).
34
+ - **Scripts reorg paths**: fixed `$PSScriptRoot` references across 10+ PowerShell scripts after `scripts/` subdirectory reorganization (#311). Fixed Dockerfile, `.dockerignore`, `sync-constitution.ps1`, and `setup-wizard-config-validate.mjs` paths.
35
+ - **PII scanner self-detection**: added pre-commit hook files to `PII_FILE_ALLOWLIST` so SAS-token regex definitions aren't flagged as leaks.
36
+ - **Constitution sync**: regenerated `constitution.md` and `.specify/memory/constitution.md` from `constitution.json`.
37
+
38
+ ### Changed
39
+
40
+ - **Scripts reorganization**: all scripts moved from `scripts/` root into purpose-based subdirectories (`build/`, `client/`, `deploy/`, `diagnostics/`, `governance/`, `hooks/`, `migration/`, `perf/`, `testing/`, `ci/`) (#311).
41
+ - **Security hardening**: input validation hardening for `index_add` (#307), security triage blockers (#309), CodeQL and Trivy dependency bumps (#298, #299).
42
+
9
43
  ## [1.27.2] - 2026-05-01
10
44
 
11
45
  ### Fixed
@@ -105,7 +139,7 @@ Also removed: `DashboardHttpConfig.rateLimitEnabled / rateLimitWindowMs / rateLi
105
139
 
106
140
  ### Removed (breaking — MCP feedback surface)
107
141
 
108
- - **MCP feedback tools collapsed to `feedback_submit` only (#111)**. The following MCP tools have been removed with no deprecation alias and no compatibility shim: `feedback_list`, `feedback_get`, `feedback_update`, `feedback_delete`, `feedback_stats`, `feedback_health`, `feedback_dispatch`. Agents must use `feedback_submit` to file entries. Human-operator CRUD now lives behind dashboard authentication at `GET/POST /admin/feedback`, `GET/PATCH/DELETE /admin/feedback/:id`, sharing the same `feedback/feedback-entries.json` store as the MCP submit path. GitHub issue handoff from the dashboard is browser-side, human-triggered, token-free, and targets `jagilber-org/index-server`. Spec: `specs/111-feedback-mcp-rip-down.md`.
142
+ - **MCP feedback tools collapsed to `feedback_submit` only (#111)**. The following MCP tools have been removed with no deprecation alias and no compatibility shim: `feedback_list`, `feedback_get`, `feedback_update`, `feedback_delete`, `feedback_stats`, `feedback_health`, `feedback_dispatch`. Agents must use the stable, core-visible `feedback_submit` tool to file entries. Human-operator CRUD now lives behind dashboard authentication at `GET/POST /admin/feedback`, `GET/PATCH/DELETE /admin/feedback/:id`, sharing the same `feedback/feedback-entries.json` store as the MCP submit path. GitHub issue handoff from the dashboard is browser-side, human-triggered, token-free, and targets `jagilber-org/index-server`. Spec: `specs/111-feedback-mcp-rip-down.md`.
109
143
 
110
144
  ## [1.24.0] - 2026-04-24
111
145
 
package/CONTRIBUTING.md CHANGED
@@ -39,14 +39,14 @@ If any code or tests in the PR were generated or materially edited by an AI agen
39
39
  - [ ] Claimed test counts match real coverage and assertions
40
40
  - [ ] No hardcoded success values such as `verified: true` or `created: true` were added unless computed from a real check
41
41
  - [ ] Agent attestation metadata present on agent-authored commits (AG-4)
42
- - [ ] Copilot `Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>` trailer present on Copilot-authored commits
42
+ - [ ] Copilot `Co-authored-by: Copilot <copilot-noreply-email>` trailer present on Copilot-authored commits
43
43
  - [ ] Tests added for behavioral changes (unit + integration where the change crosses a layer boundary)
44
44
  - [ ] No hardcoded paths or secrets — all tunables routed through `src/config/runtimeConfig.ts` (`npm run guard:env` passes)
45
45
  - [ ] `logAudit()` invoked on every mutation success and side-effecting error path
46
46
  - [ ] `src/services/toolRegistry.ts` updated for new/changed tools (INPUT_SCHEMAS + STABLE + MUTATION + describeTool) and registered via `src/services/toolHandlers.ts`
47
47
  - [ ] Conventional Commit subjects on every commit
48
- - [ ] If `constitution.json` changed: `pwsh -File scripts/sync-constitution.ps1 -Check` passes and rendered `.md` files are regenerated
49
- - [ ] Security-sensitive changes carry explicit Morpheus + Tank consensus approvals or a linked `.squad/decisions/` record
48
+ - [ ] If `constitution.json` changed: `pwsh -File scripts/build/sync-constitution.ps1 -Check` passes and rendered `.md` files are regenerated
49
+ - [ ] Security-sensitive changes carry explicit security + testing consensus approvals or a linked decision record
50
50
  - [ ] Imported external code has a documented source and a `LICENSE`-compatible license, with attribution recorded in `THIRD-PARTY-LICENSES.md` where required
51
51
 
52
52
  PRs that fail any AI-generated code/test review item must be sent back for correction before merge.
@@ -1,30 +1,30 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
- <meta name="dashboard-build-version" content="1.27.2-d7f5ec0e">
4
+ <meta name="dashboard-build-version" content="1.28.0-0009101e">
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>Index Server Admin</title>
8
- <link rel="stylesheet" href="css/admin.css?v=1.27.2-d7f5ec0e">
9
- <script defer src="js/admin.utils.js?v=1.27.2-d7f5ec0e"></script>
10
- <script defer src="js/admin.auth.js?v=1.27.2-d7f5ec0e"></script>
11
- <script defer src="js/admin.overview.js?v=1.27.2-d7f5ec0e"></script>
12
- <script defer src="js/admin.sessions.js?v=1.27.2-d7f5ec0e"></script>
13
- <script defer src="js/admin.monitor.js?v=1.27.2-d7f5ec0e"></script>
14
- <script defer src="js/admin.events.js?v=1.27.2-d7f5ec0e"></script>
15
- <script defer src="js/admin.graph.js?v=1.27.2-d7f5ec0e"></script>
8
+ <link rel="stylesheet" href="css/admin.css?v=1.28.0-0009101e">
9
+ <script defer src="js/admin.utils.js?v=1.28.0-0009101e"></script>
10
+ <script defer src="js/admin.auth.js?v=1.28.0-0009101e"></script>
11
+ <script defer src="js/admin.overview.js?v=1.28.0-0009101e"></script>
12
+ <script defer src="js/admin.sessions.js?v=1.28.0-0009101e"></script>
13
+ <script defer src="js/admin.monitor.js?v=1.28.0-0009101e"></script>
14
+ <script defer src="js/admin.events.js?v=1.28.0-0009101e"></script>
15
+ <script defer src="js/admin.graph.js?v=1.28.0-0009101e"></script>
16
16
  <script defer src="js/marked.umd.js"></script>
17
- <script defer src="js/admin.instructions.js?v=1.27.2-d7f5ec0e"></script>
18
- <script defer src="js/admin.logs.js?v=1.27.2-d7f5ec0e"></script>
19
- <script defer src="js/admin.maintenance.js?v=1.27.2-d7f5ec0e"></script>
20
- <script defer src="js/admin.config.js?v=1.27.2-d7f5ec0e"></script>
21
- <script defer src="js/admin.performance.js?v=1.27.2-d7f5ec0e"></script>
22
- <script defer src="js/admin.instances.js?v=1.27.2-d7f5ec0e"></script>
23
- <script defer src="js/admin.embeddings.js?v=1.27.2-d7f5ec0e"></script>
24
- <script defer src="js/admin.messaging.js?v=1.27.2-d7f5ec0e"></script>
25
- <script defer src="js/admin.sqlite.js?v=1.27.2-d7f5ec0e"></script>
26
- <script defer src="js/admin.boot.js?v=1.27.2-d7f5ec0e"></script>
27
- <script defer src="js/admin.feedback.js?v=1.27.2-d7f5ec0e"></script>
17
+ <script defer src="js/admin.instructions.js?v=1.28.0-0009101e"></script>
18
+ <script defer src="js/admin.logs.js?v=1.28.0-0009101e"></script>
19
+ <script defer src="js/admin.maintenance.js?v=1.28.0-0009101e"></script>
20
+ <script defer src="js/admin.config.js?v=1.28.0-0009101e"></script>
21
+ <script defer src="js/admin.performance.js?v=1.28.0-0009101e"></script>
22
+ <script defer src="js/admin.instances.js?v=1.28.0-0009101e"></script>
23
+ <script defer src="js/admin.embeddings.js?v=1.28.0-0009101e"></script>
24
+ <script defer src="js/admin.messaging.js?v=1.28.0-0009101e"></script>
25
+ <script defer src="js/admin.sqlite.js?v=1.28.0-0009101e"></script>
26
+ <script defer src="js/admin.boot.js?v=1.28.0-0009101e"></script>
27
+ <script defer src="js/admin.feedback.js?v=1.28.0-0009101e"></script>
28
28
  </head>
29
29
  <body>
30
30
  <div class="admin-container admin-root">
@@ -909,10 +909,10 @@
909
909
  }
910
910
  }
911
911
 
912
- // Graph logic was extracted to js/admin.graph.js?v=1.27.2-d7f5ec0e
912
+ // Graph logic was extracted to js/admin.graph.js?v=1.28.0-0009101e
913
913
  // Functions available globally: reloadGraphMermaid, initGraphScopeDefaults, copyMermaidSource, toggleGraphEdit, applyGraphEdit, cancelGraphEdit, refreshDrillCategories, loadDrillInstructions, clearSelections
914
914
 
915
- <!-- overview functions moved to js/admin.overview.js?v=1.27.2-d7f5ec0e -->
915
+ <!-- overview functions moved to js/admin.overview.js?v=1.28.0-0009101e -->
916
916
 
917
917
  // Lightweight overview-level maintenance display (optional)
918
918
  // Intentionally minimal to avoid blocking overview rendering.
@@ -1097,7 +1097,7 @@
1097
1097
  }
1098
1098
 
1099
1099
  // --- Backup / Restore ---
1100
- // Extracted to js/admin.maintenance.js?v=1.27.2-d7f5ec0e
1100
+ // Extracted to js/admin.maintenance.js?v=1.28.0-0009101e
1101
1101
 
1102
1102
  async function performBackup() {
1103
1103
  try {
@@ -1163,7 +1163,7 @@
1163
1163
  }
1164
1164
 
1165
1165
  async function loadConfiguration() {
1166
- // Primary implementation in js/admin.config.js?v=1.27.2-d7f5ec0e (loaded via defer).
1166
+ // Primary implementation in js/admin.config.js?v=1.28.0-0009101e (loaded via defer).
1167
1167
  // This inline fallback only fires if the external script failed to load.
1168
1168
  if (window.__configExternalLoaded) return;
1169
1169
  try {
@@ -1223,10 +1223,10 @@
1223
1223
  return false;
1224
1224
  }
1225
1225
 
1226
- // Monitoring functions moved to js/admin.monitor.js?v=1.27.2-d7f5ec0e
1226
+ // Monitoring functions moved to js/admin.monitor.js?v=1.28.0-0009101e
1227
1227
 
1228
1228
  // ===== Log Viewer =====
1229
- // Extracted to js/admin.logs.js?v=1.27.2-d7f5ec0e
1229
+ // Extracted to js/admin.logs.js?v=1.28.0-0009101e
1230
1230
 
1231
1231
  // ===== Instruction Management =====
1232
1232
  let instructionEditing = null;
@@ -1651,7 +1651,7 @@
1651
1651
  categories: ['general'],
1652
1652
  primaryCategory: 'general',
1653
1653
  reviewIntervalDays: 180,
1654
- schemaVersion: '4',
1654
+ schemaVersion: '5',
1655
1655
  description: 'Describe purpose and scope.',
1656
1656
  createdAt: now,
1657
1657
  updatedAt: now
@@ -1723,7 +1723,7 @@
1723
1723
  setInterval(fetchResourceTrends, 10000);
1724
1724
  })();
1725
1725
 
1726
- // Instruction management logic extracted to js/admin.instructions.js?v=1.27.2-d7f5ec0e
1726
+ // Instruction management logic extracted to js/admin.instructions.js?v=1.28.0-0009101e
1727
1727
  // Functions exposed globally: loadInstructions, renderInstructionList, editInstruction, saveInstruction, deleteInstruction, etc.
1728
1728
 
1729
1729
  function startAutoRefresh() {
@@ -4,7 +4,7 @@
4
4
  * Human-operator surface for browsing, creating, editing, and deleting
5
5
  * persisted feedback entries, plus a client-side GitHub issue handoff.
6
6
  *
7
- * Design constraints (Morpheus architecture review / decisions.md G-1..G-8):
7
+ * Design constraints (architecture review / decisions.md G-1..G-8):
8
8
  * - CRUD against /api/admin/feedback (operator-tier, NOT the MCP surface)
9
9
  * - GitHub handoff is client-side ONLY — window.open with pre-filled URL
10
10
  * - No server-side GitHub API calls, no token handling, no OAuth
@@ -448,7 +448,7 @@
448
448
 
449
449
  function formatInstructionJson(){ const ta = document.getElementById('instruction-content'); if(!ta) return; try{ const parsed = JSON.parse(ta.value); ta.value = JSON.stringify(parsed, null, 2); updateInstructionEditorDiagnostics(); } catch { globals.showError && globals.showError('Cannot format: invalid JSON'); } }
450
450
 
451
- function applyInstructionTemplate(){ const ta = document.getElementById('instruction-content'); if(!ta) return; if(ta.value.trim() && !confirm('Replace current content with template?')) return; const now = new Date().toISOString(); const template = { id:'sample-instruction', title:'Sample Instruction', body:'Detailed instruction content here.\nAdd multi-line guidance and steps.', contentType:'instruction', priority:50, audience:'all', requirement:'optional', categories:['general'], primaryCategory:'general', schemaVersion:'4', status:'draft', owner:'you@example.com', version:'1.0.0', reviewIntervalDays:180, semanticSummary:'Brief summary of what this instruction covers.', createdAt: now, updatedAt: now }; ta.value = JSON.stringify(template, null, 2); updateInstructionEditorDiagnostics(); }
451
+ function applyInstructionTemplate(){ const ta = document.getElementById('instruction-content'); if(!ta) return; if(ta.value.trim() && !confirm('Replace current content with template?')) return; const now = new Date().toISOString(); const template = { id:'sample-instruction', title:'Sample Instruction', body:'Detailed instruction content here.\nAdd multi-line guidance and steps.', contentType:'instruction', priority:50, audience:'all', requirement:'optional', categories:['general'], primaryCategory:'general', schemaVersion:'5', status:'draft', owner:'you@example.com', version:'1.0.0', reviewIntervalDays:180, semanticSummary:'Brief summary of what this instruction covers.', createdAt: now, updatedAt: now }; ta.value = JSON.stringify(template, null, 2); updateInstructionEditorDiagnostics(); }
452
452
 
453
453
  async function deleteInstruction(name) {
454
454
  if (!confirm('Delete instruction ' + name + '?')) return;
@@ -473,11 +473,11 @@ class SecurityMonitor {
473
473
  // Utility methods for simulated monitoring
474
474
  getCPUUsage() {
475
475
  // Simulate CPU usage between 10-80%
476
- return Math.random() * 70 + 10;
476
+ return Math.random() * 70 + 10; // nosemgrep: insecure-randomness — simulated metric, not security context
477
477
  }
478
478
  getAverageAPILatency() {
479
479
  // Simulate API latency between 50-300ms
480
- return Math.random() * 250 + 50;
480
+ return Math.random() * 250 + 50; // nosemgrep: insecure-randomness — simulated metric, not security context
481
481
  }
482
482
  checkForSuspiciousPatterns(patterns, _caseSensitive) {
483
483
  // Simulate 1% chance of detecting suspicious pattern
@@ -5,8 +5,12 @@
5
5
  * Manages active admin sessions and session history, including
6
6
  * persistence, creation, termination, and cleanup of expired sessions.
7
7
  */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
8
11
  Object.defineProperty(exports, "__esModule", { value: true });
9
12
  exports.AdminPanelState = void 0;
13
+ const crypto_1 = __importDefault(require("crypto"));
10
14
  const runtimeConfig_1 = require("../../config/runtimeConfig");
11
15
  const SessionPersistenceManager_1 = require("./SessionPersistenceManager");
12
16
  const logger_js_1 = require("../../services/logger.js");
@@ -178,7 +182,7 @@ class AdminPanelState {
178
182
  }
179
183
  }
180
184
  generateSessionId() {
181
- return `admin_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
185
+ return `admin_${crypto_1.default.randomUUID()}`;
182
186
  }
183
187
  getSessionHistory(limit) {
184
188
  const slice = typeof limit === 'number' ? this.sessionHistory.slice(0, Math.max(0, limit)) : this.sessionHistory;
@@ -148,6 +148,7 @@ function createApiRoutes(options = {}) {
148
148
  const success = res.statusCode < 500;
149
149
  const route = normalizeRoute(req);
150
150
  const toolId = `http/${req.method} ${route}`;
151
+ metricsCollector.recordToolCall('http/request', success, ms, success ? undefined : `http_${res.statusCode}`);
151
152
  metricsCollector.recordToolCall(toolId, success, ms, success ? undefined : `http_${res.statusCode}`);
152
153
  }
153
154
  catch { /* never block response path */ }
@@ -185,7 +186,7 @@ function createApiRoutes(options = {}) {
185
186
  // -------------------------------------------------------------------------
186
187
  // Pre-load instruction index once per request so route handlers can use
187
188
  // res.locals.indexState instead of calling ensureLoaded() repeatedly.
188
- // See: https://github.com/jagilber-dev/index-server/issues/45
189
+ // See internal tracker #45.
189
190
  router.use(ensureLoadedMiddleware_js_1.ensureLoadedMiddleware);
190
191
  // Mount route modules
191
192
  router.use((0, index_js_1.createStatusRoutes)(metricsCollector));
@@ -14,6 +14,7 @@ exports.MetricsCollector = void 0;
14
14
  exports.getMetricsCollector = getMetricsCollector;
15
15
  exports.setMetricsCollector = setMetricsCollector;
16
16
  const fs_1 = __importDefault(require("fs"));
17
+ const crypto_1 = __importDefault(require("crypto"));
17
18
  const path_1 = __importDefault(require("path"));
18
19
  const v8_1 = __importDefault(require("v8"));
19
20
  const FileMetricsStorage_js_1 = require("./FileMetricsStorage.js");
@@ -690,7 +691,7 @@ class MetricsCollector {
690
691
  recentActivity: (0, metricsSerializer_js_1.buildRecentActivity)(this.tools),
691
692
  streamingStats: {
692
693
  totalStreamingConnections: this.activeConnections,
693
- dataTransferRate: this.connections.size * 0.1 + Math.random() * 0.5,
694
+ dataTransferRate: this.connections.size * 0.1 + Math.random() * 0.5, // nosemgrep: insecure-randomness — simulated metric jitter
694
695
  latency,
695
696
  compressionRatio: 0.7,
696
697
  },
@@ -735,7 +736,7 @@ class MetricsCollector {
735
736
  */
736
737
  generateRealTimeAlert(type, severity, message, value, threshold) {
737
738
  const alert = {
738
- id: `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
739
+ id: `alert_${Date.now()}_${crypto_1.default.randomBytes(6).toString('hex')}`,
739
740
  type: type,
740
741
  severity: severity,
741
742
  message,
@@ -148,7 +148,7 @@ class WebSocketManager {
148
148
  }
149
149
  catch {
150
150
  // Fallback simple id
151
- ws.clientId = `client-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
151
+ ws.clientId = `client-${Date.now()}-${Math.floor(Math.random() * 1000)}`; // lgtm[js/insecure-randomness] — fallback when safeGenerateClientId() throws; loopback-only dashboard
152
152
  }
153
153
  ws.connectedAt = Date.now();
154
154
  ws.lastActivity = Date.now();
@@ -327,7 +327,7 @@ class WebSocketManager {
327
327
  if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
328
328
  return crypto.randomUUID();
329
329
  }
330
- return 'client-' + Math.random().toString(36).slice(2);
330
+ return 'client-' + Math.random().toString(36).slice(2); // lgtm[js/insecure-randomness] — fallback when crypto.randomUUID unavailable
331
331
  }
332
332
  /** Send current metrics snapshot to a specific client */
333
333
  sendCurrentMetrics(client) {
@@ -8,7 +8,7 @@
8
8
  * Mutation handlers that call invalidate() must still call ensureLoaded()
9
9
  * explicitly after invalidation to pick up their own changes.
10
10
  *
11
- * See: https://github.com/jagilber-dev/index-server/issues/45
11
+ * See internal tracker #45.
12
12
  */
13
13
  import { Request, Response, NextFunction } from 'express';
14
14
  import { IndexState } from '../../../services/indexContext.js';
@@ -9,7 +9,7 @@
9
9
  * Mutation handlers that call invalidate() must still call ensureLoaded()
10
10
  * explicitly after invalidation to pick up their own changes.
11
11
  *
12
- * See: https://github.com/jagilber-dev/index-server/issues/45
12
+ * See internal tracker #45.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.ensureLoadedMiddleware = ensureLoadedMiddleware;
@@ -5,8 +5,12 @@
5
5
  * Manages API request execution (retry, rate-limit enforcement, auth, transforms)
6
6
  * and the monitoring event bus. Depends on EndpointManager for endpoint config.
7
7
  */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
8
11
  Object.defineProperty(exports, "__esModule", { value: true });
9
12
  exports.UsageManager = void 0;
13
+ const crypto_1 = __importDefault(require("crypto"));
10
14
  const logger_js_1 = require("../../../services/logger.js");
11
15
  // ── UsageManager ─────────────────────────────────────────────────────────────────────
12
16
  class UsageManager {
@@ -22,7 +26,7 @@ class UsageManager {
22
26
  if (!this.endpointMgr.checkRateLimit(endpointId)) {
23
27
  throw new Error(`Rate limit exceeded for endpoint: ${endpointId}`);
24
28
  }
25
- const requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
29
+ const requestId = `req_${Date.now()}_${crypto_1.default.randomBytes(6).toString('hex')}`;
26
30
  const request = {
27
31
  id: requestId,
28
32
  endpointId,
@@ -16,16 +16,125 @@ const registry_js_1 = require("../../../server/registry.js");
16
16
  const indexContext_js_1 = require("../../../services/indexContext.js");
17
17
  const adminAuth_js_1 = require("./adminAuth.js");
18
18
  const handlers_search_js_1 = require("../../../services/handlers.search.js");
19
+ const schemaVersion_js_1 = require("../../../versioning/schemaVersion.js");
20
+ const runtimeConfig_js_1 = require("../../../config/runtimeConfig.js");
19
21
  const instructionRecordValidation_js_1 = require("../../../services/instructionRecordValidation.js");
20
22
  const logger_js_1 = require("../../../services/logger.js");
21
23
  const pathContainment_js_1 = require("../utils/pathContainment.js");
22
- /** Sanitize an instruction name with defense-in-depth path-traversal guard. */
24
+ /** Validate an instruction name with a defense-in-depth path-containment guard. */
23
25
  function safeName(name) {
24
- const sanitized = String(name).replace(/[^a-zA-Z0-9-_]/g, '-');
26
+ const sanitized = String(name);
27
+ const validationErrors = (0, instructionRecordValidation_js_1.validateInstructionIdSurface)(sanitized);
28
+ if (validationErrors.length) {
29
+ throw new instructionRecordValidation_js_1.InstructionValidationError(validationErrors);
30
+ }
25
31
  const base = (0, indexContext_js_1.getInstructionsDir)();
26
32
  (0, pathContainment_js_1.validatePathContainment)(node_path_1.default.resolve(base, `${sanitized}.json`), base);
27
33
  return sanitized;
28
34
  }
35
+ function requireInstructionContentObject(content) {
36
+ if (!content || typeof content !== 'object' || Array.isArray(content)) {
37
+ throw new instructionRecordValidation_js_1.InstructionValidationError(['/content: must be an object']);
38
+ }
39
+ return content;
40
+ }
41
+ function assertValidDashboardInstructionEnums(content) {
42
+ const validationErrors = (0, instructionRecordValidation_js_1.validateInstructionInputEnumMembership)(content);
43
+ if (validationErrors.length) {
44
+ throw new instructionRecordValidation_js_1.InstructionValidationError(validationErrors);
45
+ }
46
+ }
47
+ function assertValidDashboardInstructionShape(content) {
48
+ const validationErrors = [];
49
+ for (const [key, value] of Object.entries(content)) {
50
+ if (value === null)
51
+ validationErrors.push(`/${key}: null is not allowed`);
52
+ }
53
+ if (content.title !== undefined && typeof content.title !== 'string') {
54
+ validationErrors.push(`/title: must be a string, received ${typeof content.title}`);
55
+ }
56
+ if (content.body !== undefined && typeof content.body !== 'string') {
57
+ validationErrors.push(`/body: must be a string, received ${typeof content.body}`);
58
+ }
59
+ if (content.audience !== undefined && typeof content.audience !== 'string') {
60
+ validationErrors.push(`/audience: must be a string, received ${typeof content.audience}`);
61
+ }
62
+ if (content.requirement !== undefined && typeof content.requirement !== 'string') {
63
+ validationErrors.push(`/requirement: must be a string, received ${typeof content.requirement}`);
64
+ }
65
+ if (content.contentType !== undefined && typeof content.contentType !== 'string') {
66
+ validationErrors.push(`/contentType: must be a string, received ${typeof content.contentType}`);
67
+ }
68
+ if (content.priority !== undefined && typeof content.priority !== 'number') {
69
+ validationErrors.push(`/priority: must be a number, received ${typeof content.priority}`);
70
+ }
71
+ else if (typeof content.priority === 'number' && (!Number.isInteger(content.priority) || content.priority < 1 || content.priority > 100)) {
72
+ validationErrors.push('/priority: must be an integer from 1 to 100');
73
+ }
74
+ if (content.categories !== undefined && !Array.isArray(content.categories)) {
75
+ validationErrors.push(`/categories: must be an array of strings, received ${typeof content.categories}`);
76
+ }
77
+ else if (Array.isArray(content.categories)) {
78
+ for (const [index, category] of content.categories.entries()) {
79
+ if (typeof category !== 'string') {
80
+ validationErrors.push(`/categories/${index}: must be a string, received ${typeof category}`);
81
+ }
82
+ else if (!/^[a-z0-9][a-z0-9-_]{0,48}$/.test(category.toLowerCase())) {
83
+ validationErrors.push(`/categories/${index}: must match /^[a-z0-9][a-z0-9-_]{0,48}$/`);
84
+ }
85
+ }
86
+ }
87
+ if (validationErrors.length) {
88
+ throw new instructionRecordValidation_js_1.InstructionValidationError(validationErrors);
89
+ }
90
+ }
91
+ function assertDashboardBodyWithinLimit(content) {
92
+ if (typeof content.body !== 'string')
93
+ return;
94
+ const bodyLength = content.body.trim().length;
95
+ const { bodyWarnLength } = (0, runtimeConfig_js_1.getRuntimeConfig)().index;
96
+ if (bodyLength > bodyWarnLength) {
97
+ throw new instructionRecordValidation_js_1.InstructionValidationError([`/body: exceeds the ${bodyWarnLength}-character limit (${bodyLength} chars)`]);
98
+ }
99
+ }
100
+ function validationErrorResponse(res, error) {
101
+ return res.status(400).json({ success: false, error: 'invalid_instruction', validationErrors: error.validationErrors });
102
+ }
103
+ function dashboardAudience(value) {
104
+ switch (value) {
105
+ case 'individual':
106
+ case 'group':
107
+ case 'all':
108
+ return value;
109
+ default:
110
+ return 'all';
111
+ }
112
+ }
113
+ function dashboardRequirement(value) {
114
+ switch (value) {
115
+ case 'mandatory':
116
+ case 'critical':
117
+ case 'recommended':
118
+ case 'optional':
119
+ case 'deprecated':
120
+ return value;
121
+ default:
122
+ return 'optional';
123
+ }
124
+ }
125
+ function dashboardContentType(value) {
126
+ switch (value) {
127
+ case 'template':
128
+ case 'workflow':
129
+ case 'reference':
130
+ case 'example':
131
+ case 'agent':
132
+ case 'instruction':
133
+ return value;
134
+ default:
135
+ return 'instruction';
136
+ }
137
+ }
29
138
  function createInstructionsRoutes() {
30
139
  const router = (0, express_1.Router)();
31
140
  const buildSnippet = (parts, query) => {
@@ -176,6 +285,9 @@ function createInstructionsRoutes() {
176
285
  res.json({ success: true, content: entry, timestamp: Date.now() });
177
286
  }
178
287
  catch (error) {
288
+ if ((0, instructionRecordValidation_js_1.isInstructionValidationError)(error)) {
289
+ return validationErrorResponse(res, error);
290
+ }
179
291
  (0, logger_js_1.logError)('[API] Failed to load instruction:', error);
180
292
  res.status(500).json({ success: false, error: 'Failed to load instruction' });
181
293
  }
@@ -193,20 +305,28 @@ function createInstructionsRoutes() {
193
305
  const st = res.locals.indexState;
194
306
  if (st.byId.has(id))
195
307
  return res.status(409).json({ success: false, error: 'Instruction already exists' });
196
- const contentObj = typeof content === 'object' && content !== null ? content : {}; // lgtm[js/comparison-between-incompatible-types] — idiomatic typeof-object plus null check (typeof null === 'object' in JS)
308
+ const contentObj = requireInstructionContentObject(content);
309
+ assertValidDashboardInstructionEnums(contentObj);
310
+ assertValidDashboardInstructionShape(contentObj);
311
+ assertDashboardBodyWithinLimit(contentObj);
197
312
  const entry = {
198
313
  ...contentObj,
199
314
  id,
200
- title: contentObj.title || id,
201
- body: contentObj.body || (typeof content === 'string' ? content : ''),
202
- categories: Array.isArray(contentObj.categories) ? contentObj.categories : [],
315
+ title: typeof contentObj.title === 'string' ? contentObj.title : id,
316
+ body: typeof contentObj.body === 'string' ? contentObj.body : '',
317
+ categories: Array.isArray(contentObj.categories)
318
+ ? contentObj.categories.filter((category) => typeof category === 'string')
319
+ : [],
203
320
  priority: typeof contentObj.priority === 'number' ? contentObj.priority : 50,
204
- audience: contentObj.audience || 'all',
205
- requirement: contentObj.requirement || 'optional',
321
+ audience: dashboardAudience(contentObj.audience),
322
+ requirement: dashboardRequirement(contentObj.requirement),
323
+ contentType: dashboardContentType(contentObj.contentType),
324
+ sourceHash: typeof contentObj.sourceHash === 'string' ? contentObj.sourceHash : '',
325
+ schemaVersion: typeof contentObj.schemaVersion === 'string' ? contentObj.schemaVersion : schemaVersion_js_1.SCHEMA_VERSION,
206
326
  createdAt: new Date().toISOString(),
207
327
  updatedAt: new Date().toISOString(),
208
328
  };
209
- await (0, indexContext_js_1.writeEntryAsync)(entry); // lgtm[js/http-to-file-access] — writes to config-controlled instructions directory
329
+ await (0, indexContext_js_1.writeEntryAsync)(entry, { createOnly: true }); // lgtm[js/http-to-file-access] — writes to config-controlled instructions directory
210
330
  (0, indexContext_js_1.touchIndexVersion)();
211
331
  (0, indexContext_js_1.invalidate)();
212
332
  const reloaded = await (0, indexContext_js_1.ensureLoadedAsync)();
@@ -219,7 +339,10 @@ function createInstructionsRoutes() {
219
339
  }
220
340
  catch (error) {
221
341
  if ((0, instructionRecordValidation_js_1.isInstructionValidationError)(error)) {
222
- return res.status(400).json({ success: false, error: 'invalid_instruction', validationErrors: error.validationErrors });
342
+ return validationErrorResponse(res, error);
343
+ }
344
+ if ((0, indexContext_js_1.isDuplicateInstructionWriteError)(error)) {
345
+ return res.status(409).json({ success: false, error: 'Instruction already exists' });
223
346
  }
224
347
  (0, logger_js_1.logError)('[API] Failed to create instruction:', error);
225
348
  res.status(500).json({ success: false, error: 'Failed to create instruction' });
@@ -234,13 +357,17 @@ function createInstructionsRoutes() {
234
357
  const id = safeName(req.params.name);
235
358
  if (!content)
236
359
  return res.status(400).json({ success: false, error: 'Missing content' });
360
+ const contentObj = requireInstructionContentObject(content);
361
+ assertValidDashboardInstructionEnums(contentObj);
362
+ assertValidDashboardInstructionShape(contentObj);
363
+ assertDashboardBodyWithinLimit(contentObj);
237
364
  const st = res.locals.indexState;
238
365
  const existing = st.byId.get(id);
239
366
  if (!existing)
240
367
  return res.status(404).json({ success: false, error: 'Not found' });
241
368
  const updated = {
242
369
  ...existing,
243
- ...(typeof content === 'object' && content !== null ? content : {}), // lgtm[js/comparison-between-incompatible-types] — idiomatic typeof-object plus null check (typeof null === 'object' in JS)
370
+ ...contentObj,
244
371
  id, // preserve id
245
372
  updatedAt: new Date().toISOString(),
246
373
  };
@@ -257,7 +384,7 @@ function createInstructionsRoutes() {
257
384
  }
258
385
  catch (error) {
259
386
  if ((0, instructionRecordValidation_js_1.isInstructionValidationError)(error)) {
260
- return res.status(400).json({ success: false, error: 'invalid_instruction', validationErrors: error.validationErrors });
387
+ return validationErrorResponse(res, error);
261
388
  }
262
389
  (0, logger_js_1.logError)('[API] Failed to update instruction:', error);
263
390
  res.status(500).json({ success: false, error: 'Failed to update instruction' });
@@ -279,6 +406,9 @@ function createInstructionsRoutes() {
279
406
  res.json({ success: true, message: 'Instruction deleted', timestamp: Date.now() });
280
407
  }
281
408
  catch (error) {
409
+ if ((0, instructionRecordValidation_js_1.isInstructionValidationError)(error)) {
410
+ return validationErrorResponse(res, error);
411
+ }
282
412
  (0, logger_js_1.logError)('[API] Failed to delete instruction:', error);
283
413
  res.status(500).json({ success: false, error: 'Failed to delete instruction' });
284
414
  }
@@ -57,7 +57,7 @@ function createScriptsRoutes() {
57
57
  return;
58
58
  }
59
59
  try {
60
- const scriptsDir = path_1.default.join(process.cwd(), 'scripts');
60
+ const scriptsDir = path_1.default.join(process.cwd(), 'scripts', 'dist');
61
61
  const filePath = path_1.default.join(scriptsDir, meta.file); // nosemgrep: javascript.express.security.audit.express-path-join-resolve-traversal.express-path-join-resolve-traversal -- path validated below via startsWith check
62
62
  let resolved;
63
63
  try {