@objectstack/core 2.0.1 → 2.0.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +7 -0
- package/dist/index.cjs +121 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -11
- package/dist/index.d.ts +10 -11
- package/dist/index.js +121 -42
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/hot-reload.ts +4 -12
- package/src/qa/runner.ts +12 -4
- package/src/security/plugin-permission-enforcer.ts +29 -9
- package/src/security/plugin-signature-verifier.ts +48 -7
- package/src/security/sandbox-runtime.ts +61 -34
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/core@2.0.
|
|
2
|
+
> @objectstack/core@2.0.2 build /home/runner/work/spec/spec/packages/core
|
|
3
3
|
> tsup --config ../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
14
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
15
|
-
[32mESM[39m ⚡️ Build success in
|
|
16
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
17
|
-
[32mCJS[39m [1mdist/index.cjs.map [22m[
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in
|
|
13
|
+
[32mESM[39m [1mdist/index.js [22m[32m131.69 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m276.68 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 92ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m134.46 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.cjs.map [22m[32m278.04 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 97ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m52.
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[32m52.
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 3262ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m52.28 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m52.28 KB[39m
|
package/CHANGELOG.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -2138,8 +2138,18 @@ var TestRunner = class {
|
|
|
2138
2138
|
}
|
|
2139
2139
|
return result;
|
|
2140
2140
|
}
|
|
2141
|
-
resolveVariables(action,
|
|
2142
|
-
|
|
2141
|
+
resolveVariables(action, context) {
|
|
2142
|
+
const actionStr = JSON.stringify(action);
|
|
2143
|
+
const resolved = actionStr.replace(/\{\{([^}]+)\}\}/g, (_match, varPath) => {
|
|
2144
|
+
const value = this.getValueByPath(context, varPath.trim());
|
|
2145
|
+
if (value === void 0) return _match;
|
|
2146
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
2147
|
+
});
|
|
2148
|
+
try {
|
|
2149
|
+
return JSON.parse(resolved);
|
|
2150
|
+
} catch {
|
|
2151
|
+
return action;
|
|
2152
|
+
}
|
|
2143
2153
|
}
|
|
2144
2154
|
getValueByPath(obj, path) {
|
|
2145
2155
|
if (!path) return obj;
|
|
@@ -2476,9 +2486,38 @@ var PluginSignatureVerifier = class {
|
|
|
2476
2486
|
return false;
|
|
2477
2487
|
}
|
|
2478
2488
|
}
|
|
2479
|
-
async verifyCryptoSignatureBrowser(
|
|
2480
|
-
|
|
2481
|
-
|
|
2489
|
+
async verifyCryptoSignatureBrowser(data, signature, publicKey) {
|
|
2490
|
+
try {
|
|
2491
|
+
const subtle = globalThis.crypto?.subtle;
|
|
2492
|
+
if (!subtle) {
|
|
2493
|
+
this.logger.error("SubtleCrypto not available in this environment");
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2496
|
+
const pemBody = publicKey.replace(/-----BEGIN PUBLIC KEY-----/, "").replace(/-----END PUBLIC KEY-----/, "").replace(/\s/g, "");
|
|
2497
|
+
const keyBytes = Uint8Array.from(atob(pemBody), (c) => c.charCodeAt(0));
|
|
2498
|
+
let importAlgorithm;
|
|
2499
|
+
let verifyAlgorithm;
|
|
2500
|
+
if (this.config.algorithm === "ES256") {
|
|
2501
|
+
importAlgorithm = { name: "ECDSA", namedCurve: "P-256" };
|
|
2502
|
+
verifyAlgorithm = { name: "ECDSA", hash: "SHA-256" };
|
|
2503
|
+
} else {
|
|
2504
|
+
importAlgorithm = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
|
|
2505
|
+
verifyAlgorithm = { name: "RSASSA-PKCS1-v1_5" };
|
|
2506
|
+
}
|
|
2507
|
+
const cryptoKey = await subtle.importKey(
|
|
2508
|
+
"spki",
|
|
2509
|
+
keyBytes,
|
|
2510
|
+
importAlgorithm,
|
|
2511
|
+
false,
|
|
2512
|
+
["verify"]
|
|
2513
|
+
);
|
|
2514
|
+
const signatureBytes = Uint8Array.from(atob(signature), (c) => c.charCodeAt(0));
|
|
2515
|
+
const dataBytes = new TextEncoder().encode(data);
|
|
2516
|
+
return await subtle.verify(verifyAlgorithm, cryptoKey, signatureBytes, dataBytes);
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
this.logger.error("Browser signature verification failed", error);
|
|
2519
|
+
return false;
|
|
2520
|
+
}
|
|
2482
2521
|
}
|
|
2483
2522
|
validateConfig() {
|
|
2484
2523
|
if (!this.config.trustedPublicKeys || this.config.trustedPublicKeys.size === 0) {
|
|
@@ -2696,29 +2735,48 @@ var PluginPermissionEnforcer = class {
|
|
|
2696
2735
|
return false;
|
|
2697
2736
|
});
|
|
2698
2737
|
}
|
|
2699
|
-
|
|
2738
|
+
matchGlob(pattern, str) {
|
|
2739
|
+
const regexStr = pattern.split("**").map((segment) => {
|
|
2740
|
+
const escaped = segment.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
2741
|
+
return escaped.replace(/\*/g, "[^/]*");
|
|
2742
|
+
}).join(".*");
|
|
2743
|
+
return new RegExp(`^${regexStr}$`).test(str);
|
|
2744
|
+
}
|
|
2745
|
+
checkFileRead(capabilities, path) {
|
|
2700
2746
|
return capabilities.some((cap) => {
|
|
2701
2747
|
const protocolId = cap.protocol.id;
|
|
2702
2748
|
if (protocolId.includes("protocol.filesystem.read")) {
|
|
2703
|
-
|
|
2749
|
+
const paths = cap.metadata?.paths;
|
|
2750
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
2751
|
+
return true;
|
|
2752
|
+
}
|
|
2753
|
+
return paths.some((p) => typeof p === "string" && this.matchGlob(p, path));
|
|
2704
2754
|
}
|
|
2705
2755
|
return false;
|
|
2706
2756
|
});
|
|
2707
2757
|
}
|
|
2708
|
-
checkFileWrite(capabilities,
|
|
2758
|
+
checkFileWrite(capabilities, path) {
|
|
2709
2759
|
return capabilities.some((cap) => {
|
|
2710
2760
|
const protocolId = cap.protocol.id;
|
|
2711
2761
|
if (protocolId.includes("protocol.filesystem.write")) {
|
|
2712
|
-
|
|
2762
|
+
const paths = cap.metadata?.paths;
|
|
2763
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
2764
|
+
return true;
|
|
2765
|
+
}
|
|
2766
|
+
return paths.some((p) => typeof p === "string" && this.matchGlob(p, path));
|
|
2713
2767
|
}
|
|
2714
2768
|
return false;
|
|
2715
2769
|
});
|
|
2716
2770
|
}
|
|
2717
|
-
checkNetworkAccess(capabilities,
|
|
2771
|
+
checkNetworkAccess(capabilities, url) {
|
|
2718
2772
|
return capabilities.some((cap) => {
|
|
2719
2773
|
const protocolId = cap.protocol.id;
|
|
2720
2774
|
if (protocolId.includes("protocol.network")) {
|
|
2721
|
-
|
|
2775
|
+
const hosts = cap.metadata?.hosts;
|
|
2776
|
+
if (!Array.isArray(hosts) || hosts.length === 0) {
|
|
2777
|
+
return true;
|
|
2778
|
+
}
|
|
2779
|
+
return hosts.some((h) => typeof h === "string" && this.matchGlob(h, url));
|
|
2722
2780
|
}
|
|
2723
2781
|
return false;
|
|
2724
2782
|
});
|
|
@@ -2985,12 +3043,16 @@ var PluginPermissionManager = class {
|
|
|
2985
3043
|
};
|
|
2986
3044
|
|
|
2987
3045
|
// src/security/sandbox-runtime.ts
|
|
2988
|
-
var
|
|
3046
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
3047
|
+
var _PluginSandboxRuntime = class _PluginSandboxRuntime {
|
|
2989
3048
|
constructor(logger) {
|
|
2990
3049
|
// Active sandboxes (pluginId -> context)
|
|
2991
3050
|
this.sandboxes = /* @__PURE__ */ new Map();
|
|
2992
3051
|
// Resource monitoring intervals
|
|
2993
3052
|
this.monitoringIntervals = /* @__PURE__ */ new Map();
|
|
3053
|
+
// Per-plugin resource baselines for delta tracking
|
|
3054
|
+
this.memoryBaselines = /* @__PURE__ */ new Map();
|
|
3055
|
+
this.cpuBaselines = /* @__PURE__ */ new Map();
|
|
2994
3056
|
this.logger = logger.child({ component: "SandboxRuntime" });
|
|
2995
3057
|
}
|
|
2996
3058
|
/**
|
|
@@ -3011,6 +3073,9 @@ var PluginSandboxRuntime = class {
|
|
|
3011
3073
|
}
|
|
3012
3074
|
};
|
|
3013
3075
|
this.sandboxes.set(pluginId, context);
|
|
3076
|
+
const baselineMemory = getMemoryUsage();
|
|
3077
|
+
this.memoryBaselines.set(pluginId, baselineMemory.heapUsed);
|
|
3078
|
+
this.cpuBaselines.set(pluginId, process.cpuUsage());
|
|
3014
3079
|
this.startResourceMonitoring(pluginId);
|
|
3015
3080
|
this.logger.info("Sandbox created", {
|
|
3016
3081
|
pluginId,
|
|
@@ -3029,6 +3094,8 @@ var PluginSandboxRuntime = class {
|
|
|
3029
3094
|
return;
|
|
3030
3095
|
}
|
|
3031
3096
|
this.stopResourceMonitoring(pluginId);
|
|
3097
|
+
this.memoryBaselines.delete(pluginId);
|
|
3098
|
+
this.cpuBaselines.delete(pluginId);
|
|
3032
3099
|
this.sandboxes.delete(pluginId);
|
|
3033
3100
|
this.logger.info("Sandbox destroyed", { pluginId });
|
|
3034
3101
|
}
|
|
@@ -3056,45 +3123,46 @@ var PluginSandboxRuntime = class {
|
|
|
3056
3123
|
}
|
|
3057
3124
|
/**
|
|
3058
3125
|
* Check file system access
|
|
3059
|
-
*
|
|
3060
|
-
* resolution with path.resolve() and path.normalize() to prevent traversal.
|
|
3126
|
+
* Uses path.resolve() and path.normalize() to prevent directory traversal.
|
|
3061
3127
|
*/
|
|
3062
|
-
checkFileAccess(config,
|
|
3128
|
+
checkFileAccess(config, filePath) {
|
|
3063
3129
|
if (config.level === "none") {
|
|
3064
3130
|
return { allowed: true };
|
|
3065
3131
|
}
|
|
3066
3132
|
if (!config.filesystem) {
|
|
3067
3133
|
return { allowed: false, reason: "File system access not configured" };
|
|
3068
3134
|
}
|
|
3069
|
-
if (!
|
|
3135
|
+
if (!filePath) {
|
|
3070
3136
|
return { allowed: config.filesystem.mode !== "none" };
|
|
3071
3137
|
}
|
|
3072
3138
|
const allowedPaths = config.filesystem.allowedPaths || [];
|
|
3139
|
+
const resolvedPath = import_node_path.default.normalize(import_node_path.default.resolve(filePath));
|
|
3073
3140
|
const isAllowed = allowedPaths.some((allowed) => {
|
|
3074
|
-
|
|
3141
|
+
const resolvedAllowed = import_node_path.default.normalize(import_node_path.default.resolve(allowed));
|
|
3142
|
+
return resolvedPath.startsWith(resolvedAllowed);
|
|
3075
3143
|
});
|
|
3076
3144
|
if (allowedPaths.length > 0 && !isAllowed) {
|
|
3077
3145
|
return {
|
|
3078
3146
|
allowed: false,
|
|
3079
|
-
reason: `Path not in allowed list: ${
|
|
3147
|
+
reason: `Path not in allowed list: ${filePath}`
|
|
3080
3148
|
};
|
|
3081
3149
|
}
|
|
3082
3150
|
const deniedPaths = config.filesystem.deniedPaths || [];
|
|
3083
3151
|
const isDenied = deniedPaths.some((denied) => {
|
|
3084
|
-
|
|
3152
|
+
const resolvedDenied = import_node_path.default.normalize(import_node_path.default.resolve(denied));
|
|
3153
|
+
return resolvedPath.startsWith(resolvedDenied);
|
|
3085
3154
|
});
|
|
3086
3155
|
if (isDenied) {
|
|
3087
3156
|
return {
|
|
3088
3157
|
allowed: false,
|
|
3089
|
-
reason: `Path is explicitly denied: ${
|
|
3158
|
+
reason: `Path is explicitly denied: ${filePath}`
|
|
3090
3159
|
};
|
|
3091
3160
|
}
|
|
3092
3161
|
return { allowed: true };
|
|
3093
3162
|
}
|
|
3094
3163
|
/**
|
|
3095
3164
|
* Check network access
|
|
3096
|
-
*
|
|
3097
|
-
* parsing with new URL() and check hostname property.
|
|
3165
|
+
* Uses URL parsing to properly validate hostnames.
|
|
3098
3166
|
*/
|
|
3099
3167
|
checkNetworkAccess(config, url) {
|
|
3100
3168
|
if (config.level === "none") {
|
|
@@ -3109,10 +3177,16 @@ var PluginSandboxRuntime = class {
|
|
|
3109
3177
|
if (!url) {
|
|
3110
3178
|
return { allowed: config.network.mode !== "none" };
|
|
3111
3179
|
}
|
|
3180
|
+
let parsedHostname;
|
|
3181
|
+
try {
|
|
3182
|
+
parsedHostname = new URL(url).hostname;
|
|
3183
|
+
} catch {
|
|
3184
|
+
return { allowed: false, reason: `Invalid URL: ${url}` };
|
|
3185
|
+
}
|
|
3112
3186
|
const allowedHosts = config.network.allowedHosts || [];
|
|
3113
3187
|
if (allowedHosts.length > 0) {
|
|
3114
3188
|
const isAllowed = allowedHosts.some((host) => {
|
|
3115
|
-
return
|
|
3189
|
+
return parsedHostname === host;
|
|
3116
3190
|
});
|
|
3117
3191
|
if (!isAllowed) {
|
|
3118
3192
|
return {
|
|
@@ -3123,7 +3197,7 @@ var PluginSandboxRuntime = class {
|
|
|
3123
3197
|
}
|
|
3124
3198
|
const deniedHosts = config.network.deniedHosts || [];
|
|
3125
3199
|
const isDenied = deniedHosts.some((host) => {
|
|
3126
|
-
return
|
|
3200
|
+
return parsedHostname === host;
|
|
3127
3201
|
});
|
|
3128
3202
|
if (isDenied) {
|
|
3129
3203
|
return {
|
|
@@ -3200,7 +3274,7 @@ var PluginSandboxRuntime = class {
|
|
|
3200
3274
|
startResourceMonitoring(pluginId) {
|
|
3201
3275
|
const interval = setInterval(() => {
|
|
3202
3276
|
this.updateResourceUsage(pluginId);
|
|
3203
|
-
},
|
|
3277
|
+
}, _PluginSandboxRuntime.MONITORING_INTERVAL_MS);
|
|
3204
3278
|
this.monitoringIntervals.set(pluginId, interval);
|
|
3205
3279
|
}
|
|
3206
3280
|
/**
|
|
@@ -3216,10 +3290,9 @@ var PluginSandboxRuntime = class {
|
|
|
3216
3290
|
/**
|
|
3217
3291
|
* Update resource usage statistics
|
|
3218
3292
|
*
|
|
3219
|
-
*
|
|
3220
|
-
*
|
|
3221
|
-
* per-plugin
|
|
3222
|
-
* plugin boundaries.
|
|
3293
|
+
* Tracks per-plugin memory and CPU usage using delta from baseline
|
|
3294
|
+
* captured at sandbox creation time. This is an approximation since
|
|
3295
|
+
* true per-plugin isolation isn't possible in a single Node.js process.
|
|
3223
3296
|
*/
|
|
3224
3297
|
updateResourceUsage(pluginId) {
|
|
3225
3298
|
const context = this.sandboxes.get(pluginId);
|
|
@@ -3227,12 +3300,21 @@ var PluginSandboxRuntime = class {
|
|
|
3227
3300
|
return;
|
|
3228
3301
|
}
|
|
3229
3302
|
const memoryUsage = getMemoryUsage();
|
|
3230
|
-
|
|
3303
|
+
const memoryBaseline = this.memoryBaselines.get(pluginId) ?? 0;
|
|
3304
|
+
const memoryDelta = Math.max(0, memoryUsage.heapUsed - memoryBaseline);
|
|
3305
|
+
context.resourceUsage.memory.current = memoryDelta;
|
|
3231
3306
|
context.resourceUsage.memory.peak = Math.max(
|
|
3232
3307
|
context.resourceUsage.memory.peak,
|
|
3233
|
-
|
|
3308
|
+
memoryDelta
|
|
3234
3309
|
);
|
|
3235
|
-
|
|
3310
|
+
const cpuBaseline = this.cpuBaselines.get(pluginId) ?? { user: 0, system: 0 };
|
|
3311
|
+
const cpuCurrent = process.cpuUsage();
|
|
3312
|
+
const cpuDeltaUser = cpuCurrent.user - cpuBaseline.user;
|
|
3313
|
+
const cpuDeltaSystem = cpuCurrent.system - cpuBaseline.system;
|
|
3314
|
+
const totalCpuMicros = cpuDeltaUser + cpuDeltaSystem;
|
|
3315
|
+
const intervalMicros = _PluginSandboxRuntime.MONITORING_INTERVAL_MS * 1e3;
|
|
3316
|
+
context.resourceUsage.cpu.current = totalCpuMicros / intervalMicros * 100;
|
|
3317
|
+
this.cpuBaselines.set(pluginId, cpuCurrent);
|
|
3236
3318
|
const { withinLimits, violations } = this.checkResourceLimits(pluginId);
|
|
3237
3319
|
if (!withinLimits) {
|
|
3238
3320
|
this.logger.warn("Resource limit violations detected", {
|
|
@@ -3255,9 +3337,13 @@ var PluginSandboxRuntime = class {
|
|
|
3255
3337
|
this.stopResourceMonitoring(pluginId);
|
|
3256
3338
|
}
|
|
3257
3339
|
this.sandboxes.clear();
|
|
3340
|
+
this.memoryBaselines.clear();
|
|
3341
|
+
this.cpuBaselines.clear();
|
|
3258
3342
|
this.logger.info("Sandbox runtime shutdown complete");
|
|
3259
3343
|
}
|
|
3260
3344
|
};
|
|
3345
|
+
_PluginSandboxRuntime.MONITORING_INTERVAL_MS = 5e3;
|
|
3346
|
+
var PluginSandboxRuntime = _PluginSandboxRuntime;
|
|
3261
3347
|
|
|
3262
3348
|
// src/security/security-scanner.ts
|
|
3263
3349
|
var PluginSecurityScanner = class {
|
|
@@ -3724,6 +3810,7 @@ var PluginHealthMonitor = class {
|
|
|
3724
3810
|
};
|
|
3725
3811
|
|
|
3726
3812
|
// src/hot-reload.ts
|
|
3813
|
+
var import_node_crypto = require("crypto");
|
|
3727
3814
|
var generateUUID = () => {
|
|
3728
3815
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
3729
3816
|
return crypto.randomUUID();
|
|
@@ -3814,19 +3901,11 @@ var PluginStateManager = class {
|
|
|
3814
3901
|
this.logger.debug("State cleared", { pluginId });
|
|
3815
3902
|
}
|
|
3816
3903
|
/**
|
|
3817
|
-
* Calculate
|
|
3818
|
-
* WARNING: This is a simple hash for demo purposes.
|
|
3819
|
-
* In production, use a cryptographic hash like SHA-256.
|
|
3904
|
+
* Calculate checksum for state verification using SHA-256.
|
|
3820
3905
|
*/
|
|
3821
3906
|
calculateChecksum(state) {
|
|
3822
3907
|
const stateStr = JSON.stringify(state);
|
|
3823
|
-
|
|
3824
|
-
for (let i = 0; i < stateStr.length; i++) {
|
|
3825
|
-
const char = stateStr.charCodeAt(i);
|
|
3826
|
-
hash = (hash << 5) - hash + char;
|
|
3827
|
-
hash = hash & hash;
|
|
3828
|
-
}
|
|
3829
|
-
return hash.toString(16);
|
|
3908
|
+
return (0, import_node_crypto.createHash)("sha256").update(stateStr).digest("hex");
|
|
3830
3909
|
}
|
|
3831
3910
|
/**
|
|
3832
3911
|
* Shutdown state manager
|