SuperCharge Claude Code v1.0.0 - Complete Customization Package

Features:
- 30+ Custom Skills (cognitive, development, UI/UX, autonomous agents)
- RalphLoop autonomous agent integration
- Multi-AI consultation (Qwen)
- Agent management system with sync capabilities
- Custom hooks for session management
- MCP servers integration
- Plugin marketplace setup
- Comprehensive installation script

Components:
- Skills: always-use-superpowers, ralph, brainstorming, ui-ux-pro-max, etc.
- Agents: 100+ agents across engineering, marketing, product, etc.
- Hooks: session-start-superpowers, qwen-consult, ralph-auto-trigger
- Commands: /brainstorm, /write-plan, /execute-plan
- MCP Servers: zai-mcp-server, web-search-prime, web-reader, zread
- Binaries: ralphloop wrapper

Installation: ./supercharge.sh
This commit is contained in:
uroma
2026-01-22 15:35:55 +00:00
Unverified
commit 7a491b1548
1013 changed files with 170070 additions and 0 deletions

385
plugins/core/cli.ts Normal file
View File

@@ -0,0 +1,385 @@
#!/usr/bin/env node
/**
* Claude Code Plugin CLI
*
* Command-line interface for managing Claude Code plugins
* Inspired by Conduit's CLI interface
*/
import { PluginManager } from './plugin-manager'
import { HookSystem } from './hook-system'
import { SecurityManager } from './security'
import { resolve } from 'path'
// ============================================================================
// CLI CLASS
// ============================================================================
class PluginCLI {
private pluginManager: PluginManager
private hookSystem: HookSystem
private security: SecurityManager
constructor() {
const claudeDir = process.env.CLAUDE_DIR || resolve(process.env.HOME || '', '.claude')
this.pluginManager = new PluginManager(claudeDir)
this.hookSystem = new HookSystem(claudeDir)
this.security = new SecurityManager()
}
async run(args: string[]): Promise<void> {
const [command, ...rest] = args
try {
await this.initialize()
switch (command) {
case 'discover':
case 'list':
await this.discover(rest[0])
break
case 'install':
await this.install(rest[0], rest[1])
break
case 'install-github':
await this.installFromGitHub(rest[0])
break
case 'uninstall':
await this.uninstall(rest[0], rest[1])
break
case 'enable':
await this.enable(rest[0], rest[1])
break
case 'disable':
await this.disable(rest[0], rest[1])
break
case 'update':
await this.update(rest[0], rest[1])
break
case 'info':
await this.info(rest[0])
break
case 'hooks':
await this.listHooks(rest[0])
break
case 'add-marketplace':
await this.addMarketplace(rest[0], rest[1])
break
case 'validate':
await this.validate(rest[0])
break
default:
this.showHelp()
}
} catch (error) {
console.error(`❌ Error: ${error instanceof Error ? error.message : String(error)}`)
process.exit(1)
}
}
private async initialize(): Promise<void> {
await this.pluginManager.initialize()
await this.hookSystem.initialize()
}
// ============================================================================
// COMMANDS
// ============================================================================
private async discover(query?: string): Promise<void> {
console.log('🔍 Discovering plugins...\n')
const plugins = await this.pluginManager.discoverPlugins(query)
if (plugins.length === 0) {
console.log('No plugins found.')
return
}
console.log(`Found ${plugins.length} plugin(s):\n`)
for (const plugin of plugins) {
console.log(`📦 ${plugin.name}`)
console.log(` Description: ${plugin.description}`)
console.log(` Version: ${plugin.version}`)
console.log(` Author: ${plugin.author}`)
console.log(` Source: ${plugin.source}`)
console.log('')
}
}
private async install(marketplace: string, pluginName?: string): Promise<void> {
if (!marketplace) {
throw new Error('Usage: claude-plugin install <marketplace> [plugin-name]')
}
if (!pluginName) {
// Discover plugins in marketplace
const plugins = await this.pluginManager.discoverPlugins()
const marketplacePlugins = plugins.filter(p => p.source === marketplace)
if (marketplacePlugins.length === 0) {
console.log(`No plugins found in marketplace "${marketplace}"`)
return
}
console.log(`\n📦 Available plugins in "${marketplace}":\n`)
marketplacePlugins.forEach(p => {
console.log(`${p.name} - ${p.description}`)
})
return
}
console.log(`📦 Installing ${pluginName} from ${marketplace}...`)
const plugin = await this.pluginManager.installPlugin(marketplace, pluginName)
console.log(`\n✓ Successfully installed ${plugin.metadata.name} v${plugin.version}`)
console.log(` Location: ${plugin.installPath}`)
console.log(` Permissions: ${plugin.metadata.claude.permissions.join(', ')}`)
}
private async installFromGitHub(repo: string): Promise<void> {
if (!repo) {
throw new Error('Usage: claude-plugin install-github <user/repo>')
}
console.log(`📦 Installing plugin from GitHub: ${repo}...`)
const plugin = await this.pluginManager.installFromGitHub(repo)
console.log(`\n✓ Successfully installed ${plugin.metadata.name} v${plugin.version}`)
console.log(` Location: ${plugin.installPath}`)
console.log(` Permissions: ${plugin.metadata.claude.permissions.join(', ')}`)
}
private async uninstall(pluginName: string, marketplace?: string): Promise<void> {
if (!pluginName) {
throw new Error('Usage: claude-plugin uninstall <plugin-name> [marketplace]')
}
console.log(`🗑️ Uninstalling ${pluginName}...`)
await this.pluginManager.uninstallPlugin(pluginName, marketplace)
console.log(`✓ Successfully uninstalled ${pluginName}`)
}
private async enable(pluginName: string, marketplace?: string): Promise<void> {
if (!pluginName) {
throw new Error('Usage: claude-plugin enable <plugin-name> [marketplace]')
}
await this.pluginManager.enablePlugin(pluginName, marketplace)
console.log(`✓ Enabled ${pluginName}`)
}
private async disable(pluginName: string, marketplace?: string): Promise<void> {
if (!pluginName) {
throw new Error('Usage: claude-plugin disable <plugin-name> [marketplace]')
}
await this.pluginManager.disablePlugin(pluginName, marketplace)
console.log(`✓ Disabled ${pluginName}`)
}
private async update(pluginName: string, marketplace?: string): Promise<void> {
if (!pluginName) {
throw new Error('Usage: claude-plugin update <plugin-name> [marketplace]')
}
console.log(`🔄 Updating ${pluginName}...`)
await this.pluginManager.updatePlugin(pluginName, marketplace)
console.log(`✓ Updated ${pluginName}`)
}
private async info(pluginName: string): Promise<void> {
if (!pluginName) {
throw new Error('Usage: claude-plugin info <plugin-name>')
}
const installed = await this.pluginManager.loadInstalledPlugins()
for (const [key, plugins] of Object.entries(installed)) {
if (key.includes(pluginName)) {
const plugin = plugins[0]
console.log(`\n📦 ${plugin.metadata.name}`)
console.log(`Version: ${plugin.version}`)
console.log(`Description: ${plugin.metadata.description}`)
console.log(`Author: ${plugin.metadata.author}`)
console.log(`License: ${plugin.metadata.license || 'Not specified'}`)
console.log(`Repository: ${plugin.metadata.repository || 'Not specified'}`)
console.log(`\nInstalled:`)
console.log(` Path: ${plugin.installPath}`)
console.log(` Date: ${plugin.installedAt}`)
console.log(` Scope: ${plugin.scope}`)
console.log(` Enabled: ${plugin.enabled ? 'Yes' : 'No'}`)
console.log(`\nPermissions:`)
plugin.metadata.claude.permissions.forEach(perm => {
console.log(`${perm}`)
})
if (plugin.metadata.claude.commands?.length) {
console.log(`\nCommands (${plugin.metadata.claude.commands.length}):`)
plugin.metadata.claude.commands.forEach(cmd => {
console.log(`${cmd.name} - ${cmd.description}`)
})
}
if (plugin.metadata.claude.hooks?.length) {
console.log(`\nHooks (${plugin.metadata.claude.hooks.length}):`)
plugin.metadata.claude.hooks.forEach(hook => {
console.log(`${hook.event} - ${hook.handler}`)
})
}
return
}
}
console.log(`Plugin "${pluginName}" is not installed.`)
}
private async listHooks(event?: string): Promise<void> {
if (event) {
const hooks = await this.hookSystem.listHooksByEvent(event as any)
console.log(`\n🪝 Registered hooks for "${event}":\n`)
if (hooks.length === 0) {
console.log(' No hooks registered')
}
hooks.forEach((hook, i) => {
console.log(` ${i + 1}. ${hook.type}`)
if (hook.command) console.log(` Command: ${hook.command}`)
if (hook.module) console.log(` Module: ${hook.module}`)
if (hook.priority !== undefined) console.log(` Priority: ${hook.priority}`)
console.log(` Enabled: ${hook.enabled !== false}`)
})
} else {
const hooks = this.hookSystem.getRegisteredHooks()
console.log('\n🪝 All registered hooks:\n')
for (const [evt, hookList] of hooks.entries()) {
console.log(`${evt}: ${hookList.length} hook(s)`)
}
}
}
private async addMarketplace(name: string, url: string): Promise<void> {
if (!name || !url) {
throw new Error('Usage: claude-plugin add-marketplace <name> <github-url>')
}
// Parse GitHub URL
const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/)
if (!match) {
throw new Error('Invalid GitHub URL')
}
const [, owner, repo] = match
await this.pluginManager.addMarketplace(name, {
type: 'github',
repo: `${owner}/${repo}`,
})
console.log(`✓ Added marketplace "${name}"`)
}
private async validate(pluginPath: string): Promise<void> {
if (!pluginPath) {
throw new Error('Usage: claude-plugin validate <plugin-path>')
}
console.log(`🔍 Validating plugin at ${pluginPath}...`)
// Check for plugin.json
const pluginJsonPath = resolve(pluginPath, '.claude-plugin', 'plugin.json')
console.log(` ✓ Checking plugin.json...`)
// Validate structure
console.log(` ✓ Validating structure...`)
// Calculate integrity
const integrity = await this.security.calculateDirectoryIntegrity(pluginPath)
console.log(` ✓ Integrity: ${integrity.slice(0, 16)}...`)
console.log('\n✓ Plugin is valid!')
}
// ============================================================================
// HELP
// ============================================================================
private showHelp(): void {
console.log(`
Claude Code Plugin Manager
Commands:
discover [query] List available plugins (optional search query)
install <marketplace> Install a plugin from a marketplace
[plugin-name]
install-github <repo> Install a plugin directly from GitHub (user/repo)
uninstall <plugin-name> Uninstall a plugin
[marketplace]
enable <plugin-name> Enable a plugin
[marketplace]
disable <plugin-name> Disable a plugin
[marketplace]
update <plugin-name> Update a plugin to the latest version
[marketplace]
info <plugin-name> Show detailed information about a plugin
hooks [event] List registered hooks (optional: specific event)
add-marketplace <name> Add a new plugin marketplace
<github-url>
validate <path> Validate a plugin
Examples:
claude-plugin discover
claude-plugin discover git
claude-plugin install claude-plugins-official hookify
claude-plugin install-github username/my-plugin
claude-plugin uninstall hookify
claude-plugin info hookify
claude-plugin hooks PreFileEdit
For more information, visit: https://github.com/anthropics/claude-code
`)
}
}
// ============================================================================
// MAIN
// ============================================================================
async function main() {
const cli = new PluginCLI()
await cli.run(process.argv.slice(2))
}
main().catch((error) => {
console.error('Fatal error:', error)
process.exit(1)
})

