@bash0816/claude-code 2.1.137 → 2.1.139

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.
package/README.md CHANGED
@@ -52,6 +52,8 @@ npm install -g @bash0816/claude-code@2.1.126
52
52
  npm install -g @bash0816/claude-code@2.1.128
53
53
  npm install -g @bash0816/claude-code@2.1.136
54
54
  npm install -g @bash0816/claude-code@2.1.137
55
+ npm install -g @bash0816/claude-code@2.1.138
56
+ npm install -g @bash0816/claude-code@2.1.139
55
57
  ```
56
58
 
57
59
  ## Update / 更新
@@ -76,8 +78,8 @@ Normal launch also checks the same manifest with a short timeout and prints a no
76
78
 
77
79
  - Only audited versions in `config/claude-native-audited-versions.json` can run.
78
80
  - `config/claude-native-audited-versions.json` にある監査済み version だけを実行できます。
79
- - `2.1.118`, `2.1.119`, `2.1.121`, `2.1.122`, `2.1.123`, `2.1.126`, `2.1.128`, `2.1.136`, `2.1.137` are included in the package metadata.
80
- - `2.1.118`、`2.1.119`、`2.1.121`、`2.1.122`、`2.1.123`、`2.1.126`、`2.1.128`、`2.1.136`、`2.1.137` を package metadata に含めています。
81
+ - `2.1.118`, `2.1.119`, `2.1.121`, `2.1.122`, `2.1.123`, `2.1.126`, `2.1.128`, `2.1.136`, `2.1.137`, `2.1.138`, `2.1.139` are included in the package metadata.
82
+ - `2.1.118`、`2.1.119`、`2.1.121`、`2.1.122`、`2.1.123`、`2.1.126`、`2.1.128`、`2.1.136`、`2.1.137`、`2.1.138`、`2.1.139` を package metadata に含めています。
81
83
  - The metadata file is the source of truth for the currently audited set.
82
84
  - 現在の監査済み version 集合の source of truth は metadata file です。
83
85
  - Native artifacts are cached under `${HOME}/.claude-termux-native-package`.
package/bin/claude CHANGED
@@ -57,5 +57,6 @@ WORKDIR="${CACHE_DIR}/launcher-workdir" \
57
57
  ENTRY_JS_OFFSET="${ENTRY_JS_OFFSET}" \
58
58
  ENTRY_END_OFFSET="${ENTRY_END_OFFSET}" \
59
59
  CURRENT_CLAUDE_VERSION="${CLAUDE_VERSION}" \
60
+ CLAUDE_TERMUX_PACKAGE_DIR="${PACKAGE_DIR}" \
60
61
  CLAUDE_NATIVE_UPDATE_CHECK=0 \
61
62
  exec sh "${PACKAGE_DIR}/lib/termux-run-claude-native.sh" "$@"
@@ -62,6 +62,20 @@
62
62
  "entry_js_offset": 214359796,
63
63
  "entry_end_offset": 228641008,
64
64
  "status": "termux_verified"
65
+ },
66
+ "2.1.138": {
67
+ "wrapper_spec": "@anthropic-ai/claude-code@2.1.138",
68
+ "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.138",
69
+ "entry_js_offset": 214359828,
70
+ "entry_end_offset": 228641090,
71
+ "status": "termux_verified"
72
+ },
73
+ "2.1.139": {
74
+ "wrapper_spec": "@anthropic-ai/claude-code@2.1.139",
75
+ "native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.139",
76
+ "entry_js_offset": 214907060,
77
+ "entry_end_offset": 229261135,
78
+ "status": "termux_verified"
65
79
  }
66
80
  }
67
81
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "manifest_version": 1,
3
3
  "package_name": "@bash0816/claude-code",
4
- "latest_audited_version": "2.1.137",
5
- "latest_candidate_version": "2.1.137",
6
- "previous_stable_version": "2.1.136",
4
+ "latest_audited_version": "2.1.139",
5
+ "latest_candidate_version": "2.1.139",
6
+ "previous_stable_version": "2.1.138",
7
7
  "manifest_url": "https://raw.githubusercontent.com/bash0816/ClaudeCode-Termux/main/config/claude-termux-release-manifest.json"
8
8
  }
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const EventEmitter = require('events');
5
+
6
+ const OFFICIAL_PACKAGE = '@anthropic-ai/claude-code';
7
+ const BLOCK_MESSAGE = [
8
+ 'Official native self-update is disabled on Termux.',
9
+ 'Run: claude update',
10
+ ].join('\n');
11
+
12
+ function lastPathSegment(value) {
13
+ return String(value || '').replace(/\\/g, '/').split('/').pop();
14
+ }
15
+
16
+ function isNpmCommand(command) {
17
+ const text = String(command || '');
18
+ const leaf = lastPathSegment(text);
19
+ return leaf === 'npm' || leaf === 'npm-cli.js';
20
+ }
21
+
22
+ function isPnpmCommand(command) {
23
+ return lastPathSegment(command) === 'pnpm';
24
+ }
25
+
26
+ function isYarnCommand(command) {
27
+ const leaf = lastPathSegment(command);
28
+ return leaf === 'yarn' || leaf === 'yarnpkg';
29
+ }
30
+
31
+ function isNpxCommand(command) {
32
+ const leaf = lastPathSegment(command);
33
+ return leaf === 'npx' || leaf === 'pnpx';
34
+ }
35
+
36
+ function isEnvCommand(command) {
37
+ return lastPathSegment(command) === 'env';
38
+ }
39
+
40
+ function isCorepackCommand(command) {
41
+ return lastPathSegment(command) === 'corepack';
42
+ }
43
+
44
+ function isShellCommand(command) {
45
+ const leaf = lastPathSegment(command);
46
+ return leaf === 'sh' || leaf === 'bash';
47
+ }
48
+
49
+ function isSupportedOperation(value) {
50
+ return ['install', 'i', 'add', 'update', 'up', 'upgrade'].includes(String(value || ''));
51
+ }
52
+
53
+ function isOfficialTarget(value) {
54
+ return String(value || '') === OFFICIAL_PACKAGE || String(value || '').startsWith(`${OFFICIAL_PACKAGE}@`);
55
+ }
56
+
57
+ function normalizeCommand(command, args) {
58
+ let normalizedCommand = command;
59
+ let normalizedArgs = Array.isArray(args) ? args.slice() : [];
60
+
61
+ if (isEnvCommand(normalizedCommand)) {
62
+ while (normalizedArgs.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=.*/.test(String(normalizedArgs[0]))) {
63
+ normalizedArgs.shift();
64
+ }
65
+ if (normalizedArgs.length === 0) return { command: normalizedCommand, args: normalizedArgs };
66
+ normalizedCommand = normalizedArgs.shift();
67
+ }
68
+
69
+ if (isCorepackCommand(normalizedCommand)) {
70
+ if (normalizedArgs.length === 0) return { command: normalizedCommand, args: normalizedArgs };
71
+ normalizedCommand = normalizedArgs.shift();
72
+ }
73
+
74
+ return { command: normalizedCommand, args: normalizedArgs };
75
+ }
76
+
77
+ function hasOfficialTarget(args) {
78
+ return args.some(isOfficialTarget);
79
+ }
80
+
81
+ function shouldBlockNpmLike(command, args) {
82
+ if (!(isNpmCommand(command) || isPnpmCommand(command))) return false;
83
+ if (!args.find(isSupportedOperation)) return false;
84
+ return hasOfficialTarget(args);
85
+ }
86
+
87
+ function shouldBlockYarn(command, args) {
88
+ if (!isYarnCommand(command)) return false;
89
+ return String(args[0] || '') === 'global' && String(args[1] || '') === 'add' && hasOfficialTarget(args.slice(2));
90
+ }
91
+
92
+ function shouldBlockNpx(command, args) {
93
+ if (!isNpxCommand(command)) return false;
94
+ for (let index = 0; index < args.length; index += 1) {
95
+ const token = String(args[index] || '');
96
+ if (token === '-p' || token === '--package') {
97
+ if (isOfficialTarget(args[index + 1])) return true;
98
+ index += 1;
99
+ continue;
100
+ }
101
+ if (token.startsWith('--package=')) {
102
+ if (isOfficialTarget(token.slice('--package='.length))) return true;
103
+ continue;
104
+ }
105
+ if (!token.startsWith('-')) {
106
+ return isOfficialTarget(token);
107
+ }
108
+ }
109
+ return false;
110
+ }
111
+
112
+ function shouldBlockCommand(command, args) {
113
+ if (!Array.isArray(args)) return false;
114
+ const normalized = normalizeCommand(command, args);
115
+ return (
116
+ shouldBlockNpmLike(normalized.command, normalized.args) ||
117
+ shouldBlockYarn(normalized.command, normalized.args) ||
118
+ shouldBlockNpx(normalized.command, normalized.args)
119
+ );
120
+ }
121
+
122
+ function normalizeTokensForExec(tokens) {
123
+ if (tokens.length === 0) return tokens;
124
+ const normalized = normalizeCommand(tokens[0], tokens.slice(1));
125
+ return [normalized.command, ...normalized.args].filter(value => value !== undefined);
126
+ }
127
+
128
+ function unwrapShellCommand(command, args) {
129
+ if (!isShellCommand(command) || !Array.isArray(args) || args.length < 2) return null;
130
+ const first = String(args[0] || '');
131
+ const second = String(args[1] || '');
132
+ if ((first === '-c' || first === '-lc') && second) return second;
133
+ if (first === '-l' && String(args[1] || '') === '-c' && String(args[2] || '')) return String(args[2]);
134
+ return null;
135
+ }
136
+
137
+ function tokenizeShellString(command) {
138
+ const text = String(command || '').trim();
139
+ if (!text) return [];
140
+ return text.match(/(?:[^\s'"]+|'[^']*'|"[^"]*")+/g) || [];
141
+ }
142
+
143
+ function unquote(token) {
144
+ if (
145
+ (token.startsWith('"') && token.endsWith('"')) ||
146
+ (token.startsWith("'") && token.endsWith("'"))
147
+ ) {
148
+ return token.slice(1, -1);
149
+ }
150
+ return token;
151
+ }
152
+
153
+ function shouldBlockExecString(command) {
154
+ const tokens = normalizeTokensForExec(tokenizeShellString(command).map(unquote));
155
+ if (tokens.length === 0) return false;
156
+ const shellInner = unwrapShellCommand(tokens[0], tokens.slice(1));
157
+ if (shellInner) return shouldBlockExecString(shellInner);
158
+ return shouldBlockCommand(tokens[0], tokens.slice(1));
159
+ }
160
+
161
+ function createBlockedError(blockedStderr) {
162
+ const error = new Error(BLOCK_MESSAGE);
163
+ error.code = 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED';
164
+ error.status = 1;
165
+ error.stdout = Buffer.from('');
166
+ error.stderr = Buffer.from(blockedStderr);
167
+ return error;
168
+ }
169
+
170
+ function createBlockedSpawn(blockedStderr, stderrWriter, options) {
171
+ const child = new EventEmitter();
172
+ child.stdout = null;
173
+ child.stderr = null;
174
+ child.stdin = null;
175
+ child.pid = 0;
176
+ child.killed = false;
177
+ child.kill = () => false;
178
+ process.nextTick(() => {
179
+ if (!(options && options.stdio === 'ignore')) {
180
+ stderrWriter(blockedStderr);
181
+ }
182
+ child.emit('close', 1, null);
183
+ child.emit('exit', 1, null);
184
+ });
185
+ return child;
186
+ }
187
+
188
+ function createBlockedSpawnSync(blockedStderr, stderrWriter) {
189
+ stderrWriter(blockedStderr);
190
+ return {
191
+ pid: 0,
192
+ output: [null, Buffer.from(''), Buffer.from(blockedStderr)],
193
+ stdout: Buffer.from(''),
194
+ stderr: Buffer.from(blockedStderr),
195
+ status: 1,
196
+ signal: null,
197
+ error: createBlockedError(blockedStderr),
198
+ };
199
+ }
200
+
201
+ function createBlockedExecResult(blockedStderr, stderrWriter, callback) {
202
+ const child = createBlockedSpawn(blockedStderr, stderrWriter);
203
+ const error = createBlockedError(blockedStderr);
204
+ process.nextTick(() => {
205
+ if (typeof callback === 'function') {
206
+ callback(error, '', blockedStderr);
207
+ }
208
+ });
209
+ return child;
210
+ }
211
+
212
+ function shouldBlockSpawnArgs(args) {
213
+ if (!Array.isArray(args) || args.length === 0) return false;
214
+ const command = args[0];
215
+ const commandArgs = Array.isArray(args[1]) ? args[1] : [];
216
+ const shellInner = unwrapShellCommand(command, commandArgs);
217
+ if (shellInner) return shouldBlockExecString(shellInner);
218
+ return shouldBlockCommand(command, commandArgs);
219
+ }
220
+
221
+ function createGuardedChildProcess(realChild, stderrWriter = value => process.stderr.write(value)) {
222
+ const blockedStderr = `${BLOCK_MESSAGE}\n`;
223
+ return {
224
+ spawn: (...args) => {
225
+ if (shouldBlockSpawnArgs(args)) return createBlockedSpawn(blockedStderr, stderrWriter, args[2]);
226
+ return realChild.spawn(...args);
227
+ },
228
+ execFile: (...args) => {
229
+ if (shouldBlockSpawnArgs(args)) return createBlockedExecResult(blockedStderr, stderrWriter, args[args.length - 1]);
230
+ return realChild.execFile(...args);
231
+ },
232
+ exec: (...args) => {
233
+ if (shouldBlockExecString(args[0])) return createBlockedExecResult(blockedStderr, stderrWriter, args[args.length - 1]);
234
+ return realChild.exec(...args);
235
+ },
236
+ spawnSync: (...args) => {
237
+ if (shouldBlockSpawnArgs(args)) return createBlockedSpawnSync(blockedStderr, stderrWriter);
238
+ return realChild.spawnSync(...args);
239
+ },
240
+ execFileSync: (...args) => {
241
+ if (shouldBlockSpawnArgs(args)) throw createBlockedError(blockedStderr);
242
+ return realChild.execFileSync(...args);
243
+ },
244
+ execSync: (...args) => {
245
+ if (shouldBlockExecString(args[0])) throw createBlockedError(blockedStderr);
246
+ return realChild.execSync(...args);
247
+ },
248
+ };
249
+ }
250
+
251
+ module.exports = {
252
+ BLOCK_MESSAGE,
253
+ OFFICIAL_PACKAGE,
254
+ createGuardedChildProcess,
255
+ shouldBlockCommand,
256
+ shouldBlockExecString,
257
+ };
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const test = require('node:test');
5
+ const assert = require('node:assert/strict');
6
+ const EventEmitter = require('events');
7
+
8
+ const {
9
+ BLOCK_MESSAGE,
10
+ createGuardedChildProcess,
11
+ shouldBlockCommand,
12
+ shouldBlockExecString,
13
+ } = require('./native-update-guard.js');
14
+
15
+ test('blocks npm install of official package', () => {
16
+ assert.equal(
17
+ shouldBlockCommand('npm', ['install', '-g', '@anthropic-ai/claude-code@latest']),
18
+ true,
19
+ );
20
+ });
21
+
22
+ test('blocks npm update of official package via absolute npm path', () => {
23
+ assert.equal(
24
+ shouldBlockCommand('/usr/bin/npm', ['update', '-g', '@anthropic-ai/claude-code']),
25
+ true,
26
+ );
27
+ });
28
+
29
+ test('does not block canonical package install', () => {
30
+ assert.equal(
31
+ shouldBlockCommand('npm', ['install', '-g', '@bash0816/claude-code@2.1.137']),
32
+ false,
33
+ );
34
+ });
35
+
36
+ test('blocks exec string that installs official package', () => {
37
+ assert.equal(
38
+ shouldBlockExecString('npm install -g @anthropic-ai/claude-code@latest'),
39
+ true,
40
+ );
41
+ });
42
+
43
+ test('does not block unrelated exec string', () => {
44
+ assert.equal(
45
+ shouldBlockExecString('npm install -g @bash0816/claude-code@2.1.137'),
46
+ false,
47
+ );
48
+ });
49
+
50
+ test('blocks env npm install of official package', () => {
51
+ assert.equal(
52
+ shouldBlockCommand('env', ['FOO=bar', 'npm', 'install', '-g', '@anthropic-ai/claude-code@latest']),
53
+ true,
54
+ );
55
+ });
56
+
57
+ test('blocks npx official package invocation', () => {
58
+ assert.equal(shouldBlockCommand('npx', ['@anthropic-ai/claude-code']), true);
59
+ });
60
+
61
+ test('blocks npx package option official package invocation', () => {
62
+ assert.equal(shouldBlockCommand('npx', ['-p', '@anthropic-ai/claude-code', 'claude']), true);
63
+ assert.equal(shouldBlockCommand('npx', ['--package=@anthropic-ai/claude-code', 'claude']), true);
64
+ });
65
+
66
+ test('blocks pnpm add of official package', () => {
67
+ assert.equal(
68
+ shouldBlockCommand('pnpm', ['add', '-g', '@anthropic-ai/claude-code']),
69
+ true,
70
+ );
71
+ });
72
+
73
+ test('blocks yarn global add of official package', () => {
74
+ assert.equal(
75
+ shouldBlockCommand('yarn', ['global', 'add', '@anthropic-ai/claude-code']),
76
+ true,
77
+ );
78
+ });
79
+
80
+ test('blocks corepack pnpm add of official package', () => {
81
+ assert.equal(
82
+ shouldBlockCommand('corepack', ['pnpm', 'add', '-g', '@anthropic-ai/claude-code']),
83
+ true,
84
+ );
85
+ });
86
+
87
+ test('blocks corepack yarn global add of official package', () => {
88
+ assert.equal(
89
+ shouldBlockCommand('corepack', ['yarn', 'global', 'add', '@anthropic-ai/claude-code']),
90
+ true,
91
+ );
92
+ });
93
+
94
+ test('does not block corepack pnpm add of canonical package', () => {
95
+ assert.equal(
96
+ shouldBlockCommand('corepack', ['pnpm', 'add', '-g', '@bash0816/claude-code@2.1.137']),
97
+ false,
98
+ );
99
+ });
100
+
101
+ test('blocks exec string through env npm', () => {
102
+ assert.equal(
103
+ shouldBlockExecString('env FOO=bar npm install -g @anthropic-ai/claude-code@latest'),
104
+ true,
105
+ );
106
+ });
107
+
108
+ test('blocks exec string through corepack yarn', () => {
109
+ assert.equal(
110
+ shouldBlockExecString('corepack yarn global add @anthropic-ai/claude-code'),
111
+ true,
112
+ );
113
+ });
114
+
115
+ test('blocks exec string through sh -c npm install', () => {
116
+ assert.equal(
117
+ shouldBlockExecString('sh -c "npm install -g @anthropic-ai/claude-code"'),
118
+ true,
119
+ );
120
+ });
121
+
122
+ test('blocks exec string through bash -lc pnpm add', () => {
123
+ assert.equal(
124
+ shouldBlockExecString('bash -lc "pnpm add -g @anthropic-ai/claude-code"'),
125
+ true,
126
+ );
127
+ });
128
+
129
+ test('does not block canonical exec string through bash -lc', () => {
130
+ assert.equal(
131
+ shouldBlockExecString('bash -lc "npm install -g @bash0816/claude-code@2.1.137"'),
132
+ false,
133
+ );
134
+ });
135
+
136
+ test('does not block unrelated shell exec string', () => {
137
+ assert.equal(shouldBlockExecString('sh -c "echo ok"'), false);
138
+ });
139
+
140
+ function createRealChildStub() {
141
+ const calls = [];
142
+ return {
143
+ calls,
144
+ spawn(...args) {
145
+ calls.push(['spawn', args]);
146
+ return { kind: 'spawn', args };
147
+ },
148
+ execFile(...args) {
149
+ calls.push(['execFile', args]);
150
+ return { kind: 'execFile', args };
151
+ },
152
+ exec(...args) {
153
+ calls.push(['exec', args]);
154
+ return { kind: 'exec', args };
155
+ },
156
+ spawnSync(...args) {
157
+ calls.push(['spawnSync', args]);
158
+ return { kind: 'spawnSync', args, status: 0 };
159
+ },
160
+ execFileSync(...args) {
161
+ calls.push(['execFileSync', args]);
162
+ return Buffer.from('ok');
163
+ },
164
+ execSync(...args) {
165
+ calls.push(['execSync', args]);
166
+ return Buffer.from('ok');
167
+ },
168
+ };
169
+ }
170
+
171
+ test('guarded spawn blocks official npm update without delegating', async () => {
172
+ const writes = [];
173
+ const realChild = createRealChildStub();
174
+ const guarded = createGuardedChildProcess(realChild, value => writes.push(value));
175
+
176
+ const child = guarded.spawn('npm', ['install', '-g', '@anthropic-ai/claude-code@latest']);
177
+ assert.equal(child instanceof EventEmitter, true);
178
+
179
+ const result = await new Promise(resolve => {
180
+ child.once('close', (code, signal) => resolve({ code, signal }));
181
+ });
182
+
183
+ assert.deepEqual(result, { code: 1, signal: null });
184
+ assert.deepEqual(realChild.calls, []);
185
+ assert.equal(writes[0], `${BLOCK_MESSAGE}\n`);
186
+ });
187
+
188
+ test('guarded spawn delegates canonical install', () => {
189
+ const realChild = createRealChildStub();
190
+ const guarded = createGuardedChildProcess(realChild, () => {});
191
+ const result = guarded.spawn('npm', ['install', '-g', '@bash0816/claude-code@2.1.137']);
192
+
193
+ assert.equal(result.kind, 'spawn');
194
+ assert.equal(realChild.calls.length, 1);
195
+ });
196
+
197
+ test('guarded execFile blocks official npm update callback path', async () => {
198
+ const writes = [];
199
+ const realChild = createRealChildStub();
200
+ const guarded = createGuardedChildProcess(realChild, value => writes.push(value));
201
+
202
+ const callbackResult = await new Promise(resolve => {
203
+ guarded.execFile(
204
+ 'npm',
205
+ ['update', '-g', '@anthropic-ai/claude-code'],
206
+ (error, stdout, stderr) => resolve({ error, stdout, stderr }),
207
+ );
208
+ });
209
+
210
+ assert.equal(callbackResult.error.code, 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED');
211
+ assert.equal(callbackResult.stdout, '');
212
+ assert.equal(callbackResult.stderr, `${BLOCK_MESSAGE}\n`);
213
+ assert.deepEqual(realChild.calls, []);
214
+ assert.equal(writes[0], `${BLOCK_MESSAGE}\n`);
215
+ });
216
+
217
+ test('guarded exec blocks official npm update shell string', async () => {
218
+ const realChild = createRealChildStub();
219
+ const guarded = createGuardedChildProcess(realChild, () => {});
220
+
221
+ const callbackResult = await new Promise(resolve => {
222
+ guarded.exec(
223
+ 'npm install -g @anthropic-ai/claude-code@latest',
224
+ (error, stdout, stderr) => resolve({ error, stdout, stderr }),
225
+ );
226
+ });
227
+
228
+ assert.equal(callbackResult.error.code, 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED');
229
+ assert.equal(callbackResult.stdout, '');
230
+ assert.equal(callbackResult.stderr, `${BLOCK_MESSAGE}\n`);
231
+ assert.deepEqual(realChild.calls, []);
232
+ });
233
+
234
+ test('guarded spawnSync blocks official npm update with status 1', () => {
235
+ const writes = [];
236
+ const realChild = createRealChildStub();
237
+ const guarded = createGuardedChildProcess(realChild, value => writes.push(value));
238
+ const result = guarded.spawnSync('npm', ['update', '-g', '@anthropic-ai/claude-code']);
239
+
240
+ assert.equal(result.status, 1);
241
+ assert.equal(Buffer.isBuffer(result.stderr), true);
242
+ assert.equal(String(result.stderr), `${BLOCK_MESSAGE}\n`);
243
+ assert.equal(result.error.code, 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED');
244
+ assert.deepEqual(realChild.calls, []);
245
+ assert.equal(writes[0], `${BLOCK_MESSAGE}\n`);
246
+ });
247
+
248
+ test('guarded execFileSync throws on official npm update', () => {
249
+ const realChild = createRealChildStub();
250
+ const guarded = createGuardedChildProcess(realChild, () => {});
251
+
252
+ assert.throws(
253
+ () => guarded.execFileSync('npm', ['install', '-g', '@anthropic-ai/claude-code@latest']),
254
+ error => error && error.code === 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED',
255
+ );
256
+ assert.deepEqual(realChild.calls, []);
257
+ });
258
+
259
+ test('guarded execSync throws on official npm update shell string', () => {
260
+ const realChild = createRealChildStub();
261
+ const guarded = createGuardedChildProcess(realChild, () => {});
262
+
263
+ assert.throws(
264
+ () => guarded.execSync('npm update -g @anthropic-ai/claude-code'),
265
+ error => error && error.code === 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED',
266
+ );
267
+ assert.deepEqual(realChild.calls, []);
268
+ });
269
+
270
+ test('guarded spawn blocks official npm update through sh -c', async () => {
271
+ const writes = [];
272
+ const realChild = createRealChildStub();
273
+ const guarded = createGuardedChildProcess(realChild, value => writes.push(value));
274
+
275
+ const child = guarded.spawn('/bin/sh', ['-c', 'npm install -g @anthropic-ai/claude-code']);
276
+ const result = await new Promise(resolve => {
277
+ child.once('close', (code, signal) => resolve({ code, signal }));
278
+ });
279
+
280
+ assert.deepEqual(result, { code: 1, signal: null });
281
+ assert.deepEqual(realChild.calls, []);
282
+ assert.equal(writes[0], `${BLOCK_MESSAGE}\n`);
283
+ });
284
+
285
+ test('guarded execFile blocks official update through bash -lc', async () => {
286
+ const realChild = createRealChildStub();
287
+ const guarded = createGuardedChildProcess(realChild, () => {});
288
+
289
+ const callbackResult = await new Promise(resolve => {
290
+ guarded.execFile(
291
+ '/usr/bin/bash',
292
+ ['-lc', 'pnpm add -g @anthropic-ai/claude-code'],
293
+ (error, stdout, stderr) => resolve({ error, stdout, stderr }),
294
+ );
295
+ });
296
+
297
+ assert.equal(callbackResult.error.code, 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED');
298
+ assert.equal(callbackResult.stdout, '');
299
+ assert.equal(callbackResult.stderr, `${BLOCK_MESSAGE}\n`);
300
+ assert.deepEqual(realChild.calls, []);
301
+ });
302
+
303
+ test('guarded spawn delegates canonical shell install', () => {
304
+ const realChild = createRealChildStub();
305
+ const guarded = createGuardedChildProcess(realChild, () => {});
306
+ const result = guarded.spawn('/bin/sh', ['-c', 'npm install -g @bash0816/claude-code@2.1.137']);
307
+
308
+ assert.equal(result.kind, 'spawn');
309
+ assert.equal(realChild.calls.length, 1);
310
+ });
@@ -6,6 +6,7 @@ WORKDIR="${WORKDIR:-${HOME}/.claude-termux-native-package/launcher-workdir}"
6
6
  ENTRY_JS_OFFSET="${ENTRY_JS_OFFSET:?ENTRY_JS_OFFSET is required}"
7
7
  ENTRY_END_OFFSET="${ENTRY_END_OFFSET:?ENTRY_END_OFFSET is required}"
8
8
  CURRENT_CLAUDE_VERSION="${CURRENT_CLAUDE_VERSION:?CURRENT_CLAUDE_VERSION is required}"
9
+ CLAUDE_TERMUX_PACKAGE_DIR="${CLAUDE_TERMUX_PACKAGE_DIR:?CLAUDE_TERMUX_PACKAGE_DIR is required}"
9
10
  TERMUX_TMPDIR="${TMPDIR:-/data/data/com.termux/files/usr/tmp}"
10
11
  SSL_CERT_DIR="${SSL_CERT_DIR:-/data/data/com.termux/files/usr/etc/tls}"
11
12
  SSL_CERT_FILE="${SSL_CERT_FILE:-/data/data/com.termux/files/usr/etc/tls/cert.pem}"
@@ -42,6 +43,10 @@ export CURRENT_CLAUDE_VERSION
42
43
  node - "$@" <<'NODE'
43
44
  const fs = require('fs');
44
45
  const path = require('path');
46
+ const {
47
+ BLOCK_MESSAGE,
48
+ createGuardedChildProcess,
49
+ } = require(path.join(process.env.CLAUDE_TERMUX_PACKAGE_DIR, 'lib', 'native-update-guard.js'));
45
50
 
46
51
  const sourceBin = process.env.SOURCE_BIN;
47
52
  const workdir = process.env.WORKDIR;
@@ -86,6 +91,7 @@ function stringWidth(value) {
86
91
 
87
92
  function createFakeRequire(realRequire) {
88
93
  const realChild = realRequire('child_process');
94
+
89
95
  function rewriteArgs(args) {
90
96
  if (
91
97
  Array.isArray(args) &&
@@ -98,6 +104,18 @@ function createFakeRequire(realRequire) {
98
104
  return args;
99
105
  }
100
106
 
107
+ const guardedChild = createGuardedChildProcess(
108
+ {
109
+ spawn: (...args) => realChild.spawn(...rewriteArgs(args)),
110
+ execFile: (...args) => realChild.execFile(...args),
111
+ exec: (...args) => realChild.exec(...args),
112
+ spawnSync: (...args) => realChild.spawnSync(...rewriteArgs(args)),
113
+ execFileSync: (...args) => realChild.execFileSync(...args),
114
+ execSync: (...args) => realChild.execSync(...args),
115
+ },
116
+ value => process.stderr.write(value),
117
+ );
118
+
101
119
  return function fakeRequire(id) {
102
120
  if (id === 'ws') {
103
121
  class WS {
@@ -112,14 +130,11 @@ function createFakeRequire(realRequire) {
112
130
  }
113
131
 
114
132
  if (id === 'child_process') {
115
- return {
116
- spawn: (...args) => realChild.spawn(...rewriteArgs(args)),
117
- execFile: (...args) => realChild.execFile(...args),
118
- exec: (...args) => realChild.exec(...args),
119
- spawnSync: (...args) => realChild.spawnSync(...rewriteArgs(args)),
120
- execFileSync: (...args) => realChild.execFileSync(...args),
121
- execSync: (...args) => realChild.execSync(...args),
122
- };
133
+ return guardedChild;
134
+ }
135
+
136
+ if (id === 'node:child_process') {
137
+ return guardedChild;
123
138
  }
124
139
 
125
140
  if (id.startsWith('/$bunfs/root/')) {
@@ -186,6 +201,10 @@ async function main() {
186
201
  }
187
202
 
188
203
  main().catch(error => {
204
+ if (error && error.code === 'CLAUDE_TERMUX_OFFICIAL_UPDATE_BLOCKED') {
205
+ console.error(BLOCK_MESSAGE);
206
+ process.exit(error.status || 1);
207
+ }
189
208
  console.error(error && error.stack ? error.stack : String(error));
190
209
  process.exit(1);
191
210
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bash0816/claude-code",
3
- "version": "2.1.137",
3
+ "version": "2.1.139",
4
4
  "description": "Termux-native Claude Code wrapper with audited native replay",
5
5
  "license": "MIT",
6
6
  "bin": {