How to Extend the Secure-Design Tool System with Custom Tools
Developers can extend the secure-design tool system by implementing a factory function that uses Zod schemas for input validation, wraps filesystem operations with security utilities from src/tools/tool-utils.ts, and registers the tool in src/services/customAgentService.ts.
The hbmartin/secure-design repository provides a modular AI agent framework for VS Code that enables secure workspace interactions through standardized tools. When you need to extend secure-design with custom tools, the codebase provides established patterns that enforce workspace containment, type safety via Zod, and consistent error handling through centralized utilities.
Core Architecture Components
The secure-design tool architecture rests on four pillars that every custom tool must implement. According to the source code, these components work together to guarantee security and consistency:
- ExecutionContext – Defined in
src/types/agent.ts, this provides theworkingDirectoryand logger instance required by every tool operation. - Tool Utilities – The
src/tools/tool-utils.tsfile exportsvalidateWorkspacePath,resolveWorkspacePath,createSuccessResponse, andhandleToolError(see lines 49‑55 for validation logic). - Zod Schemas – Each tool declares its input parameters using Zod for runtime validation and automatic documentation generation.
- AI SDK Integration – Tools are constructed using the
tool({ description, inputSchema, execute })factory from theaiSDK, which the AI agent uses to discover and invoke capabilities.
Implementing a Custom Tool: The Copy File Example
To extend secure-design with a practical file operation, follow the pattern established in src/tools/write-tool.ts and src/tools/read-tool.ts. Below is a complete implementation of a copy-file tool that demonstrates every required step.
1. Define the Zod Input Schema
Create a new file at src/tools/copy-tool.ts and declare the tool’s contract using Zod. This ensures the AI agent passes valid arguments before execution begins.
import { z } from 'zod';
const copyToolSchema = z.object({
source_path: z
.string()
.describe('Path of the file to copy (relative to workspace or absolute).'),
destination_path: z
.string()
.describe('Target path for the copy (relative to workspace or absolute).'),
overwrite: z
.boolean()
.optional()
.default(false)
.describe('Overwrite the destination if it already exists.'),
});
2. Implement the Tool Factory Function
Export a factory function following the createCopyTool(context: ExecutionContext) pattern used by built-in tools. This function returns the configured tool instance with security checks and logging.
import { tool } from 'ai';
import * as fs from 'fs';
import * as path from 'path';
import type { ExecutionContext } from '../types/agent';
import {
handleToolError,
validateWorkspacePath,
resolveWorkspacePath,
createSuccessResponse,
type ToolResponse,
} from './tool-utils';
import { getLogger } from 'react-vscode-webview-ipc/host';
export function createCopyTool(context: ExecutionContext) {
const logger = getLogger('copy tool');
return tool({
description: 'Copy a file within the workspace, optionally overwriting the destination.',
inputSchema: copyToolSchema,
execute: async ({
source_path,
destination_path,
overwrite = false,
}): Promise<ToolResponse> => {
const start = Date.now();
try {
// Validate both paths against workspace boundaries
const srcErr = validateWorkspacePath(source_path, context);
if (srcErr) return srcErr;
const dstErr = validateWorkspacePath(destination_path, context);
if (dstErr) return dstErr;
const srcAbs = resolveWorkspacePath(source_path, context);
const dstAbs = resolveWorkspacePath(destination_path, context);
logger.info(`[copy] ${source_path} → ${destination_path}`);
// Verify source exists
if (!fs.existsSync(srcAbs) || !fs.statSync(srcAbs).isFile()) {
return handleToolError(
`Source file not found: ${source_path}`,
'Copy tool',
'file_not_found',
);
}
// Check destination collision
if (fs.existsSync(dstAbs) && !overwrite) {
return handleToolError(
`Destination already exists (set overwrite=true to replace): ${destination_path}`,
'Copy tool',
'validation',
);
}
// Ensure parent directories exist
const parentDir = path.dirname(dstAbs);
if (!fs.existsSync(parentDir)) {
fs.mkdirSync(parentDir, { recursive: true });
logger.info(`[copy] Created parent directories for ${destination_path}`);
}
// Execute the copy operation
fs.copyFileSync(srcAbs, dstAbs);
const duration = Date.now() - start;
logger.info(`[copy] Completed in ${duration}ms`);
return createSuccessResponse({
source_path,
destination_path,
overwritten: overwrite && fs.existsSync(dstAbs),
bytes_copied: fs.statSync(srcAbs).size,
duration_ms: duration,
});
} catch (error) {
return handleToolError(error, 'Copy tool execution', 'execution');
}
},
});
}
3. Register the Tool in the Agent Service
All tool factories are instantiated in src/services/customAgentService.ts. Import your factory and add it to the tools array to make it available to the AI agent:
import { createCopyTool } from '../tools/copy-tool';
// ...
this.tools = [
// existing tools...
createCopyTool(this.context),
];
Security Requirements and Best Practices
When you extend secure-design with custom tools, you must adhere to the following security protocol enforced by the existing codebase:
- Path Validation – Always call
validateWorkspacePath(filePath, context)(as implemented insrc/tools/tool-utils.tslines 49‑55) before any filesystem operation to prevent directory traversal attacks. - Absolute Resolution – Use
resolveWorkspacePath(filePath, context)to convert relative inputs into absolute paths anchored to the workspace root. - Error Handling – Wrap the entire
executebody in atry / catchblock and returnhandleToolError(error, 'Tool Name', 'execution')for unexpected exceptions. - Response Consistency – Return
createSuccessResponse({ ... })with typed metadata to match theToolSuccessResponseshape defined intool-utils.tslines 24‑27. - Logging – Initialize a logger using
getLogger('tool name')fromreact-vscode-webview-ipc/hostand emit info-level logs for operation start and completion. - Size Limits – For tools handling large payloads, enforce maximum size constraints (e.g., 10 MiB) as demonstrated in
src/tools/read-tool.tslines 73‑80.
Testing Your Custom Tool
Validate your implementation using the extension’s testing infrastructure:
- Unit Tests – Create test files under
src/test/tools/using the VS Code extension test harness to verify schema validation and error paths. - Integration Tests – Run
npm run test:toolsto execute the suite in a headless VS Code instance, ensuring your tool integrates with the agent service. - Manual Verification – Open the extension’s Chat panel and invoke your tool with JSON arguments:
{
"tool": "copy",
"arguments": {
"source_path": "README.md",
"destination_path": ".superdesign/README-copy.md",
"overwrite": false
}
}
The response should return a JSON payload with success: true and the metadata fields defined in your createSuccessResponse call.
Minimal Example: Hello World Tool
For simple utilities that do not touch the filesystem, follow this minimal scaffold from src/tools/hello-tool.ts:
import { z } from 'zod';
import { tool } from 'ai';
import type { ExecutionContext } from '../types/agent';
import { createSuccessResponse, type ToolResponse } from './tool-utils';
import { getLogger } from 'react-vscode-webview-ipc/host';
const helloSchema = z.object({
name: z.string().optional().default('Developer').describe('Who to greet'),
});
export function createHelloTool(context: ExecutionContext) {
const logger = getLogger('hello tool');
return tool({
description: 'Return a friendly greeting.',
inputSchema: helloSchema,
execute: async ({ name }): Promise<ToolResponse> => {
logger.info(`[hello] greeting ${name}`);
return createSuccessResponse({ greeting: `Hello, ${name}!` });
},
});
}
Summary
Extending the secure-design tool system requires adherence to the established factory pattern and security protocols:
- Use Zod schemas to define and validate input parameters with type safety.
- Leverage
tool-utils.tsfor path validation (validateWorkspacePath), absolute resolution (resolveWorkspacePath), and standardized responses (createSuccessResponse,handleToolError). - Implement factory functions like
createCopyToolthat acceptExecutionContextand returntoolinstances from theaiSDK. - Register tools in
src/services/customAgentService.tsto expose them to the AI agent. - Maintain security by validating all paths against workspace boundaries and wrapping execution in try/catch blocks.
- Log operations using the
getLoggerutility for traceability.
Frequently Asked Questions
How do I prevent my custom tool from accessing files outside the workspace?
Always pass file paths through validateWorkspacePath(filePath, context) before any filesystem operation. This utility, defined in src/tools/tool-utils.ts lines 49‑55, checks that the resolved absolute path remains within the workspace directory tree, preventing directory traversal attacks.
What is the purpose of the Zod schema in secure-design tools?
The Zod schema serves as both a runtime validator and a documentation generator for the AI agent. It ensures that arguments passed to your tool conform to expected types (strings, booleans, etc.) before the execute function runs, and it provides the AI with structured descriptions of each parameter to improve tool selection accuracy.
Where should I register a new tool to make it available to the AI agent?
Register your tool factory in src/services/customAgentService.ts by importing the factory function (e.g., createCopyTool) and adding it to the this.tools array instantiation. This injects your tool into the agent’s tool registry, allowing the AI to discover and invoke it alongside built-in tools like read and write.
How do I handle errors consistently across custom tools?
Import handleToolError from src/tools/tool-utils.ts and use it within a try / catch block wrapping your entire execute function. This ensures all unexpected exceptions return a standardized ToolErrorResponse with appropriate error codes and messages, maintaining consistency with the built-in tool error handling strategy.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →