icoa-cli 1.9.1 → 2.0.1
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/dist/commands/env.js +34 -11
- package/dist/index.js +1 -1
- package/dist/lib/access.js +2 -4
- package/dist/repl.js +41 -14
- package/package.json +1 -1
package/dist/commands/env.js
CHANGED
|
@@ -274,13 +274,19 @@ export function registerEnvCommand(program) {
|
|
|
274
274
|
function showStatus() {
|
|
275
275
|
console.log();
|
|
276
276
|
const os = platform();
|
|
277
|
-
const pm = os === 'darwin' ? 'brew' : os === 'linux' ? 'apt' : '
|
|
277
|
+
const pm = os === 'darwin' ? 'brew' : os === 'linux' ? 'apt' : 'winget';
|
|
278
278
|
console.log(chalk.bold.white(' ICOA Competition Environment'));
|
|
279
279
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
280
280
|
console.log(chalk.gray(' OS: ') + chalk.white(getOsInfo()));
|
|
281
281
|
console.log(chalk.gray(' Node: ') + chalk.white(getNodeInfo()));
|
|
282
282
|
console.log(chalk.gray(' Package: ') + chalk.white(pm));
|
|
283
283
|
console.log(chalk.gray(' Target: ') + chalk.white(`Python ${PYTHON_TARGET}.x`));
|
|
284
|
+
if (os === 'win32') {
|
|
285
|
+
console.log();
|
|
286
|
+
console.log(chalk.yellow(' Windows: recommend WSL (Ubuntu) for full CTF tool support'));
|
|
287
|
+
console.log(chalk.gray(' Install WSL: ') + chalk.white('wsl --install'));
|
|
288
|
+
console.log(chalk.gray(' Then run icoa inside WSL for 100% tool compatibility'));
|
|
289
|
+
}
|
|
284
290
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
285
291
|
// Python version check — use brew 3.12 on macOS
|
|
286
292
|
const pyVer = getPythonMajorMinor();
|
|
@@ -316,7 +322,7 @@ function showStatus() {
|
|
|
316
322
|
}
|
|
317
323
|
}
|
|
318
324
|
currentCategory = '';
|
|
319
|
-
const pipExe = os === 'darwin' ? '/opt/homebrew/opt/python@3.12/bin/pip3.12' : 'pip3';
|
|
325
|
+
const pipExe = os === 'darwin' ? '/opt/homebrew/opt/python@3.12/bin/pip3.12' : os === 'win32' ? 'pip' : 'pip3';
|
|
320
326
|
for (const lib of PYTHON_LIBS) {
|
|
321
327
|
if (lib.category !== currentCategory) {
|
|
322
328
|
currentCategory = lib.category;
|
|
@@ -324,12 +330,12 @@ function showStatus() {
|
|
|
324
330
|
}
|
|
325
331
|
const ok = isInstalled(lib.check);
|
|
326
332
|
if (ok) {
|
|
327
|
-
// Get actual installed version
|
|
333
|
+
// Get actual installed version
|
|
328
334
|
let ver = '';
|
|
329
|
-
for (const pip of [pipExe, 'pip3']) {
|
|
335
|
+
for (const pip of [pipExe, 'pip3', 'pip']) {
|
|
330
336
|
try {
|
|
331
|
-
const pkgName = lib.name.replace('python-magic', 'python_magic')
|
|
332
|
-
const out = execSync(`${pip} show ${pkgName}
|
|
337
|
+
const pkgName = lib.name.replace('python-magic', 'python_magic');
|
|
338
|
+
const out = execSync(`${pip} show ${pkgName}`, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'ignore'] });
|
|
333
339
|
const m = out.match(/Version:\s*(\S+)/);
|
|
334
340
|
if (m) {
|
|
335
341
|
ver = m[1];
|
|
@@ -381,13 +387,20 @@ async function installAll() {
|
|
|
381
387
|
console.log(chalk.gray(' Set default: sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1'));
|
|
382
388
|
}
|
|
383
389
|
else {
|
|
384
|
-
|
|
390
|
+
// Windows: try winget first, then choco
|
|
391
|
+
try {
|
|
392
|
+
execSync('winget install Python.Python.3.12 --accept-package-agreements --accept-source-agreements', { stdio: 'inherit' });
|
|
393
|
+
}
|
|
394
|
+
catch {
|
|
395
|
+
execSync('choco install -y python312', { stdio: 'inherit' });
|
|
396
|
+
}
|
|
385
397
|
console.log(chalk.green(' ✓ Python 3.12 installed'));
|
|
386
398
|
}
|
|
387
399
|
}
|
|
388
400
|
catch {
|
|
389
401
|
console.log(chalk.red(' ✗ Failed to install Python 3.12'));
|
|
390
|
-
|
|
402
|
+
const hint = os === 'darwin' ? 'brew install python@3.12' : os === 'linux' ? 'sudo apt install python3.12' : 'winget install Python.Python.3.12';
|
|
403
|
+
console.log(chalk.gray(` Manual install: ${hint}`));
|
|
391
404
|
}
|
|
392
405
|
console.log();
|
|
393
406
|
}
|
|
@@ -395,7 +408,14 @@ async function installAll() {
|
|
|
395
408
|
console.log(chalk.green(` ✓ Python 3.12 (${getVersion('python3')})`));
|
|
396
409
|
console.log();
|
|
397
410
|
}
|
|
398
|
-
//
|
|
411
|
+
// Windows: recommend WSL instead of installing tools natively
|
|
412
|
+
if (os === 'win32') {
|
|
413
|
+
console.log(chalk.yellow(' Windows: Most CTF tools require Linux. Recommended:'));
|
|
414
|
+
console.log(chalk.white(' wsl --install'));
|
|
415
|
+
console.log(chalk.gray(' Then run icoa inside WSL Ubuntu for full tool support.'));
|
|
416
|
+
console.log();
|
|
417
|
+
}
|
|
418
|
+
// Install system tools via brew (macOS) / apt (Linux)
|
|
399
419
|
const missingSystem = [];
|
|
400
420
|
for (const tool of SYSTEM_TOOLS) {
|
|
401
421
|
if (!isInstalled(tool.check)) {
|
|
@@ -404,8 +424,8 @@ async function installAll() {
|
|
|
404
424
|
missingSystem.push(tool);
|
|
405
425
|
}
|
|
406
426
|
}
|
|
407
|
-
if (missingSystem.length > 0) {
|
|
408
|
-
const pmName = os === 'darwin' ? 'brew' :
|
|
427
|
+
if (missingSystem.length > 0 && os !== 'win32') {
|
|
428
|
+
const pmName = os === 'darwin' ? 'brew' : 'apt';
|
|
409
429
|
console.log(chalk.bold.white(` System Tools via ${pmName} (${missingSystem.length} missing)`));
|
|
410
430
|
console.log(chalk.gray(' ─────────────────────────────────────────────'));
|
|
411
431
|
for (const tool of missingSystem) {
|
|
@@ -456,6 +476,9 @@ async function installAll() {
|
|
|
456
476
|
}
|
|
457
477
|
catch { /* fall back to pip3 */ }
|
|
458
478
|
}
|
|
479
|
+
else if (os === 'win32') {
|
|
480
|
+
pipCmd = 'pip';
|
|
481
|
+
}
|
|
459
482
|
// Pre-install system dependencies for packages that need them
|
|
460
483
|
if (os === 'darwin') {
|
|
461
484
|
console.log(chalk.gray(' Installing build dependencies...'));
|
package/dist/index.js
CHANGED
|
@@ -36,7 +36,7 @@ ${LINE}
|
|
|
36
36
|
${chalk.white('Sydney, Australia')} ${chalk.gray('Jun 27 - Jul 2, 2026')}
|
|
37
37
|
${chalk.cyan.underline('https://icoa2026.au')}
|
|
38
38
|
|
|
39
|
-
${chalk.gray('CLI-Native Competition Terminal
|
|
39
|
+
${chalk.gray('CLI-Native Competition Terminal v2.0.1')}
|
|
40
40
|
|
|
41
41
|
${LINE}
|
|
42
42
|
`;
|
package/dist/lib/access.js
CHANGED
|
@@ -143,10 +143,8 @@ function getDeviceFingerprint() {
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
else if (platform() === 'win32') {
|
|
146
|
-
const out = execSync('
|
|
147
|
-
|
|
148
|
-
if (lines.length > 1)
|
|
149
|
-
parts.push(lines[1].trim());
|
|
146
|
+
const out = execSync('powershell -Command "(Get-CimInstance Win32_ComputerSystemProduct).UUID"', { encoding: 'utf-8' });
|
|
147
|
+
parts.push(out.trim());
|
|
150
148
|
}
|
|
151
149
|
}
|
|
152
150
|
catch {
|
package/dist/repl.js
CHANGED
|
@@ -7,8 +7,27 @@ import { resetTerminalTheme } from './lib/theme.js';
|
|
|
7
7
|
import { ensureSandbox, runInSandbox, isDockerAvailable } from './lib/sandbox.js';
|
|
8
8
|
import { logCommand } from './lib/logger.js';
|
|
9
9
|
import { startLogSync, stopLogSync } from './lib/log-sync.js';
|
|
10
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
// Competition workspace — all system commands restricted here
|
|
14
|
+
const WORKSPACE = join(homedir(), 'icoa-workspace');
|
|
15
|
+
function ensureWorkspace() {
|
|
16
|
+
if (!existsSync(WORKSPACE))
|
|
17
|
+
mkdirSync(WORKSPACE, { recursive: true });
|
|
18
|
+
return WORKSPACE;
|
|
19
|
+
}
|
|
20
|
+
// Blocked commands — security risk
|
|
21
|
+
const BLOCKED_COMMANDS = new Set([
|
|
22
|
+
'sudo', 'su', 'doas', 'pkexec', // privilege escalation
|
|
23
|
+
'brew', 'apt', 'apt-get', 'yum', 'choco', // package managers (use env setup)
|
|
24
|
+
'npm', 'npx', 'pip', 'pip3', // package install (use env setup)
|
|
25
|
+
'shutdown', 'reboot', 'halt', // system control
|
|
26
|
+
'mkfs', 'fdisk', 'dd', // disk operations
|
|
27
|
+
'iptables', 'ufw', // firewall
|
|
28
|
+
]);
|
|
10
29
|
const INTERCEPT = '__REPL_NO_EXIT__';
|
|
11
|
-
const VERSION = '
|
|
30
|
+
const VERSION = '2.0.1';
|
|
12
31
|
export async function startRepl(program, resumeMode) {
|
|
13
32
|
const config = getConfig();
|
|
14
33
|
const connected = isConnected();
|
|
@@ -48,7 +67,9 @@ export async function startRepl(program, resumeMode) {
|
|
|
48
67
|
console.log();
|
|
49
68
|
// Trigger env setup
|
|
50
69
|
const { execSync: ex } = await import('node:child_process');
|
|
51
|
-
|
|
70
|
+
const { fileURLToPath } = await import('node:url');
|
|
71
|
+
const indexPath = fileURLToPath(new URL('../index.js', import.meta.url));
|
|
72
|
+
ex(`node "${indexPath}" env setup`, { stdio: 'inherit' });
|
|
52
73
|
}
|
|
53
74
|
}
|
|
54
75
|
catch {
|
|
@@ -83,7 +104,9 @@ export async function startRepl(program, resumeMode) {
|
|
|
83
104
|
console.log();
|
|
84
105
|
}
|
|
85
106
|
else if (activated) {
|
|
107
|
+
ensureWorkspace();
|
|
86
108
|
console.log(chalk.green(' Welcome, competitor! Ready to hack.'));
|
|
109
|
+
console.log(chalk.gray(` Workspace: ${WORKSPACE}`));
|
|
87
110
|
console.log();
|
|
88
111
|
console.log(chalk.gray(' Quick Start'));
|
|
89
112
|
console.log(chalk.gray(' ─────────────'));
|
|
@@ -193,20 +216,24 @@ export async function startRepl(program, resumeMode) {
|
|
|
193
216
|
'log', 'lang', 'setup', 'env', 'model', 'ctf',
|
|
194
217
|
];
|
|
195
218
|
if (!knownCommands.includes(cmd)) {
|
|
196
|
-
//
|
|
219
|
+
// Block dangerous commands
|
|
220
|
+
if (BLOCKED_COMMANDS.has(cmd)) {
|
|
221
|
+
console.log(chalk.red(` Blocked: ${cmd} is not allowed during competition.`));
|
|
222
|
+
console.log();
|
|
223
|
+
rl.prompt();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Force Python 3.12 — rewrite python3/python to 3.12 binaries
|
|
197
227
|
let resolvedInput = input;
|
|
198
228
|
if (process.platform === 'darwin') {
|
|
199
229
|
const py12 = '/opt/homebrew/opt/python@3.12/bin/python3.12';
|
|
200
|
-
const pip12 = '/opt/homebrew/opt/python@3.12/bin/pip3.12';
|
|
201
230
|
resolvedInput = resolvedInput
|
|
202
231
|
.replace(/^python3?\s/, `${py12} `)
|
|
203
|
-
.replace(/^
|
|
204
|
-
if (resolvedInput === 'python3' || resolvedInput === 'python')
|
|
205
|
-
resolvedInput = py12;
|
|
206
|
-
if (resolvedInput === 'pip3' || resolvedInput === 'pip')
|
|
207
|
-
resolvedInput = pip12;
|
|
232
|
+
.replace(/^(python3|python)$/, py12);
|
|
208
233
|
}
|
|
209
|
-
//
|
|
234
|
+
// Ensure workspace directory
|
|
235
|
+
const cwd = ensureWorkspace();
|
|
236
|
+
// Route to Docker sandbox if available, otherwise system shell (in workspace)
|
|
210
237
|
processing = true;
|
|
211
238
|
try {
|
|
212
239
|
if (isDockerAvailable()) {
|
|
@@ -215,11 +242,11 @@ export async function startRepl(program, resumeMode) {
|
|
|
215
242
|
await runInSandbox(resolvedInput, rl);
|
|
216
243
|
}
|
|
217
244
|
else {
|
|
218
|
-
await runSystemCommand(resolvedInput, rl);
|
|
245
|
+
await runSystemCommand(resolvedInput, rl, cwd);
|
|
219
246
|
}
|
|
220
247
|
}
|
|
221
248
|
else {
|
|
222
|
-
await runSystemCommand(resolvedInput, rl);
|
|
249
|
+
await runSystemCommand(resolvedInput, rl, cwd);
|
|
223
250
|
}
|
|
224
251
|
}
|
|
225
252
|
catch {
|
|
@@ -264,13 +291,13 @@ export async function startRepl(program, resumeMode) {
|
|
|
264
291
|
realExit(0);
|
|
265
292
|
});
|
|
266
293
|
}
|
|
267
|
-
function runSystemCommand(input, rl) {
|
|
294
|
+
function runSystemCommand(input, rl, cwd) {
|
|
268
295
|
return new Promise((resolve) => {
|
|
269
|
-
// Pause readline so the child process gets full terminal control
|
|
270
296
|
rl.pause();
|
|
271
297
|
const child = spawn(input, {
|
|
272
298
|
shell: true,
|
|
273
299
|
stdio: 'inherit',
|
|
300
|
+
cwd: cwd || process.cwd(),
|
|
274
301
|
});
|
|
275
302
|
child.on('close', () => {
|
|
276
303
|
rl.resume();
|