contextos-mcp 0.4.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/LICENSE +21 -0
- package/README.md +260 -0
- package/bin/mcp.mjs +61 -0
- package/bin/swarm.mjs +45 -0
- package/dist/agents/aider.d.ts +12 -0
- package/dist/agents/aider.js +192 -0
- package/dist/agents/aider.js.map +1 -0
- package/dist/agents/claude-code.d.ts +9 -0
- package/dist/agents/claude-code.js +236 -0
- package/dist/agents/claude-code.js.map +1 -0
- package/dist/agents/codex.d.ts +14 -0
- package/dist/agents/codex.js +203 -0
- package/dist/agents/codex.js.map +1 -0
- package/dist/agents/direct-llm.d.ts +22 -0
- package/dist/agents/direct-llm.js +254 -0
- package/dist/agents/direct-llm.js.map +1 -0
- package/dist/agents/mock.d.ts +10 -0
- package/dist/agents/mock.js +170 -0
- package/dist/agents/mock.js.map +1 -0
- package/dist/agents/opencode.d.ts +23 -0
- package/dist/agents/opencode.js +631 -0
- package/dist/agents/opencode.js.map +1 -0
- package/dist/agents/provider.d.ts +11 -0
- package/dist/agents/provider.js +31 -0
- package/dist/agents/provider.js.map +1 -0
- package/dist/compression/compressor.d.ts +28 -0
- package/dist/compression/compressor.js +268 -0
- package/dist/compression/compressor.js.map +1 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.js +172 -0
- package/dist/config.js.map +1 -0
- package/dist/contextos/loader.d.ts +46 -0
- package/dist/contextos/loader.js +120 -0
- package/dist/contextos/loader.js.map +1 -0
- package/dist/contextos/selector.d.ts +46 -0
- package/dist/contextos/selector.js +80 -0
- package/dist/contextos/selector.js.map +1 -0
- package/dist/core/action-dispatcher.d.ts +15 -0
- package/dist/core/action-dispatcher.js +28 -0
- package/dist/core/action-dispatcher.js.map +1 -0
- package/dist/core/action-schema.d.ts +72 -0
- package/dist/core/action-schema.js +86 -0
- package/dist/core/action-schema.js.map +1 -0
- package/dist/core/repl-interface.d.ts +48 -0
- package/dist/core/repl-interface.js +5 -0
- package/dist/core/repl-interface.js.map +1 -0
- package/dist/core/repl.d.ts +59 -0
- package/dist/core/repl.js +353 -0
- package/dist/core/repl.js.map +1 -0
- package/dist/core/rlm.d.ts +65 -0
- package/dist/core/rlm.js +564 -0
- package/dist/core/rlm.js.map +1 -0
- package/dist/core/runtime.py +470 -0
- package/dist/core/types.d.ts +265 -0
- package/dist/core/types.js +19 -0
- package/dist/core/types.js.map +1 -0
- package/dist/env.d.ts +10 -0
- package/dist/env.js +75 -0
- package/dist/env.js.map +1 -0
- package/dist/hooks/runner.d.ts +43 -0
- package/dist/hooks/runner.js +173 -0
- package/dist/hooks/runner.js.map +1 -0
- package/dist/labs/cli.d.ts +15 -0
- package/dist/labs/cli.js +295 -0
- package/dist/labs/cli.js.map +1 -0
- package/dist/labs/episodic-memory.d.ts +132 -0
- package/dist/labs/episodic-memory.js +391 -0
- package/dist/labs/episodic-memory.js.map +1 -0
- package/dist/labs/interactive-swarm.d.ts +20 -0
- package/dist/labs/interactive-swarm.js +1173 -0
- package/dist/labs/interactive-swarm.js.map +1 -0
- package/dist/labs/interactive.d.ts +10 -0
- package/dist/labs/interactive.js +2099 -0
- package/dist/labs/interactive.js.map +1 -0
- package/dist/labs/ts-analyzer.d.ts +74 -0
- package/dist/labs/ts-analyzer.js +379 -0
- package/dist/labs/ts-analyzer.js.map +1 -0
- package/dist/labs/viewer.d.ts +12 -0
- package/dist/labs/viewer.js +1284 -0
- package/dist/labs/viewer.js.map +1 -0
- package/dist/main.d.ts +15 -0
- package/dist/main.js +247 -0
- package/dist/main.js.map +1 -0
- package/dist/mcp/server.d.ts +16 -0
- package/dist/mcp/server.js +192775 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/session.d.ts +117 -0
- package/dist/mcp/session.js +450 -0
- package/dist/mcp/session.js.map +1 -0
- package/dist/mcp/state.d.ts +42 -0
- package/dist/mcp/state.js +537 -0
- package/dist/mcp/state.js.map +1 -0
- package/dist/mcp/tools/contextos.d.ts +17 -0
- package/dist/mcp/tools/contextos.js +583 -0
- package/dist/mcp/tools/contextos.js.map +1 -0
- package/dist/mcp/tools.d.ts +15 -0
- package/dist/mcp/tools.js +393 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/orchestration/reviewer-gate.d.ts +43 -0
- package/dist/orchestration/reviewer-gate.js +280 -0
- package/dist/orchestration/reviewer-gate.js.map +1 -0
- package/dist/orchestration/reviewer-prompt.d.ts +18 -0
- package/dist/orchestration/reviewer-prompt.js +67 -0
- package/dist/orchestration/reviewer-prompt.js.map +1 -0
- package/dist/orchestration/verification-runner.d.ts +53 -0
- package/dist/orchestration/verification-runner.js +352 -0
- package/dist/orchestration/verification-runner.js.map +1 -0
- package/dist/plugins/adapter.d.ts +11 -0
- package/dist/plugins/adapter.js +11 -0
- package/dist/plugins/adapter.js.map +1 -0
- package/dist/plugins/core/archive.d.ts +9 -0
- package/dist/plugins/core/archive.js +128 -0
- package/dist/plugins/core/archive.js.map +1 -0
- package/dist/plugins/core/digest.d.ts +11 -0
- package/dist/plugins/core/digest.js +70 -0
- package/dist/plugins/core/digest.js.map +1 -0
- package/dist/plugins/core/grants.d.ts +22 -0
- package/dist/plugins/core/grants.js +90 -0
- package/dist/plugins/core/grants.js.map +1 -0
- package/dist/plugins/core/index.d.ts +10 -0
- package/dist/plugins/core/index.js +11 -0
- package/dist/plugins/core/index.js.map +1 -0
- package/dist/plugins/core/pinning.d.ts +15 -0
- package/dist/plugins/core/pinning.js +86 -0
- package/dist/plugins/core/pinning.js.map +1 -0
- package/dist/plugins/core/types.d.ts +105 -0
- package/dist/plugins/core/types.js +22 -0
- package/dist/plugins/core/types.js.map +1 -0
- package/dist/plugins/core/updater.d.ts +16 -0
- package/dist/plugins/core/updater.js +64 -0
- package/dist/plugins/core/updater.js.map +1 -0
- package/dist/plugins/loader.d.ts +65 -0
- package/dist/plugins/loader.js +110 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/prompts/orchestrator.d.ts +6 -0
- package/dist/prompts/orchestrator.js +114 -0
- package/dist/prompts/orchestrator.js.map +1 -0
- package/dist/routing/model-resolver.d.ts +27 -0
- package/dist/routing/model-resolver.js +265 -0
- package/dist/routing/model-resolver.js.map +1 -0
- package/dist/routing/model-router.d.ts +130 -0
- package/dist/routing/model-router.js +515 -0
- package/dist/routing/model-router.js.map +1 -0
- package/dist/runtime/attestations.cjs +260 -0
- package/dist/runtime/reviewer-pipeline.cjs +319 -0
- package/dist/runtime/sandbox.cjs +468 -0
- package/dist/runtime/state-machine.cjs +885 -0
- package/dist/runtime/thread-store.cjs +175 -0
- package/dist/runtime/transactional-git.cjs +315 -0
- package/dist/runtime/verification-pipeline.cjs +435 -0
- package/dist/sandbox/execution.d.ts +165 -0
- package/dist/sandbox/execution.js +497 -0
- package/dist/sandbox/execution.js.map +1 -0
- package/dist/security/file-policy.d.ts +22 -0
- package/dist/security/file-policy.js +53 -0
- package/dist/security/file-policy.js.map +1 -0
- package/dist/security/repository-boundary.d.ts +21 -0
- package/dist/security/repository-boundary.js +119 -0
- package/dist/security/repository-boundary.js.map +1 -0
- package/dist/security/secret-filter.d.ts +36 -0
- package/dist/security/secret-filter.js +149 -0
- package/dist/security/secret-filter.js.map +1 -0
- package/dist/swarm.d.ts +14 -0
- package/dist/swarm.js +624 -0
- package/dist/swarm.js.map +1 -0
- package/dist/threads/cache.d.ts +64 -0
- package/dist/threads/cache.js +241 -0
- package/dist/threads/cache.js.map +1 -0
- package/dist/threads/manager.d.ts +94 -0
- package/dist/threads/manager.js +914 -0
- package/dist/threads/manager.js.map +1 -0
- package/dist/ui/banner.d.ts +14 -0
- package/dist/ui/banner.js +42 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/dashboard.d.ts +33 -0
- package/dist/ui/dashboard.js +135 -0
- package/dist/ui/dashboard.js.map +1 -0
- package/dist/ui/index.d.ts +11 -0
- package/dist/ui/index.js +12 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/log.d.ts +39 -0
- package/dist/ui/log.js +126 -0
- package/dist/ui/log.js.map +1 -0
- package/dist/ui/onboarding.d.ts +14 -0
- package/dist/ui/onboarding.js +755 -0
- package/dist/ui/onboarding.js.map +1 -0
- package/dist/ui/run-log.d.ts +50 -0
- package/dist/ui/run-log.js +60 -0
- package/dist/ui/run-log.js.map +1 -0
- package/dist/ui/spinner.d.ts +41 -0
- package/dist/ui/spinner.js +172 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/streaming-feed.d.ts +72 -0
- package/dist/ui/streaming-feed.js +386 -0
- package/dist/ui/streaming-feed.js.map +1 -0
- package/dist/ui/summary.d.ts +18 -0
- package/dist/ui/summary.js +113 -0
- package/dist/ui/summary.js.map +1 -0
- package/dist/ui/text-input.d.ts +19 -0
- package/dist/ui/text-input.js +266 -0
- package/dist/ui/text-input.js.map +1 -0
- package/dist/ui/theme.d.ts +63 -0
- package/dist/ui/theme.js +97 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/utils/command-exists.d.ts +11 -0
- package/dist/utils/command-exists.js +41 -0
- package/dist/utils/command-exists.js.map +1 -0
- package/dist/utils/process-security.d.ts +19 -0
- package/dist/utils/process-security.js +90 -0
- package/dist/utils/process-security.js.map +1 -0
- package/dist/utils/rolling-buffer.d.ts +16 -0
- package/dist/utils/rolling-buffer.js +30 -0
- package/dist/utils/rolling-buffer.js.map +1 -0
- package/dist/utils/safe-spawn.d.ts +13 -0
- package/dist/utils/safe-spawn.js +49 -0
- package/dist/utils/safe-spawn.js.map +1 -0
- package/dist/worktree/manager.d.ts +69 -0
- package/dist/worktree/manager.js +593 -0
- package/dist/worktree/manager.js.map +1 -0
- package/dist/worktree/merge.d.ts +58 -0
- package/dist/worktree/merge.js +389 -0
- package/dist/worktree/merge.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS Script Execution Capability Grants & Invalidation Engine
|
|
3
|
+
* Section 20.6 & Threat Model Hardening
|
|
4
|
+
*/
|
|
5
|
+
import type { AuthorizationResult, GrantOptions } from "./types.js";
|
|
6
|
+
export declare class ScriptGrantManager {
|
|
7
|
+
readonly grantsFilePath: string;
|
|
8
|
+
private grants;
|
|
9
|
+
constructor(grantsFilePath: string);
|
|
10
|
+
private _load;
|
|
11
|
+
private _save;
|
|
12
|
+
/**
|
|
13
|
+
* Grants execution permission to a specific script by its content hash.
|
|
14
|
+
* Binds both to plugin identity and content hash.
|
|
15
|
+
*/
|
|
16
|
+
grant(scriptPath: string, options?: GrantOptions): void;
|
|
17
|
+
/**
|
|
18
|
+
* Verifies if a script is authorized to run.
|
|
19
|
+
* Invalidates grant immediately if script was altered.
|
|
20
|
+
*/
|
|
21
|
+
checkAuthorization(scriptPath: string, options?: GrantOptions): AuthorizationResult;
|
|
22
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS Script Execution Capability Grants & Invalidation Engine
|
|
3
|
+
* Section 20.6 & Threat Model Hardening
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { sha256 } from "./digest.js";
|
|
8
|
+
export class ScriptGrantManager {
|
|
9
|
+
grantsFilePath;
|
|
10
|
+
grants;
|
|
11
|
+
constructor(grantsFilePath) {
|
|
12
|
+
this.grantsFilePath = path.resolve(grantsFilePath);
|
|
13
|
+
this.grants = this._load();
|
|
14
|
+
}
|
|
15
|
+
_load() {
|
|
16
|
+
if (!fs.existsSync(this.grantsFilePath))
|
|
17
|
+
return {};
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(fs.readFileSync(this.grantsFilePath, "utf8"));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
_save() {
|
|
26
|
+
fs.mkdirSync(path.dirname(this.grantsFilePath), { recursive: true });
|
|
27
|
+
fs.writeFileSync(this.grantsFilePath, JSON.stringify(this.grants, null, 2), "utf8");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Grants execution permission to a specific script by its content hash.
|
|
31
|
+
* Binds both to plugin identity and content hash.
|
|
32
|
+
*/
|
|
33
|
+
grant(scriptPath, options = {}) {
|
|
34
|
+
const resolved = path.resolve(scriptPath);
|
|
35
|
+
const content = fs.readFileSync(resolved);
|
|
36
|
+
const hash = sha256(content);
|
|
37
|
+
const record = {
|
|
38
|
+
hash,
|
|
39
|
+
grantedAt: Date.now(),
|
|
40
|
+
pluginId: options.pluginId,
|
|
41
|
+
relPath: options.relPath,
|
|
42
|
+
};
|
|
43
|
+
if (options.pluginId) {
|
|
44
|
+
const idKey = `plugin:${options.pluginId}:${options.relPath || path.basename(scriptPath)}`;
|
|
45
|
+
this.grants[idKey] = record;
|
|
46
|
+
}
|
|
47
|
+
// Also register by resolved file path for local execution and compatibility
|
|
48
|
+
this.grants[resolved] = record;
|
|
49
|
+
this._save();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Verifies if a script is authorized to run.
|
|
53
|
+
* Invalidates grant immediately if script was altered.
|
|
54
|
+
*/
|
|
55
|
+
checkAuthorization(scriptPath, options = {}) {
|
|
56
|
+
const resolved = path.resolve(scriptPath);
|
|
57
|
+
if (!fs.existsSync(resolved)) {
|
|
58
|
+
return { authorized: false, reason: "Script file not found." };
|
|
59
|
+
}
|
|
60
|
+
let record;
|
|
61
|
+
if (options.pluginId) {
|
|
62
|
+
const idKey = `plugin:${options.pluginId}:${options.relPath || path.basename(scriptPath)}`;
|
|
63
|
+
record = this.grants[idKey] || this.grants[resolved];
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
record = this.grants[resolved];
|
|
67
|
+
}
|
|
68
|
+
if (!record) {
|
|
69
|
+
return {
|
|
70
|
+
authorized: false,
|
|
71
|
+
reason: "Script execution is disabled by default. Requires explicit user grant.",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const currentHash = sha256(fs.readFileSync(resolved));
|
|
75
|
+
if (currentHash !== record.hash) {
|
|
76
|
+
// Invalidate immediately upon tampering
|
|
77
|
+
delete this.grants[resolved];
|
|
78
|
+
if (options.pluginId) {
|
|
79
|
+
delete this.grants[`plugin:${options.pluginId}:${options.relPath || path.basename(scriptPath)}`];
|
|
80
|
+
}
|
|
81
|
+
this._save();
|
|
82
|
+
return {
|
|
83
|
+
authorized: false,
|
|
84
|
+
reason: "Script content has been modified since grant was issued. Grant invalidated.",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return { authorized: true, reason: "Authorized" };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=grants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grants.js","sourceRoot":"","sources":["../../../src/plugins/core/grants.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS Plugin Supply Chain Core
|
|
3
|
+
* ADR-002 Canonical Core Entrypoint
|
|
4
|
+
*/
|
|
5
|
+
export * from "./archive.js";
|
|
6
|
+
export * from "./digest.js";
|
|
7
|
+
export * from "./grants.js";
|
|
8
|
+
export * from "./pinning.js";
|
|
9
|
+
export * from "./types.js";
|
|
10
|
+
export * from "./updater.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS Plugin Supply Chain Core
|
|
3
|
+
* ADR-002 Canonical Core Entrypoint
|
|
4
|
+
*/
|
|
5
|
+
export * from "./archive.js";
|
|
6
|
+
export * from "./digest.js";
|
|
7
|
+
export * from "./grants.js";
|
|
8
|
+
export * from "./pinning.js";
|
|
9
|
+
export * from "./types.js";
|
|
10
|
+
export * from "./updater.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/plugins/core/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS Source Pinning & Integrity Enforcement
|
|
3
|
+
* Section 20.1 & Threat Model Hardening
|
|
4
|
+
*/
|
|
5
|
+
import { type PinningValidationOptions, type PinningValidationResult, type PluginSourceSpec } from "./types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Validates plugin source pinning compliance.
|
|
8
|
+
* Requires 40-char SHA for GitHub and exact semver + sha512 integrity for npm.
|
|
9
|
+
* Fails closed on any unknown source types.
|
|
10
|
+
*/
|
|
11
|
+
export declare function validatePluginPinning(sourceSpec?: PluginSourceSpec, options?: PinningValidationOptions): PinningValidationResult;
|
|
12
|
+
/**
|
|
13
|
+
* Verifies that a downloaded npm tarball buffer matches the package's dist.integrity hash.
|
|
14
|
+
*/
|
|
15
|
+
export declare function verifyNpmTarballIntegrity(tarballBuffer: Buffer, expectedIntegrity: string): boolean;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS Source Pinning & Integrity Enforcement
|
|
3
|
+
* Section 20.1 & Threat Model Hardening
|
|
4
|
+
*/
|
|
5
|
+
import * as crypto from "node:crypto";
|
|
6
|
+
import { THREAT_CODES, } from "./types.js";
|
|
7
|
+
const EXACT_COMMIT_SHA = /^[0-9a-f]{40}$/i;
|
|
8
|
+
const EXACT_SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
9
|
+
/**
|
|
10
|
+
* Validates plugin source pinning compliance.
|
|
11
|
+
* Requires 40-char SHA for GitHub and exact semver + sha512 integrity for npm.
|
|
12
|
+
* Fails closed on any unknown source types.
|
|
13
|
+
*/
|
|
14
|
+
export function validatePluginPinning(sourceSpec = { type: "" }, options = {}) {
|
|
15
|
+
if (!sourceSpec || typeof sourceSpec !== "object" || !sourceSpec.type) {
|
|
16
|
+
return {
|
|
17
|
+
valid: false,
|
|
18
|
+
code: THREAT_CODES.UNKNOWN_SOURCE_TYPE,
|
|
19
|
+
error: "Missing or invalid plugin source specification",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const { type } = sourceSpec;
|
|
23
|
+
const allowFloating = options.allowFloating || false;
|
|
24
|
+
if (type === "github") {
|
|
25
|
+
const commit = sourceSpec.commit;
|
|
26
|
+
if (!commit || !EXACT_COMMIT_SHA.test(commit)) {
|
|
27
|
+
if (!allowFloating) {
|
|
28
|
+
return {
|
|
29
|
+
valid: false,
|
|
30
|
+
code: THREAT_CODES.FLOATING_SOURCE_BLOCKED,
|
|
31
|
+
error: "GitHub plugin source must be pinned to an exact 40-character commit SHA. Floating branch or tag is prohibited without --allow-floating.",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (type === "npm") {
|
|
37
|
+
const version = sourceSpec.version;
|
|
38
|
+
const integrity = sourceSpec.integrity;
|
|
39
|
+
if (!version ||
|
|
40
|
+
version === "latest" ||
|
|
41
|
+
version.startsWith("^") ||
|
|
42
|
+
version.startsWith("~") ||
|
|
43
|
+
version.includes(">") ||
|
|
44
|
+
version.includes("<") ||
|
|
45
|
+
version.includes("*") ||
|
|
46
|
+
!EXACT_SEMVER.test(version)) {
|
|
47
|
+
return {
|
|
48
|
+
valid: false,
|
|
49
|
+
code: THREAT_CODES.IMPLICIT_LATEST_BLOCKED,
|
|
50
|
+
error: 'npm plugin source must be pinned to an exact version (e.g. "1.2.3"). Ranges and "latest" are prohibited.',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (!integrity || !integrity.startsWith("sha512-")) {
|
|
54
|
+
return {
|
|
55
|
+
valid: false,
|
|
56
|
+
code: THREAT_CODES.MISSING_INTEGRITY,
|
|
57
|
+
error: "npm plugin source requires dist.integrity verification (sha512-...).",
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
return {
|
|
63
|
+
valid: false,
|
|
64
|
+
code: THREAT_CODES.UNKNOWN_SOURCE_TYPE,
|
|
65
|
+
error: `Unknown or unsupported plugin source type: "${type}". Only "github" and "npm" are permitted.`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return { valid: true };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Verifies that a downloaded npm tarball buffer matches the package's dist.integrity hash.
|
|
72
|
+
*/
|
|
73
|
+
export function verifyNpmTarballIntegrity(tarballBuffer, expectedIntegrity) {
|
|
74
|
+
if (!expectedIntegrity.startsWith("sha512-")) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const expectedHash = expectedIntegrity.slice("sha512-".length);
|
|
78
|
+
const computedHash = crypto.createHash("sha512").update(tarballBuffer).digest("base64");
|
|
79
|
+
const bufA = Buffer.from(computedHash, "utf8");
|
|
80
|
+
const bufB = Buffer.from(expectedHash, "utf8");
|
|
81
|
+
if (bufA.length !== bufB.length) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return crypto.timingSafeEqual(bufA, bufB);
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=pinning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pinning.js","sourceRoot":"","sources":["../../../src/plugins/core/pinning.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS Plugin Supply Chain Types & Threat Model Definitions
|
|
3
|
+
* ADR-002 Canonical Core
|
|
4
|
+
*/
|
|
5
|
+
export type PluginSourceType = "github" | "npm";
|
|
6
|
+
export interface GitHubSourceSpec {
|
|
7
|
+
type: "github";
|
|
8
|
+
owner: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
commit?: string;
|
|
11
|
+
gitRef?: string;
|
|
12
|
+
subPath?: string;
|
|
13
|
+
raw?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface NpmSourceSpec {
|
|
16
|
+
type: "npm";
|
|
17
|
+
package: string;
|
|
18
|
+
version?: string;
|
|
19
|
+
integrity?: string;
|
|
20
|
+
raw?: string;
|
|
21
|
+
}
|
|
22
|
+
export type PluginSourceSpec = GitHubSourceSpec | NpmSourceSpec | {
|
|
23
|
+
type: string;
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
};
|
|
26
|
+
export interface PinningValidationOptions {
|
|
27
|
+
allowFloating?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface PinningValidationResult {
|
|
30
|
+
valid: boolean;
|
|
31
|
+
code?: string;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
export type ArchiveEntryType = "file" | "directory" | "symlink" | "hardlink" | "socket" | "fifo" | "characterDevice" | "blockDevice";
|
|
35
|
+
export interface ArchiveEntryLimits {
|
|
36
|
+
currentCount?: number;
|
|
37
|
+
maxFiles?: number;
|
|
38
|
+
currentTotalBytes?: number;
|
|
39
|
+
maxTotalBytes?: number;
|
|
40
|
+
compressedSize?: number;
|
|
41
|
+
uncompressedSize?: number;
|
|
42
|
+
maxRatio?: number;
|
|
43
|
+
seenPaths?: Set<string>;
|
|
44
|
+
linkTarget?: string;
|
|
45
|
+
entryType?: ArchiveEntryType;
|
|
46
|
+
}
|
|
47
|
+
export interface ArchiveValidationResult {
|
|
48
|
+
valid: boolean;
|
|
49
|
+
code?: string;
|
|
50
|
+
error?: string;
|
|
51
|
+
}
|
|
52
|
+
export type TreeEntryType = "file" | "directory" | "symlink";
|
|
53
|
+
export interface TreeFileEntry {
|
|
54
|
+
relativePath: string;
|
|
55
|
+
size: number;
|
|
56
|
+
mode: number;
|
|
57
|
+
sha256: string;
|
|
58
|
+
type: TreeEntryType;
|
|
59
|
+
}
|
|
60
|
+
export interface TreeDigestResult {
|
|
61
|
+
treeDigest: string;
|
|
62
|
+
files: TreeFileEntry[];
|
|
63
|
+
}
|
|
64
|
+
export interface TreeDigestOptions {
|
|
65
|
+
includeDirectories?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export interface GrantRecord {
|
|
68
|
+
hash: string;
|
|
69
|
+
grantedAt: number;
|
|
70
|
+
pluginId?: string;
|
|
71
|
+
relPath?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface AuthorizationResult {
|
|
74
|
+
authorized: boolean;
|
|
75
|
+
reason: string;
|
|
76
|
+
}
|
|
77
|
+
export interface GrantOptions {
|
|
78
|
+
pluginId?: string;
|
|
79
|
+
relPath?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface AtomicUpdateOptions {
|
|
82
|
+
expectedOldTreeDigest?: string;
|
|
83
|
+
force?: boolean;
|
|
84
|
+
}
|
|
85
|
+
export interface AtomicUpdateResult {
|
|
86
|
+
success: boolean;
|
|
87
|
+
treeDigest: string;
|
|
88
|
+
}
|
|
89
|
+
export declare const THREAT_CODES: {
|
|
90
|
+
readonly UNKNOWN_SOURCE_TYPE: "CTX_PLUGIN_UNKNOWN_SOURCE_TYPE";
|
|
91
|
+
readonly FLOATING_SOURCE_BLOCKED: "CTX_PLUGIN_FLOATING_SOURCE_BLOCKED";
|
|
92
|
+
readonly IMPLICIT_LATEST_BLOCKED: "CTX_PLUGIN_IMPLICIT_LATEST_BLOCKED";
|
|
93
|
+
readonly MISSING_INTEGRITY: "CTX_PLUGIN_MISSING_INTEGRITY";
|
|
94
|
+
readonly INVALID_PATH: "CTX_ARCHIVE_INVALID_PATH";
|
|
95
|
+
readonly ABSOLUTE_PATH: "CTX_ARCHIVE_ABSOLUTE_PATH";
|
|
96
|
+
readonly PATH_TRAVERSAL: "CTX_ARCHIVE_PATH_TRAVERSAL";
|
|
97
|
+
readonly RESERVED_NAME: "CTX_ARCHIVE_RESERVED_NAME";
|
|
98
|
+
readonly MAX_FILES_EXCEEDED: "CTX_ARCHIVE_MAX_FILES_EXCEEDED";
|
|
99
|
+
readonly MAX_SIZE_EXCEEDED: "CTX_ARCHIVE_MAX_SIZE_EXCEEDED";
|
|
100
|
+
readonly RATIO_EXCEEDED: "CTX_ARCHIVE_RATIO_EXCEEDED";
|
|
101
|
+
readonly CASE_COLLISION: "CTX_ARCHIVE_CASE_COLLISION";
|
|
102
|
+
readonly SYMLINK_ESCAPE: "CTX_ARCHIVE_SYMLINK_ESCAPE";
|
|
103
|
+
readonly SPECIAL_FILE: "CTX_ARCHIVE_SPECIAL_FILE";
|
|
104
|
+
readonly MODIFIED_LOCALLY: "CTX_PLUGIN_MODIFIED_LOCALLY";
|
|
105
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS Plugin Supply Chain Types & Threat Model Definitions
|
|
3
|
+
* ADR-002 Canonical Core
|
|
4
|
+
*/
|
|
5
|
+
export const THREAT_CODES = {
|
|
6
|
+
UNKNOWN_SOURCE_TYPE: "CTX_PLUGIN_UNKNOWN_SOURCE_TYPE",
|
|
7
|
+
FLOATING_SOURCE_BLOCKED: "CTX_PLUGIN_FLOATING_SOURCE_BLOCKED",
|
|
8
|
+
IMPLICIT_LATEST_BLOCKED: "CTX_PLUGIN_IMPLICIT_LATEST_BLOCKED",
|
|
9
|
+
MISSING_INTEGRITY: "CTX_PLUGIN_MISSING_INTEGRITY",
|
|
10
|
+
INVALID_PATH: "CTX_ARCHIVE_INVALID_PATH",
|
|
11
|
+
ABSOLUTE_PATH: "CTX_ARCHIVE_ABSOLUTE_PATH",
|
|
12
|
+
PATH_TRAVERSAL: "CTX_ARCHIVE_PATH_TRAVERSAL",
|
|
13
|
+
RESERVED_NAME: "CTX_ARCHIVE_RESERVED_NAME",
|
|
14
|
+
MAX_FILES_EXCEEDED: "CTX_ARCHIVE_MAX_FILES_EXCEEDED",
|
|
15
|
+
MAX_SIZE_EXCEEDED: "CTX_ARCHIVE_MAX_SIZE_EXCEEDED",
|
|
16
|
+
RATIO_EXCEEDED: "CTX_ARCHIVE_RATIO_EXCEEDED",
|
|
17
|
+
CASE_COLLISION: "CTX_ARCHIVE_CASE_COLLISION",
|
|
18
|
+
SYMLINK_ESCAPE: "CTX_ARCHIVE_SYMLINK_ESCAPE",
|
|
19
|
+
SPECIAL_FILE: "CTX_ARCHIVE_SPECIAL_FILE",
|
|
20
|
+
MODIFIED_LOCALLY: "CTX_PLUGIN_MODIFIED_LOCALLY",
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/plugins/core/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA6GH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,mBAAmB,EAAE,gCAAgC;IACrD,uBAAuB,EAAE,oCAAoC;IAC7D,uBAAuB,EAAE,oCAAoC;IAC7D,iBAAiB,EAAE,8BAA8B;IACjD,YAAY,EAAE,0BAA0B;IACxC,aAAa,EAAE,2BAA2B;IAC1C,cAAc,EAAE,4BAA4B;IAC5C,aAAa,EAAE,2BAA2B;IAC1C,kBAAkB,EAAE,gCAAgC;IACpD,iBAAiB,EAAE,+BAA+B;IAClD,cAAc,EAAE,4BAA4B;IAC5C,cAAc,EAAE,4BAA4B;IAC5C,cAAc,EAAE,4BAA4B;IAC5C,YAAY,EAAE,0BAA0B;IACxC,gBAAgB,EAAE,6BAA6B;CACtC,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS Atomic Plugin Updater & Local Modification Protection
|
|
3
|
+
* Section 20.7 & Threat Model Hardening
|
|
4
|
+
*/
|
|
5
|
+
import { type AtomicUpdateOptions, type AtomicUpdateResult } from "./types.js";
|
|
6
|
+
export declare class AtomicPluginUpdater {
|
|
7
|
+
/**
|
|
8
|
+
* Checks if local plugin has been modified since it was installed/recorded in lockfile.
|
|
9
|
+
*/
|
|
10
|
+
static isModifiedLocally(pluginDir: string, expectedTreeDigest: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Atomically installs or updates a plugin directory with rollback.
|
|
13
|
+
* Rejects updates when local uncommitted user modifications exist unless forced.
|
|
14
|
+
*/
|
|
15
|
+
static applyUpdate(targetDir: string, stagedDir: string, options?: AtomicUpdateOptions): AtomicUpdateResult;
|
|
16
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS Atomic Plugin Updater & Local Modification Protection
|
|
3
|
+
* Section 20.7 & Threat Model Hardening
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { calculateTreeDigest } from "./digest.js";
|
|
8
|
+
import { THREAT_CODES } from "./types.js";
|
|
9
|
+
export class AtomicPluginUpdater {
|
|
10
|
+
/**
|
|
11
|
+
* Checks if local plugin has been modified since it was installed/recorded in lockfile.
|
|
12
|
+
*/
|
|
13
|
+
static isModifiedLocally(pluginDir, expectedTreeDigest) {
|
|
14
|
+
if (!fs.existsSync(pluginDir))
|
|
15
|
+
return false;
|
|
16
|
+
const current = calculateTreeDigest(pluginDir);
|
|
17
|
+
return current.treeDigest !== expectedTreeDigest;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Atomically installs or updates a plugin directory with rollback.
|
|
21
|
+
* Rejects updates when local uncommitted user modifications exist unless forced.
|
|
22
|
+
*/
|
|
23
|
+
static applyUpdate(targetDir, stagedDir, options = {}) {
|
|
24
|
+
const { expectedOldTreeDigest, force = false } = options;
|
|
25
|
+
const resolvedTarget = path.resolve(targetDir);
|
|
26
|
+
const resolvedStaged = path.resolve(stagedDir);
|
|
27
|
+
if (fs.existsSync(resolvedTarget) && expectedOldTreeDigest && !force) {
|
|
28
|
+
if (AtomicPluginUpdater.isModifiedLocally(resolvedTarget, expectedOldTreeDigest)) {
|
|
29
|
+
const err = new Error(`Plugin in "${resolvedTarget}" has local modifications. Update aborted to prevent overwriting user changes. Pass --force to overwrite.`);
|
|
30
|
+
err.code = THREAT_CODES.MODIFIED_LOCALLY;
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const backupDir = `${resolvedTarget}.bak-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
35
|
+
let hasBackup = false;
|
|
36
|
+
try {
|
|
37
|
+
if (fs.existsSync(resolvedTarget)) {
|
|
38
|
+
fs.renameSync(resolvedTarget, backupDir);
|
|
39
|
+
hasBackup = true;
|
|
40
|
+
}
|
|
41
|
+
fs.renameSync(resolvedStaged, resolvedTarget);
|
|
42
|
+
// Verify new directory digest
|
|
43
|
+
const digest = calculateTreeDigest(resolvedTarget);
|
|
44
|
+
// Clean up backup upon success
|
|
45
|
+
if (hasBackup) {
|
|
46
|
+
fs.rmSync(backupDir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
return { success: true, treeDigest: digest.treeDigest };
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
// Rollback on failure
|
|
52
|
+
if (hasBackup && !fs.existsSync(resolvedTarget)) {
|
|
53
|
+
try {
|
|
54
|
+
fs.renameSync(backupDir, resolvedTarget);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Ignore rollback rename failure to preserve original error
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=updater.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updater.js","sourceRoot":"","sources":["../../../src/plugins/core/updater.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAqD,YAAY,EAAE,MAAM,YAAY,CAAC;AAE7F,MAAM,OAAO,mBAAmB;IAC/B;;OAEG;IACH,MAAM,CAAC,iBAAiB,CAAC,SAAiB,EAAE,kBAA0B;QACrE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,KAAK,CAAC;QAC5C,MAAM,OAAO,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAC/C,OAAO,OAAO,CAAC,UAAU,KAAK,kBAAkB,CAAC;IAClD,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,WAAW,CAAC,SAAiB,EAAE,SAAiB,EAAE,UAA+B,EAAE;QACzF,MAAM,EAAE,qBAAqB,EAAE,KAAK,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;QACzD,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC/C,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAE/C,IAAI,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,IAAI,qBAAqB,IAAI,CAAC,KAAK,EAAE,CAAC;YACtE,IAAI,mBAAmB,CAAC,iBAAiB,CAAC,cAAc,EAAE,qBAAqB,CAAC,EAAE,CAAC;gBAClF,MAAM,GAAG,GAAG,IAAI,KAAK,CACpB,cAAc,cAAc,2GAA2G,CAC3G,CAAC;gBAC9B,GAAG,CAAC,IAAI,GAAG,YAAY,CAAC,gBAAgB,CAAC;gBACzC,MAAM,GAAG,CAAC;YACX,CAAC;QACF,CAAC;QAED,MAAM,SAAS,GAAG,GAAG,cAAc,QAAQ,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAClG,IAAI,SAAS,GAAG,KAAK,CAAC;QAEtB,IAAI,CAAC;YACJ,IAAI,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;gBACnC,EAAE,CAAC,UAAU,CAAC,cAAc,EAAE,SAAS,CAAC,CAAC;gBACzC,SAAS,GAAG,IAAI,CAAC;YAClB,CAAC;YAED,EAAE,CAAC,UAAU,CAAC,cAAc,EAAE,cAAc,CAAC,CAAC;YAE9C,8BAA8B;YAC9B,MAAM,MAAM,GAAG,mBAAmB,CAAC,cAAc,CAAC,CAAC;YAEnD,+BAA+B;YAC/B,IAAI,SAAS,EAAE,CAAC;gBACf,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,CAAC;YAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;QACzD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,sBAAsB;YACtB,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;gBACjD,IAAI,CAAC;oBACJ,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;gBAC1C,CAAC;gBAAC,MAAM,CAAC;oBACR,4DAA4D;gBAC7D,CAAC;YACF,CAAC;YACD,MAAM,GAAG,CAAC;QACX,CAAC;IACF,CAAC;CACD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS MCP Plugin Loader
|
|
3
|
+
* Typed orchestration layer connecting MCP to the canonical supply-chain core.
|
|
4
|
+
*/
|
|
5
|
+
import { ScriptGrantManager } from "./core/index.js";
|
|
6
|
+
import type { ArchiveEntryType, ArchiveValidationResult, AtomicUpdateOptions, AtomicUpdateResult, AuthorizationResult, GrantOptions, PinningValidationOptions, PinningValidationResult, PluginSourceSpec, TreeDigestResult } from "./core/types.js";
|
|
7
|
+
export interface ArchiveManifestEntry {
|
|
8
|
+
path: string;
|
|
9
|
+
size?: number;
|
|
10
|
+
compressedSize?: number;
|
|
11
|
+
type?: ArchiveEntryType;
|
|
12
|
+
linkTarget?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface InstalledPluginRecord {
|
|
15
|
+
name: string;
|
|
16
|
+
ref: string;
|
|
17
|
+
type: string;
|
|
18
|
+
sha256?: string;
|
|
19
|
+
treeDigest?: string;
|
|
20
|
+
installedAt?: string;
|
|
21
|
+
verified?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare class PluginLoader {
|
|
24
|
+
/**
|
|
25
|
+
* Validates a plugin source specification according to pinning rules.
|
|
26
|
+
*/
|
|
27
|
+
static validateSource(sourceSpec: PluginSourceSpec, options?: PinningValidationOptions): PinningValidationResult;
|
|
28
|
+
/**
|
|
29
|
+
* Verifies tree integrity of an installed plugin directory against expected digest.
|
|
30
|
+
*/
|
|
31
|
+
static verifyPluginDigest(pluginDir: string, expectedTreeDigest: string): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Calculates the deterministic tree digest of a plugin directory.
|
|
34
|
+
*/
|
|
35
|
+
static computeDigest(pluginDir: string): TreeDigestResult;
|
|
36
|
+
/**
|
|
37
|
+
* Pre-scans a full archive manifest before extraction.
|
|
38
|
+
* Enforces traversal, symlink escapes, ratio bombs, and case collisions.
|
|
39
|
+
*/
|
|
40
|
+
static validateArchive(entries: ArchiveManifestEntry[], options?: {
|
|
41
|
+
maxFiles?: number;
|
|
42
|
+
maxTotalBytes?: number;
|
|
43
|
+
maxRatio?: number;
|
|
44
|
+
}): ArchiveValidationResult;
|
|
45
|
+
/**
|
|
46
|
+
* Instantiates a ScriptGrantManager for a given grants file.
|
|
47
|
+
*/
|
|
48
|
+
static getGrantManager(grantsFilePath: string): ScriptGrantManager;
|
|
49
|
+
/**
|
|
50
|
+
* Verifies whether a plugin script is authorized.
|
|
51
|
+
*/
|
|
52
|
+
static checkScriptAuthorization(grantsFilePath: string, scriptPath: string, options?: GrantOptions): AuthorizationResult;
|
|
53
|
+
/**
|
|
54
|
+
* Atomically updates a plugin directory with backup and rollback.
|
|
55
|
+
*/
|
|
56
|
+
static applyAtomicUpdate(targetDir: string, stagedDir: string, options?: AtomicUpdateOptions): AtomicUpdateResult;
|
|
57
|
+
/**
|
|
58
|
+
* Loads installed plugins recorded in plugins.json and verifies their tree digests.
|
|
59
|
+
*/
|
|
60
|
+
static loadInstalledPlugins(agentsDir: string): InstalledPluginRecord[];
|
|
61
|
+
/**
|
|
62
|
+
* Verifies npm package tarball integrity.
|
|
63
|
+
*/
|
|
64
|
+
static verifyNpmIntegrity(tarballBuffer: Buffer, expectedIntegrity: string): boolean;
|
|
65
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContextOS MCP Plugin Loader
|
|
3
|
+
* Typed orchestration layer connecting MCP to the canonical supply-chain core.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "node:fs";
|
|
6
|
+
import * as path from "node:path";
|
|
7
|
+
import { AtomicPluginUpdater, calculateTreeDigest, ScriptGrantManager, validateArchiveEntry, validatePluginPinning, verifyNpmTarballIntegrity, } from "./core/index.js";
|
|
8
|
+
export class PluginLoader {
|
|
9
|
+
/**
|
|
10
|
+
* Validates a plugin source specification according to pinning rules.
|
|
11
|
+
*/
|
|
12
|
+
static validateSource(sourceSpec, options = {}) {
|
|
13
|
+
return validatePluginPinning(sourceSpec, options);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Verifies tree integrity of an installed plugin directory against expected digest.
|
|
17
|
+
*/
|
|
18
|
+
static verifyPluginDigest(pluginDir, expectedTreeDigest) {
|
|
19
|
+
if (!fs.existsSync(pluginDir))
|
|
20
|
+
return false;
|
|
21
|
+
const digest = calculateTreeDigest(pluginDir);
|
|
22
|
+
return digest.treeDigest === expectedTreeDigest;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Calculates the deterministic tree digest of a plugin directory.
|
|
26
|
+
*/
|
|
27
|
+
static computeDigest(pluginDir) {
|
|
28
|
+
return calculateTreeDigest(pluginDir);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Pre-scans a full archive manifest before extraction.
|
|
32
|
+
* Enforces traversal, symlink escapes, ratio bombs, and case collisions.
|
|
33
|
+
*/
|
|
34
|
+
static validateArchive(entries, options = {}) {
|
|
35
|
+
const seenPaths = new Set();
|
|
36
|
+
let currentTotalBytes = 0;
|
|
37
|
+
for (let i = 0; i < entries.length; i++) {
|
|
38
|
+
const entry = entries[i];
|
|
39
|
+
const uncompressedSize = entry.size ?? 0;
|
|
40
|
+
currentTotalBytes += uncompressedSize;
|
|
41
|
+
const limits = {
|
|
42
|
+
currentCount: i,
|
|
43
|
+
maxFiles: options.maxFiles ?? 1000,
|
|
44
|
+
currentTotalBytes,
|
|
45
|
+
maxTotalBytes: options.maxTotalBytes ?? 50 * 1024 * 1024,
|
|
46
|
+
uncompressedSize,
|
|
47
|
+
compressedSize: entry.compressedSize,
|
|
48
|
+
maxRatio: options.maxRatio ?? 100,
|
|
49
|
+
seenPaths,
|
|
50
|
+
linkTarget: entry.linkTarget,
|
|
51
|
+
entryType: entry.type,
|
|
52
|
+
};
|
|
53
|
+
const result = validateArchiveEntry(entry.path, limits);
|
|
54
|
+
if (!result.valid) {
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { valid: true };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Instantiates a ScriptGrantManager for a given grants file.
|
|
62
|
+
*/
|
|
63
|
+
static getGrantManager(grantsFilePath) {
|
|
64
|
+
return new ScriptGrantManager(grantsFilePath);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Verifies whether a plugin script is authorized.
|
|
68
|
+
*/
|
|
69
|
+
static checkScriptAuthorization(grantsFilePath, scriptPath, options = {}) {
|
|
70
|
+
const manager = new ScriptGrantManager(grantsFilePath);
|
|
71
|
+
return manager.checkAuthorization(scriptPath, options);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Atomically updates a plugin directory with backup and rollback.
|
|
75
|
+
*/
|
|
76
|
+
static applyAtomicUpdate(targetDir, stagedDir, options = {}) {
|
|
77
|
+
return AtomicPluginUpdater.applyUpdate(targetDir, stagedDir, options);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Loads installed plugins recorded in plugins.json and verifies their tree digests.
|
|
81
|
+
*/
|
|
82
|
+
static loadInstalledPlugins(agentsDir) {
|
|
83
|
+
const pluginsJsonPath = path.join(agentsDir, "plugins.json");
|
|
84
|
+
if (!fs.existsSync(pluginsJsonPath)) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const data = JSON.parse(fs.readFileSync(pluginsJsonPath, "utf8"));
|
|
89
|
+
const plugins = Array.isArray(data.plugins) ? data.plugins : [];
|
|
90
|
+
return plugins.map((plugin) => {
|
|
91
|
+
const pluginDir = path.join(agentsDir, "plugins", plugin.name);
|
|
92
|
+
if (!plugin.treeDigest || !fs.existsSync(pluginDir)) {
|
|
93
|
+
return { ...plugin, verified: false };
|
|
94
|
+
}
|
|
95
|
+
const isClean = PluginLoader.verifyPluginDigest(pluginDir, plugin.treeDigest);
|
|
96
|
+
return { ...plugin, verified: isClean };
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Verifies npm package tarball integrity.
|
|
105
|
+
*/
|
|
106
|
+
static verifyNpmIntegrity(tarballBuffer, expectedIntegrity) {
|
|
107
|
+
return verifyNpmTarballIntegrity(tarballBuffer, expectedIntegrity);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/plugins/loader.ts"],"names":[],"mappings":"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"}
|