mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-02-07 23:23:08 +08:00
Major OpenCode integration overhaul: - llms.txt: Comprehensive OpenCode documentation for LLMs (642 lines) - .opencode/plugins/ecc-hooks.ts: All Claude Code hooks translated to OpenCode's plugin system - .opencode/tools/*.ts: 3 custom tools (run-tests, check-coverage, security-audit) - .opencode/commands/*.md: All 24 commands in OpenCode format - .opencode/package.json: npm package structure for opencode-ecc - .opencode/index.ts: Main plugin entry point - Delete incorrect LIMITATIONS.md (hooks ARE supported via plugins) - Rewrite MIGRATION.md with correct hook event mapping - Update README.md OpenCode section to show full feature parity OpenCode has 20+ events vs Claude Code's 3 phases: - PreToolUse → tool.execute.before - PostToolUse → tool.execute.after - Stop → session.idle - SessionStart → session.created - SessionEnd → session.deleted - Plus: file.edited, file.watcher.updated, permission.asked, todo.updated - 12 agents: Full parity - 24 commands: Full parity (+1 from original 23) - 16 skills: Full parity - Hooks: OpenCode has MORE (20+ events vs 3 phases) - Custom Tools: 3 native OpenCode tools The OpenCode configuration can now be: 1. Used directly: cd everything-claude-code && opencode 2. Installed via npm: npm install opencode-ecc
278 lines
8.6 KiB
TypeScript
278 lines
8.6 KiB
TypeScript
/**
|
|
* Security Audit Tool
|
|
*
|
|
* Custom OpenCode tool to run security audits on dependencies and code.
|
|
* Combines npm audit, secret scanning, and OWASP checks.
|
|
*
|
|
* NOTE: This tool SCANS for security anti-patterns - it does not introduce them.
|
|
* The regex patterns below are used to DETECT potential issues in user code.
|
|
*/
|
|
|
|
import { tool } from "@opencode-ai/plugin"
|
|
import * as path from "path"
|
|
import * as fs from "fs"
|
|
|
|
export default tool({
|
|
description:
|
|
"Run a comprehensive security audit including dependency vulnerabilities, secret scanning, and common security issues.",
|
|
args: {
|
|
type: tool.schema
|
|
.enum(["all", "dependencies", "secrets", "code"])
|
|
.optional()
|
|
.describe("Type of audit to run (default: all)"),
|
|
fix: tool.schema
|
|
.boolean()
|
|
.optional()
|
|
.describe("Attempt to auto-fix dependency vulnerabilities (default: false)"),
|
|
severity: tool.schema
|
|
.enum(["low", "moderate", "high", "critical"])
|
|
.optional()
|
|
.describe("Minimum severity level to report (default: moderate)"),
|
|
},
|
|
async execute(args, context) {
|
|
const auditType = args.type ?? "all"
|
|
const fix = args.fix ?? false
|
|
const severity = args.severity ?? "moderate"
|
|
const cwd = context.worktree || context.directory
|
|
|
|
const results: AuditResults = {
|
|
timestamp: new Date().toISOString(),
|
|
directory: cwd,
|
|
checks: [],
|
|
summary: {
|
|
passed: 0,
|
|
failed: 0,
|
|
warnings: 0,
|
|
},
|
|
}
|
|
|
|
// Check for dependencies audit
|
|
if (auditType === "all" || auditType === "dependencies") {
|
|
results.checks.push({
|
|
name: "Dependency Vulnerabilities",
|
|
description: "Check for known vulnerabilities in dependencies",
|
|
command: fix ? "npm audit fix" : "npm audit",
|
|
severityFilter: severity,
|
|
status: "pending",
|
|
})
|
|
}
|
|
|
|
// Check for secrets
|
|
if (auditType === "all" || auditType === "secrets") {
|
|
const secretPatterns = await scanForSecrets(cwd)
|
|
if (secretPatterns.length > 0) {
|
|
results.checks.push({
|
|
name: "Secret Detection",
|
|
description: "Scan for hardcoded secrets and API keys",
|
|
status: "failed",
|
|
findings: secretPatterns,
|
|
})
|
|
results.summary.failed++
|
|
} else {
|
|
results.checks.push({
|
|
name: "Secret Detection",
|
|
description: "Scan for hardcoded secrets and API keys",
|
|
status: "passed",
|
|
})
|
|
results.summary.passed++
|
|
}
|
|
}
|
|
|
|
// Check for common code security issues
|
|
if (auditType === "all" || auditType === "code") {
|
|
const codeIssues = await scanCodeSecurity(cwd)
|
|
if (codeIssues.length > 0) {
|
|
results.checks.push({
|
|
name: "Code Security",
|
|
description: "Check for common security anti-patterns",
|
|
status: "warning",
|
|
findings: codeIssues,
|
|
})
|
|
results.summary.warnings++
|
|
} else {
|
|
results.checks.push({
|
|
name: "Code Security",
|
|
description: "Check for common security anti-patterns",
|
|
status: "passed",
|
|
})
|
|
results.summary.passed++
|
|
}
|
|
}
|
|
|
|
// Generate recommendations
|
|
results.recommendations = generateRecommendations(results)
|
|
|
|
return results
|
|
},
|
|
})
|
|
|
|
interface AuditCheck {
|
|
name: string
|
|
description: string
|
|
command?: string
|
|
severityFilter?: string
|
|
status: "pending" | "passed" | "failed" | "warning"
|
|
findings?: Array<{ file: string; issue: string; line?: number }>
|
|
}
|
|
|
|
interface AuditResults {
|
|
timestamp: string
|
|
directory: string
|
|
checks: AuditCheck[]
|
|
summary: {
|
|
passed: number
|
|
failed: number
|
|
warnings: number
|
|
}
|
|
recommendations?: string[]
|
|
}
|
|
|
|
async function scanForSecrets(
|
|
cwd: string
|
|
): Promise<Array<{ file: string; issue: string; line?: number }>> {
|
|
const findings: Array<{ file: string; issue: string; line?: number }> = []
|
|
|
|
// Patterns to DETECT potential secrets (security scanning)
|
|
const secretPatterns = [
|
|
{ pattern: /api[_-]?key\s*[:=]\s*['"][^'"]{20,}['"]/gi, name: "API Key" },
|
|
{ pattern: /password\s*[:=]\s*['"][^'"]+['"]/gi, name: "Password" },
|
|
{ pattern: /secret\s*[:=]\s*['"][^'"]{10,}['"]/gi, name: "Secret" },
|
|
{ pattern: /Bearer\s+[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+/g, name: "JWT Token" },
|
|
{ pattern: /sk-[a-zA-Z0-9]{32,}/g, name: "OpenAI API Key" },
|
|
{ pattern: /ghp_[a-zA-Z0-9]{36}/g, name: "GitHub Token" },
|
|
{ pattern: /aws[_-]?secret[_-]?access[_-]?key/gi, name: "AWS Secret" },
|
|
]
|
|
|
|
const ignorePatterns = [
|
|
"node_modules",
|
|
".git",
|
|
"dist",
|
|
"build",
|
|
".env.example",
|
|
".env.template",
|
|
]
|
|
|
|
const srcDir = path.join(cwd, "src")
|
|
if (fs.existsSync(srcDir)) {
|
|
await scanDirectory(srcDir, secretPatterns, ignorePatterns, findings)
|
|
}
|
|
|
|
// Also check root config files
|
|
const configFiles = ["config.js", "config.ts", "settings.js", "settings.ts"]
|
|
for (const configFile of configFiles) {
|
|
const filePath = path.join(cwd, configFile)
|
|
if (fs.existsSync(filePath)) {
|
|
await scanFile(filePath, secretPatterns, findings)
|
|
}
|
|
}
|
|
|
|
return findings
|
|
}
|
|
|
|
async function scanDirectory(
|
|
dir: string,
|
|
patterns: Array<{ pattern: RegExp; name: string }>,
|
|
ignorePatterns: string[],
|
|
findings: Array<{ file: string; issue: string; line?: number }>
|
|
): Promise<void> {
|
|
if (!fs.existsSync(dir)) return
|
|
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name)
|
|
|
|
if (ignorePatterns.some((p) => fullPath.includes(p))) continue
|
|
|
|
if (entry.isDirectory()) {
|
|
await scanDirectory(fullPath, patterns, ignorePatterns, findings)
|
|
} else if (entry.isFile() && entry.name.match(/\.(ts|tsx|js|jsx|json)$/)) {
|
|
await scanFile(fullPath, patterns, findings)
|
|
}
|
|
}
|
|
}
|
|
|
|
async function scanFile(
|
|
filePath: string,
|
|
patterns: Array<{ pattern: RegExp; name: string }>,
|
|
findings: Array<{ file: string; issue: string; line?: number }>
|
|
): Promise<void> {
|
|
try {
|
|
const content = fs.readFileSync(filePath, "utf-8")
|
|
const lines = content.split("\n")
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i]
|
|
for (const { pattern, name } of patterns) {
|
|
// Reset regex state
|
|
pattern.lastIndex = 0
|
|
if (pattern.test(line)) {
|
|
findings.push({
|
|
file: filePath,
|
|
issue: `Potential ${name} found`,
|
|
line: i + 1,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
// Ignore read errors
|
|
}
|
|
}
|
|
|
|
async function scanCodeSecurity(
|
|
cwd: string
|
|
): Promise<Array<{ file: string; issue: string; line?: number }>> {
|
|
const findings: Array<{ file: string; issue: string; line?: number }> = []
|
|
|
|
// Patterns to DETECT security anti-patterns (this tool scans for issues)
|
|
// These are detection patterns, not code that uses these anti-patterns
|
|
const securityPatterns = [
|
|
{ pattern: /\beval\s*\(/g, name: "eval() usage - potential code injection" },
|
|
{ pattern: /innerHTML\s*=/g, name: "innerHTML assignment - potential XSS" },
|
|
{ pattern: /dangerouslySetInnerHTML/g, name: "dangerouslySetInnerHTML - potential XSS" },
|
|
{ pattern: /document\.write/g, name: "document.write - potential XSS" },
|
|
{ pattern: /\$\{.*\}.*sql/gi, name: "Potential SQL injection" },
|
|
]
|
|
|
|
const srcDir = path.join(cwd, "src")
|
|
if (fs.existsSync(srcDir)) {
|
|
await scanDirectory(srcDir, securityPatterns, ["node_modules", ".git", "dist"], findings)
|
|
}
|
|
|
|
return findings
|
|
}
|
|
|
|
function generateRecommendations(results: AuditResults): string[] {
|
|
const recommendations: string[] = []
|
|
|
|
for (const check of results.checks) {
|
|
if (check.status === "failed" && check.name === "Secret Detection") {
|
|
recommendations.push(
|
|
"CRITICAL: Remove hardcoded secrets and use environment variables instead"
|
|
)
|
|
recommendations.push("Add a .env file (gitignored) for local development")
|
|
recommendations.push("Use a secrets manager for production deployments")
|
|
}
|
|
|
|
if (check.status === "warning" && check.name === "Code Security") {
|
|
recommendations.push(
|
|
"Review flagged code patterns for potential security vulnerabilities"
|
|
)
|
|
recommendations.push("Consider using DOMPurify for HTML sanitization")
|
|
recommendations.push("Use parameterized queries for database operations")
|
|
}
|
|
|
|
if (check.status === "pending" && check.name === "Dependency Vulnerabilities") {
|
|
recommendations.push("Run 'npm audit' to check for dependency vulnerabilities")
|
|
recommendations.push("Consider using 'npm audit fix' to auto-fix issues")
|
|
}
|
|
}
|
|
|
|
if (recommendations.length === 0) {
|
|
recommendations.push("No critical security issues found. Continue following security best practices.")
|
|
}
|
|
|
|
return recommendations
|
|
}
|