codymaster 4.5.1 → 4.5.2

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.
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * šŸ› ļø CodyMaster Security Fixer
4
+ * Automated remediation for security vulnerabilities and secret leaks.
5
+ *
6
+ * Usage:
7
+ * node scripts/security-fixer.js # Audit only
8
+ * node scripts/security-fixer.js --fix # Audit + Fix
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { execSync } = require('child_process');
14
+
15
+ const DANGEROUS_PATTERNS = [
16
+ { name: 'Service Key Variable', regex: /(?:SERVICE_KEY|SERVICE_ROLE)\s*[=:]\s*['\"][a-zA-Z0-9._\/-]{20,}/g },
17
+ { name: 'Anon Key Variable', regex: /ANON_KEY\s*[=:]\s*['\"][a-zA-Z0-9._\/-]{20,}/g },
18
+ { name: 'Private Key Block', regex: /-----BEGIN\s+(RSA|EC|DSA|OPENSSH)?\s*PRIVATE KEY-----/g },
19
+ { name: 'JWT Token', regex: /eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
20
+ { name: 'Generic API Key', regex: /(?:api[_-]?key|api[_-]?secret|access[_-]?token)\s*[=:]\s*['\"][a-zA-Z0-9\/+=]{20,}['\"/]/gi },
21
+ { name: 'AWS Key', regex: /AKIA[0-9A-Z]{16}/g },
22
+ { name: 'Slack Token', regex: /xox[baprs]-[0-9a-zA-Z-]{10,}/g },
23
+ { name: 'GitHub Token', regex: /gh[ps]_[a-zA-Z0-9]{36,}/g },
24
+ { name: 'Stripe Key', regex: /[sr]k_(test|live)_[a-zA-Z0-9]{20,}/g },
25
+ { name: 'DB Password', regex: /(?:DB_PASSWORD|DATABASE_URL)\s*[=:]\s*['\"][^'\"]{8,}/gi },
26
+ ];
27
+
28
+ const SKIP_DIRS = ['node_modules', '.git', 'dist', '.wrangler', '.next', 'coverage', '.cm'];
29
+ const SCAN_EXTS = ['.js', '.ts', '.jsx', '.tsx', '.json', '.toml', '.yaml', '.yml',
30
+ '.env', '.cfg', '.conf', '.ini', '.md', '.html', '.jsonc'];
31
+
32
+ const args = process.argv.slice(2);
33
+ const shouldFix = args.includes('--fix');
34
+ const isDryRun = args.includes('--dry-run');
35
+
36
+ let findings = [];
37
+
38
+ function scanDir(dir) {
39
+ try {
40
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
41
+ for (const entry of entries) {
42
+ if (SKIP_DIRS.includes(entry.name)) continue;
43
+ const fullPath = path.join(dir, entry.name);
44
+ if (entry.isDirectory()) {
45
+ scanDir(fullPath);
46
+ } else if (entry.isFile() && SCAN_EXTS.some(ext => entry.name.endsWith(ext))) {
47
+ const content = fs.readFileSync(fullPath, 'utf-8');
48
+ let fileStatus = { file: fullPath, issues: [] };
49
+
50
+ for (const pattern of DANGEROUS_PATTERNS) {
51
+ const matches = content.match(pattern.regex);
52
+ if (matches) {
53
+ fileStatus.issues.push({ pattern: pattern.name, matches: matches });
54
+ }
55
+ }
56
+
57
+ if (fileStatus.issues.length > 0) {
58
+ findings.push(fileStatus);
59
+ }
60
+ }
61
+ }
62
+ } catch (e) {}
63
+ }
64
+
65
+ console.log('šŸ” Starting Security Audit...');
66
+ scanDir('.');
67
+
68
+ if (findings.length === 0) {
69
+ console.log('āœ… No vulnerabilities detected.');
70
+ process.exit(0);
71
+ }
72
+
73
+ console.error(`āŒ Found ${findings.length} files with issues:`);
74
+ findings.forEach(f => {
75
+ console.error(` ⚠ ${f.file}`);
76
+ f.issues.forEach(i => console.error(` - ${i.pattern}: ${i.matches.length} matches`));
77
+ });
78
+
79
+ if (shouldFix) {
80
+ console.log('\nšŸ”§ Applying Automated Remediation...');
81
+ findings.forEach(f => {
82
+ let content = fs.readFileSync(f.file, 'utf-8');
83
+ let originalContent = content;
84
+
85
+ f.issues.forEach(issue => {
86
+ issue.matches.forEach(match => {
87
+ const half = Math.floor(match.length / 2);
88
+ const prefix = match.substring(0, Math.max(4, match.length - 12));
89
+ const masked = prefix + '*'.repeat(match.length - prefix.length);
90
+ content = content.replace(match, masked);
91
+ });
92
+ });
93
+
94
+ if (content !== originalContent) {
95
+ if (isDryRun) {
96
+ console.log(` [DRY RUN] Would mask secrets in ${f.file}`);
97
+ } else {
98
+ fs.writeFileSync(f.file, content);
99
+ console.log(` āœ… Masked secrets in ${f.file}`);
100
+ }
101
+ }
102
+
103
+ // Auto-gitignore check
104
+ if (!SKIP_DIRS.includes(path.basename(f.file))) {
105
+ const gitIgnorePath = path.join(path.dirname(f.file), '.gitignore');
106
+ const fileName = path.basename(f.file);
107
+ if (fs.existsSync(gitIgnorePath)) {
108
+ const gitIgnoreContent = fs.readFileSync(gitIgnorePath, 'utf-8');
109
+ if (!gitIgnoreContent.includes(fileName)) {
110
+ if (isDryRun) {
111
+ console.log(` [DRY RUN] Would add ${fileName} to ${gitIgnorePath}`);
112
+ } else {
113
+ fs.appendFileSync(gitIgnorePath, `\n${fileName}\n`);
114
+ console.log(` āœ… Added ${fileName} to .gitignore`);
115
+ }
116
+ }
117
+ }
118
+ }
119
+ });
120
+
121
+ // Dependency check
122
+ console.log('\nšŸ“¦ Checking for dependency vulnerabilities...');
123
+ try {
124
+ execSync('npm audit fix', { stdio: 'inherit' });
125
+ } catch (e) {
126
+ console.error(' āš ļø npm audit fix failed or found issues that require manual attention.');
127
+ }
128
+ } else {
129
+ console.log('\nšŸ’” Tip: Run with --fix to apply automated remediation.');
130
+ }
131
+
132
+ // Generate Report
133
+ const report = {
134
+ timestamp: new Date().toISOString(),
135
+ summary: {
136
+ total_files_scanned: findings.length, // approximation of issues
137
+ total_issues: findings.reduce((acc, f) => acc + f.issues.length, 0),
138
+ },
139
+ findings: findings
140
+ };
141
+
142
+ fs.writeFileSync('security-report.json', JSON.stringify(report, null, 2));
143
+ console.log('\nšŸ“Š Security report generated: security-report.json');
@@ -0,0 +1,55 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const DANGEROUS_PATTERNS = [
5
+ { name: 'Service Key Variable', regex: /(?:SERVICE_KEY|SERVICE_ROLE)\s*[=:]\s*['\"][a-zA-Z0-9._\/-]{20,}/g },
6
+ { name: 'Anon Key Variable', regex: /ANON_KEY\s*[=:]\s*['\"][a-zA-Z0-9._\/-]{20,}/g },
7
+ { name: 'Private Key Block', regex: /-----BEGIN\s+(RSA|EC|DSA|OPENSSH)?\s*PRIVATE KEY-----/g },
8
+ { name: 'JWT Token', regex: /eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/g },
9
+ { name: 'Generic API Key', regex: /(?:api[_-]?key|api[_-]?secret|access[_-]?token)\s*[=:]\s*['\"][a-zA-Z0-9\/+=]{20,}['\"/]/gi },
10
+ { name: 'AWS Key', regex: /AKIA[0-9A-Z]{16}/g },
11
+ { name: 'Slack Token', regex: /xox[baprs]-[0-9a-zA-Z-]{10,}/g },
12
+ { name: 'GitHub Token', regex: /gh[ps]_[a-zA-Z0-9]{36,}/g },
13
+ { name: 'Stripe Key', regex: /[sr]k_(test|live)_[a-zA-Z0-9]{20,}/g },
14
+ { name: 'DB Password', regex: /(?:DB_PASSWORD|DATABASE_URL)\s*[=:]\s*['\"][^'\"]{8,}/gi },
15
+ ];
16
+
17
+ const SKIP_DIRS = ['node_modules', '.git', 'dist', '.wrangler', '.next', 'coverage'];
18
+ const SCAN_EXTS = ['.js', '.ts', '.jsx', '.tsx', '.json', '.toml', '.yaml', '.yml',
19
+ '.env', '.cfg', '.conf', '.ini', '.md', '.html', '.jsonc'];
20
+
21
+ let findings = [];
22
+
23
+ function scanDir(dir) {
24
+ try {
25
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
26
+ for (const entry of entries) {
27
+ if (SKIP_DIRS.includes(entry.name)) continue;
28
+ const fullPath = path.join(dir, entry.name);
29
+ if (entry.isDirectory()) {
30
+ scanDir(fullPath);
31
+ } else if (entry.isFile() && SCAN_EXTS.some(ext => entry.name.endsWith(ext))) {
32
+ const content = fs.readFileSync(fullPath, 'utf-8');
33
+ for (const pattern of DANGEROUS_PATTERNS) {
34
+ const matches = content.match(pattern.regex);
35
+ if (matches) {
36
+ findings.push({ file: fullPath, pattern: pattern.name, count: matches.length });
37
+ }
38
+ }
39
+ }
40
+ }
41
+ } catch (e) { /* skip unreadable dirs */ }
42
+ }
43
+
44
+ scanDir('.');
45
+
46
+ if (findings.length > 0) {
47
+ console.error('āŒ SECRET SCAN FOUND ' + findings.length + ' POTENTIAL ISSUES:');
48
+ findings.forEach(f => {
49
+ console.error(' ⚠ ' + f.file + ' — ' + f.pattern + ' (' + f.count + ' match(es))');
50
+ });
51
+ console.error('');
52
+ process.exit(1);
53
+ } else {
54
+ console.log('āœ… Repo scan: no secrets detected');
55
+ }
@@ -0,0 +1,13 @@
1
+ const { exec, spawn } = require('child_process');
2
+
3
+ // Method 1: exec
4
+ exec('gemini -y -p "Hello!"', (err, stdout, stderr) => {
5
+ console.log('EXEC Result:', stdout || stderr);
6
+ });
7
+
8
+ // Method 2: spawn
9
+ const child = spawn('gemini', ['-y', '-p', 'Hello!']);
10
+ let out = '';
11
+ child.stdout.on('data', d => out += d);
12
+ child.stderr.on('data', d => out += d);
13
+ child.on('close', () => console.log('SPAWN Result:', out));
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cm-dashboard Todo Bridge — Phase 1 (Claude Code PostToolUse hook)
4
+ *
5
+ * Invoked by Claude Code as a PostToolUse hook on every TodoWrite event.
6
+ * Reads the hook payload from stdin, maps todos to dashboard tasks,
7
+ * and POSTs each to /api/tasks/auto-sync.
8
+ *
9
+ * Install:
10
+ * cp scripts/todo-bridge.js ~/.claude/scripts/todo-bridge.js
11
+ *
12
+ * Hook config (~/.claude/settings.json):
13
+ * {
14
+ * "hooks": {
15
+ * "PostToolUse": [{
16
+ * "matcher": "TodoWrite",
17
+ * "hooks": [{ "type": "command", "command": "node ~/.claude/scripts/todo-bridge.js" }]
18
+ * }]
19
+ * }
20
+ * }
21
+ */
22
+
23
+ 'use strict';
24
+
25
+ const http = require('http');
26
+ const path = require('path');
27
+
28
+ const DASHBOARD_PORT = process.env.CM_DASHBOARD_PORT || 6969;
29
+
30
+ // Map TodoWrite status → dashboard column
31
+ const STATUS_MAP = {
32
+ pending: 'backlog',
33
+ in_progress: 'in-progress',
34
+ completed: 'done',
35
+ };
36
+
37
+ // Map TodoWrite priority → dashboard priority
38
+ const PRIORITY_MAP = {
39
+ high: 'high',
40
+ medium: 'medium',
41
+ low: 'low',
42
+ urgent: 'urgent',
43
+ };
44
+
45
+ function postJson(payload) {
46
+ return new Promise((resolve) => {
47
+ const body = JSON.stringify(payload);
48
+ const req = http.request(
49
+ {
50
+ hostname: 'localhost',
51
+ port: DASHBOARD_PORT,
52
+ path: '/api/tasks/auto-sync',
53
+ method: 'POST',
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ 'Content-Length': Buffer.byteLength(body),
57
+ },
58
+ },
59
+ (res) => {
60
+ res.resume(); // drain
61
+ resolve(res.statusCode);
62
+ }
63
+ );
64
+ req.on('error', () => resolve(null)); // silent — dashboard may not be running
65
+ req.write(body);
66
+ req.end();
67
+ });
68
+ }
69
+
70
+ async function main() {
71
+ // Read stdin
72
+ const chunks = [];
73
+ for await (const chunk of process.stdin) chunks.push(chunk);
74
+ const raw = Buffer.concat(chunks).toString('utf8').trim();
75
+ if (!raw) process.exit(0);
76
+
77
+ let payload;
78
+ try {
79
+ payload = JSON.parse(raw);
80
+ } catch {
81
+ process.exit(0); // not JSON — ignore
82
+ }
83
+
84
+ // Only handle TodoWrite
85
+ if (payload.tool_name !== 'TodoWrite') process.exit(0);
86
+
87
+ const todos = payload.tool_input?.todos;
88
+ if (!Array.isArray(todos) || todos.length === 0) process.exit(0);
89
+
90
+ const sessionId = payload.session_id || 'unknown';
91
+ const cwd = payload.cwd || process.cwd();
92
+ const projectName = path.basename(cwd);
93
+
94
+ // Fire all syncs concurrently
95
+ await Promise.all(
96
+ todos.map((todo) => {
97
+ const column = STATUS_MAP[todo.status] || 'backlog';
98
+ return postJson({
99
+ conversationId: `${sessionId}:${todo.id}`,
100
+ title: todo.content,
101
+ status: column,
102
+ agent: 'claude-code',
103
+ priority: PRIORITY_MAP[todo.priority] || 'medium',
104
+ projectName,
105
+ });
106
+ })
107
+ );
108
+
109
+ process.exit(0);
110
+ }
111
+
112
+ main().catch(() => process.exit(0)); // always silent