@devilsdev/rag-pipeline-utils 2.4.5 → 2.5.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 (225) hide show
  1. package/.ragrc.schema.json +83 -192
  2. package/CHANGELOG.md +345 -0
  3. package/README.md +418 -117
  4. package/SECURITY.md +5 -5
  5. package/dist/experimental.cjs +2 -0
  6. package/dist/experimental.d.ts +1 -0
  7. package/dist/experimental.mjs +5 -0
  8. package/dist/index.cjs +4992 -1242
  9. package/dist/index.d.ts +80 -2
  10. package/dist/index.mjs +1 -1
  11. package/dist/types/agentic/agentic-pipeline.d.ts +103 -0
  12. package/dist/types/agentic/iterative-retriever.d.ts +85 -0
  13. package/dist/types/agentic/query-planner.d.ts +145 -0
  14. package/dist/types/agentic/self-critique.d.ts +45 -0
  15. package/dist/types/ai/adaptive-retrieval.d.ts +44 -0
  16. package/dist/types/ai/federation/federated-learning-coordinator.d.ts +62 -0
  17. package/dist/types/ai/multimodal-processing.d.ts +32 -0
  18. package/dist/types/ai/multimodal.d.ts +39 -0
  19. package/dist/types/ai/retrieval-engine.d.ts +37 -0
  20. package/dist/types/ai/training/model-training-orchestrator.d.ts +31 -0
  21. package/dist/types/chunking/chunking-engine.d.ts +70 -0
  22. package/dist/types/chunking/strategies/fixed-size.d.ts +17 -0
  23. package/dist/types/chunking/strategies/recursive.d.ts +20 -0
  24. package/dist/types/chunking/strategies/semantic.d.ts +19 -0
  25. package/dist/types/chunking/strategies/sentence.d.ts +13 -0
  26. package/dist/types/chunking/strategies/structure-aware.d.ts +19 -0
  27. package/dist/types/citation/citation-tracker.d.ts +64 -0
  28. package/dist/types/citation/hallucination-detector.d.ts +40 -0
  29. package/dist/types/citation/source-mapper.d.ts +37 -0
  30. package/dist/types/connectors/anthropic-connector.d.ts +92 -0
  31. package/dist/types/connectors/base-connector.d.ts +87 -0
  32. package/dist/types/connectors/cohere-connector.d.ts +89 -0
  33. package/dist/types/connectors/connector-utils.d.ts +110 -0
  34. package/dist/types/connectors/local-embedder.d.ts +86 -0
  35. package/dist/types/connectors/memory-retriever.d.ts +91 -0
  36. package/dist/types/connectors/ollama-connector.d.ts +110 -0
  37. package/dist/types/connectors/openai-connector.d.ts +114 -0
  38. package/dist/types/connectors/rag-prompt.d.ts +40 -0
  39. package/dist/types/cost/cost-calculator.d.ts +152 -0
  40. package/dist/types/cost/cost-tracker.d.ts +126 -0
  41. package/dist/types/cost/token-budget.d.ts +110 -0
  42. package/dist/types/dag/core/error-context.d.ts +239 -0
  43. package/dist/types/dag/core/execution-scheduler.d.ts +191 -0
  44. package/dist/types/dag/core/topology-validation.d.ts +226 -0
  45. package/dist/types/dag/dag-engine.d.ts +156 -0
  46. package/dist/types/dag/dag-node.d.ts +37 -0
  47. package/dist/types/dashboard/dashboard-generator.d.ts +127 -0
  48. package/dist/types/dashboard/metrics-aggregator.d.ts +218 -0
  49. package/dist/types/debug/execution-tracer.d.ts +57 -0
  50. package/dist/types/debug/trace-inspector.d.ts +31 -0
  51. package/dist/types/ecosystem/plugin-certification.d.ts +169 -0
  52. package/dist/types/enterprise/data-governance.d.ts +203 -0
  53. package/dist/types/enterprise/multi-tenancy.d.ts +137 -0
  54. package/dist/types/enterprise/sso-integration.d.ts +420 -0
  55. package/dist/types/evaluate/context-metrics.d.ts +55 -0
  56. package/dist/types/evaluate/faithfulness.d.ts +41 -0
  57. package/dist/types/evaluate/groundedness.d.ts +60 -0
  58. package/dist/types/evaluate/pipeline-evaluator.d.ts +88 -0
  59. package/dist/types/evaluate/relevance.d.ts +21 -0
  60. package/dist/types/evaluate/scoring.d.ts +123 -0
  61. package/dist/types/experimental.d.ts +15 -0
  62. package/dist/types/graph/entity-extractor.d.ts +84 -0
  63. package/dist/types/graph/graph-index.d.ts +49 -0
  64. package/dist/types/graph/graph-retriever.d.ts +81 -0
  65. package/dist/types/graph/knowledge-graph.d.ts +129 -0
  66. package/dist/types/guardrails/guardrails-pipeline.d.ts +63 -0
  67. package/dist/types/guardrails/injection-detector.d.ts +47 -0
  68. package/dist/types/guardrails/post-generation-guard.d.ts +61 -0
  69. package/dist/types/guardrails/pre-retrieval-guard.d.ts +64 -0
  70. package/dist/types/guardrails/retrieval-guard.d.ts +57 -0
  71. package/dist/types/mcp/mcp-server.d.ts +85 -0
  72. package/dist/types/mcp/mcp-tool-builder.d.ts +55 -0
  73. package/dist/types/reranker/cascade-reranker.d.ts +40 -0
  74. package/dist/types/reranker/embedding-reranker.d.ts +77 -0
  75. package/dist/types/reranker/scoring-reranker.d.ts +41 -0
  76. package/dist/types/retrieval/bm25-search.d.ts +43 -0
  77. package/dist/types/retrieval/file-vector-store.d.ts +87 -0
  78. package/dist/types/retrieval/hybrid-retriever.d.ts +35 -0
  79. package/dist/types/retrieval/rank-fusion.d.ts +10 -0
  80. package/dist/types/security/jwt-config.d.ts +80 -0
  81. package/dist/types/security/jwt-validator.d.ts +195 -0
  82. package/dist/types/security/plugin-sandbox.d.ts +158 -0
  83. package/dist/types/security/plugin-signature-verifier.d.ts +139 -0
  84. package/dist/types/security/validator.d.ts +5 -0
  85. package/dist/types/streaming/embedding-stream.d.ts +65 -0
  86. package/dist/types/streaming/sse-adapter.d.ts +59 -0
  87. package/dist/types/streaming/stream-router.d.ts +88 -0
  88. package/dist/types/streaming/streaming-embedder.d.ts +99 -0
  89. package/dist/types/streaming/websocket-adapter.d.ts +71 -0
  90. package/dist/types/templates/scaffold.d.ts +55 -0
  91. package/dist/types/templates/template-registry.d.ts +63 -0
  92. package/dist/types/utils/embed-query.d.ts +20 -0
  93. package/dist/types/utils/logger.d.ts +50 -0
  94. package/dist/types/utils/rate-limiter.d.ts +66 -0
  95. package/dist/types/utils/retry.d.ts +27 -0
  96. package/dist/types/utils/run-abort.d.ts +34 -0
  97. package/dist/types/utils/secure-logging.d.ts +51 -0
  98. package/dist/types/utils/structured-logger.d.ts +126 -0
  99. package/package.json +53 -29
  100. package/src/agentic/agentic-pipeline.js +75 -7
  101. package/src/agentic/iterative-retriever.js +47 -10
  102. package/src/agentic/query-planner.js +329 -78
  103. package/src/citation/citation-tracker.js +6 -4
  104. package/src/citation/hallucination-detector.js +2 -2
  105. package/src/citation/source-mapper.js +15 -28
  106. package/src/cli/commands/ai.js +101 -0
  107. package/src/cli/commands/plugin-hub/event-handlers.js +6 -0
  108. package/src/cli/commands/plugin-hub/hub-commands.js +0 -74
  109. package/src/cli/commands/plugin-hub/local-commands.js +143 -0
  110. package/src/cli/commands/plugin-hub.js +96 -36
  111. package/src/cli/diagnostics/config-checker.js +76 -63
  112. package/src/cli/diagnostics/environment-checker.js +62 -31
  113. package/src/cli/diagnostics/plugin-checker.js +36 -89
  114. package/src/cli/doctor-command.js +39 -5
  115. package/src/cli/enhanced-cli-commands.js +72 -217
  116. package/src/cli/handlers/ai-handler.js +364 -0
  117. package/src/cli/handlers/diagnostics-handler.js +78 -31
  118. package/src/cli/handlers/ingest-handler.js +28 -22
  119. package/src/cli/handlers/init-handler.js +148 -25
  120. package/src/cli/handlers/query-handler.js +41 -26
  121. package/src/cli/handlers/run-handler.js +16 -167
  122. package/src/cli/handlers/serve-handler.js +60 -0
  123. package/src/cli/services/bench-stats.js +54 -0
  124. package/src/cli/services/benchmark.js +161 -0
  125. package/src/cli/services/feedback.js +155 -0
  126. package/src/cli/services/rag-service.js +322 -0
  127. package/src/cli/shared/cli-utils.js +24 -7
  128. package/src/cli/shared/config-context.js +21 -0
  129. package/src/cli/wizard/config-builder.js +81 -0
  130. package/src/cli/wizard/init-wizard.js +617 -0
  131. package/src/config/component-factory.js +125 -0
  132. package/src/config/load-config.js +29 -10
  133. package/src/config/load-plugin-config.js +46 -2
  134. package/src/connectors/anthropic-connector.js +93 -55
  135. package/src/connectors/base-connector.js +74 -19
  136. package/src/connectors/cohere-connector.js +88 -32
  137. package/src/connectors/connector-utils.js +302 -0
  138. package/src/connectors/local-embedder.js +43 -5
  139. package/src/connectors/memory-retriever.js +108 -15
  140. package/src/connectors/ollama-connector.js +83 -49
  141. package/src/connectors/openai-connector.js +108 -44
  142. package/src/connectors/rag-prompt.js +88 -0
  143. package/src/core/create-pipeline.js +209 -60
  144. package/src/core/plugin-registry.js +87 -11
  145. package/src/cost/cost-calculator.js +205 -32
  146. package/src/dev/dev-server.js +166 -11
  147. package/src/dev/hot-reload.js +40 -1
  148. package/src/ecosystem/plugin-checks.js +181 -0
  149. package/src/ecosystem/plugin-hub.js +50 -125
  150. package/src/ecosystem/plugin-installer.js +298 -0
  151. package/src/ecosystem/plugin-probe.js +127 -0
  152. package/src/ecosystem/plugin-scanner.js +185 -0
  153. package/src/ecosystem/tar-extract.js +114 -0
  154. package/src/enterprise/audit-logging.js +158 -29
  155. package/src/enterprise/sso-integration.js +715 -228
  156. package/src/evaluate/context-metrics.js +28 -16
  157. package/src/evaluate/evaluator.js +3 -3
  158. package/src/evaluate/faithfulness.js +18 -14
  159. package/src/evaluate/groundedness.js +43 -12
  160. package/src/evaluate/pipeline-evaluator.js +35 -8
  161. package/src/evaluate/scoring.js +251 -3
  162. package/src/experimental.js +71 -0
  163. package/src/guardrails/guardrails-pipeline.js +69 -7
  164. package/src/guardrails/injection-detector.js +283 -0
  165. package/src/guardrails/post-generation-guard.js +19 -0
  166. package/src/guardrails/pre-retrieval-guard.js +36 -36
  167. package/src/guardrails/retrieval-guard.js +58 -8
  168. package/src/index.js +36 -1
  169. package/src/loader/document-reader.js +91 -0
  170. package/src/reranker/cascade-reranker.js +3 -0
  171. package/src/reranker/embedding-reranker.js +136 -21
  172. package/src/reranker/llm-reranker.js +79 -12
  173. package/src/reranker/scoring-reranker.js +3 -0
  174. package/src/retrieval/bm25-search.js +12 -6
  175. package/src/retrieval/file-vector-store.js +212 -0
  176. package/src/retrieval/hybrid-retriever.js +22 -19
  177. package/src/security/jwt-config.js +13 -2
  178. package/src/security/jwt-validator.js +134 -30
  179. package/src/security/plugin-sandbox.js +18 -1
  180. package/src/security/plugin-signature-verifier.js +245 -70
  181. package/src/utils/deprecate.js +69 -0
  182. package/src/utils/embed-query.js +33 -0
  183. package/src/utils/input-sanitizer.js +19 -4
  184. package/src/utils/logger.js +34 -4
  185. package/src/utils/run-abort.js +102 -0
  186. package/src/ai/federated-learning.js +0 -211
  187. package/src/ai/federation.js +0 -71
  188. package/src/ai/index.js +0 -22
  189. package/src/ai/model-trainer.js +0 -217
  190. package/src/ai/model-training.js +0 -202
  191. package/src/ai/multimodal/multi-modal-processor.js +0 -345
  192. package/src/ai/orchestrator.js +0 -65
  193. package/src/ai/retrieval/adaptive-retrieval-engine.js +0 -398
  194. package/src/cli/commands/ai-ml/adaptive-commands.js +0 -178
  195. package/src/cli/commands/ai-ml/federated-commands.js +0 -207
  196. package/src/cli/commands/ai-ml/index.js +0 -17
  197. package/src/cli/commands/ai-ml/multimodal-commands.js +0 -187
  198. package/src/cli/commands/ai-ml/shared.js +0 -51
  199. package/src/cli/commands/ai-ml/training-commands.js +0 -228
  200. package/src/cli/commands/ai-ml.js +0 -386
  201. package/src/cli/commands/docs.js +0 -228
  202. package/src/cli/commands/dx.js +0 -538
  203. package/src/cli/commands/wizard.js +0 -627
  204. package/src/cli/input-sanitization-middleware.js +0 -356
  205. package/src/cli/interactive-wizard.js +0 -373
  206. package/src/cli/templates/development.template.json +0 -64
  207. package/src/cli/templates/minimal.template.json +0 -33
  208. package/src/cli/templates/production.template.json +0 -104
  209. package/src/cli/templates/testing.template.json +0 -45
  210. package/src/cli/wizard/configuration.js +0 -148
  211. package/src/cli/wizard/observability.js +0 -79
  212. package/src/cli/wizard/performance.js +0 -76
  213. package/src/cli/wizard/plugin-selection.js +0 -297
  214. package/src/cli/wizard/preview-save.js +0 -172
  215. package/src/cli/wizard/project-setup.js +0 -66
  216. package/src/core/pipeline-factory.js +0 -59
  217. package/src/dag/enhanced-dag-engine.js +0 -894
  218. package/src/dx/index.js +0 -40
  219. package/src/dx/integration-templates.js +0 -662
  220. package/src/dx/performance-profiler.js +0 -968
  221. package/src/dx/realtime-debugger.js +0 -755
  222. package/src/dx/visual-pipeline-builder.js +0 -578
  223. package/src/ingest.js +0 -59
  224. package/src/pipeline/pipeline.js +0 -18
  225. package/src/query.js +0 -117
