@crewx/sdk 0.8.0-rc.78 → 0.8.0-rc.80
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/plugin/plugin-provider.js +1 -1
- package/dist/provider/bridge.js +1 -0
- package/package.json +1 -1
- package/dist/esm/agent/resolver.js +0 -41
- package/dist/esm/boxing/box-storage.interface.js +0 -5
- package/dist/esm/boxing/box.service.js +0 -69
- package/dist/esm/boxing/box.types.js +0 -5
- package/dist/esm/boxing/context-builder.js +0 -76
- package/dist/esm/client/CrewxClient.js +0 -82
- package/dist/esm/client/index.js +0 -2
- package/dist/esm/config/loader.browser.js +0 -54
- package/dist/esm/config/loader.js +0 -77
- package/dist/esm/events/TypedEventEmitter.js +0 -61
- package/dist/esm/events/types.js +0 -8
- package/dist/esm/facade/Crewx.browser.js +0 -310
- package/dist/esm/facade/Crewx.js +0 -941
- package/dist/esm/hooks/define.js +0 -10
- package/dist/esm/hooks/dispatch.js +0 -76
- package/dist/esm/hooks/index.js +0 -6
- package/dist/esm/hooks/observer.js +0 -56
- package/dist/esm/hooks/plugin.js +0 -12
- package/dist/esm/hooks/types.js +0 -9
- package/dist/esm/index.browser.js +0 -15
- package/dist/esm/index.js +0 -60
- package/dist/esm/layout/loader.js +0 -268
- package/dist/esm/layout/props-validator.js +0 -297
- package/dist/esm/layout/renderer.js +0 -180
- package/dist/esm/layout/types.js +0 -31
- package/dist/esm/parallel/agent-runtime.js +0 -21
- package/dist/esm/parallel/helpers.js +0 -214
- package/dist/esm/parallel/index.js +0 -5
- package/dist/esm/parallel/parallel-runner.js +0 -221
- package/dist/esm/parallel/types.js +0 -5
- package/dist/esm/parsers/agent-call.util.js +0 -15
- package/dist/esm/parsers/claude.parser.js +0 -64
- package/dist/esm/parsers/codex.parser.js +0 -97
- package/dist/esm/parsers/copilot.parser.js +0 -63
- package/dist/esm/parsers/gemini.parser.js +0 -43
- package/dist/esm/parsers/opencode.parser.js +0 -73
- package/dist/esm/parsers/router.js +0 -53
- package/dist/esm/platform/BrowserFsAdapter.js +0 -80
- package/dist/esm/platform/IFsAdapter.js +0 -2
- package/dist/esm/platform/NodeFsAdapter.js +0 -34
- package/dist/esm/plugin/plugin-provider.js +0 -202
- package/dist/esm/plugin/types.js +0 -8
- package/dist/esm/plugin.js +0 -25
- package/dist/esm/provider/bridge.browser.js +0 -43
- package/dist/esm/provider/bridge.js +0 -373
- package/dist/esm/provider/parse-usage.js +0 -80
- package/dist/esm/provider/register-api.js +0 -21
- package/dist/esm/provider/vercel-runtime.js +0 -310
- package/dist/esm/remote/index.js +0 -10
- package/dist/esm/remote/remote-agent-manager.js +0 -194
- package/dist/esm/remote/remote-provider.js +0 -98
- package/dist/esm/remote/remote-transport.js +0 -79
- package/dist/esm/remote/types.js +0 -8
- package/dist/esm/server/auth.js +0 -31
- package/dist/esm/server/handler.js +0 -72
- package/dist/esm/server/index.js +0 -5
- package/dist/esm/server/tool-adapter.js +0 -92
- package/dist/esm/template/engine.js +0 -100
- package/dist/esm/template/helpers/exec.browser.js +0 -31
- package/dist/esm/template/helpers/exec.js +0 -220
- package/dist/esm/template/helpers/fenced_code.js +0 -17
- package/dist/esm/template/helpers/include.js +0 -20
- package/dist/esm/template/helpers/p1p2.js +0 -83
- package/dist/esm/template/loader/DocumentLoader.js +0 -124
- package/dist/esm/template/types.js +0 -5
- package/dist/esm/tools/delegate.js +0 -57
- package/dist/esm/tools/index.js +0 -5
- package/dist/esm/tools/node/builtin.js +0 -541
- package/dist/esm/tools/node/index.js +0 -54
- package/dist/esm/types/index.js +0 -27
- package/dist/esm/types/task-log.types.js +0 -5
- package/dist/esm/utils/env-defaults.js +0 -23
- package/dist/esm/utils/glob-match.js +0 -38
- package/dist/esm/utils/id.js +0 -46
- package/dist/esm/utils/workspace.js +0 -21
- package/dist/parsers/api.parser.d.ts +0 -10
- package/dist/parsers/api.parser.js +0 -26
- package/dist/provider/mastra-runtime.d.ts +0 -45
- package/dist/provider/mastra-runtime.js +0 -208
package/dist/esm/remote/types.js
DELETED
package/dist/esm/server/auth.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Server authentication & Origin validation.
|
|
3
|
-
*
|
|
4
|
-
* - Origin header 검증 (DNS rebinding 방어, MCP 스펙 필수)
|
|
5
|
-
* - 커스텀 auth 콜백 래퍼
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Validate the Origin header is present (MCP spec requirement).
|
|
9
|
-
* Returns a 403 Response if validation fails, or null if ok.
|
|
10
|
-
*/
|
|
11
|
-
export function validateOrigin(req) {
|
|
12
|
-
const origin = req.headers.get('Origin');
|
|
13
|
-
if (!origin) {
|
|
14
|
-
return new Response(JSON.stringify({ error: 'Forbidden: Origin header required' }), { status: 403, headers: { 'Content-Type': 'application/json' } });
|
|
15
|
-
}
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Run the custom auth callback if provided.
|
|
20
|
-
* Returns a 401 Response if auth fails, or null if ok.
|
|
21
|
-
*/
|
|
22
|
-
export async function runAuthCallback(req, auth) {
|
|
23
|
-
if (!auth)
|
|
24
|
-
return null;
|
|
25
|
-
const allowed = await auth(req);
|
|
26
|
-
if (!allowed) {
|
|
27
|
-
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' } });
|
|
28
|
-
}
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=auth.js.map
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* crewx.handler() — Framework-independent MCP HTTP handler.
|
|
3
|
-
*
|
|
4
|
-
* Returns a Web Standard `(req: Request) => Promise<Response>` handler
|
|
5
|
-
* that can be mounted on Express, Next.js, Hono, Bun, etc.
|
|
6
|
-
*/
|
|
7
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
-
import { WebStandardStreamableHTTPServerTransport, } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
9
|
-
import { registerCrewxTools } from './tool-adapter';
|
|
10
|
-
import { validateOrigin, runAuthCallback } from './auth';
|
|
11
|
-
/**
|
|
12
|
-
* Create a fresh McpServer with tools registered.
|
|
13
|
-
*/
|
|
14
|
-
function createMcpServer(crewx, allowedAgents) {
|
|
15
|
-
const mcpServer = new McpServer({
|
|
16
|
-
name: 'crewx-mcp-server',
|
|
17
|
-
version: '1.0.0',
|
|
18
|
-
}, {
|
|
19
|
-
capabilities: {
|
|
20
|
-
tools: {},
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
registerCrewxTools(mcpServer, crewx, allowedAgents);
|
|
24
|
-
return mcpServer;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Create an MCP handler bound to the given Crewx instance.
|
|
28
|
-
*
|
|
29
|
-
* @param crewx - Crewx facade instance
|
|
30
|
-
* @param options - Handler configuration (agents list required)
|
|
31
|
-
* @returns Web Standard request handler
|
|
32
|
-
*/
|
|
33
|
-
export function createHandler(crewx, options) {
|
|
34
|
-
if (!options.agents || options.agents.length === 0) {
|
|
35
|
-
throw new Error('crewx.handler() requires at least one agent in the agents option');
|
|
36
|
-
}
|
|
37
|
-
const allowedAgents = new Set(options.agents);
|
|
38
|
-
// Per-session transport + server map
|
|
39
|
-
const sessions = new Map();
|
|
40
|
-
return async (req) => {
|
|
41
|
-
// ── Security: Origin validation ──────────────────────────────────────
|
|
42
|
-
const originError = validateOrigin(req);
|
|
43
|
-
if (originError)
|
|
44
|
-
return originError;
|
|
45
|
-
// ── Security: Custom auth callback ───────────────────────────────────
|
|
46
|
-
const authError = await runAuthCallback(req, options.auth);
|
|
47
|
-
if (authError)
|
|
48
|
-
return authError;
|
|
49
|
-
// ── Route to transport ───────────────────────────────────────────────
|
|
50
|
-
const sessionId = req.headers.get('mcp-session-id');
|
|
51
|
-
if (sessionId && sessions.has(sessionId)) {
|
|
52
|
-
// Existing session — reuse transport
|
|
53
|
-
const session = sessions.get(sessionId);
|
|
54
|
-
return session.transport.handleRequest(req);
|
|
55
|
-
}
|
|
56
|
-
// New session: create McpServer + transport pair
|
|
57
|
-
const mcpServer = createMcpServer(crewx, allowedAgents);
|
|
58
|
-
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
59
|
-
sessionIdGenerator: () => crypto.randomUUID(),
|
|
60
|
-
onsessioninitialized: (sid) => {
|
|
61
|
-
sessions.set(sid, { transport, server: mcpServer });
|
|
62
|
-
},
|
|
63
|
-
onsessionclosed: (sid) => {
|
|
64
|
-
sessions.delete(sid);
|
|
65
|
-
},
|
|
66
|
-
enableJsonResponse: true,
|
|
67
|
-
});
|
|
68
|
-
await mcpServer.connect(transport);
|
|
69
|
-
return transport.handleRequest(req);
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
//# sourceMappingURL=handler.js.map
|
package/dist/esm/server/index.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP tool adapter — 범용 MCP 도구 3개를 McpServer에 등록한다.
|
|
3
|
-
*
|
|
4
|
-
* 도구:
|
|
5
|
-
* crewx_queryAgent(agentId, query, context?, thread?)
|
|
6
|
-
* crewx_executeAgent(agentId, task, context?, thread?)
|
|
7
|
-
* crewx_listAgents()
|
|
8
|
-
*/
|
|
9
|
-
import { z } from 'zod';
|
|
10
|
-
const querySchema = {
|
|
11
|
-
agentId: z.string().describe('The agent ID to query'),
|
|
12
|
-
query: z.string().describe('The query message to send'),
|
|
13
|
-
context: z.string().optional().describe('Additional context'),
|
|
14
|
-
thread: z.string().optional().describe('Thread ID for conversation continuity'),
|
|
15
|
-
};
|
|
16
|
-
const executeSchema = {
|
|
17
|
-
agentId: z.string().describe('The agent ID to execute'),
|
|
18
|
-
task: z.string().describe('The task description to execute'),
|
|
19
|
-
context: z.string().optional().describe('Additional context'),
|
|
20
|
-
thread: z.string().optional().describe('Thread ID for conversation continuity'),
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Register the 3 universal tools on the given McpServer.
|
|
24
|
-
*
|
|
25
|
-
* @param server - McpServer instance
|
|
26
|
-
* @param crewx - Crewx facade instance
|
|
27
|
-
* @param allowedAgents - Set of agent IDs exposed via handler
|
|
28
|
-
*/
|
|
29
|
-
export function registerCrewxTools(server, crewx, allowedAgents) {
|
|
30
|
-
// ── crewx_listAgents (no schema — zero-arg tool) ────────────────────────
|
|
31
|
-
server.tool('crewx_listAgents', 'List available CrewX agents exposed by this server', async () => {
|
|
32
|
-
const agents = [];
|
|
33
|
-
for (const agentId of allowedAgents) {
|
|
34
|
-
const agent = crewx.agents.get(agentId);
|
|
35
|
-
if (agent) {
|
|
36
|
-
agents.push({
|
|
37
|
-
id: agent.id,
|
|
38
|
-
name: agent.name ?? agent.id,
|
|
39
|
-
description: agent.description ?? '',
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
content: [{ type: 'text', text: JSON.stringify(agents, null, 2) }],
|
|
45
|
-
};
|
|
46
|
-
});
|
|
47
|
-
// ── crewx_queryAgent ────────────────────────────────────────────────────
|
|
48
|
-
server.tool('crewx_queryAgent', 'Query a CrewX agent with a read-only question', querySchema, async ({ agentId, query, context, thread }) => {
|
|
49
|
-
if (!allowedAgents.has(agentId)) {
|
|
50
|
-
return {
|
|
51
|
-
content: [{ type: 'text', text: `Agent '${agentId}' is not exposed by this server` }],
|
|
52
|
-
isError: true,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
const result = await crewx.query(agentId, query, {
|
|
56
|
-
context,
|
|
57
|
-
threadId: thread,
|
|
58
|
-
});
|
|
59
|
-
if (!result.ok) {
|
|
60
|
-
return {
|
|
61
|
-
content: [{ type: 'text', text: result.error?.message ?? 'Query failed' }],
|
|
62
|
-
isError: true,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
return {
|
|
66
|
-
content: [{ type: 'text', text: result.data }],
|
|
67
|
-
};
|
|
68
|
-
});
|
|
69
|
-
// ── crewx_executeAgent ──────────────────────────────────────────────────
|
|
70
|
-
server.tool('crewx_executeAgent', 'Execute a task on a CrewX agent (may modify state)', executeSchema, async ({ agentId, task, context, thread }) => {
|
|
71
|
-
if (!allowedAgents.has(agentId)) {
|
|
72
|
-
return {
|
|
73
|
-
content: [{ type: 'text', text: `Agent '${agentId}' is not exposed by this server` }],
|
|
74
|
-
isError: true,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
const result = await crewx.execute(agentId, task, {
|
|
78
|
-
context,
|
|
79
|
-
threadId: thread,
|
|
80
|
-
});
|
|
81
|
-
if (!result.ok) {
|
|
82
|
-
return {
|
|
83
|
-
content: [{ type: 'text', text: result.error?.message ?? 'Execute failed' }],
|
|
84
|
-
isError: true,
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
content: [{ type: 'text', text: result.data }],
|
|
89
|
-
};
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
//# sourceMappingURL=tool-adapter.js.map
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CrewX template engine — Handlebars-based rendering with P0 helpers.
|
|
3
|
-
*
|
|
4
|
-
* P0 helpers implemented here:
|
|
5
|
-
* - exec : shell command execution (5-stage security)
|
|
6
|
-
* - include : inline string content passthrough
|
|
7
|
-
* - fenced_code : Markdown fenced code block wrapper
|
|
8
|
-
* - eq, ne, and, or, not, json (condition helpers)
|
|
9
|
-
*/
|
|
10
|
-
import * as Handlebars from 'handlebars';
|
|
11
|
-
import { executeCommand } from './helpers/exec';
|
|
12
|
-
import { includeHelper } from './helpers/include';
|
|
13
|
-
import { fencedCodeHelper } from './helpers/fenced_code';
|
|
14
|
-
import { truncateHelper, lengthHelper, escapeHandlebarsHelper, formatFileSizeHelper, formatTimestampHelper, } from './helpers/p1p2';
|
|
15
|
-
export class TemplateEngine {
|
|
16
|
-
hbs;
|
|
17
|
-
execPolicy;
|
|
18
|
-
execEnabled;
|
|
19
|
-
constructor(options = {}) {
|
|
20
|
-
// Create an isolated Handlebars environment to avoid global state conflicts
|
|
21
|
-
this.hbs = Handlebars.create();
|
|
22
|
-
this.execPolicy = options.execPolicy ?? { allow: [], deny: [] };
|
|
23
|
-
this.execEnabled = options.execEnabled ?? true;
|
|
24
|
-
this.registerHelpers();
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Register all P0 helpers onto this engine's Handlebars instance.
|
|
28
|
-
*/
|
|
29
|
-
registerHelpers() {
|
|
30
|
-
const hbs = this.hbs;
|
|
31
|
-
const policy = this.execPolicy;
|
|
32
|
-
// ── exec: shell command execution ────────────────────────────────────────
|
|
33
|
-
const execEnabled = this.execEnabled;
|
|
34
|
-
hbs.registerHelper('exec', function (command) {
|
|
35
|
-
if (typeof command !== 'string')
|
|
36
|
-
return '';
|
|
37
|
-
if (!execEnabled)
|
|
38
|
-
return '(exec disabled)';
|
|
39
|
-
return new hbs.SafeString(executeCommand(command, policy));
|
|
40
|
-
});
|
|
41
|
-
// ── include: inline string passthrough ───────────────────────────────────
|
|
42
|
-
hbs.registerHelper('include', function (content) {
|
|
43
|
-
return new hbs.SafeString(includeHelper(content));
|
|
44
|
-
});
|
|
45
|
-
// ── fenced_code: Markdown code block wrapper ──────────────────────────────
|
|
46
|
-
hbs.registerHelper('fenced_code', function (content, options) {
|
|
47
|
-
return new hbs.SafeString(fencedCodeHelper(content, options));
|
|
48
|
-
});
|
|
49
|
-
// ── Condition helpers ─────────────────────────────────────────────────────
|
|
50
|
-
hbs.registerHelper('eq', function (a, b) {
|
|
51
|
-
return a === b;
|
|
52
|
-
});
|
|
53
|
-
hbs.registerHelper('ne', function (a, b) {
|
|
54
|
-
return a !== b;
|
|
55
|
-
});
|
|
56
|
-
hbs.registerHelper('and', function (a, b) {
|
|
57
|
-
return a && b;
|
|
58
|
-
});
|
|
59
|
-
hbs.registerHelper('or', function (a, b) {
|
|
60
|
-
return a || b;
|
|
61
|
-
});
|
|
62
|
-
hbs.registerHelper('not', function (a) {
|
|
63
|
-
return !a;
|
|
64
|
-
});
|
|
65
|
-
hbs.registerHelper('contains', function (array, value) {
|
|
66
|
-
return Array.isArray(array) && array.includes(value);
|
|
67
|
-
});
|
|
68
|
-
hbs.registerHelper('json', function (context) {
|
|
69
|
-
return JSON.stringify(context, null, 2);
|
|
70
|
-
});
|
|
71
|
-
// ── P1 helpers ────────────────────────────────────────────────────────────
|
|
72
|
-
hbs.registerHelper('truncate', function (text, maxLength) {
|
|
73
|
-
return truncateHelper(text, maxLength);
|
|
74
|
-
});
|
|
75
|
-
hbs.registerHelper('length', function (value) {
|
|
76
|
-
return lengthHelper(value);
|
|
77
|
-
});
|
|
78
|
-
hbs.registerHelper('escapeHandlebars', function (text) {
|
|
79
|
-
return new hbs.SafeString(escapeHandlebarsHelper(text));
|
|
80
|
-
});
|
|
81
|
-
// ── P2 helpers ────────────────────────────────────────────────────────────
|
|
82
|
-
hbs.registerHelper('formatFileSize', function (bytes) {
|
|
83
|
-
return formatFileSizeHelper(bytes);
|
|
84
|
-
});
|
|
85
|
-
hbs.registerHelper('formatTimestamp', function (timestamp) {
|
|
86
|
-
return formatTimestampHelper(timestamp);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Render a Handlebars template string with the given context.
|
|
91
|
-
* @param template - Handlebars template string
|
|
92
|
-
* @param context - Template context (documents, env, agent, etc.)
|
|
93
|
-
* @returns Rendered string
|
|
94
|
-
*/
|
|
95
|
-
async render(template, context = {}) {
|
|
96
|
-
const compiled = this.hbs.compile(template, { noEscape: true });
|
|
97
|
-
return compiled(context);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
//# sourceMappingURL=engine.js.map
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser stub for the exec helper.
|
|
3
|
-
*
|
|
4
|
-
* Replaces the Node.js exec helper in browser bundles (via package.json "browser" field).
|
|
5
|
-
* All functions are no-ops or return safe defaults — child_process is not available in browsers.
|
|
6
|
-
*/
|
|
7
|
-
export function getSanitizedEnv() {
|
|
8
|
-
return {};
|
|
9
|
-
}
|
|
10
|
-
export function validateCommand(_command) {
|
|
11
|
-
// noop in browser
|
|
12
|
-
}
|
|
13
|
-
export function isBuiltinAllowed(_command) {
|
|
14
|
-
return false;
|
|
15
|
-
}
|
|
16
|
-
export function validateAllowPattern(_pattern) {
|
|
17
|
-
// noop in browser
|
|
18
|
-
}
|
|
19
|
-
export function isAllowed(_command, _policy) {
|
|
20
|
-
return false;
|
|
21
|
-
}
|
|
22
|
-
export function setAuditVerbose(_enabled) {
|
|
23
|
-
// noop in browser
|
|
24
|
-
}
|
|
25
|
-
export function logExecAudit(_entry) {
|
|
26
|
-
// noop in browser
|
|
27
|
-
}
|
|
28
|
-
export function executeCommand(_command, _policy) {
|
|
29
|
-
return '(exec disabled: browser environment)';
|
|
30
|
-
}
|
|
31
|
-
//# sourceMappingURL=exec.browser.js.map
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* exec Handlebars helper — 5-stage security implementation.
|
|
3
|
-
*
|
|
4
|
-
* Ports the security logic from packages/cli-bak/src/utils/template-processor.ts
|
|
5
|
-
* without using shell-quote or minimatch (not in allowed library list).
|
|
6
|
-
*
|
|
7
|
-
* Stages:
|
|
8
|
-
* 1. Recursion depth check (fail-closed on NaN)
|
|
9
|
-
* 2. Shell metacharacter validation
|
|
10
|
-
* 3. Allow-list enforcement (deny > builtin > allow > reject)
|
|
11
|
-
* 4. Environment isolation (strip sensitive keys)
|
|
12
|
-
* 5. Result capture (execFileSync with timeout + maxBuffer, wrapped in tags)
|
|
13
|
-
*/
|
|
14
|
-
import { execFileSync } from 'child_process';
|
|
15
|
-
import { matchesPattern } from '../../utils/glob-match';
|
|
16
|
-
// Sensitive env key patterns — removed from exec child processes
|
|
17
|
-
const SENSITIVE_ENV_PATTERNS = [
|
|
18
|
-
/^ANTHROPIC_/i, /^OPENAI_/i, /^AWS_SECRET/i, /^SLACK_/i,
|
|
19
|
-
/^GITHUB_TOKEN$/i, /^CREWX_MCP_KEY$/i, /^DATABASE_/i,
|
|
20
|
-
/^AWS_ACCESS_KEY/i, /^AWS_SESSION_TOKEN$/i,
|
|
21
|
-
/^GH_TOKEN$/i, /^GH_APP_/i,
|
|
22
|
-
/^NPM_TOKEN$/i, /^NPM_AUTH/i,
|
|
23
|
-
/^GOOGLE_APPLICATION/i, /^GCLOUD_/i,
|
|
24
|
-
/^AZURE_/i, /^ARM_/i,
|
|
25
|
-
/^DOCKER_PASSWORD$/i, /^DOCKER_AUTH/i,
|
|
26
|
-
/^SSH_PRIVATE/i, /^SSH_AUTH_SOCK$/i,
|
|
27
|
-
/^CI_JOB_TOKEN$/i, /^ACTIONS_RUNTIME_TOKEN$/i,
|
|
28
|
-
/^VAULT_TOKEN$/i, /^SONAR_TOKEN$/i, /^CODECOV_TOKEN$/i,
|
|
29
|
-
/TOKEN$/i, /SECRET$/i, /PASSWORD$/i, /API_KEY$/i,
|
|
30
|
-
];
|
|
31
|
-
// Shell metacharacter pattern — blocks command injection
|
|
32
|
-
const SHELL_METACHAR = /[;|&`$(){}!><\n\r]/;
|
|
33
|
-
// Max recursion depth for exec helper
|
|
34
|
-
const MAX_EXEC_DEPTH = 2;
|
|
35
|
-
// Overly broad patterns that must be rejected in allow/deny lists
|
|
36
|
-
const DANGEROUS_PATTERNS = ['*', '**', '*:*', '* *', '**/*'];
|
|
37
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
38
|
-
// Stage 4: Environment isolation
|
|
39
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
-
/**
|
|
41
|
-
* Returns a copy of process.env with all sensitive keys removed.
|
|
42
|
-
*/
|
|
43
|
-
export function getSanitizedEnv() {
|
|
44
|
-
const env = {};
|
|
45
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
46
|
-
if (value === undefined)
|
|
47
|
-
continue;
|
|
48
|
-
if (SENSITIVE_ENV_PATTERNS.some(p => p.test(key)))
|
|
49
|
-
continue;
|
|
50
|
-
env[key] = value;
|
|
51
|
-
}
|
|
52
|
-
return env;
|
|
53
|
-
}
|
|
54
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
-
// Stage 1: Shell metacharacter validation
|
|
56
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
-
/**
|
|
58
|
-
* Validates a command string for shell metacharacter injection.
|
|
59
|
-
* @throws Error if shell metacharacters are detected.
|
|
60
|
-
*/
|
|
61
|
-
export function validateCommand(command) {
|
|
62
|
-
if (SHELL_METACHAR.test(command)) {
|
|
63
|
-
throw new Error(`exec blocked: shell metacharacter detected in "${command}"`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
-
// Stage 2: Allow-list enforcement
|
|
68
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
69
|
-
/**
|
|
70
|
-
* Checks if a command matches the builtin allowed pattern.
|
|
71
|
-
* Only npx @crewx/<package> scope is allowed by default.
|
|
72
|
-
*/
|
|
73
|
-
export function isBuiltinAllowed(command) {
|
|
74
|
-
return /^npx\s+@crewx\/[\w-]+/.test(command);
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Validates allow/deny patterns — rejects overly broad wildcards.
|
|
78
|
-
* @throws Error if pattern is too broad or malformed.
|
|
79
|
-
*/
|
|
80
|
-
export function validateAllowPattern(pattern) {
|
|
81
|
-
// Trim only for dangerous-pattern check (exact match list)
|
|
82
|
-
if (DANGEROUS_PATTERNS.includes(pattern.trim())) {
|
|
83
|
-
throw new Error(`exec config error: overly broad pattern "${pattern}" is not allowed`);
|
|
84
|
-
}
|
|
85
|
-
// Start-character check uses original pattern (space prefix → rejected)
|
|
86
|
-
if (!/^[\w./@-]/.test(pattern)) {
|
|
87
|
-
throw new Error(`exec config error: pattern must start with a command name`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Checks if a command is allowed by the exec policy.
|
|
92
|
-
* Priority: deny > builtin(@crewx) > allow > reject
|
|
93
|
-
*/
|
|
94
|
-
export function isAllowed(command, policy) {
|
|
95
|
-
const allow = policy.allow ?? [];
|
|
96
|
-
const deny = policy.deny ?? [];
|
|
97
|
-
// Validate patterns
|
|
98
|
-
for (const p of [...allow, ...deny]) {
|
|
99
|
-
validateAllowPattern(p);
|
|
100
|
-
}
|
|
101
|
-
// Step 1: deny takes highest priority
|
|
102
|
-
if (deny.some(pattern => matchesPattern(command, pattern)))
|
|
103
|
-
return false;
|
|
104
|
-
// Step 2: builtin allowed (@crewx/* scope only)
|
|
105
|
-
if (isBuiltinAllowed(command))
|
|
106
|
-
return true;
|
|
107
|
-
// Step 3: explicit allow patterns
|
|
108
|
-
return allow.some(pattern => matchesPattern(command, pattern));
|
|
109
|
-
}
|
|
110
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
111
|
-
// Audit log
|
|
112
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
113
|
-
/** When false (default), audit logs are suppressed. Set true via setAuditVerbose(). */
|
|
114
|
-
let _verboseAudit = false;
|
|
115
|
-
/**
|
|
116
|
-
* Enable or disable exec audit log output to stderr.
|
|
117
|
-
* Call with true to show span JSON (verbose mode); false to suppress (default).
|
|
118
|
-
*/
|
|
119
|
-
export function setAuditVerbose(enabled) {
|
|
120
|
-
_verboseAudit = enabled;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Emits a structured audit log entry to stderr.
|
|
124
|
-
* Only writes when verbose audit mode is enabled via setAuditVerbose(true).
|
|
125
|
-
*/
|
|
126
|
-
export function logExecAudit(entry) {
|
|
127
|
-
if (!_verboseAudit)
|
|
128
|
-
return;
|
|
129
|
-
const log = JSON.stringify({ span: 'template_exec', ...entry, timestamp: new Date().toISOString() });
|
|
130
|
-
console.error(log);
|
|
131
|
-
}
|
|
132
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
133
|
-
// Simple shell argument parser (replaces shell-quote)
|
|
134
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
135
|
-
/**
|
|
136
|
-
* Parses a command string into an array of arguments.
|
|
137
|
-
* Handles single and double quoted strings.
|
|
138
|
-
*/
|
|
139
|
-
function parseShellArgs(command) {
|
|
140
|
-
const tokens = [];
|
|
141
|
-
let current = '';
|
|
142
|
-
let inSingle = false;
|
|
143
|
-
let inDouble = false;
|
|
144
|
-
for (let i = 0; i < command.length; i++) {
|
|
145
|
-
const ch = command[i];
|
|
146
|
-
if (ch === "'" && !inDouble) {
|
|
147
|
-
inSingle = !inSingle;
|
|
148
|
-
}
|
|
149
|
-
else if (ch === '"' && !inSingle) {
|
|
150
|
-
inDouble = !inDouble;
|
|
151
|
-
}
|
|
152
|
-
else if (ch === ' ' && !inSingle && !inDouble) {
|
|
153
|
-
if (current.length > 0) {
|
|
154
|
-
tokens.push(current);
|
|
155
|
-
current = '';
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
current += ch;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
if (current.length > 0)
|
|
163
|
-
tokens.push(current);
|
|
164
|
-
return tokens;
|
|
165
|
-
}
|
|
166
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
167
|
-
// Stage 3 + 5: Execute command with timeout and result capture
|
|
168
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
169
|
-
/**
|
|
170
|
-
* Executes a command and returns the output wrapped in exec-output tags.
|
|
171
|
-
* Implements all 5 security stages.
|
|
172
|
-
*/
|
|
173
|
-
export function executeCommand(command, policy) {
|
|
174
|
-
const startMs = Date.now();
|
|
175
|
-
// Stage 1: Recursion depth check (NaN fail-closed)
|
|
176
|
-
const execDepth = parseInt(process.env.CREWX_EXEC_DEPTH ?? '0', 10);
|
|
177
|
-
if (isNaN(execDepth) || execDepth >= MAX_EXEC_DEPTH) {
|
|
178
|
-
logExecAudit({ command, status: 'denied', reason: 'invalid or max recursion depth' });
|
|
179
|
-
return '(exec blocked: max recursion depth reached)';
|
|
180
|
-
}
|
|
181
|
-
// Stage 2a: Shell metacharacter validation
|
|
182
|
-
try {
|
|
183
|
-
validateCommand(command);
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
logExecAudit({ command, status: 'denied', reason: 'shell metacharacter detected' });
|
|
187
|
-
return `(exec blocked: shell metacharacter detected in "${command}")`;
|
|
188
|
-
}
|
|
189
|
-
// Stage 2b/c: Allow-list enforcement (deny > builtin > allow > reject)
|
|
190
|
-
if (!isAllowed(command, policy)) {
|
|
191
|
-
logExecAudit({ command, status: 'denied', reason: 'not in allow list' });
|
|
192
|
-
return `(exec blocked: ${command})`;
|
|
193
|
-
}
|
|
194
|
-
// Stage 3 + 4 + 5: Execute with timeout, env isolation, and result capture
|
|
195
|
-
try {
|
|
196
|
-
const parsed = parseShellArgs(command);
|
|
197
|
-
const [bin, ...args] = parsed;
|
|
198
|
-
if (!bin)
|
|
199
|
-
return `(exec failed: ${command})`;
|
|
200
|
-
// Stage 4: Environment isolation
|
|
201
|
-
const sanitizedEnv = getSanitizedEnv();
|
|
202
|
-
// Stage 3: Timeout + Stage 5: Result capture
|
|
203
|
-
const result = execFileSync(bin, args, {
|
|
204
|
-
timeout: policy.timeout ?? 5000,
|
|
205
|
-
maxBuffer: 64 * 1024, // 64KB — OOM prevention
|
|
206
|
-
encoding: 'utf-8',
|
|
207
|
-
env: { ...sanitizedEnv, CREWX_EXEC_DEPTH: String(execDepth + 1) },
|
|
208
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
209
|
-
});
|
|
210
|
-
const durationMs = Date.now() - startMs;
|
|
211
|
-
logExecAudit({ command, status: 'allowed', allowed_by: 'policy', duration_ms: durationMs });
|
|
212
|
-
// Stage 5: Wrap result in structured tags
|
|
213
|
-
return `<exec-output cmd="${command}">\n${result.trim()}\n</exec-output>`;
|
|
214
|
-
}
|
|
215
|
-
catch {
|
|
216
|
-
logExecAudit({ command, status: 'error', reason: 'execution failed' });
|
|
217
|
-
return `(exec failed: ${command})`;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
//# sourceMappingURL=exec.js.map
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* fenced_code Handlebars helper — wraps content in a Markdown fenced code block.
|
|
3
|
-
*
|
|
4
|
-
* Usage in templates:
|
|
5
|
-
* {{fenced_code myCode lang="typescript"}}
|
|
6
|
-
* {{fenced_code myCode}} ← no language specified
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* Wraps content in a Markdown fenced code block.
|
|
10
|
-
* @param content - The code content to wrap.
|
|
11
|
-
* @param options - Handlebars options object (may contain hash.lang).
|
|
12
|
-
*/
|
|
13
|
-
export function fencedCodeHelper(content, options) {
|
|
14
|
-
const lang = options?.hash?.lang ?? '';
|
|
15
|
-
return `\`\`\`${lang}\n${content ?? ''}\n\`\`\``;
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=fenced_code.js.map
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* include Handlebars helper — returns inline string content as-is.
|
|
3
|
-
*
|
|
4
|
-
* Usage in templates:
|
|
5
|
-
* {{{include someVariable}}}
|
|
6
|
-
* {{{include "literal string"}}}
|
|
7
|
-
*
|
|
8
|
-
* Note: File-path based document loading is handled in SDK-006 (DocumentLoader).
|
|
9
|
-
* This helper supports only inline string content (already in context).
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Returns the given string content unchanged.
|
|
13
|
-
* Returns empty string for null/undefined.
|
|
14
|
-
*/
|
|
15
|
-
export function includeHelper(content) {
|
|
16
|
-
if (content === null || content === undefined)
|
|
17
|
-
return '';
|
|
18
|
-
return String(content);
|
|
19
|
-
}
|
|
20
|
-
//# sourceMappingURL=include.js.map
|