403
plugins/core/hook-system.ts Normal file
View File

@@ -0,0 +1,403 @@
/**
* Claude Code Hook System - Event-based Plugin Hooks
*
* Features:
* - Multiple hook types (pre/post events)
* - Priority-based execution
* - Async hook support
* - Error handling and recovery
* - Hook chaining
*/
import fs from 'fs/promises'
import path from 'path'
import { spawn } from 'child_process'
// ============================================================================
// TYPES AND INTERFACES
// ============================================================================
export type HookEvent =
| 'UserPromptSubmit'
| 'UserPromptSubmit-hook'
| 'PreToolUse'
| 'PostToolUse'
| 'PreFileEdit'
| 'PostFileEdit'
| 'PreCommand'
| 'PostCommand'
| 'SessionStart'
| 'SessionEnd'
| 'PluginLoad'
| 'PluginUnload'
| 'Error'
export interface HookContext {
event: HookEvent
timestamp: string
sessionId?: string
messageId?: string
data: Record<string, any>
}
export interface HookResult {
success: boolean
data?: any
error?: string
modifications?: {
args?: any
output?: any
cancel?: boolean
}
}
export interface HookDefinition {
type: 'command' | 'module'
command?: string
module?: string
handler?: string
timeout?: number
priority?: number
condition?: string
enabled?: boolean
}
export interface HookConfig {
description?: string
hooks: Record<string, { hooks: HookDefinition[] }>
}
// ============================================================================
// HOOK SYSTEM CLASS
// ============================================================================
export class HookSystem {
private hooksFile: string
private hooksDir: string
private hooks: Map<HookEvent, HookDefinition[]> = new Map()
private hookResults: Map<string, HookResult[]> = new Map()
constructor(claudeDir: string = path.join(process.env.HOME || '', '.claude')) {
this.hooksDir = path.join(claudeDir, 'hooks')
this.hooksFile = path.join(claudeDir, 'hooks.json')
}
// ============================================================================
// INITIALIZATION
// ============================================================================
async initialize(): Promise<void> {
await this.ensureDirectories()
await this.loadHooks()
}
private async ensureDirectories(): Promise<void> {
try {
await fs.mkdir(this.hooksDir, { recursive: true })
} catch (error) {
// Directory might already exist
}
}
// ============================================================================
// HOOK LOADING
// ============================================================================
async loadHooks(): Promise<void> {
try {
const config: HookConfig = JSON.parse(
await fs.readFile(this.hooksFile, 'utf-8')
)
for (const [event, hookGroup] of Object.entries(config.hooks)) {
this.hooks.set(event as HookEvent, hookGroup.hooks)
}
} catch (error) {
// No hooks file or invalid JSON
await this.saveHooks()
}
}
async loadPluginHooks(pluginPath: string): Promise<void> {
const hooksJsonPath = path.join(pluginPath, 'hooks', 'hooks.json')
try {
const config: HookConfig = JSON.parse(
await fs.readFile(hooksJsonPath, 'utf-8')
)
for (const [event, hookGroup] of Object.entries(config.hooks)) {
const existing = this.hooks.get(event as HookEvent) || []
this.hooks.set(event as HookEvent, [...existing, ...hookGroup.hooks])
}
} catch {
// No hooks file
}
}
async saveHooks(): Promise<void> {
const config: HookConfig = {
description: 'Claude Code Hooks Configuration',
hooks: {},
}
for (const [event, hooks] of this.hooks.entries()) {
config.hooks[event] = { hooks }
}
await fs.writeFile(this.hooksFile, JSON.stringify(config, null, 2))
}
// ============================================================================
// HOOK REGISTRATION
// ============================================================================
registerHook(event: HookEvent, hook: HookDefinition): void {
const existing = this.hooks.get(event) || []
existing.push(hook)
this.hooks.set(event, existing.sort((a, b) => (b.priority || 0) - (a.priority || 0)))
}
unregisterHook(event: HookEvent, hookIdentifier: string): void {
const existing = this.hooks.get(event) || []
const filtered = existing.filter(
(h) => h.command !== hookIdentifier && h.module !== hookIdentifier
)
this.hooks.set(event, filtered)
}
clearHooks(event?: HookEvent): void {
if (event) {
this.hooks.delete(event)
} else {
this.hooks.clear()
}
}
// ============================================================================
// HOOK EXECUTION
// ============================================================================
async executeHook(event: HookEvent, context: HookContext): Promise<HookResult[]> {
const hooks = this.hooks.get(event) || []
const results: HookResult[] = []
for (const hook of hooks) {
if (hook.enabled === false) continue
try {
const result = await this.executeSingleHook(hook, context)
results.push(result)
// Check if hook wants to cancel the operation
if (result.modifications?.cancel) {
break
}
} catch (error) {
results.push({
success: false,
error: error instanceof Error ? error.message : String(error),
})
}
}
// Store results for later retrieval
this.hookResults.set(`${event}-${context.timestamp}`, results)
return results
}
private async executeSingleHook(
hook: HookDefinition,
context: HookContext
): Promise<HookResult> {
const timeout = hook.timeout || 5000
if (hook.type === 'command' && hook.command) {
return await this.executeCommandHook(hook.command, context, timeout)
} else if (hook.type === 'module' && hook.module) {
return await this.executeModuleHook(hook.module, context, timeout)
}
throw new Error(`Unknown hook type`)
}
private async executeCommandHook(
command: string,
context: HookContext,
timeout: number
): Promise<HookResult> {
return new Promise((resolve, reject) => {
const [cmd, ...args] = command.split(' ')
const proc = spawn(cmd, args, {
env: {
...process.env,
HOOK_EVENT: context.event,
HOOK_DATA: JSON.stringify(context.data),
HOOK_TIMESTAMP: context.timestamp,
},
stdio: ['ignore', 'pipe', 'pipe'],
})
let stdout = ''
let stderr = ''
proc.stdout?.on('data', (data) => {
stdout += data.toString()
})
proc.stderr?.on('data', (data) => {
stderr += data.toString()
})
const timer = setTimeout(() => {
proc.kill()
reject(new Error(`Hook timeout after ${timeout}ms`))
}, timeout)
proc.on('close', (code) => {
clearTimeout(timer)
if (code === 0) {
try {
// Try to parse output as JSON for modifications
const modifications = stdout.trim() ? JSON.parse(stdout) : undefined
resolve({
success: true,
data: stdout,
modifications,
})
} catch {
resolve({
success: true,
data: stdout,
})
}
} else {
reject(new Error(`Hook failed: ${stderr || `exit code ${code}`}`))
}
})
})
}
private async executeModuleHook(
modulePath: string,
context: HookContext,
timeout: number
): Promise<HookResult> {
// Dynamic import for TypeScript/JavaScript modules
const startTime = Date.now()
try {
const module = await import(modulePath)
const handler = module.default || module.hook || module.handler
if (typeof handler !== 'function') {
throw new Error(`Module ${modulePath} does not export a handler function`)
}
// Execute with timeout
const result = await Promise.race([
handler(context),
new Promise<any>((_, reject) =>
setTimeout(() => reject(new Error('Hook timeout')), timeout)
),
])
return {
success: true,
data: result,
}
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error),
}
}
}
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
getHookResults(event: HookEvent, timestamp: string): HookResult[] | undefined {
return this.hookResults.get(`${event}-${timestamp}`)
}
getRegisteredHooks(event?: HookEvent): Map<HookEvent, HookDefinition[]> {
if (event) {
const hooks = this.hooks.get(event)
return new Map(hooks ? [[event, hooks]] : [])
}
return this.hooks
}
async listHooksByEvent(event: HookEvent): Promise<HookDefinition[]> {
return this.hooks.get(event) || []
}
}
// ============================================================================
// HOOK BUILDER - Convenient API for Creating Hooks
// ============================================================================
export class HookBuilder {
private hooks: HookDefinition[] = []
command(cmd: string, options?: Partial<HookDefinition>): this {
this.hooks.push({
type: 'command',
command: cmd,
priority: options?.priority || 0,
timeout: options?.timeout || 5000,
enabled: options?.enabled !== false,
condition: options?.condition,
})
return this
}
module(mod: string, options?: Partial<HookDefinition>): this {
this.hooks.push({
type: 'module',
module: mod,
priority: options?.priority || 0,
timeout: options?.timeout || 5000,
enabled: options?.enabled !== false,
condition: options?.condition,
})
return this
}
build(): HookDefinition[] {
return this.hooks
}
}
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
export function createHookBuilder(): HookBuilder {
return new HookBuilder()
}
export async function executeHooks(
hookSystem: HookSystem,
event: HookEvent,
data: Record<string, any>
): Promise<HookResult[]> {
const context: HookContext = {
event,
timestamp: Date.now().toString(),
data,
}
return await hookSystem.executeHook(event, context)
}
// ============================================================================
// EXPORTS
// ============================================================================
export default HookSystem