@@ -3,6 +3,22 @@
3
3
  * Verifies Ed25519 digital signatures for plugin manifests to ensure authenticity
4
4
  * Enforces fail-closed verification for production security
5
5
  *
6
+ * What is signed: the canonical JSON of the manifest (keys sorted
7
+ * recursively, without `signature`, `signerId` and `signedAt`). To bind the
8
+ * plugin's code to the signature, put a `contentDigest` in the manifest
9
+ * before signing (see computeContentDigest). Because `contentDigest` is part
10
+ * of the signed manifest, changing any covered file invalidates it.
11
+ *
12
+ * Defaults (2.5.0):
13
+ * - The trust store starts EMPTY. Nothing verifies until a signer is added
14
+ * with addTrustedSigner() or a trustedKeysPath file.
15
+ * - If a manifest declares `contentDigest`, the plugin content (files or
16
+ * contents) MUST be supplied to verifyPluginSignature() and must match;
17
+ * otherwise verification fails.
18
+ * - A manifest without `contentDigest` still verifies (manifest-only
19
+ * signatures keep working); pass `requireContentDigest: true` to reject
20
+ * manifests that do not bind the code.
21
+ *
6
22
  * @author DevilsDev Team
7
23
  * @since 2.1.8
8
24
  */
@@ -12,6 +28,132 @@ const fs = require('fs');
12
28
  const path = require('path');
