@devilsdev/rag-core 3.8.0 → 3.8.2
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/dist/index.cjs +206 -44
- package/dist/types/config/component-factory.d.ts +4 -2
- package/dist/types/config/load-plugin-config.d.ts +35 -0
- package/dist/types/config/validate-schema.d.ts +18 -0
- package/dist/types/core/plugin-registry.d.ts +13 -0
- package/dist/types/ecosystem/plugin-probe.d.ts +12 -0
- package/dist/types/ecosystem/plugin-scanner.d.ts +9 -1
- package/dist/types/enterprise/audit-logging.d.ts +143 -10
- package/dist/types/guardrails/guardrails-pipeline.d.ts +28 -0
- package/dist/types/retrieval/access-fields.d.ts +38 -0
- package/dist/types/security/plugin-signature-verifier.d.ts +36 -0
- package/dist/types/utils/deprecate.d.ts +23 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -302,6 +302,19 @@ var require_plugin_signature_verifier = __commonJS({
|
|
|
302
302
|
format: "jwk"
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
|
+
function rejectReservedKeys(value) {
|
|
306
|
+
if (value === null || typeof value !== "object") return;
|
|
307
|
+
if (Array.isArray(value)) {
|
|
308
|
+
value.forEach(rejectReservedKeys);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
for (const key of Object.keys(value)) {
|
|
312
|
+
if (key === "__proto__") {
|
|
313
|
+
throw new Error(`Invalid manifest: reserved key "${key}"`);
|
|
314
|
+
}
|
|
315
|
+
rejectReservedKeys(value[key]);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
305
318
|
function computeContentDigest({ files = [], baseDir, contents = {} } = {}) {
|
|
306
319
|
const root = path.resolve(baseDir || process.cwd());
|
|
307
320
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -331,6 +344,9 @@ var require_plugin_signature_verifier = __commonJS({
|
|
|
331
344
|
}
|
|
332
345
|
return DIGEST_PREFIX + hash.digest("hex");
|
|
333
346
|
}
|
|
347
|
+
function hasContentDigest(manifest) {
|
|
348
|
+
return Boolean(manifest) && Object.hasOwn(manifest, "contentDigest") && manifest.contentDigest !== void 0 && manifest.contentDigest !== null;
|
|
349
|
+
}
|
|
334
350
|
var PluginSignatureVerifier = class {
|
|
335
351
|
constructor(options = {}) {
|
|
336
352
|
this.enabled = options.enabled !== false;
|
|
@@ -399,37 +415,13 @@ var require_plugin_signature_verifier = __commonJS({
|
|
|
399
415
|
}
|
|
400
416
|
return verificationResult;
|
|
401
417
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
throw new Error("Invalid signature: must be a non-empty string");
|
|
407
|
-
}
|
|
408
|
-
if (!signerId || typeof signerId !== "string") {
|
|
409
|
-
throw new Error("Invalid signerId: must be a non-empty string");
|
|
410
|
-
}
|
|
411
|
-
const trustedKey = this.trustStore.get(signerId);
|
|
412
|
-
if (!trustedKey) {
|
|
413
|
-
throw new Error(`Untrusted signer: ${signerId}`);
|
|
414
|
-
}
|
|
415
|
-
const canonicalManifest = this._canonicalizeManifest(manifest);
|
|
416
|
-
const manifestData = Buffer.from(
|
|
417
|
-
JSON.stringify(canonicalManifest),
|
|
418
|
-
"utf8"
|
|
419
|
-
);
|
|
420
|
-
const signatureBuffer = Buffer.from(signature, "base64");
|
|
421
|
-
const isValid = await this._verifyEd25519Signature(
|
|
422
|
-
manifestData,
|
|
423
|
-
signatureBuffer,
|
|
424
|
-
trustedKey.publicKey
|
|
425
|
-
);
|
|
426
|
-
if (!isValid) {
|
|
427
|
-
throw new Error("Signature verification failed");
|
|
428
|
-
}
|
|
429
|
-
verificationResult.contentVerified = this._verifyContentDigest(
|
|
430
|
-
canonicalManifest,
|
|
418
|
+
const { canonicalManifest, contentVerified } = this.verifyManifestSync(
|
|
419
|
+
manifest,
|
|
420
|
+
signature,
|
|
421
|
+
signerId,
|
|
431
422
|
content
|
|
432
423
|
);
|
|
424
|
+
verificationResult.contentVerified = contentVerified;
|
|
433
425
|
verificationResult.verified = true;
|
|
434
426
|
verificationResult.auditTrail.push({
|
|
435
427
|
action: "signature_verified",
|
|
@@ -457,6 +449,51 @@ var require_plugin_signature_verifier = __commonJS({
|
|
|
457
449
|
this._auditVerification(verificationResult, manifest);
|
|
458
450
|
return verificationResult;
|
|
459
451
|
}
|
|
452
|
+
/**
|
|
453
|
+
* Synchronous signature check shared by every verification path: input
|
|
454
|
+
* validation, trusted-signer lookup, Ed25519 signature over the canonical
|
|
455
|
+
* manifest, and the contentDigest check. Does not consult `enabled` and
|
|
456
|
+
* does not write audit entries; callers decide both.
|
|
457
|
+
*
|
|
458
|
+
* @param {object} manifest - Plugin manifest
|
|
459
|
+
* @param {string} signature - Base64 encoded signature
|
|
460
|
+
* @param {string} signerId - Signer identifier
|
|
461
|
+
* @param {object} [content] - `{ files, baseDir, contents }` to check
|
|
462
|
+
* against manifest.contentDigest (see computeContentDigest)
|
|
463
|
+
* @returns {{contentVerified: boolean, canonicalManifest: object}}
|
|
464
|
+
* contentVerified is false when the manifest has no contentDigest
|
|
465
|
+
* @throws {Error} When the manifest must not be trusted
|
|
466
|
+
*/
|
|
467
|
+
verifyManifestSync(manifest, signature, signerId, content = null) {
|
|
468
|
+
if (!manifest || typeof manifest !== "object") {
|
|
469
|
+
throw new Error("Invalid manifest: must be an object");
|
|
470
|
+
}
|
|
471
|
+
if (!signature || typeof signature !== "string") {
|
|
472
|
+
throw new Error("Invalid signature: must be a non-empty string");
|
|
473
|
+
}
|
|
474
|
+
if (!signerId || typeof signerId !== "string") {
|
|
475
|
+
throw new Error("Invalid signerId: must be a non-empty string");
|
|
476
|
+
}
|
|
477
|
+
const trustedKey = this.trustStore.get(signerId);
|
|
478
|
+
if (!trustedKey) {
|
|
479
|
+
throw new Error(`Untrusted signer: ${signerId}`);
|
|
480
|
+
}
|
|
481
|
+
const canonicalManifest = this._canonicalizeManifest(manifest);
|
|
482
|
+
const manifestData = Buffer.from(JSON.stringify(canonicalManifest), "utf8");
|
|
483
|
+
const signatureBuffer = Buffer.from(signature, "base64");
|
|
484
|
+
if (!this._checkEd25519Signature(
|
|
485
|
+
manifestData,
|
|
486
|
+
signatureBuffer,
|
|
487
|
+
trustedKey.publicKey
|
|
488
|
+
)) {
|
|
489
|
+
throw new Error("Signature verification failed");
|
|
490
|
+
}
|
|
491
|
+
const contentVerified = this._verifyContentDigest(
|
|
492
|
+
canonicalManifest,
|
|
493
|
+
content
|
|
494
|
+
);
|
|
495
|
+
return { contentVerified, canonicalManifest };
|
|
496
|
+
}
|
|
460
497
|
/**
|
|
461
498
|
* Check the supplied plugin content against the signed contentDigest.
|
|
462
499
|
* @param {object} canonicalManifest - Signed manifest
|
|
@@ -465,9 +502,9 @@ var require_plugin_signature_verifier = __commonJS({
|
|
|
465
502
|
* @private
|
|
466
503
|
*/
|
|
467
504
|
_verifyContentDigest(canonicalManifest, content) {
|
|
468
|
-
const declared = canonicalManifest.contentDigest;
|
|
505
|
+
const declared = hasContentDigest(canonicalManifest) ? canonicalManifest.contentDigest : void 0;
|
|
469
506
|
const hasContent = content && (Array.isArray(content.files) && content.files.length > 0 || content.contents && Object.keys(content.contents).length > 0);
|
|
470
|
-
if (declared === void 0
|
|
507
|
+
if (declared === void 0) {
|
|
471
508
|
if (this.requireContentDigest) {
|
|
472
509
|
throw new Error(
|
|
473
510
|
"Manifest has no contentDigest; the signature does not cover the plugin code"
|
|
@@ -501,6 +538,17 @@ var require_plugin_signature_verifier = __commonJS({
|
|
|
501
538
|
* @returns {Promise<boolean>} Verification result
|
|
502
539
|
*/
|
|
503
540
|
async _verifyEd25519Signature(data, signature, publicKey) {
|
|
541
|
+
return this._checkEd25519Signature(data, signature, publicKey);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Synchronous form of _verifyEd25519Signature.
|
|
545
|
+
* @param {Buffer} data - Data that was signed
|
|
546
|
+
* @param {Buffer} signature - Signature bytes
|
|
547
|
+
* @param {crypto.KeyObject|Buffer|string} publicKey - Ed25519 public key
|
|
548
|
+
* @returns {boolean}
|
|
549
|
+
* @private
|
|
550
|
+
*/
|
|
551
|
+
_checkEd25519Signature(data, signature, publicKey) {
|
|
504
552
|
const keyObject = toEd25519PublicKey(publicKey);
|
|
505
553
|
if (!Buffer.isBuffer(signature) || signature.length !== 64) {
|
|
506
554
|
return false;
|
|
@@ -514,6 +562,7 @@ var require_plugin_signature_verifier = __commonJS({
|
|
|
514
562
|
*/
|
|
515
563
|
_canonicalizeManifest(manifest) {
|
|
516
564
|
const canonical = JSON.parse(JSON.stringify(manifest));
|
|
565
|
+
rejectReservedKeys(canonical);
|
|
517
566
|
delete canonical.signature;
|
|
518
567
|
delete canonical.signerId;
|
|
519
568
|
delete canonical.signedAt;
|
|
@@ -681,6 +730,7 @@ var require_plugin_signature_verifier = __commonJS({
|
|
|
681
730
|
PluginSignatureVerifier,
|
|
682
731
|
createPluginVerifier,
|
|
683
732
|
computeContentDigest,
|
|
733
|
+
hasContentDigest,
|
|
684
734
|
toEd25519PublicKey
|
|
685
735
|
};
|
|
686
736
|
}
|
|
@@ -1123,6 +1173,23 @@ ${errorMessage}`
|
|
|
1123
1173
|
isSignatureVerificationEnabled() {
|
|
1124
1174
|
return Boolean(this._signatureVerifier && this._signatureVerifier.enabled);
|
|
1125
1175
|
}
|
|
1176
|
+
/**
|
|
1177
|
+
* The signature verifier this registry uses (trust store, policy flags).
|
|
1178
|
+
* @returns {object} The PluginSignatureVerifier
|
|
1179
|
+
*/
|
|
1180
|
+
getSignatureVerifier() {
|
|
1181
|
+
return this._signatureVerifier;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Record an audit entry for a plugin verification decision made outside the
|
|
1185
|
+
* registry (for example when a configured plugin is checked before load).
|
|
1186
|
+
* @param {string} action - Audit action
|
|
1187
|
+
* @param {object} details - Audit details
|
|
1188
|
+
* @returns {void}
|
|
1189
|
+
*/
|
|
1190
|
+
recordAuditEntry(action, details) {
|
|
1191
|
+
this._emitAuditEntry(action, details);
|
|
1192
|
+
}
|
|
1126
1193
|
/**
|
|
1127
1194
|
* Whether plugins without a signed manifest are rejected.
|
|
1128
1195
|
* @returns {boolean}
|
|
@@ -1370,7 +1437,7 @@ var require_package = __commonJS({
|
|
|
1370
1437
|
"package.json"(exports2, module2) {
|
|
1371
1438
|
module2.exports = {
|
|
1372
1439
|
name: "@devilsdev/rag-pipeline-utils",
|
|
1373
|
-
version: "3.8.
|
|
1440
|
+
version: "3.8.2",
|
|
1374
1441
|
description: "Composable retrieval-augmented generation (RAG) for Node.js: grounded citations, evaluation, guardrails, hybrid retrieval and a CLI.",
|
|
1375
1442
|
main: "dist/index.cjs",
|
|
1376
1443
|
type: "commonjs",
|
|
@@ -1629,10 +1696,10 @@ var require_package = __commonJS({
|
|
|
1629
1696
|
"why-is-node-running": "^3.2.2"
|
|
1630
1697
|
},
|
|
1631
1698
|
dependencies: {
|
|
1632
|
-
"@devilsdev/rag-connectors": "3.8.
|
|
1633
|
-
"@devilsdev/rag-core": "3.8.
|
|
1634
|
-
"@devilsdev/rag-eval": "3.8.
|
|
1635
|
-
"@devilsdev/rag-guardrails": "3.8.
|
|
1699
|
+
"@devilsdev/rag-connectors": "3.8.2",
|
|
1700
|
+
"@devilsdev/rag-core": "3.8.2",
|
|
1701
|
+
"@devilsdev/rag-eval": "3.8.2",
|
|
1702
|
+
"@devilsdev/rag-guardrails": "3.8.2",
|
|
1636
1703
|
ajv: "^8.17.1",
|
|
1637
1704
|
axios: "^1.21.0",
|
|
1638
1705
|
chokidar: "^4.0.3",
|
|
@@ -2903,18 +2970,21 @@ var require_scoring = __commonJS({
|
|
|
2903
2970
|
`\\b(${REFERENCE_WORDS})\\.?\\s+\\d+[a-z]?\\b`,
|
|
2904
2971
|
"giu"
|
|
2905
2972
|
);
|
|
2906
|
-
var IDENTIFIER_RE = new RegExp("(\\p{L}[\\p{L}\\p{N}]*)-(\\d+)\\b", "gu");
|
|
2973
|
+
var IDENTIFIER_RE = new RegExp("(?<![\\p{L}\\p{N}])(\\p{N}*)(\\p{L}[\\p{L}\\p{N}]*)-(\\d+)\\b", "gu");
|
|
2907
2974
|
function identifiers(text) {
|
|
2908
2975
|
if (!text || typeof text !== "string") return [];
|
|
2909
2976
|
return [
|
|
2910
2977
|
...new Set(
|
|
2911
|
-
[...text.matchAll(IDENTIFIER_RE)].map(
|
|
2978
|
+
[...text.matchAll(IDENTIFIER_RE)].map(
|
|
2979
|
+
// m[1] is digits before the word ("2kinesin-8"), not part of it
|
|
2980
|
+
(m) => `id:${m[0].slice(m[1].length).toLowerCase()}`
|
|
2981
|
+
)
|
|
2912
2982
|
)
|
|
2913
2983
|
];
|
|
2914
2984
|
}
|
|
2915
2985
|
function withoutReferences(text) {
|
|
2916
2986
|
if (!text || typeof text !== "string") return text;
|
|
2917
|
-
return text.replace(REFERENCE_RE, " ").replace(IDENTIFIER_RE, (_, word) => word);
|
|
2987
|
+
return text.replace(REFERENCE_RE, " ").replace(IDENTIFIER_RE, (_, lead, word) => lead + word);
|
|
2918
2988
|
}
|
|
2919
2989
|
var MONTHS = [
|
|
2920
2990
|
["january", "jan"],
|
|
@@ -5227,11 +5297,95 @@ var require_chunking_engine = __commonJS({
|
|
|
5227
5297
|
}
|
|
5228
5298
|
});
|
|
5229
5299
|
|
|
5300
|
+
// src/retrieval/access-fields.js
|
|
5301
|
+
var require_access_fields = __commonJS({
|
|
5302
|
+
"src/retrieval/access-fields.js"(exports2, module2) {
|
|
5303
|
+
"use strict";
|
|
5304
|
+
var ACCESS_KEYS = ["requiredRole", "acl"];
|
|
5305
|
+
var STORED_KEY = "rag_access";
|
|
5306
|
+
var NO_ACCESS = "rag:no-access";
|
|
5307
|
+
function accessFields(doc) {
|
|
5308
|
+
const fields = {};
|
|
5309
|
+
if (doc.acl !== void 0) fields.acl = doc.acl;
|
|
5310
|
+
if (doc.requiredRole !== void 0) fields.requiredRole = doc.requiredRole;
|
|
5311
|
+
return fields;
|
|
5312
|
+
}
|
|
5313
|
+
function storedMetadata(doc) {
|
|
5314
|
+
const metadata = doc.metadata || {};
|
|
5315
|
+
const access = accessFields(doc);
|
|
5316
|
+
return Object.keys(access).length ? { ...metadata, [STORED_KEY]: access } : metadata;
|
|
5317
|
+
}
|
|
5318
|
+
function readStoredMetadata(stored) {
|
|
5319
|
+
if (!stored || typeof stored !== "object" || !(STORED_KEY in stored)) {
|
|
5320
|
+
return { metadata: stored || {}, access: {} };
|
|
5321
|
+
}
|
|
5322
|
+
const { [STORED_KEY]: access, ...metadata } = stored;
|
|
5323
|
+
const readable = access && typeof access === "object" && !Array.isArray(access);
|
|
5324
|
+
return {
|
|
5325
|
+
metadata,
|
|
5326
|
+
access: readable ? accessFields(access) : { acl: [NO_ACCESS] }
|
|
5327
|
+
};
|
|
5328
|
+
}
|
|
5329
|
+
function restrictionLists(result) {
|
|
5330
|
+
const lists = [];
|
|
5331
|
+
if (!result || typeof result !== "object") return lists;
|
|
5332
|
+
for (const source of [result, result.metadata, result.doc]) {
|
|
5333
|
+
if (!source || typeof source !== "object") continue;
|
|
5334
|
+
for (const key of ACCESS_KEYS) {
|
|
5335
|
+
const value = source[key];
|
|
5336
|
+
if (value === void 0 || value === null || value === "") continue;
|
|
5337
|
+
const roles = [].concat(value);
|
|
5338
|
+
if (roles.length > 0) lists.push(roles);
|
|
5339
|
+
}
|
|
5340
|
+
}
|
|
5341
|
+
return lists;
|
|
5342
|
+
}
|
|
5343
|
+
var sameRoles = (a, b) => a.length === b.length && a.every((role) => b.includes(role));
|
|
5344
|
+
function mergeAccess(first, others) {
|
|
5345
|
+
const have = restrictionLists(first);
|
|
5346
|
+
const extra = [];
|
|
5347
|
+
for (const copy of others) {
|
|
5348
|
+
for (const roles of restrictionLists(copy)) {
|
|
5349
|
+
if (![...have, ...extra].some((list) => sameRoles(list, roles))) {
|
|
5350
|
+
extra.push(roles);
|
|
5351
|
+
}
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
if (extra.length === 0) return first;
|
|
5355
|
+
const merged = { ...first };
|
|
5356
|
+
const free = ACCESS_KEYS.filter(
|
|
5357
|
+
(key) => restrictionLists({ [key]: merged[key] }).length === 0
|
|
5358
|
+
);
|
|
5359
|
+
if (extra.length <= free.length) {
|
|
5360
|
+
extra.forEach((roles, i) => {
|
|
5361
|
+
merged[free[i]] = roles;
|
|
5362
|
+
});
|
|
5363
|
+
return merged;
|
|
5364
|
+
}
|
|
5365
|
+
const lists = [...restrictionLists(accessFields(merged)), ...extra];
|
|
5366
|
+
const shared = lists[0].filter(
|
|
5367
|
+
(role) => lists.every((list) => list.includes(role))
|
|
5368
|
+
);
|
|
5369
|
+
delete merged.requiredRole;
|
|
5370
|
+
merged.acl = shared.length ? shared : [NO_ACCESS];
|
|
5371
|
+
return merged;
|
|
5372
|
+
}
|
|
5373
|
+
module2.exports = {
|
|
5374
|
+
NO_ACCESS,
|
|
5375
|
+
accessFields,
|
|
5376
|
+
storedMetadata,
|
|
5377
|
+
readStoredMetadata,
|
|
5378
|
+
mergeAccess
|
|
5379
|
+
};
|
|
5380
|
+
}
|
|
5381
|
+
});
|
|
5382
|
+
|
|
5230
5383
|
// src/retrieval/bm25-search.js
|
|
5231
5384
|
var require_bm25_search = __commonJS({
|
|
5232
5385
|
"src/retrieval/bm25-search.js"(exports2, module2) {
|
|
5233
5386
|
"use strict";
|
|
5234
5387
|
var { EventEmitter } = require("events");
|
|
5388
|
+
var { accessFields } = require_access_fields();
|
|
5235
5389
|
var scoring = require_scoring();
|
|
5236
5390
|
var DEFAULT_CONFIG = { k1: 1.2, b: 0.75 };
|
|
5237
5391
|
var BM25Search = class extends EventEmitter {
|
|
@@ -5320,7 +5474,8 @@ var require_bm25_search = __commonJS({
|
|
|
5320
5474
|
id: doc.id,
|
|
5321
5475
|
score,
|
|
5322
5476
|
content: doc.content || doc.text || "",
|
|
5323
|
-
metadata: doc.metadata || {}
|
|
5477
|
+
metadata: doc.metadata || {},
|
|
5478
|
+
...accessFields(doc)
|
|
5324
5479
|
});
|
|
5325
5480
|
}
|
|
5326
5481
|
results.sort((a, b2) => b2.score - a.score);
|
|
@@ -5362,6 +5517,7 @@ var require_bm25_search = __commonJS({
|
|
|
5362
5517
|
var require_rank_fusion = __commonJS({
|
|
5363
5518
|
"src/retrieval/rank-fusion.js"(exports2, module2) {
|
|
5364
5519
|
"use strict";
|
|
5520
|
+
var { mergeAccess } = require_access_fields();
|
|
5365
5521
|
var DEFAULT_CONFIG = { k: 60 };
|
|
5366
5522
|
function reciprocalRankFusion(resultSets, options = {}) {
|
|
5367
5523
|
const { k = DEFAULT_CONFIG.k, weights } = options;
|
|
@@ -5385,15 +5541,17 @@ var require_rank_fusion = __commonJS({
|
|
|
5385
5541
|
if (scoreMap.has(id)) {
|
|
5386
5542
|
const entry = scoreMap.get(id);
|
|
5387
5543
|
entry.score += rrfScore;
|
|
5544
|
+
entry.copies.push(doc);
|
|
5388
5545
|
} else {
|
|
5389
5546
|
const { score: _origScore, ...rest } = doc;
|
|
5390
|
-
scoreMap.set(id, { score: rrfScore, fields: rest });
|
|
5547
|
+
scoreMap.set(id, { score: rrfScore, fields: rest, copies: [] });
|
|
5391
5548
|
}
|
|
5392
5549
|
}
|
|
5393
5550
|
}
|
|
5394
5551
|
const fused = [];
|
|
5395
5552
|
for (const [id, entry] of scoreMap) {
|
|
5396
|
-
|
|
5553
|
+
const fields = mergeAccess(entry.fields, entry.copies);
|
|
5554
|
+
fused.push({ ...fields, id, score: entry.score });
|
|
5397
5555
|
}
|
|
5398
5556
|
fused.sort((a, b) => b.score - a.score);
|
|
5399
5557
|
return fused;
|
|
@@ -7285,6 +7443,7 @@ var require_memory_retriever = __commonJS({
|
|
|
7285
7443
|
var { randomBytes } = require("crypto");
|
|
7286
7444
|
var { BaseConnector } = require_base_connector();
|
|
7287
7445
|
var { throwIfAborted } = require_connector_utils();
|
|
7446
|
+
var { accessFields } = require_access_fields();
|
|
7288
7447
|
var DEFAULT_CONFIG = {
|
|
7289
7448
|
maxDocuments: 1e5,
|
|
7290
7449
|
// 'error' rejects a store() that would exceed maxDocuments; 'fifo' evicts
|
|
@@ -7366,7 +7525,8 @@ var require_memory_retriever = __commonJS({
|
|
|
7366
7525
|
this.documents.set(doc.id, {
|
|
7367
7526
|
vector: doc.vector,
|
|
7368
7527
|
metadata: doc.metadata || {},
|
|
7369
|
-
content: doc.content || ""
|
|
7528
|
+
content: doc.content || "",
|
|
7529
|
+
...accessFields(doc)
|
|
7370
7530
|
});
|
|
7371
7531
|
}
|
|
7372
7532
|
if (vectors.length > 0) this.dimensions = dimensions;
|
|
@@ -7424,7 +7584,8 @@ var require_memory_retriever = __commonJS({
|
|
|
7424
7584
|
id,
|
|
7425
7585
|
score,
|
|
7426
7586
|
metadata: doc.metadata,
|
|
7427
|
-
content: doc.content
|
|
7587
|
+
content: doc.content,
|
|
7588
|
+
...accessFields(doc)
|
|
7428
7589
|
});
|
|
7429
7590
|
}
|
|
7430
7591
|
results.sort((a, b) => b.score - a.score);
|
|
@@ -7592,6 +7753,7 @@ var modules = {
|
|
|
7592
7753
|
"reranker/cascade-reranker.js": () => require_cascade_reranker(),
|
|
7593
7754
|
"reranker/embedding-reranker.js": () => require_embedding_reranker(),
|
|
7594
7755
|
"reranker/scoring-reranker.js": () => require_scoring_reranker(),
|
|
7756
|
+
"retrieval/access-fields.js": () => require_access_fields(),
|
|
7595
7757
|
"retrieval/bm25-search.js": () => require_bm25_search(),
|
|
7596
7758
|
"retrieval/hybrid-retriever.js": () => require_hybrid_retriever(),
|
|
7597
7759
|
"retrieval/rank-fusion.js": () => require_rank_fusion(),
|
|
@@ -24,9 +24,11 @@ export function buildComponents(config: object, options?: {
|
|
|
24
24
|
/**
|
|
25
25
|
* Instantiate a custom plugin from `plugins.<stage>.<name>` in the config.
|
|
26
26
|
* The module may export a class (constructed with the entry config), an
|
|
27
|
-
* instance, or either of those as `default`.
|
|
27
|
+
* instance, or either of those as `default`. When the shared plugin registry
|
|
28
|
+
* has signature verification enabled (production by default), the module is
|
|
29
|
+
* verified against its signed manifest before it is loaded.
|
|
28
30
|
*/
|
|
29
|
-
export function loadPluginModule(modulePath: any, entryConfig: any, configDir: any): any;
|
|
31
|
+
export function loadPluginModule(modulePath: any, entryConfig: any, configDir: any, name: any): any;
|
|
30
32
|
export namespace BUILT_INS {
|
|
31
33
|
namespace loader {
|
|
32
34
|
function auto(): typeof import("../loader/document-reader");
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads plugins from validated _config path and registers them.
|
|
3
|
+
* @param {string} configPath
|
|
4
|
+
*/
|
|
5
|
+
export function loadPluginsFromJson(configPath: string): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Verify a plugin file against its signed manifest BEFORE it is imported
|
|
8
|
+
* (importing runs the plugin's code).
|
|
9
|
+
*
|
|
10
|
+
* The manifest is read from `<plugin file>.manifest.json`. Its
|
|
11
|
+
* `contentDigest` (if any) must cover the plugin file plus any extra files
|
|
12
|
+
* listed in `manifest.files` (paths relative to the plugin file's
|
|
13
|
+
* directory). Nothing is checked when the registry's signature verification
|
|
14
|
+
* is disabled; when it is enabled and the registry was created with
|
|
15
|
+
* `requireSignedPlugins: true`, a missing manifest is an error.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} name - Plugin name
|
|
18
|
+
* @param {string} absPath - Absolute path of the plugin entry file
|
|
19
|
+
* @returns {Promise<void>}
|
|
20
|
+
*/
|
|
21
|
+
export function verifyPluginBeforeImport(name: string, absPath: string): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Synchronous form of verifyPluginBeforeImport for loaders that `require`
|
|
24
|
+
* the plugin. Same policy: nothing happens while the registry's signature
|
|
25
|
+
* verification is disabled; otherwise a plugin with a manifest must have a
|
|
26
|
+
* valid signature from a trusted signer and matching content, and a plugin
|
|
27
|
+
* without one loads with a one-time warning unless the registry requires
|
|
28
|
+
* signed plugins. Every check completes before any plugin code runs.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} name - Plugin name
|
|
31
|
+
* @param {string} absPath - Absolute path of the file that will be loaded
|
|
32
|
+
* @returns {void}
|
|
33
|
+
* @throws {Error} When the plugin must not be loaded
|
|
34
|
+
*/
|
|
35
|
+
export function verifyPluginBeforeLoad(name: string, absPath: string): void;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates the full .ragrc.json _config structure
|
|
3
|
+
* @param {object} config
|
|
4
|
+
* @returns {{ valid: boolean, errors?: any[] }}
|
|
5
|
+
*/
|
|
6
|
+
export function validateRagrcSchema(config: object): {
|
|
7
|
+
valid: boolean;
|
|
8
|
+
errors?: any[];
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Validates a plugin-only structure (for runtime plugin loading)
|
|
12
|
+
* @param {object} config
|
|
13
|
+
* @returns {{ valid: boolean, errors?: any[] }}
|
|
14
|
+
*/
|
|
15
|
+
export function validatePluginSchema(config: object): {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
errors?: any[];
|
|
18
|
+
};
|
|
@@ -113,6 +113,19 @@ declare class PluginRegistry {
|
|
|
113
113
|
* @returns {boolean}
|
|
114
114
|
*/
|
|
115
115
|
isSignatureVerificationEnabled(): boolean;
|
|
116
|
+
/**
|
|
117
|
+
* The signature verifier this registry uses (trust store, policy flags).
|
|
118
|
+
* @returns {object} The PluginSignatureVerifier
|
|
119
|
+
*/
|
|
120
|
+
getSignatureVerifier(): object;
|
|
121
|
+
/**
|
|
122
|
+
* Record an audit entry for a plugin verification decision made outside the
|
|
123
|
+
* registry (for example when a configured plugin is checked before load).
|
|
124
|
+
* @param {string} action - Audit action
|
|
125
|
+
* @param {object} details - Audit details
|
|
126
|
+
* @returns {void}
|
|
127
|
+
*/
|
|
128
|
+
recordAuditEntry(action: string, details: object): void;
|
|
116
129
|
/**
|
|
117
130
|
* Whether plugins without a signed manifest are rejected.
|
|
118
131
|
* @returns {boolean}
|
|
@@ -12,6 +12,18 @@ export function probeContract(modulePath: string, stage: string, options?: {
|
|
|
12
12
|
methods: string[];
|
|
13
13
|
error?: string;
|
|
14
14
|
}>;
|
|
15
|
+
/**
|
|
16
|
+
* Environment for the probe process: only what Node needs to start, so the
|
|
17
|
+
* plugin code being loaded does not see the caller's other variables. HOME,
|
|
18
|
+
* NODE_OPTIONS and NODE_PATH are left out on purpose.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} [platform] - Defaults to process.platform
|
|
21
|
+
* @param {object} [source] - Defaults to process.env
|
|
22
|
+
* @returns {Object<string, string>}
|
|
23
|
+
*/
|
|
24
|
+
export function probeEnvironment(platform?: string, source?: object): {
|
|
25
|
+
[x: string]: string;
|
|
26
|
+
};
|
|
15
27
|
export namespace STAGE_METHODS {
|
|
16
28
|
let loader: string[];
|
|
17
29
|
let embedder: string[];
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* Besides files with a source extension, the package entry file and any
|
|
3
|
+
* file a scanned file loads by relative path are scanned whatever their
|
|
4
|
+
* extension, because Node runs them as JavaScript.
|
|
5
|
+
*
|
|
2
6
|
* @param {string} pluginPath - Plugin directory or single .js file
|
|
7
|
+
* @param {object} [options]
|
|
8
|
+
* @param {string} [options.entry] - Package entry file (absolute or relative to pluginPath)
|
|
3
9
|
* @returns {{
|
|
4
10
|
* findings: Array<{severity: string, rule: string, message: string, file: string, line?: number}>,
|
|
5
11
|
* counts: {high: number, medium: number, low: number},
|
|
6
12
|
* filesScanned: number
|
|
7
13
|
* }}
|
|
8
14
|
*/
|
|
9
|
-
export function scanPlugin(pluginPath: string
|
|
15
|
+
export function scanPlugin(pluginPath: string, options?: {
|
|
16
|
+
entry?: string;
|
|
17
|
+
}): {
|
|
10
18
|
findings: Array<{
|
|
11
19
|
severity: string;
|
|
12
20
|
rule: string;
|
|
@@ -65,8 +65,11 @@ export class AuditLogger extends EventEmitter {
|
|
|
65
65
|
};
|
|
66
66
|
logBuffer: any[];
|
|
67
67
|
encryptionKey: string | Buffer<ArrayBufferLike>;
|
|
68
|
-
signingKey:
|
|
69
|
-
|
|
68
|
+
signingKey: string | Buffer<ArrayBufferLike>;
|
|
69
|
+
_chainState: any;
|
|
70
|
+
_chainLoad: any;
|
|
71
|
+
_chainDirty: boolean;
|
|
72
|
+
_flushQueue: Promise<void>;
|
|
70
73
|
/**
|
|
71
74
|
* Initialize audit logging system
|
|
72
75
|
*/
|
|
@@ -105,9 +108,20 @@ export class AuditLogger extends EventEmitter {
|
|
|
105
108
|
*/
|
|
106
109
|
_writeAuditLog(auditEvent: any): Promise<any>;
|
|
107
110
|
/**
|
|
108
|
-
* Flush logs to persistent storage
|
|
111
|
+
* Flush logs to persistent storage. Flushes run one at a time, so records
|
|
112
|
+
* are numbered and chained in the order they are appended.
|
|
109
113
|
*/
|
|
110
114
|
_flushLogs(): Promise<void>;
|
|
115
|
+
_flushOnce(): Promise<void>;
|
|
116
|
+
_endsMidLine(file: any): Promise<boolean>;
|
|
117
|
+
_stripChainFields(log: any): void;
|
|
118
|
+
/**
|
|
119
|
+
* Give each record its sequence, the link to the previous record, the key
|
|
120
|
+
* id, the integrity hash and (when signing is on) the signature, advancing
|
|
121
|
+
* `state` as it goes.
|
|
122
|
+
* @private
|
|
123
|
+
*/
|
|
124
|
+
private _numberRecords;
|
|
111
125
|
/**
|
|
112
126
|
* Query audit logs
|
|
113
127
|
*/
|
|
@@ -115,13 +129,14 @@ export class AuditLogger extends EventEmitter {
|
|
|
115
129
|
results: any[];
|
|
116
130
|
total: number;
|
|
117
131
|
hasMore: boolean;
|
|
132
|
+
skippedLines: number;
|
|
118
133
|
}>;
|
|
119
134
|
/**
|
|
120
135
|
* Generate compliance report
|
|
121
136
|
*/
|
|
122
137
|
generateComplianceReport(_options: any): Promise<{
|
|
123
138
|
metadata: {
|
|
124
|
-
_tenantId:
|
|
139
|
+
_tenantId: string;
|
|
125
140
|
standard: any;
|
|
126
141
|
period: {
|
|
127
142
|
startDate: any;
|
|
@@ -148,7 +163,24 @@ export class AuditLogger extends EventEmitter {
|
|
|
148
163
|
evidence: any[];
|
|
149
164
|
}>;
|
|
150
165
|
/**
|
|
151
|
-
* Verify log integrity
|
|
166
|
+
* Verify log integrity.
|
|
167
|
+
*
|
|
168
|
+
* Besides the per-record checks it reads the chain: every record carries its
|
|
169
|
+
* sequence, a link to the previous record and a keyed hash over the whole
|
|
170
|
+
* record, and the chain file holds the latest state.
|
|
171
|
+
*
|
|
172
|
+
* - `integrityBreaks`: `{ file, line, sequence, reason }`, where reason is
|
|
173
|
+
* `edited`, `chain`, `order`, `tail`, `unparseable`, `chain file missing`,
|
|
174
|
+
* `chain file reset` or `chain file forged`.
|
|
175
|
+
* - `missingLogs`: gaps in the sequence, `{ after, before, count }`.
|
|
176
|
+
* - `unchained`: records written before chain links existed (counted, and
|
|
177
|
+
* they do not make `verified` false).
|
|
178
|
+
* - `otherKey`: records written with another signing key; they can't be
|
|
179
|
+
* checked, so `verified` is false.
|
|
180
|
+
* - `secondWriter`: duplicate or interleaved sequences that look like two
|
|
181
|
+
* writers on one log directory. A break is only reported here when every
|
|
182
|
+
* record involved verifies and every interleaved run links up on its own;
|
|
183
|
+
* otherwise it is reported as the tampering it may be.
|
|
152
184
|
*/
|
|
153
185
|
verifyIntegrity(_options?: {}): Promise<{
|
|
154
186
|
verified: boolean;
|
|
@@ -157,6 +189,15 @@ export class AuditLogger extends EventEmitter {
|
|
|
157
189
|
tamperedLogs: any[];
|
|
158
190
|
missingLogs: any[];
|
|
159
191
|
integrityBreaks: any[];
|
|
192
|
+
unchained: {
|
|
193
|
+
count: number;
|
|
194
|
+
ranges: any[];
|
|
195
|
+
};
|
|
196
|
+
otherKey: {
|
|
197
|
+
count: number;
|
|
198
|
+
ranges: any[];
|
|
199
|
+
};
|
|
200
|
+
secondWriter: any[];
|
|
160
201
|
}>;
|
|
161
202
|
_calculateIntegrityHash(auditEvent: any): Promise<string>;
|
|
162
203
|
_encryptSensitiveFields(auditEvent: any): Promise<any>;
|
|
@@ -190,9 +231,89 @@ export class AuditLogger extends EventEmitter {
|
|
|
190
231
|
*/
|
|
191
232
|
_getCipherKey(): Buffer<ArrayBufferLike>;
|
|
192
233
|
_createDecipher(encryptedField: any): crypto.DecipherGCM;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Resolve the key behind the integrity hashes, chain links and signatures.
|
|
236
|
+
*
|
|
237
|
+
* Order: the `signingKey` option, then the RAG_AUDIT_SIGNING_KEY env var,
|
|
238
|
+
* then a key stored in `<logDir>/integrity/signing.key` (created on first
|
|
239
|
+
* use), and only if that file can't be used a random per-process key. A
|
|
240
|
+
* stored key lets the logs be checked after a restart, but anyone who can
|
|
241
|
+
* read the log folder can read it, so it only detects accidental damage and
|
|
242
|
+
* casual edits. Both of the last two warn once per process.
|
|
243
|
+
*
|
|
244
|
+
* @param {Object} _options - Constructor options
|
|
245
|
+
* @returns {Buffer|string} Key material
|
|
246
|
+
* @private
|
|
247
|
+
*/
|
|
248
|
+
private _resolveSigningKey;
|
|
249
|
+
/**
|
|
250
|
+
* The key stored in `<logDir>/integrity/signing.key` as 128 hex digits and
|
|
251
|
+
* a newline, created if it doesn't exist yet. It is created with the 'wx'
|
|
252
|
+
* flag, so of several processes starting together exactly one writes it and
|
|
253
|
+
* the rest read what it wrote (briefly retrying while it is still empty).
|
|
254
|
+
* Modes are owner-only (0o600 file, 0o700 directory); Windows ignores POSIX
|
|
255
|
+
* modes. A file that exists but is unreadable or invalid is never
|
|
256
|
+
* overwritten. It lives next to chain.json, not under daily/, so it is never
|
|
257
|
+
* read as a log file.
|
|
258
|
+
* @returns {{key: ?Buffer, file: string, reason?: string}}
|
|
259
|
+
* @private
|
|
260
|
+
*/
|
|
261
|
+
private _storedSigningKey;
|
|
262
|
+
_hmac(text: any): string;
|
|
263
|
+
_keyId(): string;
|
|
264
|
+
_genesisLink(chainId: any): string;
|
|
265
|
+
_chainLink(chainPrev: any, integrity: any): string;
|
|
266
|
+
_stateTag(chainId: any, sequence: any, lastHash: any): string;
|
|
267
|
+
_chainFilePath(): string;
|
|
268
|
+
/**
|
|
269
|
+
* Read chain.json. Status is `missing`, `unreadable`, `legacy` (a file from
|
|
270
|
+
* before 3.8.2), `invalid` (wrong shape, or a state tag that doesn't match
|
|
271
|
+
* under this key), `otherKey` (written with another signing key) or `ok`.
|
|
272
|
+
* @private
|
|
273
|
+
*/
|
|
274
|
+
private _readChainFile;
|
|
275
|
+
/**
|
|
276
|
+
* The chain state (chain id, last sequence and link), loaded once, at the
|
|
277
|
+
* first write.
|
|
278
|
+
* @private
|
|
279
|
+
*/
|
|
280
|
+
private _loadChainState;
|
|
281
|
+
/**
|
|
282
|
+
* Work out where the chain stands before the first write of this process.
|
|
283
|
+
*
|
|
284
|
+
* - chain.json is valid and the newest records don't go past it: continue.
|
|
285
|
+
* - Records go past it and link from it (the process ended after the
|
|
286
|
+
* file was appended to but before chain.json was rewritten): advance the state and continue.
|
|
287
|
+
* - Otherwise (chain.json missing, replaced, not valid under this key, or
|
|
288
|
+
* records that don't link) a new chain segment starts. It is never
|
|
289
|
+
* silent: it is recorded in chain.json `resets`, logged as a warning and
|
|
290
|
+
* emitted as `chain_reset`, and the sequence carries on from the highest
|
|
291
|
+
* one in the files. A chain problem never blocks logging.
|
|
292
|
+
* @private
|
|
293
|
+
*/
|
|
294
|
+
private _recoverChainState;
|
|
295
|
+
/** Chained records of the newest daily file that has any, in file order. */
|
|
296
|
+
_newestChainedRecords(files: any): Promise<any[]>;
|
|
297
|
+
/**
|
|
298
|
+
* Move `state` forward over `records` if they continue it exactly: this
|
|
299
|
+
* key, consecutive sequences, each linked to the one before and with a
|
|
300
|
+
* valid hash. Leaves `state` alone otherwise.
|
|
301
|
+
* @private
|
|
302
|
+
*/
|
|
303
|
+
private _advanceThrough;
|
|
304
|
+
/**
|
|
305
|
+
* Start a chain: fresh when no chained records exist yet (keeping the
|
|
306
|
+
* values of a pre-3.8.2 chain.json under `legacy`), or a new segment that
|
|
307
|
+
* continues the sequence when they do.
|
|
308
|
+
* @private
|
|
309
|
+
*/
|
|
310
|
+
private _startChainSegment;
|
|
311
|
+
/**
|
|
312
|
+
* Write chain.json with the latest state. If the write fails the state is
|
|
313
|
+
* kept and written on the next flush.
|
|
314
|
+
* @private
|
|
315
|
+
*/
|
|
316
|
+
private _persistChainState;
|
|
196
317
|
_signAuditEvent(auditEvent: any): Promise<string>;
|
|
197
318
|
_verifySignature(auditEvent: any): Promise<boolean>;
|
|
198
319
|
/**
|
|
@@ -203,14 +324,26 @@ export class AuditLogger extends EventEmitter {
|
|
|
203
324
|
* @private
|
|
204
325
|
*/
|
|
205
326
|
private _getLogFilesInRange;
|
|
206
|
-
|
|
327
|
+
/**
|
|
328
|
+
* Every non-blank line of a daily file (queryLogs() counts the unreadable ones as skippedLines) as `{ line, record }`, where `record`
|
|
329
|
+
* is null when the line isn't a JSON object (a half-written or damaged
|
|
330
|
+
* line). `line` counts from 1.
|
|
331
|
+
*/
|
|
332
|
+
_readLogEntries(filepath: any): Promise<any[]>;
|
|
333
|
+
/**
|
|
334
|
+
* The tenant a query asks for, or undefined when it names none. A tenantId
|
|
335
|
+
* (or the underscore form `_tenantId`, read only when `tenantId` is absent)
|
|
336
|
+
* that is present must be a string: null, undefined and other types used to
|
|
337
|
+
* match every tenant.
|
|
338
|
+
* @private
|
|
339
|
+
*/
|
|
340
|
+
private _resolveTenantFilter;
|
|
207
341
|
_matchesQuery(log: any, query: any): boolean;
|
|
208
342
|
_checkCompliance(log: any, standard: any): {
|
|
209
343
|
requirement: any;
|
|
210
344
|
violation: boolean;
|
|
211
345
|
evidence: any;
|
|
212
346
|
};
|
|
213
|
-
_sendToWebhook(logs: any): Promise<void>;
|
|
214
347
|
/**
|
|
215
348
|
* Cleanup old logs based on retention policy
|
|
216
349
|
*/
|
|
@@ -21,6 +21,12 @@ export class GuardrailsPipeline extends EventEmitter {
|
|
|
21
21
|
/**
|
|
22
22
|
* Run the full guarded pipeline.
|
|
23
23
|
*
|
|
24
|
+
* With `options.stream` and a post-generation guard, the token stream is
|
|
25
|
+
* buffered, checked and redacted as a whole, then released as an async
|
|
26
|
+
* iterable: one token chunk with the sanitized answer, then a done chunk
|
|
27
|
+
* carrying the redacted citations/evaluation and the `guardrails` summary.
|
|
28
|
+
* A strict-mode block yields only a done chunk with `success: false`.
|
|
29
|
+
*
|
|
24
30
|
* @param {object} input - Pipeline input
|
|
25
31
|
* @param {string} input.query - The user query
|
|
26
32
|
* @param {Float32Array} [input.queryVector] - Optional precomputed query vector
|
|
@@ -34,6 +40,28 @@ export class GuardrailsPipeline extends EventEmitter {
|
|
|
34
40
|
options?: object;
|
|
35
41
|
context?: object;
|
|
36
42
|
}): Promise<object>;
|
|
43
|
+
/**
|
|
44
|
+
* Whether the retrieval guard has a check that needs documents.
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
private _retrievalChecksActive;
|
|
48
|
+
/**
|
|
49
|
+
* Milliseconds left of the caller's options.timeout, or undefined.
|
|
50
|
+
* @private
|
|
51
|
+
*/
|
|
52
|
+
private _remainingTimeout;
|
|
53
|
+
/** @private One warning per pipeline instance. */
|
|
54
|
+
private _warnRetrievalSkipped;
|
|
55
|
+
_warnedRetrievalSkipped: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Collect a token stream, run the post-generation guard on the whole
|
|
58
|
+
* answer and return an async iterable that yields the sanitized answer as
|
|
59
|
+
* one token chunk and then the done chunk. A blocked answer yields no
|
|
60
|
+
* tokens, only a done chunk with the error.
|
|
61
|
+
*
|
|
62
|
+
* @private
|
|
63
|
+
*/
|
|
64
|
+
private _releaseStream;
|
|
37
65
|
/**
|
|
38
66
|
* Citations and evaluation details copy sentences from the raw answer, so
|
|
39
67
|
* scrub every string in them with the same PII patterns as the answer.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const NO_ACCESS: "rag:no-access";
|
|
2
|
+
/**
|
|
3
|
+
* The access fields (`acl`, `requiredRole`) set on a document's top level.
|
|
4
|
+
* Stores keep them on each result, as well as whatever metadata holds, so
|
|
5
|
+
* RetrievalGuard sees a restriction wherever it was set.
|
|
6
|
+
* @param {object} doc
|
|
7
|
+
* @returns {object}
|
|
8
|
+
*/
|
|
9
|
+
export function accessFields(doc: object): object;
|
|
10
|
+
/**
|
|
11
|
+
* The metadata to store for a document: its metadata, plus its top-level
|
|
12
|
+
* access fields under a reserved key when it has any.
|
|
13
|
+
* @param {object} doc
|
|
14
|
+
* @returns {object}
|
|
15
|
+
*/
|
|
16
|
+
export function storedMetadata(doc: object): object;
|
|
17
|
+
/**
|
|
18
|
+
* Splits stored metadata back into the document's metadata and its
|
|
19
|
+
* top-level access fields. Unreadable access fields restrict the document
|
|
20
|
+
* to no one.
|
|
21
|
+
* @param {object} stored
|
|
22
|
+
* @returns {{ metadata: object, access: object }}
|
|
23
|
+
*/
|
|
24
|
+
export function readStoredMetadata(stored: object): {
|
|
25
|
+
metadata: object;
|
|
26
|
+
access: object;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* One result for copies of the same document: the first copy's fields,
|
|
30
|
+
* restricted by every restriction any copy sets. Restrictions the first
|
|
31
|
+
* copy lacks go on its free top-level access fields; when they don't fit,
|
|
32
|
+
* its top-level restrictions and the new ones become a single list of the
|
|
33
|
+
* roles they all share, which is at least as strict.
|
|
34
|
+
* @param {object} first
|
|
35
|
+
* @param {object[]} others
|
|
36
|
+
* @returns {object}
|
|
37
|
+
*/
|
|
38
|
+
export function mergeAccess(first: object, others: object[]): object;
|
|
@@ -25,6 +25,25 @@ export class PluginSignatureVerifier {
|
|
|
25
25
|
* @returns {Promise<object>} Verification result
|
|
26
26
|
*/
|
|
27
27
|
verifyPluginSignature(manifest: object, signature: string, signerId: string, content?: object): Promise<object>;
|
|
28
|
+
/**
|
|
29
|
+
* Synchronous signature check shared by every verification path: input
|
|
30
|
+
* validation, trusted-signer lookup, Ed25519 signature over the canonical
|
|
31
|
+
* manifest, and the contentDigest check. Does not consult `enabled` and
|
|
32
|
+
* does not write audit entries; callers decide both.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} manifest - Plugin manifest
|
|
35
|
+
* @param {string} signature - Base64 encoded signature
|
|
36
|
+
* @param {string} signerId - Signer identifier
|
|
37
|
+
* @param {object} [content] - `{ files, baseDir, contents }` to check
|
|
38
|
+
* against manifest.contentDigest (see computeContentDigest)
|
|
39
|
+
* @returns {{contentVerified: boolean, canonicalManifest: object}}
|
|
40
|
+
* contentVerified is false when the manifest has no contentDigest
|
|
41
|
+
* @throws {Error} When the manifest must not be trusted
|
|
42
|
+
*/
|
|
43
|
+
verifyManifestSync(manifest: object, signature: string, signerId: string, content?: object): {
|
|
44
|
+
contentVerified: boolean;
|
|
45
|
+
canonicalManifest: object;
|
|
46
|
+
};
|
|
28
47
|
/**
|
|
29
48
|
* Check the supplied plugin content against the signed contentDigest.
|
|
30
49
|
* @param {object} canonicalManifest - Signed manifest
|
|
@@ -42,6 +61,15 @@ export class PluginSignatureVerifier {
|
|
|
42
61
|
* @returns {Promise<boolean>} Verification result
|
|
43
62
|
*/
|
|
44
63
|
_verifyEd25519Signature(data: Buffer, signature: Buffer, publicKey: crypto.KeyObject | Buffer | string): Promise<boolean>;
|
|
64
|
+
/**
|
|
65
|
+
* Synchronous form of _verifyEd25519Signature.
|
|
66
|
+
* @param {Buffer} data - Data that was signed
|
|
67
|
+
* @param {Buffer} signature - Signature bytes
|
|
68
|
+
* @param {crypto.KeyObject|Buffer|string} publicKey - Ed25519 public key
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
* @private
|
|
71
|
+
*/
|
|
72
|
+
private _checkEd25519Signature;
|
|
45
73
|
/**
|
|
46
74
|
* Canonicalize manifest for consistent signing
|
|
47
75
|
* @param {object} manifest - Plugin manifest
|
|
@@ -125,6 +153,14 @@ export function computeContentDigest({ files, baseDir, contents }?: {
|
|
|
125
153
|
[x: string]: Buffer | string;
|
|
126
154
|
};
|
|
127
155
|
}): string;
|
|
156
|
+
/**
|
|
157
|
+
* Whether a manifest binds the plugin code: it has a contentDigest that is
|
|
158
|
+
* neither undefined nor null. Every path that decides "digest or no digest"
|
|
159
|
+
* (verification and the missing-digest warning) uses this one test.
|
|
160
|
+
* @param {object} manifest - Plugin manifest
|
|
161
|
+
* @returns {boolean}
|
|
162
|
+
*/
|
|
163
|
+
export function hasContentDigest(manifest: object): boolean;
|
|
128
164
|
/**
|
|
129
165
|
* Convert supported public key encodings to an Ed25519 KeyObject.
|
|
130
166
|
*
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a class so constructing it (or calling a static method) emits a
|
|
3
|
+
* one-time DeprecationWarning. A Proxy keeps the original's identity for
|
|
4
|
+
* prototype, name, statics, `extends` and `instanceof`, so instances created
|
|
5
|
+
* inside the library still satisfy `instanceof Wrapped`. The same class
|
|
6
|
+
* always gets the same wrapper, so aliases stay identical.
|
|
7
|
+
*
|
|
8
|
+
* @param {Function} Class - Class to wrap
|
|
9
|
+
* @param {object} info
|
|
10
|
+
* @param {string} info.name - Export name, used in the message and code
|
|
11
|
+
* @param {string} info.message - Full warning text
|
|
12
|
+
* @returns {Function} The wrapped class
|
|
13
|
+
*/
|
|
14
|
+
export function deprecateClass(Class: Function, { name, message }: {
|
|
15
|
+
name: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}): Function;
|
|
18
|
+
/**
|
|
19
|
+
* Emit a DeprecationWarning once per process for each code.
|
|
20
|
+
* @param {string} code - Warning code, e.g. RAG_DEP_...
|
|
21
|
+
* @param {string} message - Full warning text
|
|
22
|
+
*/
|
|
23
|
+
export function warnOnce(code: string, message: string): void;
|