@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.
- package/CHANGELOG.md +35 -1
- package/CONTRIBUTING.md +3 -3
- package/dist/dashboard/client/admin.html +28 -28
- package/dist/dashboard/client/js/admin.feedback.js +1 -1
- package/dist/dashboard/client/js/admin.instructions.js +1 -1
- package/dist/dashboard/security/SecurityMonitor.js +2 -2
- package/dist/dashboard/server/AdminPanelState.js +5 -1
- package/dist/dashboard/server/ApiRoutes.js +2 -1
- package/dist/dashboard/server/MetricsCollector.js +3 -2
- package/dist/dashboard/server/WebSocketManager.js +2 -2
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.d.ts +1 -1
- package/dist/dashboard/server/middleware/ensureLoadedMiddleware.js +1 -1
- package/dist/dashboard/server/routes/api.usage.routes.js +5 -1
- package/dist/dashboard/server/routes/instructions.routes.js +142 -12
- package/dist/dashboard/server/routes/scripts.routes.js +1 -1
- package/dist/dashboard/server/routes/sqlite.routes.js +74 -0
- package/dist/models/instruction.d.ts +1 -1
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +1 -1
- package/dist/services/auditLog.d.ts +1 -1
- package/dist/services/auditLog.js +1 -1
- package/dist/services/feedbackStorage.js +1 -1
- package/dist/services/handlers/instructions.add.js +36 -3
- package/dist/services/handlers.feedback.js +1 -1
- package/dist/services/handlers.instructionSchema.js +4 -4
- package/dist/services/handlers.search.js +3 -3
- package/dist/services/instructionRecordValidation.d.ts +3 -0
- package/dist/services/instructionRecordValidation.js +64 -4
- package/dist/services/seedBootstrap.js +2 -2
- package/dist/services/toolRegistry.js +8 -8
- package/dist/services/toolRegistry.zod.js +3 -3
- package/dist/versioning/schemaVersion.d.ts +1 -1
- package/dist/versioning/schemaVersion.js +47 -2
- package/package.json +44 -40
- package/schemas/index-server.code-schema.json +1 -1
- package/schemas/instruction.schema.json +3 -3
- package/schemas/json-schema/instruction-content-type.schema.json +1 -1
- package/schemas/json-schema/instruction-instruction-entry.schema.json +1 -1
- package/scripts/README.md +48 -0
- package/scripts/{generate-certs.mjs → build/generate-certs.mjs} +1 -1
- package/scripts/{setup-wizard.mjs → build/setup-wizard.mjs} +1 -1
- package/scripts/{setup-hooks.cjs → hooks/setup-hooks.cjs} +3 -3
- package/server.json +2 -2
- /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 <
|
|
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
|
|
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.
|
|
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.
|
|
9
|
-
<script defer src="js/admin.utils.js?v=1.
|
|
10
|
-
<script defer src="js/admin.auth.js?v=1.
|
|
11
|
-
<script defer src="js/admin.overview.js?v=1.
|
|
12
|
-
<script defer src="js/admin.sessions.js?v=1.
|
|
13
|
-
<script defer src="js/admin.monitor.js?v=1.
|
|
14
|
-
<script defer src="js/admin.events.js?v=1.
|
|
15
|
-
<script defer src="js/admin.graph.js?v=1.
|
|
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.
|
|
18
|
-
<script defer src="js/admin.logs.js?v=1.
|
|
19
|
-
<script defer src="js/admin.maintenance.js?v=1.
|
|
20
|
-
<script defer src="js/admin.config.js?v=1.
|
|
21
|
-
<script defer src="js/admin.performance.js?v=1.
|
|
22
|
-
<script defer src="js/admin.instances.js?v=1.
|
|
23
|
-
<script defer src="js/admin.embeddings.js?v=1.
|
|
24
|
-
<script defer src="js/admin.messaging.js?v=1.
|
|
25
|
-
<script defer src="js/admin.sqlite.js?v=1.
|
|
26
|
-
<script defer src="js/admin.boot.js?v=1.
|
|
27
|
-
<script defer src="js/admin.feedback.js?v=1.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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: '
|
|
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.
|
|
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 (
|
|
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:'
|
|
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_${
|
|
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
|
|
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()}_${
|
|
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
|
|
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
|
|
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()}_${
|
|
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
|
-
/**
|
|
24
|
+
/** Validate an instruction name with a defense-in-depth path-containment guard. */
|
|
23
25
|
function safeName(name) {
|
|
24
|
-
const sanitized = String(name)
|
|
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 =
|
|
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
|
|
201
|
-
body: contentObj.body
|
|
202
|
-
categories: Array.isArray(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
|
|
205
|
-
requirement: contentObj.requirement
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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 {
|