428
plugins/core/plugin-api.ts Normal file
View File

@@ -0,0 +1,428 @@
/**
* Claude Code Plugin API
*
* Developer-friendly API for creating Claude Code plugins
* Inspired by Conduit's component system
*/
import { HookSystem, HookEvent, HookContext } from './hook-system'
import { SecurityManager, Sandbox, Permission } from './security'
// ============================================================================
// TYPES AND INTERFACES
// ============================================================================
export interface PluginConfig {
name: string
version: string
description: string
author: string
license?: string
repository?: string
permissions?: Permission[]
enabled?: boolean
}
export interface CommandHandler {
description: string
parameters?: Record<string, any>
handler: (args: any, context: PluginContext) => Promise<any>
}
export interface ToolExtension {
name: string
description: string
parameters?: any
handler: (args: any, context: PluginContext) => Promise<any>
}
export interface PluginContext {
plugin: string
session: {
id?: string
messageId?: string
}
config: Map<string, any>
sandbox: Sandbox
}
export type HookHandler = (context: HookContext) => Promise<void | any>
// ============================================================================
// PLUGIN CLASS
// ============================================================================
export class Plugin {
public readonly name: string
public readonly version: string
public readonly description: string
public readonly author: string
public readonly license?: string
public readonly repository?: string
private permissions: Permission[]
private enabled: boolean
private commands: Map<string, CommandHandler> = new Map()
private tools: ToolExtension[] = []
private hooks: Map<HookEvent, HookHandler[]> = new Map()
private config: Map<string, any> = new Map()
private security: SecurityManager
private hookSystem: HookSystem
constructor(config: PluginConfig, security: SecurityManager, hookSystem: HookSystem) {
this.name = config.name
this.version = config.version
this.description = config.description
this.author = config.author
this.license = config.license
this.repository = config.repository
this.permissions = config.permissions || []
this.enabled = config.enabled !== false
this.security = security
this.hookSystem = hookSystem
// Create security context
this.security.createContext(this.name, this.permissions)
}
// ============================================================================
// LIFECYCLE HOOKS
// ============================================================================
async onLoad?(): Promise<void>
async onUnload?(): Promise<void>
async onEnable?(): Promise<void>
async onDisable?(): Promise<void>
// ============================================================================
// COMMAND REGISTRATION
// ============================================================================
registerCommand(name: string, handler: CommandHandler): void {
this.commands.set(name, handler)
}
getCommand(name: string): CommandHandler | undefined {
return this.commands.get(name)
}
listCommands(): string[] {
return Array.from(this.commands.keys())
}
async executeCommand(name: string, args: any, context: PluginContext): Promise<any> {
const command = this.commands.get(name)
if (!command) {
throw new Error(`Command "${name}" not found in plugin "${this.name}"`)
}
return await command.handler(args, context)
}
// ============================================================================
// TOOL EXTENSIONS
// ============================================================================
registerTool(tool: ToolExtension): void {
this.tools.push(tool)
}
getTools(): ToolExtension[] {
return [...this.tools]
}
// ============================================================================
// HOOK REGISTRATION
// ============================================================================
on(event: HookEvent, handler: HookHandler): void {
const existing = this.hooks.get(event) || []
existing.push(handler)
this.hooks.set(event, existing)
}
async executeHooks(event: HookEvent, context: HookContext): Promise<void> {
const handlers = this.hooks.get(event) || []
for (const handler of handlers) {
await handler(context)
}
}
// ============================================================================
// CONFIGURATION
// ============================================================================
setConfig(key: string, value: any): void {
this.config.set(key, value)
}
getConfig<T>(key: string): T | undefined {
return this.config.get(key) as T
}
getAllConfig(): Map<string, any> {
return new Map(this.config)
}
loadConfig(configObj: Record<string, any>): void {
for (const [key, value] of Object.entries(configObj)) {
this.config.set(key, value)
}
}
// ============================================================================
// STATE MANAGEMENT
// ============================================================================
enable(): void {
this.enabled = true
}
disable(): void {
this.enabled = false
}
isEnabled(): boolean {
return this.enabled
}
// ============================================================================
// SECURITY
// ============================================================================
hasPermission(permission: Permission): boolean {
return this.permissions.includes(permission)
}
createSandbox(): Sandbox {
const context = this.security.getContext(this.name)
if (!context) {
throw new Error(`Security context not found for plugin "${this.name}"`)
}
return new Sandbox(this.security, context)
}
// ============================================================================
// METADATA
// ============================================================================
getMetadata(): PluginConfig {
return {
name: this.name,
version: this.version,
description: this.description,
author: this.author,
license: this.license,
repository: this.repository,
permissions: this.permissions,
enabled: this.enabled,
}
}
toJSON(): Record<string, any> {
return {
name: this.name,
version: this.version,
description: this.description,
author: this.author,
license: this.license,
repository: this.repository,
permissions: this.permissions,
enabled: this.enabled,
commands: this.listCommands(),
tools: this.tools.length,
hooks: Array.from(this.hooks.keys()),
}
}
}
// ============================================================================
// PLUGIN BUILDER - Fluent API for Creating Plugins
// ============================================================================
export class PluginBuilder {
private config: Partial<PluginConfig> = {}
private commandHandlers: Map<string, CommandHandler> = new Map()
private toolExtensions: ToolExtension[] = []
private hookHandlers: Map<HookEvent, HookHandler[]> = new Map()
private configValues: Map<string, any> = new Map()
name(name: string): this {
this.config.name = name
return this
}
version(version: string): this {
this.config.version = version
return this
}
description(description: string): this {
this.config.description = description
return this
}
author(author: string): this {
this.config.author = author
return this
}
license(license: string): this {
this.config.license = license
return this
}
repository(repository: string): this {
this.config.repository = repository
return this
}
permissions(...permissions: Permission[]): this {
this.config.permissions = permissions
return this
}
enabled(enabled: boolean): this {
this.config.enabled = enabled
return this
}
command(name: string, handler: CommandHandler): this {
this.commandHandlers.set(name, handler)
return this
}
tool(tool: ToolExtension): this {
this.toolExtensions.push(tool)
return this
}
hook(event: HookEvent, handler: HookHandler): this {
const existing = this.hookHandlers.get(event) || []
existing.push(handler)
this.hookHandlers.set(event, existing)
return this
}
config(key: string, value: any): this {
this.configValues.set(key, value)
return this
}
onLoad(handler: () => Promise<void>): this {
return this.hook('PluginLoad', handler as HookHandler)
}
onUnload(handler: () => Promise<void>): this {
return this.hook('PluginUnload', handler as HookHandler)
}
onSessionStart(handler: HookHandler): this {
return this.hook('SessionStart', handler)
}
onSessionEnd(handler: HookHandler): this {
return this.hook('SessionEnd', handler)
}
onPreToolUse(handler: HookHandler): this {
return this.hook('PreToolUse', handler)
}
onPostToolUse(handler: HookHandler): this {
return this.hook('PostToolUse', handler)
}
onPreFileEdit(handler: HookHandler): this {
return this.hook('PreFileEdit', handler)
}
onPostFileEdit(handler: HookHandler): this {
return this.hook('PostFileEdit', handler)
}
build(security: SecurityManager, hookSystem: HookSystem): Plugin {
if (!this.config.name || !this.config.version || !this.config.author) {
throw new Error('Plugin must have name, version, and author')
}
const plugin = new Plugin(
this.config as PluginConfig,
security,
hookSystem
)
// Register commands
for (const [name, handler] of this.commandHandlers) {
plugin.registerCommand(name, handler)
}
// Register tools
for (const tool of this.toolExtensions) {
plugin.registerTool(tool)
}
// Register hooks
for (const [event, handlers] of this.hookHandlers) {
for (const handler of handlers) {
plugin.on(event, handler)
}
}
// Load config
plugin.loadConfig(Object.fromEntries(this.configValues))
return plugin
}
}
// ============================================================================
// HELPER FUNCTIONS
// ============================================================================
export function createPlugin(
config: PluginConfig,
builder?: (plugin: PluginBuilder) => PluginBuilder
): PluginBuilder {
const pb = new PluginBuilder()
if (config.name) pb.name(config.name)
if (config.version) pb.version(config.version)
if (config.description) pb.description(config.description)
if (config.author) pb.author(config.author)
if (config.license) pb.license(config.license)
if (config.repository) pb.repository(config.repository)
if (config.permissions) pb.permissions(...config.permissions)
if (config.enabled !== undefined) pb.enabled(config.enabled)
return builder ? builder(pb) : pb
}
export function definePlugin(
config: PluginConfig,
definition: (pb: PluginBuilder) => void
): PluginBuilder {
const builder = new PluginBuilder()
// Set basic config
if (config.name) builder.name(config.name)
if (config.version) builder.version(config.version)
if (config.description) builder.description(config.description)
if (config.author) builder.author(config.author)
if (config.license) builder.license(config.license)
if (config.repository) builder.repository(config.repository)
if (config.permissions) builder.permissions(...config.permissions)
if (config.enabled !== undefined) builder.enabled(config.enabled)
// Apply definition
definition(builder)
return builder
}
// ============================================================================
// EXPORTS
// ============================================================================
export default Plugin

