keyway_inject_run
Execute commands with securely injected environment variables from Keyway secrets manager, isolating sensitive data to specific processes.
Instructions
Run a command with Keyway secrets injected as environment variables. Secrets are only available to this command.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| command | Yes | The command to run (e.g., "npm", "python") | |
| args | No | Arguments to pass to the command | |
| environment | No | Environment to pull secrets from (default: "development") | |
| timeout | No | Timeout in milliseconds (default: 300000 = 5 minutes) |
Implementation Reference
- src/tools/inject-run.ts:78-195 (handler)The main handler function `injectRun` implements the logic for the `keyway_inject_run` tool by pulling secrets, merging them into the environment, executing the requested command, and sanitizing the output.
export async function injectRun(args: { command: string; args?: string[]; environment?: string; timeout?: number; }): Promise<CallToolResult> { // Validate command is not empty if (!args.command || !args.command.trim()) { return { content: [{ type: 'text', text: 'Error: command is required' }], isError: true, }; } const token = await getToken(); const repository = getRepository(); const environment = args.environment || 'development'; const timeout = Math.min(args.timeout || DEFAULT_TIMEOUT_MS, DEFAULT_TIMEOUT_MS); // Cap at 5 min // Pull secrets const content = await pullSecrets(repository, environment, token); const secrets = parseEnvContent(content); // Merge secrets with current environment const env = { ...process.env, ...secrets }; // Run command with secrets injected const result = await new Promise<{ exitCode: number; stdout: string; stderr: string; stdoutTruncated: boolean; stderrTruncated: boolean; }>((resolve, reject) => { const child = spawn(args.command, args.args || [], { cwd: process.cwd(), env, shell: false, // Prevent shell injection stdio: ['ignore', 'pipe', 'pipe'], }); let stdout = ''; let stderr = ''; let stdoutBytes = 0; let stderrBytes = 0; const timer = setTimeout(() => { child.kill('SIGTERM'); // Give process time to cleanup, then force kill setTimeout(() => child.kill('SIGKILL'), 5000); reject(new Error(`Command timed out after ${timeout / 1000}s`)); }, timeout); child.stdout.on('data', (data: Buffer) => { // Limit memory usage by stopping accumulation after max size if (stdoutBytes < MAX_OUTPUT_BYTES) { stdout += data.toString(); stdoutBytes += data.length; } }); child.stderr.on('data', (data: Buffer) => { if (stderrBytes < MAX_OUTPUT_BYTES) { stderr += data.toString(); stderrBytes += data.length; } }); child.on('close', (code) => { clearTimeout(timer); // Truncate and mask output const stdoutResult = truncateOutput(stdout, MAX_OUTPUT_BYTES); const stderrResult = truncateOutput(stderr, MAX_OUTPUT_BYTES); resolve({ exitCode: code ?? 1, stdout: maskSecrets(stdoutResult.text, secrets), stderr: maskSecrets(stderrResult.text, secrets), stdoutTruncated: stdoutResult.truncated || stdoutBytes >= MAX_OUTPUT_BYTES, stderrTruncated: stderrResult.truncated || stderrBytes >= MAX_OUTPUT_BYTES, }); }); child.on('error', (err) => { clearTimeout(timer); reject(new Error(`Failed to execute command: ${err.message}`)); }); }); const response: Record<string, unknown> = { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr, secretsInjected: Object.keys(secrets).length, }; // Include truncation warnings if applicable if (result.stdoutTruncated || result.stderrTruncated) { response.warnings = []; if (result.stdoutTruncated) { (response.warnings as string[]).push('stdout was truncated (exceeded 1MB)'); } if (result.stderrTruncated) { (response.warnings as string[]).push('stderr was truncated (exceeded 1MB)'); } } return { content: [ { type: 'text', text: JSON.stringify(response, null, 2), }, ], isError: result.exitCode !== 0, }; }