13
29
  const { logger } = require('../utils/logger');
14
30
 
31
+ const DIGEST_PREFIX = 'sha256-';
32
+ const HEX_KEY_RE = /^[0-9a-fA-F]{64}$/;
33
+ const INVALID_KEY_MESSAGE =
34
+ 'Public key must be 64 hex characters (32 bytes), or a PEM/DER-encoded Ed25519 public key';
35
+
36
+ /**
37
+ * Convert supported public key encodings to an Ed25519 KeyObject.
38
+ *
39
+ * Accepted: a KeyObject; a 64-character hex string or 32-byte Buffer (raw
40
+ * key); a PEM string ("-----BEGIN PUBLIC KEY-----"); a DER (SPKI) Buffer or
41
+ * base64 string.
42
+ *
43
+ * @param {crypto.KeyObject|Buffer|string} publicKey
44
+ * @returns {crypto.KeyObject}
45
+ */
46
+ function toEd25519PublicKey(publicKey) {
47
+ let keyObject;
48
+ try {
49
+ if (publicKey instanceof crypto.KeyObject) {
50
+ keyObject =
51
+ publicKey.type === 'private'
52
+ ? crypto.createPublicKey(publicKey)
53
+ : publicKey;
54
+ } else if (Buffer.isBuffer(publicKey) && publicKey.length === 32) {
55
+ keyObject = rawToKeyObject(publicKey);
56
+ } else if (Buffer.isBuffer(publicKey)) {
57
+ keyObject = crypto.createPublicKey({
58
+ key: publicKey,
59
+ format: 'der',
60
+ type: 'spki',
61
+ });
62
+ } else if (typeof publicKey === 'string' && HEX_KEY_RE.test(publicKey)) {
63
+ keyObject = rawToKeyObject(Buffer.from(publicKey, 'hex'));
64
+ } else if (
65
+ typeof publicKey === 'string' &&
66
+ publicKey.includes('-----BEGIN')
67
+ ) {
68
+ keyObject = crypto.createPublicKey({ key: publicKey, format: 'pem' });
69
+ } else if (typeof publicKey === 'string' && publicKey.length > 0) {
70
+ keyObject = crypto.createPublicKey({
71
+ key: Buffer.from(publicKey, 'base64'),
72
+ format: 'der',
73
+ type: 'spki',
74
+ });
75
+ }
76
+ } catch {
77
+ keyObject = undefined;
78
+ }
79
+
80
+ if (
81
+ !keyObject ||
82
+ keyObject.type !== 'public' ||
83
+ keyObject.asymmetricKeyType !== 'ed25519'
84
+ ) {
85
+ throw new Error(INVALID_KEY_MESSAGE);
86
+ }
87
+ return keyObject;
88
+ }
89
+
90
+ function rawToKeyObject(raw) {
91
+ return crypto.createPublicKey({
92
+ key: { kty: 'OKP', crv: 'Ed25519', x: raw.toString('base64url') },
93
+ format: 'jwk',
94
+ });
95
+ }
96
+
97
+ /**
98
+ * Compute the content digest of a plugin.
99
+ *
100
+ * The digest is `sha256-<hex>` of the lines `<relativePath>\0<sha256 of
101
+ * file>\n`, sorted by relative path. Paths are relative to `baseDir` and use
102
+ * forward slashes, so the digest is identical on Windows and POSIX.
103
+ *
104
+ * @param {Object} options
105
+ * @param {string[]} [options.files] - File paths (absolute or relative to baseDir)
106
+ * @param {string} [options.baseDir] - Plugin root; files must be inside it
107
+ * @param {Object<string, Buffer|string>} [options.contents] - In-memory
108
+ * files keyed by relative path (alternative or addition to `files`)
109
+ * @returns {string} `sha256-<hex>`
110
+ */
111
+ function computeContentDigest({ files = [], baseDir, contents = {} } = {}) {
112
+ const root = path.resolve(baseDir || process.cwd());
113
+ const entries = new Map();
114
+
115
+ for (const file of files) {
116
+ const abs = path.resolve(root, file);
117
+ const rel = path.relative(root, abs);
118
+ if (
119
+ !rel ||
120
+ rel === '..' ||
121
+ rel.startsWith(`..${path.sep}`) ||
122
+ path.isAbsolute(rel)
123
+ ) {
124
+ throw new Error(`Plugin file is outside the plugin directory: ${file}`);
125
+ }
126
+ entries.set(rel.split(path.sep).join('/'), fs.readFileSync(abs));
127
+ }
128
+
129
+ for (const [name, value] of Object.entries(contents)) {
130
+ const rel = path.posix.normalize(String(name).split('\\').join('/'));
131
+ if (
132
+ rel === '..' ||
133
+ rel.startsWith('../') ||
134
+ rel === '.' ||
135
+ path.posix.isAbsolute(rel)
136
+ ) {
137
+ throw new Error(`Plugin content path is not relative: ${name}`);
138
+ }
139
+ entries.set(rel, Buffer.isBuffer(value) ? value : Buffer.from(value));
140
+ }
141
+
142
+ if (entries.size === 0) {
143
+ throw new Error('No plugin content supplied for digest');
144
+ }
145
+
146
+ const hash = crypto.createHash('sha256');
147
+ for (const rel of [...entries.keys()].sort()) {
148
+ const fileHash = crypto
149
+ .createHash('sha256')
150
+ .update(entries.get(rel))
151
+ .digest('hex');
152
+ hash.update(`${rel}\0${fileHash}\n`, 'utf8');
153
+ }
154
+ return DIGEST_PREFIX + hash.digest('hex');
155
+ }
156
+
15
157
  /**
16
158
  * Plugin signature verification with Ed25519
17
159
  */