View File

@@ -0,0 +1,579 @@
/**
* Claude Code Plugin Manager - Conduit-style Plugin System
*
* Features:
* - GitHub-based plugin discovery
* - Secure plugin installation with validation
* - Version management and updates
* - Dependency resolution
* - Plugin lifecycle management
*/
import fs from 'fs/promises'
import path from 'path'
import { spawn } from 'child_process'
import { createHash } from 'crypto'
// ============================================================================
// TYPES AND INTERFACES
// ============================================================================
export interface PluginMetadata {
name: string
version: string
description: string
author: string
license?: string
repository?: string
homepage?: string
keywords?: string[]
claude: {
minVersion?: string
maxVersion?: string
permissions: string[]
hooks?: HookDefinition[]
commands?: CommandDefinition[]
skills?: SkillDefinition[]
}
dependencies?: Record<string, string>
}
export interface HookDefinition {
event: string
handler: string
priority?: number
condition?: string
}
export interface CommandDefinition {
name: string
description: string
handler: string
permissions?: string[]
}
export interface SkillDefinition {
name: string
description: string
file: string
}
export interface InstalledPlugin {
metadata: PluginMetadata
installPath: string
version: string
installedAt: string
lastUpdated: string
scope: 'global' | 'project'
projectPath?: string
enabled: boolean
integrity: string
}
export interface MarketplaceSource {
type: 'github' | 'directory' | 'npm'
url?: string
repo?: string
path?: string
lastUpdated?: string
}
export interface PluginDiscoveryResult {
name: string
description: string
version: string
author: string
source: string
downloads?: number
stars?: number
updated: string
}
// ============================================================================
// PLUGIN MANAGER CLASS
// ============================================================================
export class PluginManager {
private pluginsDir: string
private cacheDir: string
private marketplacesFile: string
private installedFile: string
private configDir: string
constructor(claudeDir: string = path.join(process.env.HOME || '', '.claude')) {
this.configDir = claudeDir
this.pluginsDir = path.join(claudeDir, 'plugins')
this.cacheDir = path.join(this.pluginsDir, 'cache')
this.marketplacesFile = path.join(this.pluginsDir, 'known_marketplaces.json')
this.installedFile = path.join(this.pluginsDir, 'installed_plugins.json')
}
// ============================================================================
// INITIALIZATION
// ============================================================================
async initialize(): Promise<void> {
await this.ensureDirectories()
await this.loadMarketplaces()
await this.loadInstalledPlugins()
}
private async ensureDirectories(): Promise<void> {
const dirs = [
this.pluginsDir,
this.cacheDir,
path.join(this.pluginsDir, 'marketplaces'),
path.join(this.pluginsDir, 'tmp'),
]
for (const dir of dirs) {
try {
await fs.mkdir(dir, { recursive: true })
} catch (error) {
// Directory might already exist
}
}
}
// ============================================================================
// MARKETPLACE MANAGEMENT
// ============================================================================
async addMarketplace(name: string, source: MarketplaceSource): Promise<void> {
const marketplaces = await this.loadMarketplaces()
marketplaces[name] = {
source,
installLocation: path.join(this.pluginsDir, 'marketplaces', name),
lastUpdated: new Date().toISOString(),
}
await fs.writeFile(
this.marketplacesFile,
JSON.stringify(marketplaces, null, 2)
)
// Clone/download marketplace if it's a GitHub repo
if (source.type === 'github' && source.repo) {
await this.cloneRepository(
`https://github.com/${source.repo}.git`,
path.join(this.pluginsDir, 'marketplaces', name)
)
}
}
async loadMarketplaces(): Promise<Record<string, any>> {
try {
const content = await fs.readFile(this.marketplacesFile, 'utf-8')
return JSON.parse(content)
} catch {
return {}
}
}
// ============================================================================
// PLUGIN DISCOVERY
// ============================================================================
async discoverPlugins(query?: string): Promise<PluginDiscoveryResult[]> {
const marketplaces = await this.loadMarketplaces()
const results: PluginDiscoveryResult[] = []
for (const [name, marketplace]: Object.entries(marketplaces)) {
const mp = marketplace as any
const pluginsPath = path.join(mp.installLocation, 'plugins')
try {
const pluginDirs = await fs.readdir(pluginsPath)
for (const pluginDir of pluginDirs) {
const pluginJsonPath = path.join(
pluginsPath,
pluginDir,
'.claude-plugin',
'plugin.json'
)
try {
const metadata = JSON.parse(
await fs.readFile(pluginJsonPath, 'utf-8')
) as PluginMetadata
// Filter by query if provided
if (
query &&
!metadata.name.toLowerCase().includes(query.toLowerCase()) &&
!metadata.description?.toLowerCase().includes(query.toLowerCase()) &&
!metadata.keywords?.some((k) =>
k.toLowerCase().includes(query.toLowerCase())
)
) {
continue
}
results.push({
name: metadata.name,
description: metadata.description,
version: metadata.version,
author: metadata.author,
source: name,
updated: new Date().toISOString(),
})
} catch {
// Skip invalid plugins
}
}
} catch {
// Marketplace might not have plugins directory
}
}
return results
}
// ============================================================================
// PLUGIN INSTALLATION
// ============================================================================
async installPlugin(
marketplace: string,
pluginName: string,
scope: 'global' | 'project' = 'global'
): Promise<InstalledPlugin> {
const marketplaces = await this.loadMarketplaces()
const mp = marketplaces[marketplace] as any
if (!mp) {
throw new Error(`Marketplace "${marketplace}" not found`)
}
const sourcePath = path.join(mp.installLocation, 'plugins', pluginName)
const pluginJsonPath = path.join(sourcePath, '.claude-plugin', 'plugin.json')
// Read plugin metadata
const metadata: PluginMetadata = JSON.parse(
await fs.readFile(pluginJsonPath, 'utf-8')
)
// Validate permissions
await this.validatePermissions(metadata.claude.permissions)
// Calculate integrity hash
const integrity = await this.calculateIntegrity(sourcePath)
// Install to cache
const versionedPath = path.join(
this.cacheDir,
marketplace,
`${pluginName}-${metadata.version}`
)
await fs.mkdir(versionedPath, { recursive: true })
await this.copyDirectory(sourcePath, versionedPath)
const installedPlugin: InstalledPlugin = {
metadata,
installPath: versionedPath,
version: metadata.version,
installedAt: new Date().toISOString(),
lastUpdated: new Date().toISOString(),
scope,
enabled: true,
integrity,
}
// Register plugin
await this.registerPlugin(installedPlugin)
// Run install script if present
const installScript = path.join(sourcePath, 'install.sh')
try {
await this.runScript(installScript, versionedPath)
} catch {
// No install script or failed
}
return installedPlugin
}
async installFromGitHub(
repo: string,
scope: 'global' | 'project' = 'global'
): Promise<InstalledPlugin> {
const [owner, name] = repo.split('/')
const tempDir = path.join(this.pluginsDir, 'tmp', `${name}-${Date.now()}`)
// Clone repository
await this.cloneRepository(`https://github.com/${repo}.git`, tempDir)
// Read plugin metadata
const pluginJsonPath = path.join(tempDir, '.claude-plugin', 'plugin.json')
const metadata: PluginMetadata = JSON.parse(
await fs.readFile(pluginJsonPath, 'utf-8')
)
// Validate and install
const integrity = await this.calculateIntegrity(tempDir)
const versionedPath = path.join(
this.cacheDir,
'github',
`${name}-${metadata.version}`
)
await fs.mkdir(versionedPath, { recursive: true })
await this.copyDirectory(tempDir, versionedPath)
const installedPlugin: InstalledPlugin = {
metadata,
installPath: versionedPath,
version: metadata.version,
installedAt: new Date().toISOString(),
lastUpdated: new Date().toISOString(),
scope,
enabled: true,
integrity,
}
await this.registerPlugin(installedPlugin)
// Cleanup temp dir
await fs.rm(tempDir, { recursive: true, force: true })
return installedPlugin
}
// ============================================================================
// PLUGIN MANAGEMENT
// ============================================================================
async uninstallPlugin(name: string, marketplace?: string): Promise<void> {
const installed = await this.loadInstalledPlugins()
const key = marketplace ? `${name}@${marketplace}` : name
if (!installed[key]) {
throw new Error(`Plugin "${name}" is not installed`)
}
const plugin = installed[key][0] as InstalledPlugin
// Run uninstall script if present
const uninstallScript = path.join(plugin.installPath, 'uninstall.sh')
try {
await this.runScript(uninstallScript, plugin.installPath)
} catch {
// No uninstall script or failed
}
// Remove from cache
await fs.rm(plugin.installPath, { recursive: true, force: true })
// Unregister
delete installed[key]
await fs.writeFile(
this.installedFile,
JSON.stringify({ version: 2, plugins: installed }, null, 2)
)
}
async enablePlugin(name: string, marketplace?: string): Promise<void> {
const installed = await this.loadInstalledPlugins()
const key = marketplace ? `${name}@${marketplace}` : name
if (installed[key]) {
installed[key][0].enabled = true
await fs.writeFile(
this.installedFile,
JSON.stringify({ version: 2, plugins: installed }, null, 2)
)
}
}
async disablePlugin(name: string, marketplace?: string): Promise<void> {
const installed = await this.loadInstalledPlugins()
const key = marketplace ? `${name}@${marketplace}` : name
if (installed[key]) {
installed[key][0].enabled = false
await fs.writeFile(
this.installedFile,
JSON.stringify({ version: 2, plugins: installed }, null, 2)
)
}
}
async updatePlugin(name: string, marketplace?: string): Promise<void> {
const installed = await this.loadInstalledPlugins()
const key = marketplace ? `${name}@${marketplace}` : name
if (!installed[key]) {
throw new Error(`Plugin "${name}" is not installed`)
}
const plugin = installed[key][0] as InstalledPlugin
// Reinstall to update
if (plugin.metadata.repository) {
await this.installFromGitHub(
plugin.metadata.repository.replace('https://github.com/', ''),
plugin.scope
)
} else {
// Reinstall from marketplace
// Implementation depends on marketplace type
}
}
// ============================================================================
// PLUGIN LOADING
// ============================================================================
async loadInstalledPlugins(): Promise<Record<string, InstalledPlugin[]>> {
try {
const content = await fs.readFile(this.installedFile, 'utf-8')
const data = JSON.parse(content)
return data.plugins || {}
} catch {
return {}
}
}
async getEnabledPlugins(): Promise<InstalledPlugin[]> {
const installed = await this.loadInstalledPlugins()
const enabled: InstalledPlugin[] = []
for (const plugins of Object.values(installed)) {
for (const plugin of plugins) {
if (plugin.enabled) {
enabled.push(plugin as InstalledPlugin)
}
}
}
return enabled
}
// ============================================================================
// SECURITY
// ============================================================================
private async validatePermissions(permissions: string[]): Promise<void> {
const allowedPermissions = [
'read:files',
'write:files',
'execute:commands',
'network:request',
'read:config',
'write:config',
'hook:events',
]
for (const perm of permissions) {
if (!allowedPermissions.includes(perm)) {
throw new Error(`Unknown permission: ${perm}`)
}
}
}
private async calculateIntegrity(dirPath: string): Promise<string> {
const hash = createHash('sha256')
const files = await this.getAllFiles(dirPath)
for (const file of files.sort()) {
const content = await fs.readFile(file)
hash.update(content)
}
return hash.digest('hex')
}
private async getAllFiles(dirPath: string): Promise<string[]> {
const files: string[] = []
const entries = await fs.readdir(dirPath, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name)
if (entry.isDirectory()) {
files.push(...(await this.getAllFiles(fullPath)))
} else {
files.push(fullPath)
}
}
return files
}
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
private async registerPlugin(plugin: InstalledPlugin): Promise<void> {
const installed = await this.loadInstalledPlugins()
const key = `${plugin.metadata.name}@${plugin.installPath.split('/').slice(-2).join('/')}`
if (!installed[key]) {
installed[key] = []
}
installed[key].push(plugin)
await fs.writeFile(
this.installedFile,
JSON.stringify({ version: 2, plugins: installed }, null, 2)
)
}
private async cloneRepository(repoUrl: string, targetPath: string): Promise<void> {
return new Promise((resolve, reject) => {
const git = spawn('git', ['clone', '--depth', '1', repoUrl, targetPath], {
stdio: 'inherit',
})
git.on('close', (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`Git clone failed with code ${code}`))
}
})
})
}
private async copyDirectory(source: string, target: string): Promise<void> {
await fs.mkdir(target, { recursive: true })
const entries = await fs.readdir(source, { withFileTypes: true })
for (const entry of entries) {
const srcPath = path.join(source, entry.name)
const destPath = path.join(target, entry.name)
if (entry.isDirectory()) {
await this.copyDirectory(srcPath, destPath)
} else {
await fs.copyFile(srcPath, destPath)
}
}
}
private async runScript(scriptPath: string, cwd: string): Promise<void> {
return new Promise((resolve, reject) => {
const shell = spawn('bash', [scriptPath], {
cwd,
stdio: 'inherit',
})
shell.on('close', (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`Script failed with code ${code}`))
}
})
})
}
}
// ============================================================================
// EXPORTS
// ============================================================================
export default PluginManager

