@objectstack/core 7.5.0 → 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 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
- this.logger.debug(`Plugin ${plugin.name} has signature (use PluginSignatureVerifier for verification)`);
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 import_node_crypto = require("crypto");
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, import_node_crypto.createHash)("sha256").update(stateStr).digest("hex");
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