@@ -22,23 +164,20 @@ class PluginSignatureVerifier {
22
164
  this.auditLogger =
23
165
  options.auditLogger || this._defaultAuditLogger.bind(this);
24
166
  this.failClosed = options.failClosed !== false; // Default to fail-closed
167
+ // Opt-in: reject manifests that do not bind the plugin code
168
+ this.requireContentDigest = options.requireContentDigest === true;
25
169
 
26
170
  // Load trusted public keys
27
171
  this._loadTrustedKeys(options.trustedKeysPath);
28
172
  }
29
173
 
30
174
  /**
31
- * Load trusted public keys from configuration
175
+ * Load trusted public keys from configuration.
176
+ * Without a keys file the trust store stays empty (no built-in keys).
32
177
  * @param {string} keysPath - Path to trusted keys file
33
178
  */
34
179
  _loadTrustedKeys(keysPath) {
35
180
  if (!keysPath) {
36
- // Default trusted keys for the DevilsDev organization
37
- this.trustStore.set('devilsdev-official', {
38
- publicKey: 'your-ed25519-public-key-here',
39
- name: 'DevilsDev Official Plugins',
40
- verified: true,
41
- });
42
181
  return;
43
182
  }
44
183
 
@@ -49,6 +188,8 @@ class PluginSignatureVerifier {
49
188
 
50
189
  for (const [keyId, keyInfo] of Object.entries(trustedKeys)) {
51
190
  if (keyInfo.publicKey && keyInfo.name) {
191
+ // Reject unusable keys up front instead of at verification time
192
+ toEd25519PublicKey(keyInfo.publicKey);
52
193
  this.trustStore.set(keyId, keyInfo);
53
194
  }
54
195
  }
@@ -57,6 +198,9 @@ class PluginSignatureVerifier {
57
198
  if (this.failClosed) {
58
199
  throw new Error(`Failed to load trusted keys: ${error.message}`);
59
200
  }
201
+ logger.warn(
202
+ `[PLUGIN_SIGNATURE] Failed to load trusted keys: ${error.message}`,
203
+ );
60
204
  }
61
205
  }
62
206
 
@@ -65,11 +209,15 @@ class PluginSignatureVerifier {
65
209
  * @param {object} manifest - Plugin manifest
66
210
  * @param {string} signature - Base64 encoded signature
67
211
  * @param {string} signerId - Signer identifier
212
+ * @param {object} [content] - Plugin content to check against
213
+ * manifest.contentDigest: `{ files, baseDir, contents }` (see
214
+ * computeContentDigest)
68
215
  * @returns {Promise<object>} Verification result
69
216
  */
70
- async verifyPluginSignature(manifest, signature, signerId) {
217
+ async verifyPluginSignature(manifest, signature, signerId, content = null) {
71
218
  const verificationResult = {
72
219
  verified: false,
220
+ contentVerified: false,
73
221
  signerId,
74
222
  timestamp: new Date().toISOString(),
75
223
  error: null,
@@ -115,15 +263,8 @@ class PluginSignatureVerifier {
115
263
  'utf8',
116
264
  );
117
265
 
118
- // Decode signature
119
- let signatureBuffer;
120
- try {
121
- signatureBuffer = Buffer.from(signature, 'base64');
122
- } catch (error) {
123
- throw new Error(`Invalid signature encoding: ${error.message}`);
124
- }
266
+ const signatureBuffer = Buffer.from(signature, 'base64');
125
267
 
126
- // Verify signature using Web Crypto API or Node.js crypto
127
268
  const isValid = await this._verifyEd25519Signature(
128
269
  manifestData,
129
270
  signatureBuffer,
@@ -134,14 +275,22 @@ class PluginSignatureVerifier {
134
275
  throw new Error('Signature verification failed');
135
276
  }
136
277
 
278
+ // The signature covers contentDigest; now check the code matches it.
279
+ verificationResult.contentVerified = this._verifyContentDigest(
280
+ canonicalManifest,
281
+ content,
282
+ );
283
+
137
284
  verificationResult.verified = true;
138
285
  verificationResult.auditTrail.push({
139
286
  action: 'signature_verified',
140
287
  signerId,
141
288
  timestamp: new Date().toISOString(),
142
289
  manifestHash: this._hashManifest(canonicalManifest),
290
+ contentVerified: verificationResult.contentVerified,
143
291
  });
144
292
  } catch (error) {
293
+ verificationResult.verified = false;
145
294
  verificationResult.error = error.message;
146
295
  verificationResult.auditTrail.push({
147
296
  action: 'verification_failed',
@@ -163,58 +312,66 @@ class PluginSignatureVerifier {
163
312
  }
164
313
 
165
314
  /**
166
- * Verify Ed25519 signature
167
- * @param {Buffer} data - Data that was signed
168
- * @param {Buffer} signature - Signature bytes
169
- * @param {string} publicKeyHex - Public key in hex format
170
- * @returns {Promise<boolean>} Verification result
315
+ * Check the supplied plugin content against the signed contentDigest.
316
+ * @param {object} canonicalManifest - Signed manifest
317
+ * @param {object|null} content - `{ files, baseDir, contents }`
318
+ * @returns {boolean} true when the content digest was checked and matched
319
+ * @private
171
320
  */
172
- async _verifyEd25519Signature(data, signature, publicKeyHex) {
173
- try {
174
- // Convert hex public key to buffer
175
- const publicKeyBuffer = Buffer.from(publicKeyHex, 'hex');
176
-
177
- // Use Node.js crypto for Ed25519 verification
178
- const verify = crypto.createVerify('Ed25519');
179
- verify.update(data);
321
+ _verifyContentDigest(canonicalManifest, content) {
322
+ const declared = canonicalManifest.contentDigest;
323
+ const hasContent =
324
+ content &&
325
+ ((Array.isArray(content.files) && content.files.length > 0) ||
326
+ (content.contents && Object.keys(content.contents).length > 0));
327
+
328
+ if (declared === undefined || declared === null) {
329
+ if (this.requireContentDigest) {
330
+ throw new Error(
331
+ 'Manifest has no contentDigest; the signature does not cover the plugin code',
332
+ );
333
+ }
334
+ return false;
335
+ }
180
336
 
181
- // Create public key object
182
- const publicKeyObject = crypto.createPublicKey({
183
- key: publicKeyBuffer,
184
- format: 'der',
185
- type: 'spki',
186
- });
337
+ if (typeof declared !== 'string' || !declared.startsWith(DIGEST_PREFIX)) {
338
+ throw new Error('Invalid contentDigest: expected "sha256-<hex>"');
339
+ }
187
340
 
188
- return verify.verify(publicKeyObject, signature);
189
- } catch (error) {
190
- // Fallback to Web Crypto API if available
191
- if (typeof crypto.subtle !== 'undefined') {
192
- try {
193
- const publicKeyBuffer = Buffer.from(publicKeyHex, 'hex');
194
- const publicKey = await crypto.subtle.importKey(
195
- 'raw',
196
- publicKeyBuffer,
197
- {
198
- name: 'Ed25519',
199
- namedCurve: 'Ed25519',
200
- },
201
- false,
202
- ['verify'],
203
- );
341
+ if (!hasContent) {
342
+ throw new Error(
343
+ 'Manifest declares a contentDigest but no plugin content was supplied to verify it',
344
+ );
345
+ }
204
346
 
205
- return await crypto.subtle.verify(
206
- 'Ed25519',
207
- publicKey,
208
- signature,
209
- data,
210
- );
211
- } catch (webCryptoError) {
212
- throw new Error(`Ed25519 verification failed: ${error.message}`);
213
- }
214
- }
347
+ const actual = Buffer.from(computeContentDigest(content), 'utf8');
348
+ const expected = Buffer.from(declared.toLowerCase(), 'utf8');
349
+ if (
350
+ actual.length !== expected.length ||
351
+ !crypto.timingSafeEqual(actual, expected)
352
+ ) {
353
+ throw new Error(
354
+ 'Plugin content digest mismatch: plugin code does not match the signed manifest',
355
+ );
356
+ }
357
+ return true;
358
+ }
215
359
 
216
- throw new Error(`Ed25519 verification failed: ${error.message}`);
360
+ /**
361
+ * Verify Ed25519 signature
362
+ * @param {Buffer} data - Data that was signed
363
+ * @param {Buffer} signature - Signature bytes
364
+ * @param {crypto.KeyObject|Buffer|string} publicKey - Ed25519 public key
365
+ * (raw hex, PEM, DER or KeyObject)
366
+ * @returns {Promise<boolean>} Verification result
367
+ */
368
+ async _verifyEd25519Signature(data, signature, publicKey) {
369
+ const keyObject = toEd25519PublicKey(publicKey);
370
+ if (!Buffer.isBuffer(signature) || signature.length !== 64) {
371
+ return false;
217
372
  }
373
+ // Ed25519 takes no digest algorithm: pass null.
374
+ return crypto.verify(null, data, keyObject, signature);
218
375
  }
219
376
 
220
377
  /**
@@ -233,6 +390,18 @@ class PluginSignatureVerifier {
233
390
  return this._sortObjectKeys(canonical);
234
391
  }
235
392
 
393
+ /**
394
+ * Bytes that a signer must sign for a manifest (canonical JSON).
395
+ * @param {object} manifest - Plugin manifest
396
+ * @returns {Buffer}
397
+ */
398
+ getSigningPayload(manifest) {
399
+ return Buffer.from(
400
+ JSON.stringify(this._canonicalizeManifest(manifest)),
401
+ 'utf8',
402
+ );
403
+ }
404
+
236
405
  /**
237
406
  * Sort object keys recursively
238
407
  * @param {any} obj - Object to sort
@@ -276,10 +445,11 @@ class PluginSignatureVerifier {
276
445
  const auditEntry = {
277
446
  timestamp: result.timestamp,
278
447
  action: 'plugin_signature_verification',
279
- pluginId: manifest.name || 'unknown',
280
- pluginVersion: manifest.version || 'unknown',
448
+ pluginId: (manifest && manifest.name) || 'unknown',
449
+ pluginVersion: (manifest && manifest.version) || 'unknown',
281
450
  signerId: result.signerId,
282
451
  verified: result.verified,
452
+ contentVerified: result.contentVerified,
283
453
  error: result.error,
284
454
  auditTrail: result.auditTrail,
285
455
  };
@@ -309,7 +479,8 @@ class PluginSignatureVerifier {
309
479
  /**
310
480
  * Add trusted signer
311
481
  * @param {string} signerId - Signer identifier
312
- * @param {string} publicKey - Ed25519 public key in hex
482
+ * @param {string|Buffer|crypto.KeyObject} publicKey - Ed25519 public key:
483
+ * 64 hex characters (raw 32 bytes), PEM, DER (SPKI) or a KeyObject
313
484
  * @param {string} name - Human-readable name
314
485
  * @param {object} metadata - Additional metadata
315
486
  */
@@ -318,13 +489,15 @@ class PluginSignatureVerifier {
318
489
  throw new Error('signerId, publicKey, and name are required');
319
490
  }
320
491
 
321
- // Validate public key format
322
- if (!/^[0-9a-fA-F]{64}$/.test(publicKey)) {
323
- throw new Error('Public key must be 64 hex characters (32 bytes)');
324
- }
492
+ // Validate the key is a usable Ed25519 public key
493
+ const keyObject = toEd25519PublicKey(publicKey);
494
+ const storedKey =
495
+ typeof publicKey === 'string' && HEX_KEY_RE.test(publicKey)
496
+ ? publicKey.toLowerCase()
497
+ : keyObject.export({ format: 'pem', type: 'spki' });
325
498
 
326
499
  this.trustStore.set(signerId, {
327
- publicKey: publicKey.toLowerCase(),
500
+ publicKey: storedKey,
328
501
  name,
329
502
  verified: metadata.verified || false,
330
503
  addedAt: new Date().toISOString(),
@@ -409,4 +582,6 @@ function createPluginVerifier(options = {}) {
409
582
  module.exports = {
410
583
  PluginSignatureVerifier,
411
584
  createPluginVerifier,
585
+ computeContentDigest,
586
+ toEd25519PublicKey,
412
587
  };
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Deprecation wrappers for public exports (see SEMVER.md, "Deprecation
5
+ * process"): the export keeps working and warns once per process when used.
6
+ */
7
+
8
+ const wrapped = new WeakMap();
9
+ const warned = new Set();
10
+
11
+ function warnOnce(code, message) {
12
+ if (warned.has(code)) return;
13
+ warned.add(code);
14
+ process.emitWarning(message, { type: 'DeprecationWarning', code });
15
+ }
16
+
17
+ /**
18
+ * Wrap a class so constructing it (or calling a static method) emits a
19
+ * one-time DeprecationWarning. A Proxy keeps the original's identity for
20
+ * prototype, name, statics, `extends` and `instanceof`, so instances created
21
+ * inside the library still satisfy `instanceof Wrapped`. The same class
22
+ * always gets the same wrapper, so aliases stay identical.
23
+ *
24
+ * @param {Function} Class - Class to wrap
25
+ * @param {object} info
26
+ * @param {string} info.name - Export name, used in the message and code
27
+ * @param {string} info.message - Full warning text
28
+ * @returns {Function} The wrapped class
29
+ */
30
+ function deprecateClass(Class, { name, message }) {
31
+ if (wrapped.has(Class)) return wrapped.get(Class);
32
+ // SSOManager -> RAG_DEP_SSO_MANAGER, OAuth2Provider -> RAG_DEP_O_AUTH2_PROVIDER
33
+ const snake = name
34
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
35
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
36
+ .toUpperCase();
37
+ const code = `RAG_DEP_${snake}`;
38
+ const proxy = new Proxy(Class, {
39
+ construct(target, args, newTarget) {
40
+ warnOnce(code, message);
41
+ return Reflect.construct(
42
+ target,
43
+ args,
44
+ newTarget === proxy ? target : newTarget,
45
+ );
46
+ },
47
+ get(target, property, receiver) {
48
+ const value = Reflect.get(target, property, receiver);
49
+ // Only the class's own static methods; inherited Function.prototype
50
+ // members (call, bind, toString, Symbol.hasInstance for instanceof)
51
+ // must not warn.
52
+ const ownStatic =
53
+ typeof value === 'function' &&
54
+ property !== 'prototype' &&
55
+ Object.prototype.hasOwnProperty.call(target, property);
56
+ if (ownStatic) {
57
+ return function deprecatedStatic(...args) {
58
+ warnOnce(code, message);
59
+ return value.apply(this === proxy ? target : this, args);
60
+ };
61
+ }
62
+ return value;
63
+ },
64
+ });
65
+ wrapped.set(Class, proxy);
66
+ return proxy;
67
+ }
68
+
69
+ module.exports = { deprecateClass };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Embed a single query string with any supported embedder shape.
5
+ *
6
+ * Prefers embedQuery(); falls back to embed(). Accepts the common return
7
+ * shapes: a vector, a list of vectors, or [{ values | vector | embedding }].
8
+ *
9
+ * @param {object} embedder - Object with embedQuery() or embed()
10
+ * @param {string} query - Text to embed
11
+ * @param {{ signal?: AbortSignal }} [options] - Passed to the embedder as its
12
+ * second argument (only when given, so embedders keep their usual arity)
13
+ * @returns {Promise<number[]|undefined>} The query vector
14
+ */
15
+ async function embedQuery(embedder, query, options) {
16
+ const args = options ? [query, options] : [query];
17
+ const raw = embedder.embedQuery
18
+ ? await embedder.embedQuery(...args)
19
+ : await embedder.embed(...args);
20
+ const first = Array.isArray(raw) && typeof raw[0] !== 'number' ? raw[0] : raw;
21
+ if (Array.isArray(first)) return first;
22
+ return first?.values || first?.vector || first?.embedding;
23
+ }
24
+
25
+ /**
26
+ * @param {object} [embedder]
27
+ * @returns {boolean} Whether embedQuery() can use this embedder
28
+ */
29
+ function canEmbed(embedder) {
30
+ return Boolean(embedder && (embedder.embedQuery || embedder.embed));
31
+ }
32
+
33
+ module.exports = { embedQuery, canEmbed };
@@ -65,6 +65,21 @@ const ValidationPatterns = {
65
65
  hex: /^[0-9a-fA-F]+$/,
66
66
  };
67
67
 
68
+ /**
69
+ * Test a shared pattern from start of string. The DangerousPatterns regexes
70
+ * carry the /g flag (needed by replace()), and RegExp#test() on a /g regex
71
+ * resumes from lastIndex left by the previous call, which made a match
72
+ * against one input skip part of the next and let payloads through.
73
+ *
74
+ * @param {RegExp} pattern
75
+ * @param {string} str
76
+ * @returns {boolean}
77
+ */
78
+ function matchesPattern(pattern, str) {
79
+ pattern.lastIndex = 0;
80
+ return pattern.test(str);
81
+ }
82
+
68
83
  /**
69
84
  * Dangerous patterns that indicate attacks (compiled for performance)
70
85
  */
@@ -476,7 +491,7 @@ class InputSanitizer {
476
491
 
477
492
  // Apply all XSS patterns
478
493
  for (const pattern of DangerousPatterns.xss) {
479
- if (pattern.test(sanitized)) {
494
+ if (matchesPattern(pattern, sanitized)) {
480
495
  this.stats.blocked++;
481
496
  sanitized = sanitized.replace(pattern, '');
482
497
  }
@@ -497,7 +512,7 @@ class InputSanitizer {
497
512
 
498
513
  // Check for SQL injection patterns
499
514
  for (const pattern of DangerousPatterns.sql) {
500
- if (pattern.test(sanitized)) {
515
+ if (matchesPattern(pattern, sanitized)) {
501
516
  this.stats.blocked++;
502
517
  if (this.options.throwOnInvalid) {
503
518
  throw new Error('Potential SQL injection detected');
@@ -520,7 +535,7 @@ class InputSanitizer {
520
535
  _sanitizeCommand(str) {
521
536
  // Check for command injection patterns
522
537
  for (const pattern of DangerousPatterns.command) {
523
- if (pattern.test(str)) {
538
+ if (matchesPattern(pattern, str)) {
524
539
  this.stats.blocked++;
525
540
  if (this.options.throwOnInvalid) {
526
541
  throw new Error('Potential command injection detected');
@@ -560,7 +575,7 @@ class InputSanitizer {
560
575
 
561
576
  // Check for path traversal patterns in both original and fully decoded versions
562
577
  for (const pattern of DangerousPatterns.pathTraversal) {
563
- if (pattern.test(str) || pattern.test(current)) {
578
+ if (matchesPattern(pattern, str) || matchesPattern(pattern, current)) {
564
579
  this.stats.blocked++;
565
580
  // SECURITY: Always throw on path traversal - it should never be silently sanitized
566
581
  // Empty paths could resolve to application root, creating security vulnerabilities
@@ -31,9 +31,20 @@ const getLogLevel = () => {
31
31
  // Determine if pretty printing should be enabled
32
32
  const shouldUsePrettyPrint = () => {
33
33
  // Enable pretty print in development or when explicitly requested
34
- return (
35
- process.env.NODE_ENV === 'development' || process.env.LOG_PRETTY === 'true'
36
- );
34
+ const requested =
35
+ process.env.NODE_ENV === 'development' || process.env.LOG_PRETTY === 'true';
36
+ return requested && isPinoPrettyAvailable();
37
+ };
38
+
39
+ // pino-pretty is an optional peer dependency; pino throws at startup if the
40
+ // transport target cannot be resolved, so fall back to JSON output instead.
41
+ const isPinoPrettyAvailable = () => {
42
+ try {
43
+ require.resolve('pino-pretty');
44
+ return true;
45
+ } catch {
46
+ return false;
47
+ }
37
48
  };
38
49
 
39
50
  /**
@@ -68,6 +79,25 @@ const baseLogger = pino({
68
79
  }),
69
80
  });
70
81
 
82
+ /**
83
+ * Coerce the meta argument into an object. Callers often pass
84
+ * console-style extras such as `logger.error("Failed:", error.message)`;
85
+ * spreading a string would log one key per character.
86
+ *
87
+ * @param {*} meta
88
+ * @returns {object}
89
+ */
90
+ function normalizeMeta(meta) {
91
+ if (meta == null) return {};
92
+ if (meta instanceof Error) {
93
+ return { error: meta.message, stack: meta.stack };
94
+ }
95
+ if (typeof meta !== 'object' || Array.isArray(meta)) {
96
+ return { detail: meta };
97
+ }
98
+ return meta;
99
+ }
100
+
71
101
  /**
72
102
  * Enhanced logger with correlation ID support and structured context
73
103
  */
@@ -118,7 +148,7 @@ class StructuredLogger {
118
148
  ...this.context,
119
149
  correlationId: this.correlationId || crypto.randomUUID(),
120
150
  timestamp: new Date().toISOString(),
121
- ...meta,
151
+ ...normalizeMeta(meta),
122
152
  };
123
153
 
124
154
  this.logger[level](logData, message);