533
plugins/core/security.ts Normal file
View File

@@ -0,0 +1,533 @@
/**
* Claude Code Plugin Security System
*
* Features:
* - Permission validation
* - File system sandboxing
* - Command execution validation
* - Network access control
* - Resource limits
* - Code injection prevention
*/
import fs from 'fs/promises'
import path from 'path'
import { createHash } from 'crypto'
// ============================================================================
// TYPES AND INTERFACES
// ============================================================================
export type Permission =
| 'read:files'
| 'write:files'
| 'execute:commands'
| 'network:request'
| 'read:config'
| 'write:config'
| 'hook:events'
| 'read:secrets'
export interface SecurityPolicy {
allowedPaths: string[]
deniedPaths: string[]
allowedCommands: string[]
deniedCommands: string[]
allowedDomains: string[]
deniedDomains: string[]
maxFileSize: number
maxExecutionTime: number
requireCodeSigning: boolean
}
export interface SecurityContext {
pluginName: string
permissions: Permission[]
workingDirectory: string
startTime: number
}
export interface ValidationResult {
allowed: boolean
reason?: string
modifiedValue?: any
}
// ============================================================================
// SECURITY MANAGER CLASS
// ============================================================================
export class SecurityManager {
private policy: SecurityPolicy
private contexts: Map<string, SecurityContext> = new Map()
private auditLog: AuditLog[] = []
constructor(policy?: Partial<SecurityPolicy>) {
this.policy = {
allowedPaths: [],
deniedPaths: [],
allowedCommands: [],
deniedCommands: ['rm -rf /', 'format', 'mkfs'],
allowedDomains: [],
deniedDomains: [],
maxFileSize: 100 * 1024 * 1024, // 100MB
maxExecutionTime: 30000, // 30 seconds
requireCodeSigning: false,
...policy,
}
}
// ============================================================================
// CONTEXT MANAGEMENT
// ============================================================================
createContext(pluginName: string, permissions: Permission[]): SecurityContext {
const context: SecurityContext = {
pluginName,
permissions,
workingDirectory: process.cwd(),
startTime: Date.now(),
}
this.contexts.set(pluginName, context)
return context
}
getContext(pluginName: string): SecurityContext | undefined {
return this.contexts.get(pluginName)
}
removeContext(pluginName: string): void {
this.contexts.delete(pluginName)
}
// ============================================================================
// PERMISSION VALIDATION
// ============================================================================
hasPermission(pluginName: string, permission: Permission): boolean {
const context = this.getContext(pluginName)
if (!context) return false
return context.permissions.includes(permission)
}
validatePermissions(
pluginName: string,
requiredPermissions: Permission[]
): ValidationResult {
const context = this.getContext(pluginName)
if (!context) {
return {
allowed: false,
reason: 'Plugin context not found',
}
}
const missing = requiredPermissions.filter(
(perm) => !context.permissions.includes(perm)
)
if (missing.length > 0) {
return {
allowed: false,
reason: `Missing permissions: ${missing.join(', ')}`,
}
}
return { allowed: true }
}
// ============================================================================
// FILE SYSTEM VALIDATION
// ============================================================================
async validateFileAccess(
pluginName: string,
filePath: string,
mode: 'read' | 'write'
): Promise<ValidationResult> {
const permission = mode === 'read' ? 'read:files' : 'write:files'
if (!this.hasPermission(pluginName, permission)) {
return {
allowed: false,
reason: `Missing permission: ${permission}`,
}
}
const resolvedPath = path.resolve(filePath)
// Check denied paths first
for (const denied of this.policy.deniedPaths) {
if (resolvedPath.startsWith(path.resolve(denied))) {
return {
allowed: false,
reason: `Access denied to path: ${filePath}`,
}
}
}
// If allowed paths are specified, check against them
if (this.policy.allowedPaths.length > 0) {
const allowed = this.policy.allowedPaths.some((allowedPath) =>
resolvedPath.startsWith(path.resolve(allowedPath))
)
if (!allowed) {
return {
allowed: false,
reason: `Path not in allowed list: ${filePath}`,
}
}
}
// Check file size for writes
if (mode === 'write') {
try {
const stats = await fs.stat(resolvedPath)
if (stats.size > this.policy.maxFileSize) {
return {
allowed: false,
reason: `File too large: ${stats.size} bytes`,
}
}
} catch {
// File doesn't exist yet, that's fine for writes
}
}
return { allowed: true }
}
// ============================================================================
// COMMAND VALIDATION
// ============================================================================
validateCommand(pluginName: string, command: string): ValidationResult {
if (!this.hasPermission(pluginName, 'execute:commands')) {
return {
allowed: false,
reason: 'Missing permission: execute:commands',
}
}
// Check denied commands
for (const denied of this.policy.deniedCommands) {
if (command.includes(denied)) {
return {
allowed: false,
reason: `Command contains denied pattern: ${denied}`,
}
}
}
// Check for dangerous patterns
const dangerousPatterns = [
/\brm\s+-rf\s+\//,
/\bformat\s+[a-z]:/i,
/\bdel\s+\/[sq]/i,
/\>\s*\/dev\/[a-z]+/,
/\|.*\brm\b/,
/&&.*\brm\b/,
/;.*\brm\b/,
]
for (const pattern of dangerousPatterns) {
if (pattern.test(command)) {
return {
allowed: false,
reason: 'Command contains dangerous pattern',
}
}
}
return { allowed: true }
}
// ============================================================================
// NETWORK VALIDATION
// ============================================================================
validateNetworkRequest(pluginName: string, url: string): ValidationResult {
if (!this.hasPermission(pluginName, 'network:request')) {
return {
allowed: false,
reason: 'Missing permission: network:request',
}
}
try {
const urlObj = new URL(url)
const domain = urlObj.hostname
// Check denied domains
for (const denied of this.policy.deniedDomains) {
if (domain === denied || domain.endsWith(`.${denied}`)) {
return {
allowed: false,
reason: `Access denied to domain: ${domain}`,
}
}
}
// If allowed domains are specified, check against them
if (this.policy.allowedDomains.length > 0) {
const allowed = this.policy.allowedDomains.some(
(allowed) => domain === allowed || domain.endsWith(`.${allowed}`)
)
if (!allowed) {
return {
allowed: false,
reason: `Domain not in allowed list: ${domain}`,
}
}
}
} catch {
return {
allowed: false,
reason: 'Invalid URL',
}
}
return { allowed: true }
}
// ============================================================================
// CODE INJECTION PREVENTION
// ============================================================================
sanitizeInput(input: string): string {
// Remove potential code injection patterns
return input
.replace(/<script[^>]*>.*?<\/script>/gi, '')
.replace(/javascript:/gi, '')
.replace(/on\w+\s*=/gi, '')
.replace(/\${.*?}/g, '') // Template literals
.replace(/`.*?`/g, '') // Backtick expressions
.replace(/eval\s*\(/gi, '')
.replace(/Function\s*\(/gi, '')
.replace(/setTimeout\s*\(/gi, '')
.replace(/setInterval\s*\(/gi, '')
}
validateScriptCode(code: string): ValidationResult {
const dangerousPatterns = [
/eval\s*\(/,
/Function\s*\(/,
/require\s*\(\s*['"`]fs['"`]\s*\)/,
/require\s*\(\s*['"`]child_process['"`]\s*\)/,
/process\s*\.\s*exit/,
/\.\.\//, // Path traversal
/~\//, // Home directory access
]
for (const pattern of dangerousPatterns) {
if (pattern.test(code)) {
return {
allowed: false,
reason: `Code contains dangerous pattern: ${pattern.source}`,
}
}
}
return { allowed: true }
}
// ============================================================================
// INTEGRITY CHECKING
// ============================================================================
async calculateFileIntegrity(filePath: string): Promise<string> {
const content = await fs.readFile(filePath)
return createHash('sha256').update(content).digest('hex')
}
async verifyPluginIntegrity(
pluginPath: string,
expectedIntegrity: string
): Promise<boolean> {
const actualIntegrity = await this.calculateDirectoryIntegrity(pluginPath)
return actualIntegrity === expectedIntegrity
}
async calculateDirectoryIntegrity(dirPath: string): Promise<string> {
const hash = createHash('sha256')
const files = await this.getAllFiles(dirPath)
for (const file of files.sort()) {
const content = await fs.readFile(file)
hash.update(content)
}
return hash.digest('hex')
}
private async getAllFiles(dirPath: string): Promise<string[]> {
const files: string[] = []
const entries = await fs.readdir(dirPath, { withFileTypes: true })
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name)
if (entry.isDirectory()) {
files.push(...(await this.getAllFiles(fullPath)))
} else {
files.push(fullPath)
}
}
return files
}
// ============================================================================
// AUDIT LOGGING
// ============================================================================
logAccess(
pluginName: string,
action: string,
resource: string,
allowed: boolean
): void {
const entry: AuditLog = {
timestamp: new Date().toISOString(),
pluginName,
action,
resource,
allowed,
}
this.auditLog.push(entry)
// Keep only last 1000 entries
if (this.auditLog.length > 1000) {
this.auditLog = this.auditLog.slice(-1000)
}
}
getAuditLog(pluginName?: string): AuditLog[] {
if (pluginName) {
return this.auditLog.filter((entry) => entry.pluginName === pluginName)
}
return [...this.auditLog]
}
clearAuditLog(): void {
this.auditLog = []
}
}
// ============================================================================
// TYPES
// ============================================================================
interface AuditLog {
timestamp: string
pluginName: string
action: string
resource: string
allowed: boolean
}
// ============================================================================
// SANDBOX CLASS - Isolated Execution Environment
// ============================================================================
export class Sandbox {
private security: SecurityManager
private context: SecurityContext
constructor(security: SecurityManager, context: SecurityContext) {
this.security = security
this.context = context
}
async readFile(filePath: string): Promise<string> {
const validation = await this.security.validateFileAccess(
this.context.pluginName,
filePath,
'read'
)
if (!validation.allowed) {
this.security.logAccess(
this.context.pluginName,
'read:file',
filePath,
false
)
throw new Error(validation.reason)
}
this.security.logAccess(
this.context.pluginName,
'read:file',
filePath,
true
)
return await fs.readFile(filePath, 'utf-8')
}
async writeFile(filePath: string, content: string): Promise<void> {
const validation = await this.security.validateFileAccess(
this.context.pluginName,
filePath,
'write'
)
if (!validation.allowed) {
this.security.logAccess(
this.context.pluginName,
'write:file',
filePath,
false
)
throw new Error(validation.reason)
}
this.security.logAccess(
this.context.pluginName,
'write:file',
filePath,
true
)
await fs.writeFile(filePath, content, 'utf-8')
}
executeCommand(command: string): Promise<string> {
const validation = this.security.validateCommand(
this.context.pluginName,
command
)
if (!validation.allowed) {
this.security.logAccess(
this.context.pluginName,
'execute:command',
command,
false
)
throw new Error(validation.reason)
}
this.security.logAccess(
this.context.pluginName,
'execute:command',
command,
true
)
// Command execution would be implemented here
return Promise.resolve('')
}
}
// ============================================================================
// EXPORTS
// ============================================================================
export default SecurityManager