icoa-cli 2.19.61 → 2.19.62

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.
@@ -311,8 +311,12 @@ export async function handleChatMessage(input) {
311
311
  function printExamAi4ctfWelcome(q, qNum) {
312
312
  const config = getConfig();
313
313
  const modelName = config.geminiModel || 'gemma-4-31b-it';
314
+ const isResume = chatTokensUsed > 0;
314
315
  console.log();
315
316
  console.log(chalk.green.bold(` ═══ AI4CTF — Q${qNum}: ${q.category} ═══`));
317
+ if (isResume) {
318
+ console.log(chalk.gray(` (resuming — prior chat is not remembered, but tokens already used stay deducted)`));
319
+ }
316
320
  console.log();
317
321
  console.log(chalk.cyan(' ┌─────────────────────────────────────────────'));
318
322
  console.log(chalk.cyan(' │ ') + chalk.bold.white(`Q${qNum} [${q.category}] · ${q.points || 6} pts`));
@@ -426,6 +430,16 @@ async function handleExamAi4ctfMessage(input) {
426
430
  const submitMatch = trimmed.match(/^submit\s+(.+)/i);
427
431
  if (submitMatch) {
428
432
  const flag = submitMatch[1].trim();
433
+ // Letter-only footgun: reject 'A'/'B'/'C'/'D' as flags. Same defence we
434
+ // added at the REPL and exam-answer layers; duplicated here because chat
435
+ // bypasses both.
436
+ if (/^[A-Da-d]$/.test(flag)) {
437
+ console.log();
438
+ console.log(chalk.yellow(` "${flag}" looks like an MCQ letter, not a flag.`));
439
+ console.log(chalk.gray(' Flag format: ') + chalk.green('ICOA{your_flag}') + chalk.gray('. Try again inside this chat.'));
440
+ console.log();
441
+ return 'continue';
442
+ }
429
443
  const { getExamState, saveExamState } = await import('../lib/exam-state.js');
430
444
  const state = getExamState();
431
445
  if (!state)
@@ -179,8 +179,12 @@ export async function handleCtf4aiMessage(input) {
179
179
  function printExamCtf4aiWelcome(q, qNum) {
180
180
  const config = getConfig();
181
181
  const modelName = config.geminiModel || 'gemma-4-31b-it';
182
+ const isResume = ctf4aiTokens > 0;
182
183
  console.log();
183
184
  console.log(chalk.red.bold(` ═══ CTF4AI — Q${qNum}: ${q.category} (${q.points || 16} pts) ═══`));
185
+ if (isResume) {
186
+ console.log(chalk.gray(` (resuming — prior chat is not remembered, but tokens already used stay deducted)`));
187
+ }
184
188
  console.log();
185
189
  console.log(chalk.red(' ┌─────────────────────────────────────────────'));
186
190
  console.log(chalk.red(' │ ') + chalk.bold.white(`Q${qNum} [${q.category}] · adversarial AI`));
@@ -297,6 +301,13 @@ async function handleExamCtf4aiMessage(input) {
297
301
  const submitMatch = trimmed.match(/^submit\s+(.+)/i);
298
302
  if (submitMatch) {
299
303
  const flag = submitMatch[1].trim();
304
+ if (/^[A-Da-d]$/.test(flag)) {
305
+ console.log();
306
+ console.log(chalk.yellow(` "${flag}" looks like an MCQ letter, not a flag.`));
307
+ console.log(chalk.gray(' Flag format: ') + chalk.green('ICOA{your_flag}') + chalk.gray('. Try again.'));
308
+ console.log();
309
+ return 'continue';
310
+ }
300
311
  const { getExamState, saveExamState } = await import('../lib/exam-state.js');
301
312
  const state = getExamState();
302
313
  if (!state)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icoa-cli",
3
- "version": "2.19.61",
3
+ "version": "2.19.62",
4
4
  "description": "ICOA CLI — The world's first CLI-native CTF competition terminal",
5
5
  "type": "module",
6
6
  "bin": {