@objectstack/core 7.4.1 → 7.6.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/dist/index.cjs +178 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +141 -2
- package/dist/index.d.ts +141 -2
- package/dist/index.js +171 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -48,9 +48,12 @@ __export(index_exports, {
|
|
|
48
48
|
PluginSecurityScanner: () => PluginSecurityScanner,
|
|
49
49
|
PluginSignatureVerifier: () => PluginSignatureVerifier,
|
|
50
50
|
QA: () => qa_exports,
|
|
51
|
+
SIGNATURE_ALG: () => SIGNATURE_ALG,
|
|
51
52
|
SecurePluginContext: () => SecurePluginContext,
|
|
52
53
|
SemanticVersionManager: () => SemanticVersionManager,
|
|
53
54
|
ServiceLifecycle: () => ServiceLifecycle,
|
|
55
|
+
buildPermissionsFromGrants: () => buildPermissionsFromGrants,
|
|
56
|
+
counterSignPayload: () => counterSignPayload,
|
|
54
57
|
createApiRegistryPlugin: () => createApiRegistryPlugin,
|
|
55
58
|
createLogger: () => createLogger,
|
|
56
59
|
createMemoryCache: () => createMemoryCache,
|
|
@@ -60,11 +63,18 @@ __export(index_exports, {
|
|
|
60
63
|
createMemoryQueue: () => createMemoryQueue,
|
|
61
64
|
createPluginConfigValidator: () => createPluginConfigValidator,
|
|
62
65
|
createPluginPermissionEnforcer: () => createPluginPermissionEnforcer,
|
|
66
|
+
generateEd25519KeyPair: () => generateEd25519KeyPair,
|
|
63
67
|
getEnv: () => getEnv,
|
|
64
68
|
getMemoryUsage: () => getMemoryUsage,
|
|
65
69
|
isNode: () => isNode,
|
|
70
|
+
parseSignature: () => parseSignature,
|
|
66
71
|
resolveLocale: () => resolveLocale,
|
|
67
|
-
safeExit: () => safeExit
|
|
72
|
+
safeExit: () => safeExit,
|
|
73
|
+
signPayload: () => signPayload,
|
|
74
|
+
verifyPayload: () => verifyPayload,
|
|
75
|
+
verifyPlatformSignature: () => verifyPlatformSignature,
|
|
76
|
+
verifyPluginArtifact: () => verifyPluginArtifact,
|
|
77
|
+
verifyPublisherSignature: () => verifyPublisherSignature
|
|
68
78
|
});
|
|
69
79
|
module.exports = __toCommonJS(index_exports);
|
|
70
80
|
|
|
@@ -566,6 +576,102 @@ function createPluginConfigValidator(logger) {
|
|
|
566
576
|
return new PluginConfigValidator(logger);
|
|
567
577
|
}
|
|
568
578
|
|
|
579
|
+
// src/security/plugin-artifact-signature.ts
|
|
580
|
+
var import_node_crypto = require("crypto");
|
|
581
|
+
var SIGNATURE_ALG = "ed25519";
|
|
582
|
+
var SIG_PREFIX = "ed25519:";
|
|
583
|
+
function toPrivateKey(key) {
|
|
584
|
+
return typeof key === "string" ? (0, import_node_crypto.createPrivateKey)(key) : key;
|
|
585
|
+
}
|
|
586
|
+
function toPublicKey(key) {
|
|
587
|
+
return typeof key === "string" ? (0, import_node_crypto.createPublicKey)(key) : key;
|
|
588
|
+
}
|
|
589
|
+
function toBytes(payload) {
|
|
590
|
+
return typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
|
|
591
|
+
}
|
|
592
|
+
function generateEd25519KeyPair() {
|
|
593
|
+
const { publicKey, privateKey } = (0, import_node_crypto.generateKeyPairSync)("ed25519");
|
|
594
|
+
return {
|
|
595
|
+
publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString(),
|
|
596
|
+
privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString()
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
function signPayload(payload, privateKey, keyId = "default") {
|
|
600
|
+
if (keyId.includes(":")) throw new Error('keyId must not contain ":"');
|
|
601
|
+
const sig = (0, import_node_crypto.sign)(null, toBytes(payload), toPrivateKey(privateKey));
|
|
602
|
+
return `${SIG_PREFIX}${keyId}:${sig.toString("base64url")}`;
|
|
603
|
+
}
|
|
604
|
+
function parseSignature(s) {
|
|
605
|
+
if (typeof s !== "string" || !s.startsWith(SIG_PREFIX)) return null;
|
|
606
|
+
const rest = s.slice(SIG_PREFIX.length);
|
|
607
|
+
const idx = rest.indexOf(":");
|
|
608
|
+
if (idx <= 0) return null;
|
|
609
|
+
const keyId = rest.slice(0, idx);
|
|
610
|
+
const b64 = rest.slice(idx + 1);
|
|
611
|
+
if (!keyId || !b64) return null;
|
|
612
|
+
try {
|
|
613
|
+
return { alg: "ed25519", keyId, signature: new Uint8Array(Buffer.from(b64, "base64url")) };
|
|
614
|
+
} catch {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function verifyPayload(payload, signature, publicKey) {
|
|
619
|
+
const parsed = parseSignature(signature);
|
|
620
|
+
if (!parsed) return false;
|
|
621
|
+
try {
|
|
622
|
+
return (0, import_node_crypto.verify)(null, toBytes(payload), toPublicKey(publicKey), parsed.signature);
|
|
623
|
+
} catch {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function counterSignPayload(version) {
|
|
628
|
+
return [
|
|
629
|
+
version.package_id,
|
|
630
|
+
version.version,
|
|
631
|
+
version.blob_key ?? "",
|
|
632
|
+
version.signature ?? ""
|
|
633
|
+
].join("\n");
|
|
634
|
+
}
|
|
635
|
+
async function verifyPublisherSignature(args, getPublicKey) {
|
|
636
|
+
const sig = args.signature;
|
|
637
|
+
if (!sig) return { ok: true, verified: false, reason: "no signature supplied" };
|
|
638
|
+
const parsed = parseSignature(sig);
|
|
639
|
+
if (!parsed) return { ok: false, verified: false, reason: "signature is malformed" };
|
|
640
|
+
if (!getPublicKey) {
|
|
641
|
+
return { ok: true, verified: false, reason: "no publisher key registry configured" };
|
|
642
|
+
}
|
|
643
|
+
const pub = await getPublicKey(parsed.keyId);
|
|
644
|
+
if (!pub) return { ok: false, verified: false, reason: `unknown publisher key '${parsed.keyId}'` };
|
|
645
|
+
return verifyPayload(args.artifact, sig, pub) ? { ok: true, verified: true } : { ok: false, verified: false, reason: "publisher signature does not match artifact" };
|
|
646
|
+
}
|
|
647
|
+
function verifyPlatformSignature(version, platformPublicKey) {
|
|
648
|
+
if (!version.platform_signature) return false;
|
|
649
|
+
return verifyPayload(counterSignPayload(version), version.platform_signature, platformPublicKey);
|
|
650
|
+
}
|
|
651
|
+
async function verifyPluginArtifact(input, keys) {
|
|
652
|
+
const requirePlatform = keys.requirePlatform ?? true;
|
|
653
|
+
const publisher = await verifyPublisherSignature(
|
|
654
|
+
{ artifact: input.artifact, signature: input.version.signature },
|
|
655
|
+
keys.getPublisherPublicKey
|
|
656
|
+
);
|
|
657
|
+
if (!publisher.ok) {
|
|
658
|
+
return { ok: false, publisherVerified: false, platformVerified: false, reason: publisher.reason };
|
|
659
|
+
}
|
|
660
|
+
let platformVerified = false;
|
|
661
|
+
if (keys.platformPublicKey) {
|
|
662
|
+
platformVerified = verifyPlatformSignature(input.version, keys.platformPublicKey);
|
|
663
|
+
}
|
|
664
|
+
if (requirePlatform && !platformVerified) {
|
|
665
|
+
return {
|
|
666
|
+
ok: false,
|
|
667
|
+
publisherVerified: publisher.verified,
|
|
668
|
+
platformVerified,
|
|
669
|
+
reason: keys.platformPublicKey ? "platform counter-signature missing or invalid" : "no platform public key configured"
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
return { ok: true, publisherVerified: publisher.verified, platformVerified };
|
|
673
|
+
}
|
|
674
|
+
|
|
569
675
|
// src/plugin-loader.ts
|
|
570
676
|
var ServiceLifecycle = /* @__PURE__ */ ((ServiceLifecycle2) => {
|
|
571
677
|
ServiceLifecycle2["SINGLETON"] = "singleton";
|
|
@@ -823,7 +929,15 @@ var PluginLoader = class {
|
|
|
823
929
|
if (!plugin.signature) {
|
|
824
930
|
return;
|
|
825
931
|
}
|
|
826
|
-
|
|
932
|
+
const parsed = parseSignature(plugin.signature);
|
|
933
|
+
if (!parsed) {
|
|
934
|
+
throw new Error(
|
|
935
|
+
`Plugin ${plugin.name} carries a malformed signature (expected ed25519:<keyId>:<base64url>)`
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
this.logger.debug(
|
|
939
|
+
`Plugin ${plugin.name} signature well-formed (alg=${parsed.alg}, keyId=${parsed.keyId}); artifact verification occurs at materialize time`
|
|
940
|
+
);
|
|
827
941
|
}
|
|
828
942
|
async getSingletonService(registration) {
|
|
829
943
|
let instance = this.serviceInstances.get(registration.name);
|
|
@@ -2767,9 +2881,31 @@ var PluginPermissionEnforcer = class {
|
|
|
2767
2881
|
capabilityCount: capabilities.length
|
|
2768
2882
|
});
|
|
2769
2883
|
}
|
|
2884
|
+
/**
|
|
2885
|
+
* Register the install-time GRANTED permission set for a plugin
|
|
2886
|
+
* (ADR-0025 F4). This is the structured `{ services, hooks, network, fs }`
|
|
2887
|
+
* grant that the cloud control plane persists to
|
|
2888
|
+
* `sys_package_installation.granted_permissions` after the user consents
|
|
2889
|
+
* at install (ADR §3.5 step 2). The runtime calls this when materializing
|
|
2890
|
+
* a third-party plugin so {@link SecurePluginContext} enforces exactly the
|
|
2891
|
+
* consented surface — independent of whatever the manifest *requested*.
|
|
2892
|
+
*
|
|
2893
|
+
* Prefer this over {@link registerPluginPermissions} for distributed
|
|
2894
|
+
* plugins: it enforces what was granted, not what was declared.
|
|
2895
|
+
*/
|
|
2896
|
+
registerGrantedPermissions(pluginName, granted) {
|
|
2897
|
+
this.permissionRegistry.set(pluginName, buildPermissionsFromGrants(granted));
|
|
2898
|
+
this.logger.info(`Granted permissions registered for plugin: ${pluginName}`, {
|
|
2899
|
+
plugin: pluginName,
|
|
2900
|
+
services: granted?.services?.length ?? 0,
|
|
2901
|
+
hooks: granted?.hooks?.length ?? 0,
|
|
2902
|
+
network: granted?.network?.length ?? 0,
|
|
2903
|
+
fs: granted?.fs?.length ?? 0
|
|
2904
|
+
});
|
|
2905
|
+
}
|
|
2770
2906
|
/**
|
|
2771
2907
|
* Enforce service access permission
|
|
2772
|
-
*
|
|
2908
|
+
*
|
|
2773
2909
|
* @param pluginName - Plugin requesting access
|
|
2774
2910
|
* @param serviceName - Service to access
|
|
2775
2911
|
* @throws Error if permission denied
|
|
@@ -3032,6 +3168,32 @@ var SecurePluginContext = class {
|
|
|
3032
3168
|
function createPluginPermissionEnforcer(logger) {
|
|
3033
3169
|
return new PluginPermissionEnforcer(logger);
|
|
3034
3170
|
}
|
|
3171
|
+
function grantGlobMatch(pattern, value) {
|
|
3172
|
+
if (pattern === "*" || pattern === "**") return true;
|
|
3173
|
+
const regexStr = pattern.split("**").map((segment) => segment.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*")).join(".*");
|
|
3174
|
+
return new RegExp(`^${regexStr}$`).test(value);
|
|
3175
|
+
}
|
|
3176
|
+
function hostOf(url) {
|
|
3177
|
+
try {
|
|
3178
|
+
return new URL(url).host;
|
|
3179
|
+
} catch {
|
|
3180
|
+
return url;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
var inList = (list, value) => Array.isArray(list) && list.some((p) => p === value || grantGlobMatch(p, value));
|
|
3184
|
+
function buildPermissionsFromGrants(granted) {
|
|
3185
|
+
const services = granted?.services;
|
|
3186
|
+
const hooks = granted?.hooks;
|
|
3187
|
+
const network = granted?.network;
|
|
3188
|
+
const fs = granted?.fs;
|
|
3189
|
+
return {
|
|
3190
|
+
canAccessService: (name) => inList(services, name),
|
|
3191
|
+
canTriggerHook: (name) => inList(hooks, name),
|
|
3192
|
+
canReadFile: (path) => inList(fs, path),
|
|
3193
|
+
canWriteFile: (path) => inList(fs, path),
|
|
3194
|
+
canNetworkRequest: (url) => inList(network, hostOf(url)) || inList(network, url)
|
|
3195
|
+
};
|
|
3196
|
+
}
|
|
3035
3197
|
|
|
3036
3198
|
// src/security/permission-manager.ts
|
|
3037
3199
|
var PluginPermissionManager = class {
|
|
@@ -4027,7 +4189,7 @@ var PluginHealthMonitor = class {
|
|
|
4027
4189
|
};
|
|
4028
4190
|
|
|
4029
4191
|
// src/hot-reload.ts
|
|
4030
|
-
var
|
|
4192
|
+
var import_node_crypto2 = require("crypto");
|
|
4031
4193
|
var generateUUID = () => {
|
|
4032
4194
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
4033
4195
|
return crypto.randomUUID();
|
|
@@ -4122,7 +4284,7 @@ var PluginStateManager = class {
|
|
|
4122
4284
|
*/
|
|
4123
4285
|
calculateChecksum(state) {
|
|
4124
4286
|
const stateStr = JSON.stringify(state);
|
|
4125
|
-
return (0,
|
|
4287
|
+
return (0, import_node_crypto2.createHash)("sha256").update(stateStr).digest("hex");
|
|
4126
4288
|
}
|
|
4127
4289
|
/**
|
|
4128
4290
|
* Shutdown state manager
|
|
@@ -4717,9 +4879,12 @@ var NamespaceResolver = class {
|
|
|
4717
4879
|
PluginSecurityScanner,
|
|
4718
4880
|
PluginSignatureVerifier,
|
|
4719
4881
|
QA,
|
|
4882
|
+
SIGNATURE_ALG,
|
|
4720
4883
|
SecurePluginContext,
|
|
4721
4884
|
SemanticVersionManager,
|
|
4722
4885
|
ServiceLifecycle,
|
|
4886
|
+
buildPermissionsFromGrants,
|
|
4887
|
+
counterSignPayload,
|
|
4723
4888
|
createApiRegistryPlugin,
|
|
4724
4889
|
createLogger,
|
|
4725
4890
|
createMemoryCache,
|
|
@@ -4729,10 +4894,17 @@ var NamespaceResolver = class {
|
|
|
4729
4894
|
createMemoryQueue,
|
|
4730
4895
|
createPluginConfigValidator,
|
|
4731
4896
|
createPluginPermissionEnforcer,
|
|
4897
|
+
generateEd25519KeyPair,
|
|
4732
4898
|
getEnv,
|
|
4733
4899
|
getMemoryUsage,
|
|
4734
4900
|
isNode,
|
|
4901
|
+
parseSignature,
|
|
4735
4902
|
resolveLocale,
|
|
4736
|
-
safeExit
|
|
4903
|
+
safeExit,
|
|
4904
|
+
signPayload,
|
|
4905
|
+
verifyPayload,
|
|
4906
|
+
verifyPlatformSignature,
|
|
4907
|
+
verifyPluginArtifact,
|
|
4908
|
+
verifyPublisherSignature
|
|
4737
4909
|
});
|
|
4738
4910
|
//# sourceMappingURL=index.cjs.map
|