@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.d.cts
CHANGED
|
@@ -6,7 +6,8 @@ import { ObjectLogger } from './logger.cjs';
|
|
|
6
6
|
export { createLogger } from './logger.cjs';
|
|
7
7
|
import { ConflictResolutionStrategy, ApiRegistryEntryInput, ApiRegistryEntry, ApiDiscoveryQuery, ApiDiscoveryResponse, ApiEndpointRegistration, ApiRegistry as ApiRegistry$1 } from '@objectstack/spec/api';
|
|
8
8
|
import * as QA from '@objectstack/spec/qa';
|
|
9
|
-
import {
|
|
9
|
+
import { KeyObject } from 'node:crypto';
|
|
10
|
+
import { PluginCapability, PluginPermissions as PluginPermissions$1, PluginPermissionSet, ResourceType, PermissionAction, PluginPermission, SandboxConfig, KernelSecurityScanResult, KernelSecurityVulnerability, PluginHealthCheck, PluginHealthStatus as PluginHealthStatus$1, PluginHealthReport, HotReloadConfig, VersionConstraint, DependencyConflict, SemanticVersion, CompatibilityLevel } from '@objectstack/spec/kernel';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Service Lifecycle Types
|
|
@@ -1000,6 +1001,120 @@ declare class PluginSignatureVerifier {
|
|
|
1000
1001
|
private validateConfig;
|
|
1001
1002
|
}
|
|
1002
1003
|
|
|
1004
|
+
/**
|
|
1005
|
+
* Plugin artifact signing & verification (ADR-0025 §3.4–§3.7, framework F3).
|
|
1006
|
+
*
|
|
1007
|
+
* This is the CANONICAL Ed25519 detached-signature contract shared by the
|
|
1008
|
+
* whole plugin distribution pipeline. It is intentionally byte-for-byte
|
|
1009
|
+
* compatible with the cloud control plane's `package-signing.ts` so the
|
|
1010
|
+
* two never drift:
|
|
1011
|
+
*
|
|
1012
|
+
* - signature string format: `ed25519:<keyId>:<base64url(signature)>`
|
|
1013
|
+
* - publisher signature: Ed25519 over the raw `.osplugin` artifact bytes,
|
|
1014
|
+
* produced by `os plugin sign`, verified by cloud at publish time and by
|
|
1015
|
+
* the runtime when it materializes the artifact.
|
|
1016
|
+
* - platform counter-signature: Ed25519 over {@link counterSignPayload}
|
|
1017
|
+
* (the version identity), produced by cloud at approval, verified by the
|
|
1018
|
+
* runtime at load time as the marketplace's "reviewed + approved" attest.
|
|
1019
|
+
*
|
|
1020
|
+
* Algorithm: Ed25519 via node:crypto (`sign(null, …)` / `verify(null, …)`):
|
|
1021
|
+
* short, deterministic, no padding ambiguity. The `keyId` is an opaque
|
|
1022
|
+
* rotation handle used to resolve the verifying public key.
|
|
1023
|
+
*
|
|
1024
|
+
* The two trust chains the runtime checks before loading a third-party
|
|
1025
|
+
* plugin are combined in {@link verifyPluginArtifact}.
|
|
1026
|
+
*/
|
|
1027
|
+
|
|
1028
|
+
declare const SIGNATURE_ALG = "ed25519";
|
|
1029
|
+
type KeyInput = string | KeyObject;
|
|
1030
|
+
/** Generate an Ed25519 keypair as PEM strings (publisher bootstrap / tests). */
|
|
1031
|
+
declare function generateEd25519KeyPair(): {
|
|
1032
|
+
publicKeyPem: string;
|
|
1033
|
+
privateKeyPem: string;
|
|
1034
|
+
};
|
|
1035
|
+
/**
|
|
1036
|
+
* Sign `payload` with an Ed25519 private key, returning the formatted
|
|
1037
|
+
* signature string `ed25519:<keyId>:<base64url(sig)>`.
|
|
1038
|
+
*/
|
|
1039
|
+
declare function signPayload(payload: string | Uint8Array, privateKey: KeyInput, keyId?: string): string;
|
|
1040
|
+
interface ParsedSignature {
|
|
1041
|
+
alg: 'ed25519';
|
|
1042
|
+
keyId: string;
|
|
1043
|
+
signature: Uint8Array;
|
|
1044
|
+
}
|
|
1045
|
+
/** Parse an `ed25519:<keyId>:<base64url>` signature string. Returns null if malformed. */
|
|
1046
|
+
declare function parseSignature(s: string | undefined | null): ParsedSignature | null;
|
|
1047
|
+
/** Verify a formatted signature string over `payload` with the given public key. */
|
|
1048
|
+
declare function verifyPayload(payload: string | Uint8Array, signature: string, publicKey: KeyInput): boolean;
|
|
1049
|
+
/**
|
|
1050
|
+
* Canonical payload the platform counter-signs at approval. Binds the
|
|
1051
|
+
* attestation to the version identity + artifact location + the publisher
|
|
1052
|
+
* signature (which itself binds the artifact bytes). MUST match the cloud
|
|
1053
|
+
* control plane's `counterSignPayload` exactly.
|
|
1054
|
+
*/
|
|
1055
|
+
declare function counterSignPayload(version: {
|
|
1056
|
+
package_id: string;
|
|
1057
|
+
version: string;
|
|
1058
|
+
blob_key?: string | null;
|
|
1059
|
+
signature?: string | null;
|
|
1060
|
+
}): string;
|
|
1061
|
+
interface PublisherVerifyResult {
|
|
1062
|
+
/** Whether loading may proceed on signature grounds. */
|
|
1063
|
+
ok: boolean;
|
|
1064
|
+
/** True when a signature was present AND cryptographically verified. */
|
|
1065
|
+
verified: boolean;
|
|
1066
|
+
reason?: string;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Verify a publisher signature over the raw artifact bytes. `getPublicKey`
|
|
1070
|
+
* resolves the verifying key from the signature's embedded keyId.
|
|
1071
|
+
*
|
|
1072
|
+
* Mirrors cloud's publish-time policy:
|
|
1073
|
+
* - no signature → ok, verified=false (caller decides via trust tier).
|
|
1074
|
+
* - malformed / fails verification → NOT ok.
|
|
1075
|
+
* - unknown keyId → NOT ok (never silently trust).
|
|
1076
|
+
*/
|
|
1077
|
+
declare function verifyPublisherSignature(args: {
|
|
1078
|
+
artifact: Uint8Array;
|
|
1079
|
+
signature?: string | null;
|
|
1080
|
+
}, getPublicKey?: (keyId: string) => Promise<KeyInput | null> | KeyInput | null): Promise<PublisherVerifyResult>;
|
|
1081
|
+
/** Verify a platform counter-signature against the version identity + platform public key. */
|
|
1082
|
+
declare function verifyPlatformSignature(version: {
|
|
1083
|
+
package_id: string;
|
|
1084
|
+
version: string;
|
|
1085
|
+
blob_key?: string | null;
|
|
1086
|
+
signature?: string | null;
|
|
1087
|
+
platform_signature?: string | null;
|
|
1088
|
+
}, platformPublicKey: KeyInput): boolean;
|
|
1089
|
+
interface PluginArtifactVerifyResult {
|
|
1090
|
+
/** Overall verdict: both required chains satisfied under the given policy. */
|
|
1091
|
+
ok: boolean;
|
|
1092
|
+
publisherVerified: boolean;
|
|
1093
|
+
platformVerified: boolean;
|
|
1094
|
+
reason?: string;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Verify both trust chains for a downloaded plugin artifact at load time
|
|
1098
|
+
* (ADR-0025 §3.7). The platform counter-signature is the authoritative
|
|
1099
|
+
* marketplace attestation; the publisher signature additionally binds the
|
|
1100
|
+
* exact bytes. `requirePlatform` (default true) rejects artifacts that lack
|
|
1101
|
+
* a valid platform counter-sign — set false for first-party / local builds.
|
|
1102
|
+
*/
|
|
1103
|
+
declare function verifyPluginArtifact(input: {
|
|
1104
|
+
artifact: Uint8Array;
|
|
1105
|
+
version: {
|
|
1106
|
+
package_id: string;
|
|
1107
|
+
version: string;
|
|
1108
|
+
blob_key?: string | null;
|
|
1109
|
+
signature?: string | null;
|
|
1110
|
+
platform_signature?: string | null;
|
|
1111
|
+
};
|
|
1112
|
+
}, keys: {
|
|
1113
|
+
platformPublicKey?: KeyInput;
|
|
1114
|
+
getPublisherPublicKey?: (keyId: string) => Promise<KeyInput | null> | KeyInput | null;
|
|
1115
|
+
requirePlatform?: boolean;
|
|
1116
|
+
}): Promise<PluginArtifactVerifyResult>;
|
|
1117
|
+
|
|
1003
1118
|
/**
|
|
1004
1119
|
* Plugin Configuration Validator
|
|
1005
1120
|
*
|
|
@@ -1136,6 +1251,19 @@ declare class PluginPermissionEnforcer {
|
|
|
1136
1251
|
* @param capabilities - Array of capability declarations
|
|
1137
1252
|
*/
|
|
1138
1253
|
registerPluginPermissions(pluginName: string, capabilities: PluginCapability[]): void;
|
|
1254
|
+
/**
|
|
1255
|
+
* Register the install-time GRANTED permission set for a plugin
|
|
1256
|
+
* (ADR-0025 F4). This is the structured `{ services, hooks, network, fs }`
|
|
1257
|
+
* grant that the cloud control plane persists to
|
|
1258
|
+
* `sys_package_installation.granted_permissions` after the user consents
|
|
1259
|
+
* at install (ADR §3.5 step 2). The runtime calls this when materializing
|
|
1260
|
+
* a third-party plugin so {@link SecurePluginContext} enforces exactly the
|
|
1261
|
+
* consented surface — independent of whatever the manifest *requested*.
|
|
1262
|
+
*
|
|
1263
|
+
* Prefer this over {@link registerPluginPermissions} for distributed
|
|
1264
|
+
* plugins: it enforces what was granted, not what was declared.
|
|
1265
|
+
*/
|
|
1266
|
+
registerGrantedPermissions(pluginName: string, granted: PluginPermissions$1 | null | undefined): void;
|
|
1139
1267
|
/**
|
|
1140
1268
|
* Enforce service access permission
|
|
1141
1269
|
*
|
|
@@ -1231,6 +1359,17 @@ declare class SecurePluginContext implements PluginContext {
|
|
|
1231
1359
|
* @returns Plugin permission enforcer
|
|
1232
1360
|
*/
|
|
1233
1361
|
declare function createPluginPermissionEnforcer(logger: Logger): PluginPermissionEnforcer;
|
|
1362
|
+
/**
|
|
1363
|
+
* Build the runtime {@link PluginPermissions} bag from a structured
|
|
1364
|
+
* install-time grant set (ADR-0025 §3.2 `{ services, hooks, network, fs }`).
|
|
1365
|
+
*
|
|
1366
|
+
* Matching: an entry allows when it equals the requested value, is a glob
|
|
1367
|
+
* that matches it, or is the wildcard `*`. Network grants match against the
|
|
1368
|
+
* request URL's host (or the raw URL). `fs` governs both read and write —
|
|
1369
|
+
* the structured grant set does not split the two. A null/empty grant set
|
|
1370
|
+
* denies everything (principle of least privilege).
|
|
1371
|
+
*/
|
|
1372
|
+
declare function buildPermissionsFromGrants(granted: PluginPermissions$1 | null | undefined): PluginPermissions;
|
|
1234
1373
|
|
|
1235
1374
|
/**
|
|
1236
1375
|
* Permission Grant
|
|
@@ -1941,4 +2080,4 @@ declare class NamespaceResolver {
|
|
|
1941
2080
|
private suggestAlternative;
|
|
1942
2081
|
}
|
|
1943
2082
|
|
|
1944
|
-
export { ApiRegistry, type ApiRegistryPluginConfig, CORE_FALLBACK_FACTORIES, DependencyResolver, HotReloadManager, type KernelState, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, index as QA, type ResourceUsage, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, getEnv, getMemoryUsage, isNode, resolveLocale, safeExit };
|
|
2083
|
+
export { ApiRegistry, type ApiRegistryPluginConfig, CORE_FALLBACK_FACTORIES, DependencyResolver, HotReloadManager, type KernelState, type KeyInput, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PublisherVerifyResult, index as QA, type ResourceUsage, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, generateEd25519KeyPair, getEnv, getMemoryUsage, isNode, parseSignature, resolveLocale, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { ObjectLogger } from './logger.js';
|
|
|
6
6
|
export { createLogger } from './logger.js';
|
|
7
7
|
import { ConflictResolutionStrategy, ApiRegistryEntryInput, ApiRegistryEntry, ApiDiscoveryQuery, ApiDiscoveryResponse, ApiEndpointRegistration, ApiRegistry as ApiRegistry$1 } from '@objectstack/spec/api';
|
|
8
8
|
import * as QA from '@objectstack/spec/qa';
|
|
9
|
-
import {
|
|
9
|
+
import { KeyObject } from 'node:crypto';
|
|
10
|
+
import { PluginCapability, PluginPermissions as PluginPermissions$1, PluginPermissionSet, ResourceType, PermissionAction, PluginPermission, SandboxConfig, KernelSecurityScanResult, KernelSecurityVulnerability, PluginHealthCheck, PluginHealthStatus as PluginHealthStatus$1, PluginHealthReport, HotReloadConfig, VersionConstraint, DependencyConflict, SemanticVersion, CompatibilityLevel } from '@objectstack/spec/kernel';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Service Lifecycle Types
|
|
@@ -1000,6 +1001,120 @@ declare class PluginSignatureVerifier {
|
|
|
1000
1001
|
private validateConfig;
|
|
1001
1002
|
}
|
|
1002
1003
|
|
|
1004
|
+
/**
|
|
1005
|
+
* Plugin artifact signing & verification (ADR-0025 §3.4–§3.7, framework F3).
|
|
1006
|
+
*
|
|
1007
|
+
* This is the CANONICAL Ed25519 detached-signature contract shared by the
|
|
1008
|
+
* whole plugin distribution pipeline. It is intentionally byte-for-byte
|
|
1009
|
+
* compatible with the cloud control plane's `package-signing.ts` so the
|
|
1010
|
+
* two never drift:
|
|
1011
|
+
*
|
|
1012
|
+
* - signature string format: `ed25519:<keyId>:<base64url(signature)>`
|
|
1013
|
+
* - publisher signature: Ed25519 over the raw `.osplugin` artifact bytes,
|
|
1014
|
+
* produced by `os plugin sign`, verified by cloud at publish time and by
|
|
1015
|
+
* the runtime when it materializes the artifact.
|
|
1016
|
+
* - platform counter-signature: Ed25519 over {@link counterSignPayload}
|
|
1017
|
+
* (the version identity), produced by cloud at approval, verified by the
|
|
1018
|
+
* runtime at load time as the marketplace's "reviewed + approved" attest.
|
|
1019
|
+
*
|
|
1020
|
+
* Algorithm: Ed25519 via node:crypto (`sign(null, …)` / `verify(null, …)`):
|
|
1021
|
+
* short, deterministic, no padding ambiguity. The `keyId` is an opaque
|
|
1022
|
+
* rotation handle used to resolve the verifying public key.
|
|
1023
|
+
*
|
|
1024
|
+
* The two trust chains the runtime checks before loading a third-party
|
|
1025
|
+
* plugin are combined in {@link verifyPluginArtifact}.
|
|
1026
|
+
*/
|
|
1027
|
+
|
|
1028
|
+
declare const SIGNATURE_ALG = "ed25519";
|
|
1029
|
+
type KeyInput = string | KeyObject;
|
|
1030
|
+
/** Generate an Ed25519 keypair as PEM strings (publisher bootstrap / tests). */
|
|
1031
|
+
declare function generateEd25519KeyPair(): {
|
|
1032
|
+
publicKeyPem: string;
|
|
1033
|
+
privateKeyPem: string;
|
|
1034
|
+
};
|
|
1035
|
+
/**
|
|
1036
|
+
* Sign `payload` with an Ed25519 private key, returning the formatted
|
|
1037
|
+
* signature string `ed25519:<keyId>:<base64url(sig)>`.
|
|
1038
|
+
*/
|
|
1039
|
+
declare function signPayload(payload: string | Uint8Array, privateKey: KeyInput, keyId?: string): string;
|
|
1040
|
+
interface ParsedSignature {
|
|
1041
|
+
alg: 'ed25519';
|
|
1042
|
+
keyId: string;
|
|
1043
|
+
signature: Uint8Array;
|
|
1044
|
+
}
|
|
1045
|
+
/** Parse an `ed25519:<keyId>:<base64url>` signature string. Returns null if malformed. */
|
|
1046
|
+
declare function parseSignature(s: string | undefined | null): ParsedSignature | null;
|
|
1047
|
+
/** Verify a formatted signature string over `payload` with the given public key. */
|
|
1048
|
+
declare function verifyPayload(payload: string | Uint8Array, signature: string, publicKey: KeyInput): boolean;
|
|
1049
|
+
/**
|
|
1050
|
+
* Canonical payload the platform counter-signs at approval. Binds the
|
|
1051
|
+
* attestation to the version identity + artifact location + the publisher
|
|
1052
|
+
* signature (which itself binds the artifact bytes). MUST match the cloud
|
|
1053
|
+
* control plane's `counterSignPayload` exactly.
|
|
1054
|
+
*/
|
|
1055
|
+
declare function counterSignPayload(version: {
|
|
1056
|
+
package_id: string;
|
|
1057
|
+
version: string;
|
|
1058
|
+
blob_key?: string | null;
|
|
1059
|
+
signature?: string | null;
|
|
1060
|
+
}): string;
|
|
1061
|
+
interface PublisherVerifyResult {
|
|
1062
|
+
/** Whether loading may proceed on signature grounds. */
|
|
1063
|
+
ok: boolean;
|
|
1064
|
+
/** True when a signature was present AND cryptographically verified. */
|
|
1065
|
+
verified: boolean;
|
|
1066
|
+
reason?: string;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* Verify a publisher signature over the raw artifact bytes. `getPublicKey`
|
|
1070
|
+
* resolves the verifying key from the signature's embedded keyId.
|
|
1071
|
+
*
|
|
1072
|
+
* Mirrors cloud's publish-time policy:
|
|
1073
|
+
* - no signature → ok, verified=false (caller decides via trust tier).
|
|
1074
|
+
* - malformed / fails verification → NOT ok.
|
|
1075
|
+
* - unknown keyId → NOT ok (never silently trust).
|
|
1076
|
+
*/
|
|
1077
|
+
declare function verifyPublisherSignature(args: {
|
|
1078
|
+
artifact: Uint8Array;
|
|
1079
|
+
signature?: string | null;
|
|
1080
|
+
}, getPublicKey?: (keyId: string) => Promise<KeyInput | null> | KeyInput | null): Promise<PublisherVerifyResult>;
|
|
1081
|
+
/** Verify a platform counter-signature against the version identity + platform public key. */
|
|
1082
|
+
declare function verifyPlatformSignature(version: {
|
|
1083
|
+
package_id: string;
|
|
1084
|
+
version: string;
|
|
1085
|
+
blob_key?: string | null;
|
|
1086
|
+
signature?: string | null;
|
|
1087
|
+
platform_signature?: string | null;
|
|
1088
|
+
}, platformPublicKey: KeyInput): boolean;
|
|
1089
|
+
interface PluginArtifactVerifyResult {
|
|
1090
|
+
/** Overall verdict: both required chains satisfied under the given policy. */
|
|
1091
|
+
ok: boolean;
|
|
1092
|
+
publisherVerified: boolean;
|
|
1093
|
+
platformVerified: boolean;
|
|
1094
|
+
reason?: string;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Verify both trust chains for a downloaded plugin artifact at load time
|
|
1098
|
+
* (ADR-0025 §3.7). The platform counter-signature is the authoritative
|
|
1099
|
+
* marketplace attestation; the publisher signature additionally binds the
|
|
1100
|
+
* exact bytes. `requirePlatform` (default true) rejects artifacts that lack
|
|
1101
|
+
* a valid platform counter-sign — set false for first-party / local builds.
|
|
1102
|
+
*/
|
|
1103
|
+
declare function verifyPluginArtifact(input: {
|
|
1104
|
+
artifact: Uint8Array;
|
|
1105
|
+
version: {
|
|
1106
|
+
package_id: string;
|
|
1107
|
+
version: string;
|
|
1108
|
+
blob_key?: string | null;
|
|
1109
|
+
signature?: string | null;
|
|
1110
|
+
platform_signature?: string | null;
|
|
1111
|
+
};
|
|
1112
|
+
}, keys: {
|
|
1113
|
+
platformPublicKey?: KeyInput;
|
|
1114
|
+
getPublisherPublicKey?: (keyId: string) => Promise<KeyInput | null> | KeyInput | null;
|
|
1115
|
+
requirePlatform?: boolean;
|
|
1116
|
+
}): Promise<PluginArtifactVerifyResult>;
|
|
1117
|
+
|
|
1003
1118
|
/**
|
|
1004
1119
|
* Plugin Configuration Validator
|
|
1005
1120
|
*
|
|
@@ -1136,6 +1251,19 @@ declare class PluginPermissionEnforcer {
|
|
|
1136
1251
|
* @param capabilities - Array of capability declarations
|
|
1137
1252
|
*/
|
|
1138
1253
|
registerPluginPermissions(pluginName: string, capabilities: PluginCapability[]): void;
|
|
1254
|
+
/**
|
|
1255
|
+
* Register the install-time GRANTED permission set for a plugin
|
|
1256
|
+
* (ADR-0025 F4). This is the structured `{ services, hooks, network, fs }`
|
|
1257
|
+
* grant that the cloud control plane persists to
|
|
1258
|
+
* `sys_package_installation.granted_permissions` after the user consents
|
|
1259
|
+
* at install (ADR §3.5 step 2). The runtime calls this when materializing
|
|
1260
|
+
* a third-party plugin so {@link SecurePluginContext} enforces exactly the
|
|
1261
|
+
* consented surface — independent of whatever the manifest *requested*.
|
|
1262
|
+
*
|
|
1263
|
+
* Prefer this over {@link registerPluginPermissions} for distributed
|
|
1264
|
+
* plugins: it enforces what was granted, not what was declared.
|
|
1265
|
+
*/
|
|
1266
|
+
registerGrantedPermissions(pluginName: string, granted: PluginPermissions$1 | null | undefined): void;
|
|
1139
1267
|
/**
|
|
1140
1268
|
* Enforce service access permission
|
|
1141
1269
|
*
|
|
@@ -1231,6 +1359,17 @@ declare class SecurePluginContext implements PluginContext {
|
|
|
1231
1359
|
* @returns Plugin permission enforcer
|
|
1232
1360
|
*/
|
|
1233
1361
|
declare function createPluginPermissionEnforcer(logger: Logger): PluginPermissionEnforcer;
|
|
1362
|
+
/**
|
|
1363
|
+
* Build the runtime {@link PluginPermissions} bag from a structured
|
|
1364
|
+
* install-time grant set (ADR-0025 §3.2 `{ services, hooks, network, fs }`).
|
|
1365
|
+
*
|
|
1366
|
+
* Matching: an entry allows when it equals the requested value, is a glob
|
|
1367
|
+
* that matches it, or is the wildcard `*`. Network grants match against the
|
|
1368
|
+
* request URL's host (or the raw URL). `fs` governs both read and write —
|
|
1369
|
+
* the structured grant set does not split the two. A null/empty grant set
|
|
1370
|
+
* denies everything (principle of least privilege).
|
|
1371
|
+
*/
|
|
1372
|
+
declare function buildPermissionsFromGrants(granted: PluginPermissions$1 | null | undefined): PluginPermissions;
|
|
1234
1373
|
|
|
1235
1374
|
/**
|
|
1236
1375
|
* Permission Grant
|
|
@@ -1941,4 +2080,4 @@ declare class NamespaceResolver {
|
|
|
1941
2080
|
private suggestAlternative;
|
|
1942
2081
|
}
|
|
1943
2082
|
|
|
1944
|
-
export { ApiRegistry, type ApiRegistryPluginConfig, CORE_FALLBACK_FACTORIES, DependencyResolver, HotReloadManager, type KernelState, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, index as QA, type ResourceUsage, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, getEnv, getMemoryUsage, isNode, resolveLocale, safeExit };
|
|
2083
|
+
export { ApiRegistry, type ApiRegistryPluginConfig, CORE_FALLBACK_FACTORIES, DependencyResolver, HotReloadManager, type KernelState, type KeyInput, LiteKernel, type NamespaceCheckResult, type NamespaceConflict, type NamespaceEntry, NamespaceResolver, ObjectKernel, ObjectKernelBase, type ObjectKernelConfig, ObjectLogger, type ParsedSignature, type PermissionCheckResult$1 as PermissionCheckResult, type PermissionGrant, type Plugin, type PluginArtifactVerifyResult, PluginConfigValidator, type PluginContext, PluginHealthMonitor, type PluginHealthStatus, type PluginLoadResult, PluginLoader, type PluginMetadata, type PermissionCheckResult as PluginPermissionCheckResult, PluginPermissionEnforcer, PluginPermissionManager, type PluginPermissions, PluginSandboxRuntime, PluginSecurityScanner, type PluginSignatureConfig, PluginSignatureVerifier, type PluginStartupResult, type PublisherVerifyResult, index as QA, type ResourceUsage, SIGNATURE_ALG, type SandboxContext, type ScanTarget, SecurePluginContext, type SecurityIssue, SemanticVersionManager, type ServiceFactory, ServiceLifecycle, type ServiceRegistration, type SignatureVerificationResult, type VersionCompatibility, buildPermissionsFromGrants, counterSignPayload, createApiRegistryPlugin, createMemoryCache, createMemoryI18n, createMemoryJob, createMemoryMetadata, createMemoryQueue, createPluginConfigValidator, createPluginPermissionEnforcer, generateEd25519KeyPair, getEnv, getMemoryUsage, isNode, parseSignature, resolveLocale, safeExit, signPayload, verifyPayload, verifyPlatformSignature, verifyPluginArtifact, verifyPublisherSignature };
|
package/dist/index.js
CHANGED
|
@@ -508,6 +508,108 @@ function createPluginConfigValidator(logger) {
|
|
|
508
508
|
return new PluginConfigValidator(logger);
|
|
509
509
|
}
|
|
510
510
|
|
|
511
|
+
// src/security/plugin-artifact-signature.ts
|
|
512
|
+
import {
|
|
513
|
+
sign as cryptoSign,
|
|
514
|
+
verify as cryptoVerify,
|
|
515
|
+
createPublicKey,
|
|
516
|
+
createPrivateKey,
|
|
517
|
+
generateKeyPairSync
|
|
518
|
+
} from "crypto";
|
|
519
|
+
var SIGNATURE_ALG = "ed25519";
|
|
520
|
+
var SIG_PREFIX = "ed25519:";
|
|
521
|
+
function toPrivateKey(key) {
|
|
522
|
+
return typeof key === "string" ? createPrivateKey(key) : key;
|
|
523
|
+
}
|
|
524
|
+
function toPublicKey(key) {
|
|
525
|
+
return typeof key === "string" ? createPublicKey(key) : key;
|
|
526
|
+
}
|
|
527
|
+
function toBytes(payload) {
|
|
528
|
+
return typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
|
|
529
|
+
}
|
|
530
|
+
function generateEd25519KeyPair() {
|
|
531
|
+
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
532
|
+
return {
|
|
533
|
+
publicKeyPem: publicKey.export({ type: "spki", format: "pem" }).toString(),
|
|
534
|
+
privateKeyPem: privateKey.export({ type: "pkcs8", format: "pem" }).toString()
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function signPayload(payload, privateKey, keyId = "default") {
|
|
538
|
+
if (keyId.includes(":")) throw new Error('keyId must not contain ":"');
|
|
539
|
+
const sig = cryptoSign(null, toBytes(payload), toPrivateKey(privateKey));
|
|
540
|
+
return `${SIG_PREFIX}${keyId}:${sig.toString("base64url")}`;
|
|
541
|
+
}
|
|
542
|
+
function parseSignature(s) {
|
|
543
|
+
if (typeof s !== "string" || !s.startsWith(SIG_PREFIX)) return null;
|
|
544
|
+
const rest = s.slice(SIG_PREFIX.length);
|
|
545
|
+
const idx = rest.indexOf(":");
|
|
546
|
+
if (idx <= 0) return null;
|
|
547
|
+
const keyId = rest.slice(0, idx);
|
|
548
|
+
const b64 = rest.slice(idx + 1);
|
|
549
|
+
if (!keyId || !b64) return null;
|
|
550
|
+
try {
|
|
551
|
+
return { alg: "ed25519", keyId, signature: new Uint8Array(Buffer.from(b64, "base64url")) };
|
|
552
|
+
} catch {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function verifyPayload(payload, signature, publicKey) {
|
|
557
|
+
const parsed = parseSignature(signature);
|
|
558
|
+
if (!parsed) return false;
|
|
559
|
+
try {
|
|
560
|
+
return cryptoVerify(null, toBytes(payload), toPublicKey(publicKey), parsed.signature);
|
|
561
|
+
} catch {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function counterSignPayload(version) {
|
|
566
|
+
return [
|
|
567
|
+
version.package_id,
|
|
568
|
+
version.version,
|
|
569
|
+
version.blob_key ?? "",
|
|
570
|
+
version.signature ?? ""
|
|
571
|
+
].join("\n");
|
|
572
|
+
}
|
|
573
|
+
async function verifyPublisherSignature(args, getPublicKey) {
|
|
574
|
+
const sig = args.signature;
|
|
575
|
+
if (!sig) return { ok: true, verified: false, reason: "no signature supplied" };
|
|
576
|
+
const parsed = parseSignature(sig);
|
|
577
|
+
if (!parsed) return { ok: false, verified: false, reason: "signature is malformed" };
|
|
578
|
+
if (!getPublicKey) {
|
|
579
|
+
return { ok: true, verified: false, reason: "no publisher key registry configured" };
|
|
580
|
+
}
|
|
581
|
+
const pub = await getPublicKey(parsed.keyId);
|
|
582
|
+
if (!pub) return { ok: false, verified: false, reason: `unknown publisher key '${parsed.keyId}'` };
|
|
583
|
+
return verifyPayload(args.artifact, sig, pub) ? { ok: true, verified: true } : { ok: false, verified: false, reason: "publisher signature does not match artifact" };
|
|
584
|
+
}
|
|
585
|
+
function verifyPlatformSignature(version, platformPublicKey) {
|
|
586
|
+
if (!version.platform_signature) return false;
|
|
587
|
+
return verifyPayload(counterSignPayload(version), version.platform_signature, platformPublicKey);
|
|
588
|
+
}
|
|
589
|
+
async function verifyPluginArtifact(input, keys) {
|
|
590
|
+
const requirePlatform = keys.requirePlatform ?? true;
|
|
591
|
+
const publisher = await verifyPublisherSignature(
|
|
592
|
+
{ artifact: input.artifact, signature: input.version.signature },
|
|
593
|
+
keys.getPublisherPublicKey
|
|
594
|
+
);
|
|
595
|
+
if (!publisher.ok) {
|
|
596
|
+
return { ok: false, publisherVerified: false, platformVerified: false, reason: publisher.reason };
|
|
597
|
+
}
|
|
598
|
+
let platformVerified = false;
|
|
599
|
+
if (keys.platformPublicKey) {
|
|
600
|
+
platformVerified = verifyPlatformSignature(input.version, keys.platformPublicKey);
|
|
601
|
+
}
|
|
602
|
+
if (requirePlatform && !platformVerified) {
|
|
603
|
+
return {
|
|
604
|
+
ok: false,
|
|
605
|
+
publisherVerified: publisher.verified,
|
|
606
|
+
platformVerified,
|
|
607
|
+
reason: keys.platformPublicKey ? "platform counter-signature missing or invalid" : "no platform public key configured"
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
return { ok: true, publisherVerified: publisher.verified, platformVerified };
|
|
611
|
+
}
|
|
612
|
+
|
|
511
613
|
// src/plugin-loader.ts
|
|
512
614
|
var ServiceLifecycle = /* @__PURE__ */ ((ServiceLifecycle2) => {
|
|
513
615
|
ServiceLifecycle2["SINGLETON"] = "singleton";
|
|
@@ -765,7 +867,15 @@ var PluginLoader = class {
|
|
|
765
867
|
if (!plugin.signature) {
|
|
766
868
|
return;
|
|
767
869
|
}
|
|
768
|
-
|
|
870
|
+
const parsed = parseSignature(plugin.signature);
|
|
871
|
+
if (!parsed) {
|
|
872
|
+
throw new Error(
|
|
873
|
+
`Plugin ${plugin.name} carries a malformed signature (expected ed25519:<keyId>:<base64url>)`
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
this.logger.debug(
|
|
877
|
+
`Plugin ${plugin.name} signature well-formed (alg=${parsed.alg}, keyId=${parsed.keyId}); artifact verification occurs at materialize time`
|
|
878
|
+
);
|
|
769
879
|
}
|
|
770
880
|
async getSingletonService(registration) {
|
|
771
881
|
let instance = this.serviceInstances.get(registration.name);
|
|
@@ -2709,9 +2819,31 @@ var PluginPermissionEnforcer = class {
|
|
|
2709
2819
|
capabilityCount: capabilities.length
|
|
2710
2820
|
});
|
|
2711
2821
|
}
|
|
2822
|
+
/**
|
|
2823
|
+
* Register the install-time GRANTED permission set for a plugin
|
|
2824
|
+
* (ADR-0025 F4). This is the structured `{ services, hooks, network, fs }`
|
|
2825
|
+
* grant that the cloud control plane persists to
|
|
2826
|
+
* `sys_package_installation.granted_permissions` after the user consents
|
|
2827
|
+
* at install (ADR §3.5 step 2). The runtime calls this when materializing
|
|
2828
|
+
* a third-party plugin so {@link SecurePluginContext} enforces exactly the
|
|
2829
|
+
* consented surface — independent of whatever the manifest *requested*.
|
|
2830
|
+
*
|
|
2831
|
+
* Prefer this over {@link registerPluginPermissions} for distributed
|
|
2832
|
+
* plugins: it enforces what was granted, not what was declared.
|
|
2833
|
+
*/
|
|
2834
|
+
registerGrantedPermissions(pluginName, granted) {
|
|
2835
|
+
this.permissionRegistry.set(pluginName, buildPermissionsFromGrants(granted));
|
|
2836
|
+
this.logger.info(`Granted permissions registered for plugin: ${pluginName}`, {
|
|
2837
|
+
plugin: pluginName,
|
|
2838
|
+
services: granted?.services?.length ?? 0,
|
|
2839
|
+
hooks: granted?.hooks?.length ?? 0,
|
|
2840
|
+
network: granted?.network?.length ?? 0,
|
|
2841
|
+
fs: granted?.fs?.length ?? 0
|
|
2842
|
+
});
|
|
2843
|
+
}
|
|
2712
2844
|
/**
|
|
2713
2845
|
* Enforce service access permission
|
|
2714
|
-
*
|
|
2846
|
+
*
|
|
2715
2847
|
* @param pluginName - Plugin requesting access
|
|
2716
2848
|
* @param serviceName - Service to access
|
|
2717
2849
|
* @throws Error if permission denied
|
|
@@ -2974,6 +3106,32 @@ var SecurePluginContext = class {
|
|
|
2974
3106
|
function createPluginPermissionEnforcer(logger) {
|
|
2975
3107
|
return new PluginPermissionEnforcer(logger);
|
|
2976
3108
|
}
|
|
3109
|
+
function grantGlobMatch(pattern, value) {
|
|
3110
|
+
if (pattern === "*" || pattern === "**") return true;
|
|
3111
|
+
const regexStr = pattern.split("**").map((segment) => segment.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*")).join(".*");
|
|
3112
|
+
return new RegExp(`^${regexStr}$`).test(value);
|
|
3113
|
+
}
|
|
3114
|
+
function hostOf(url) {
|
|
3115
|
+
try {
|
|
3116
|
+
return new URL(url).host;
|
|
3117
|
+
} catch {
|
|
3118
|
+
return url;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
var inList = (list, value) => Array.isArray(list) && list.some((p) => p === value || grantGlobMatch(p, value));
|
|
3122
|
+
function buildPermissionsFromGrants(granted) {
|
|
3123
|
+
const services = granted?.services;
|
|
3124
|
+
const hooks = granted?.hooks;
|
|
3125
|
+
const network = granted?.network;
|
|
3126
|
+
const fs = granted?.fs;
|
|
3127
|
+
return {
|
|
3128
|
+
canAccessService: (name) => inList(services, name),
|
|
3129
|
+
canTriggerHook: (name) => inList(hooks, name),
|
|
3130
|
+
canReadFile: (path) => inList(fs, path),
|
|
3131
|
+
canWriteFile: (path) => inList(fs, path),
|
|
3132
|
+
canNetworkRequest: (url) => inList(network, hostOf(url)) || inList(network, url)
|
|
3133
|
+
};
|
|
3134
|
+
}
|
|
2977
3135
|
|
|
2978
3136
|
// src/security/permission-manager.ts
|
|
2979
3137
|
var PluginPermissionManager = class {
|
|
@@ -4658,9 +4816,12 @@ export {
|
|
|
4658
4816
|
PluginSecurityScanner,
|
|
4659
4817
|
PluginSignatureVerifier,
|
|
4660
4818
|
qa_exports as QA,
|
|
4819
|
+
SIGNATURE_ALG,
|
|
4661
4820
|
SecurePluginContext,
|
|
4662
4821
|
SemanticVersionManager,
|
|
4663
4822
|
ServiceLifecycle,
|
|
4823
|
+
buildPermissionsFromGrants,
|
|
4824
|
+
counterSignPayload,
|
|
4664
4825
|
createApiRegistryPlugin,
|
|
4665
4826
|
createLogger,
|
|
4666
4827
|
createMemoryCache,
|
|
@@ -4670,10 +4831,17 @@ export {
|
|
|
4670
4831
|
createMemoryQueue,
|
|
4671
4832
|
createPluginConfigValidator,
|
|
4672
4833
|
createPluginPermissionEnforcer,
|
|
4834
|
+
generateEd25519KeyPair,
|
|
4673
4835
|
getEnv,
|
|
4674
4836
|
getMemoryUsage,
|
|
4675
4837
|
isNode,
|
|
4838
|
+
parseSignature,
|
|
4676
4839
|
resolveLocale,
|
|
4677
|
-
safeExit
|
|
4840
|
+
safeExit,
|
|
4841
|
+
signPayload,
|
|
4842
|
+
verifyPayload,
|
|
4843
|
+
verifyPlatformSignature,
|
|
4844
|
+
verifyPluginArtifact,
|
|
4845
|
+
verifyPublisherSignature
|
|
4678
4846
|
};
|
|
4679
4847
|
//# sourceMappingURL=index.js.map
|