@the-bearded-bear/claude-craft 8.2.4 → 8.2.5

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.
@@ -39,27 +39,44 @@ if [[ "$FORMAT" != "markdown" && "$FORMAT" != "plain" ]]; then
39
39
  exit 1
40
40
  fi
41
41
 
42
+ # Validate --output: relative path only, no traversal, no shell metachars
43
+ if [[ "$OUTPUT" = /* ]] || [[ "$OUTPUT" == *".."* ]] || [[ "$OUTPUT" =~ [[:space:]\;\|\&\$\`\(\)\<\>] ]]; then
44
+ echo "❌ Invalid --output: must be a relative path without '..' or shell metacharacters" >&2
45
+ exit 1
46
+ fi
47
+
48
+ # Validate --exclude and --include: no shell metachars (prevents eval/grep injection)
49
+ if [[ -n "$EXCLUDE_EXTRA" && "$EXCLUDE_EXTRA" =~ [[:space:]\;\|\&\$\`\(\)\<\>] ]]; then
50
+ echo "❌ Invalid --exclude: shell metacharacters are not allowed" >&2
51
+ exit 1
52
+ fi
53
+ if [[ -n "$INCLUDE" && "$INCLUDE" =~ [[:space:]\;\|\&\$\`\(\)\<\>] ]]; then
54
+ echo "❌ Invalid --include: shell metacharacters are not allowed" >&2
55
+ exit 1
56
+ fi
57
+
42
58
  echo "📦 Pack repo (fallback shell) → $OUTPUT"
43
59
  echo " Format: $FORMAT | Root: $(pwd)"
44
60
 
45
- # Build find excludes
46
- FIND_EXCLUDES=""
61
+ # Build find args as an array (no eval, no injection)
62
+ FIND_ARGS=()
47
63
  for excl in "${DEFAULT_EXCLUDES[@]}"; do
48
- FIND_EXCLUDES+=" -not -path '*/$excl/*' -not -path '*/$excl'"
64
+ FIND_ARGS+=(-not -path "*/$excl/*" -not -path "*/$excl")
49
65
  done
50
66
  if [[ -n "$EXCLUDE_EXTRA" ]]; then
51
- FIND_EXCLUDES+=" -not -path '*$EXCLUDE_EXTRA*'"
67
+ FIND_ARGS+=(-not -path "*$EXCLUDE_EXTRA*")
52
68
  fi
53
69
 
54
70
  # Collect files — respect .gitignore si disponible
55
71
  if command -v git &>/dev/null && git rev-parse --git-dir &>/dev/null; then
56
72
  FILES=$(git ls-files --cached --others --exclude-standard 2>/dev/null | grep -Ev "\.($BINARY_EXTS)$" || true)
57
73
  else
58
- FILES=$(eval "find . -type f $FIND_EXCLUDES" | grep -Ev "\.($BINARY_EXTS)$" | sed 's|^\./||')
74
+ FILES=$(find . -type f "${FIND_ARGS[@]}" | grep -Ev "\.($BINARY_EXTS)$" | sed 's|^\./||')
59
75
  fi
60
76
 
61
77
  if [[ -n "$INCLUDE" ]]; then
62
- FILES=$(echo "$FILES" | grep -E "$INCLUDE" || true)
78
+ # timeout on grep to prevent ReDoS
79
+ FILES=$(echo "$FILES" | timeout 5 grep -E "$INCLUDE" || true)
63
80
  fi
64
81
 
65
82
  FILE_COUNT=$(echo "$FILES" | grep -c . || echo 0)
package/cli/lib/update.js CHANGED
@@ -5,11 +5,46 @@
5
5
 
6
6
  import fs from 'fs';
7
7
  import path from 'path';
8
- import { execSync } from 'child_process';
8
+ import { spawnSync } from 'child_process';
9
9
  import c from './colors.js';
10
10
  import { listDirs } from './fs-utils.js';
11
11
  import { TECH_REGISTRY } from './tech-registry.js';
12
12
 
13
+ // Security: reject paths into system directories to prevent accidental or malicious
14
+ // installation outside the user's expected workspace.
15
+ const FORBIDDEN_SYSTEM_DIRS = [
16
+ '/',
17
+ '/etc',
18
+ '/usr',
19
+ '/bin',
20
+ '/sbin',
21
+ '/boot',
22
+ '/lib',
23
+ '/var',
24
+ '/root',
25
+ '/proc',
26
+ '/sys',
27
+ '/dev',
28
+ ];
29
+
30
+ function assertSafeTarget(targetPath) {
31
+ const resolved = path.resolve(targetPath);
32
+ for (const forbidden of FORBIDDEN_SYSTEM_DIRS) {
33
+ if (resolved === forbidden || resolved.startsWith(forbidden + path.sep)) {
34
+ throw new Error(`Refusing to operate on system directory: ${resolved}`);
35
+ }
36
+ }
37
+ return resolved;
38
+ }
39
+
40
+ // Security: enforce allowlist on language code (prevents argument injection via --lang).
41
+ function assertSafeLang(lang) {
42
+ if (!/^[a-z]{2}$/.test(lang)) {
43
+ throw new Error(`Invalid --lang value: "${lang}" (expected 2-letter lowercase code)`);
44
+ }
45
+ return lang;
46
+ }
47
+
13
48
  /**
14
49
  * Run the update command against a target directory.
15
50
  * @param {string} targetPath - Absolute path to the project directory
@@ -19,11 +54,14 @@ import { TECH_REGISTRY } from './tech-registry.js';
19
54
  * @param {string} cliRoot - Path to the CLI package root
20
55
  */
21
56
  function runUpdate(targetPath, options, cliRoot) {
22
- const claudeDir = path.join(targetPath, '.claude');
23
- const lang = options.lang || 'en';
57
+ // Security: validate inputs before any side effect (CWE-78, CWE-22).
58
+ const safeTarget = assertSafeTarget(targetPath);
59
+ const lang = assertSafeLang(options.lang || 'en');
60
+
61
+ const claudeDir = path.join(safeTarget, '.claude');
24
62
 
25
63
  console.log(`\n${c.bold}Claude Craft Update${c.reset}`);
26
- console.log(`${c.dim}Directory: ${targetPath}${c.reset}\n`);
64
+ console.log(`${c.dim}Directory: ${safeTarget}${c.reset}\n`);
27
65
 
28
66
  // Verify existing installation
29
67
  if (!fs.existsSync(claudeDir)) {
@@ -60,16 +98,17 @@ function runUpdate(targetPath, options, cliRoot) {
60
98
 
61
99
  if (fs.existsSync(commonScript)) {
62
100
  console.log(` ${c.cyan}Refreshing common rules...${c.reset}`);
63
- try {
64
- execSync(`bash "${commonScript}" --lang="${lang}" --force "${targetPath}"`, {
65
- encoding: 'utf8',
66
- timeout: 60_000,
67
- stdio: 'pipe',
68
- });
101
+ // Security: spawnSync with argv array — no shell interpretation, no injection.
102
+ const result = spawnSync('bash', [commonScript, `--lang=${lang}`, '--force', safeTarget], {
103
+ encoding: 'utf8',
104
+ timeout: 60_000,
105
+ stdio: 'pipe',
106
+ });
107
+ if (result.status === 0) {
69
108
  console.log(` ${c.green}[OK]${c.reset} Common rules updated`);
70
109
  updated++;
71
- } catch (e) {
72
- console.log(` ${c.red}[FAIL]${c.reset} Common rules: ${e.message}`);
110
+ } else {
111
+ console.log(` ${c.red}[FAIL]${c.reset} Common rules: ${result.stderr || result.error?.message || 'unknown'}`);
73
112
  }
74
113
  }
75
114
 
@@ -84,16 +123,19 @@ function runUpdate(targetPath, options, cliRoot) {
84
123
  }
85
124
 
86
125
  console.log(` ${c.cyan}Refreshing ${entry.displayName}...${c.reset}`);
87
- try {
88
- execSync(`bash "${script}" --lang="${lang}" --force "${targetPath}"`, {
89
- encoding: 'utf8',
90
- timeout: 60_000,
91
- stdio: 'pipe',
92
- });
126
+ // Security: spawnSync with argv array — no shell interpretation, no injection.
127
+ const result = spawnSync('bash', [script, `--lang=${lang}`, '--force', safeTarget], {
128
+ encoding: 'utf8',
129
+ timeout: 60_000,
130
+ stdio: 'pipe',
131
+ });
132
+ if (result.status === 0) {
93
133
  console.log(` ${c.green}[OK]${c.reset} ${entry.displayName} updated`);
94
134
  updated++;
95
- } catch (e) {
96
- console.log(` ${c.red}[FAIL]${c.reset} ${entry.displayName}: ${e.message}`);
135
+ } else {
136
+ console.log(
137
+ ` ${c.red}[FAIL]${c.reset} ${entry.displayName}: ${result.stderr || result.error?.message || 'unknown'}`
138
+ );
97
139
  }
98
140
  }
99
141
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the-bearded-bear/claude-craft",
3
- "version": "8.2.4",
3
+ "version": "8.2.5",
4
4
  "description": "A comprehensive framework for AI-assisted development with Claude Code. Install standardized rules, agents, and commands for your projects.",
5
5
  "type": "module",
6
6
  "main": "cli/index.js",