icoa-cli 2.19.110 → 2.19.112
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/ai4ctf.d.ts +0 -6
- package/dist/commands/ai4ctf.js +1 -1
- package/dist/commands/connect.js +1 -1
- package/dist/commands/ctf.js +1 -1
- package/dist/commands/ctf4ai-demo.d.ts +0 -6
- package/dist/commands/ctf4ai-demo.js +1 -1
- package/dist/commands/env.js +1 -1
- package/dist/commands/exam.js +1 -1
- package/dist/commands/files.js +1 -1
- package/dist/commands/log.js +1 -1
- package/dist/commands/setup.js +1 -1
- package/dist/commands/theme.js +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/access.d.ts +1 -11
- package/dist/lib/access.js +1 -1
- package/dist/lib/budget.d.ts +0 -13
- package/dist/lib/budget.js +1 -1
- package/dist/lib/colors.d.ts +0 -4
- package/dist/lib/colors.js +1 -1
- package/dist/lib/ctfd-client.js +1 -1
- package/dist/lib/demo-exam.d.ts +0 -1
- package/dist/lib/demo-exam.js +1 -1
- package/dist/lib/demo-stats.d.ts +2 -1
- package/dist/lib/exam-setup.d.ts +2 -1
- package/dist/lib/exam-state.d.ts +0 -2
- package/dist/lib/exam-state.js +1 -1
- package/dist/lib/gemini.d.ts +1 -11
- package/dist/lib/gemini.js +1 -1
- package/dist/lib/i18n.d.ts +0 -1
- package/dist/lib/i18n.js +1 -1
- package/dist/lib/logger.d.ts +1 -3
- package/dist/lib/logger.js +1 -1
- package/dist/lib/paper-upgrade.d.ts +0 -5
- package/dist/lib/paper-upgrade.js +1 -1
- package/dist/lib/platform.d.ts +38 -31
- package/dist/lib/platform.js +1 -1
- package/dist/lib/sandbox.d.ts +0 -1
- package/dist/lib/sandbox.js +1 -1
- package/dist/lib/theme.d.ts +2 -1
- package/dist/lib/ui.js +1 -1
- package/dist/lib/update-check.js +1 -1
- package/dist/postinstall.js +1 -1
- package/dist/repl.js +1 -1
- package/dist/types/index.d.ts +0 -11
- package/package.json +11 -2
- package/dist/lib/terminal.d.ts +0 -7
- package/dist/lib/terminal.js +0 -1
package/dist/commands/log.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";import{readFileSync as o,existsSync as t}from"node:fs";import{join as e}from"node:path";import{getSessionLog as n}from"../lib/logger.js";import{getIcoaDir as s,getConfig as i}from"../lib/config.js";import{printHeader as l,printInfo as a,printTable as r}from"../lib/ui.js";export function registerLogCommand(c){const g=c.command("log").description("Display session history").action(()=>{!function(){const o=n();if(0===o.length)return void a("No session log entries yet.");l("Session Log");const t=o.map(o=>{const t=o.timestamp.replace("T"," ").substring(0,19),e={A:chalk.green,B:chalk.yellow,C:chalk.red,command:chalk.blue,submit:chalk.magenta}[o.level]||chalk.gray,n=o.input.length>60
|
|
1
|
+
import chalk from"chalk";import{readFileSync as o,existsSync as t}from"node:fs";import{join as e}from"node:path";import{getSessionLog as n}from"../lib/logger.js";import{getIcoaDir as s,getConfig as i}from"../lib/config.js";import{printHeader as l,printInfo as a,printTable as r}from"../lib/ui.js";export function registerLogCommand(c){const g=c.command("log").description("Display session history").action(()=>{!function(){const o=n();if(0===o.length)return void a("No session log entries yet.");l("Session Log");const t=o.map(o=>{const t=o.timestamp.replace("T"," ").substring(0,19),e={A:chalk.green,B:chalk.yellow,C:chalk.red,command:chalk.blue,submit:chalk.magenta}[o.level]||chalk.gray,n=o.input.length>60?`${o.input.substring(0,57)}...`:o.input;return[chalk.gray(t),e(o.level.padEnd(7)),n]});r(["Time","Type","Content"],t),console.log(chalk.gray(` ${o.length} entries total`)),console.log(),console.log(chalk.gray(" You are at the ")+chalk.cyan("icoa>")+chalk.gray(" prompt. Also: ")+chalk.cyan("log stats")+chalk.gray(" · ")+chalk.cyan("log export")+chalk.gray(" · ")+chalk.cyan("help")+chalk.gray(" all commands.")),console.log()}()});g.command("export").description("Export full audit log for review").action(async()=>{await async function(){const l=i(),a=s(),r=(e(a,"session.log"),e(a,"session-state.json")),c=(e(a,"config.json"),(new Date).toISOString().replace(/[:.]/g,"-").substring(0,19)),g=`icoa-audit-${l.userName||"unknown"}-${c}.json`,m=e(process.cwd(),g),y={exportedAt:(new Date).toISOString(),version:"1.7.2",competitor:{userName:l.userName,userId:l.userId,teamName:l.teamName,teamId:l.teamId,sessionId:l.sessionId},connection:{ctfdUrl:l.ctfdUrl},session:t(r)?JSON.parse(o(r,"utf-8")):null,commands:n()},d=n(),u={};for(const o of d)u[o.level]=(u[o.level]||0)+1;y.summary={totalCommands:d.length,byType:u,firstEntry:d[0]?.timestamp||null,lastEntry:d[d.length-1]?.timestamp||null};const{writeFileSync:p}=await import("node:fs");p(m,JSON.stringify(y,null,2)),console.log(),console.log(chalk.green(" ✓ Audit log exported")),console.log(chalk.white(` ${m}`)),console.log(),console.log(chalk.gray(" Contents:")),console.log(chalk.gray(` Commands: ${d.length}`)),Object.entries(u).forEach(([o,t])=>{console.log(chalk.gray(` ${o}: ${t}`))}),console.log(),console.log(chalk.gray(" This file contains the complete session audit trail.")),console.log(chalk.gray(" Submit to organizers for post-competition verification.")),console.log()}()}),g.command("stats").description("Show session statistics").action(()=>{!function(){const i=n(),l=s(),a=e(l,"session-state.json");if(console.log(),console.log(chalk.bold.white(" Session Statistics")),console.log(chalk.gray(" ─────────────────────────────────────────────")),0===i.length)return console.log(chalk.gray(" No activity recorded yet.")),void console.log();const r=new Date(i[0].timestamp),c=new Date(i[i.length-1].timestamp),g=Math.round((c.getTime()-r.getTime())/6e4);console.log(chalk.gray(" First activity: ")+chalk.white(r.toLocaleString())),console.log(chalk.gray(" Last activity: ")+chalk.white(c.toLocaleString())),console.log(chalk.gray(" Duration: ")+chalk.white(`${g} min`)),console.log();const m={};for(const o of i)m[o.level]=(m[o.level]||0)+1;if(console.log(chalk.gray(" Total commands: ")+chalk.white(String(i.length))),m.command&&console.log(chalk.blue(" commands: ")+chalk.white(String(m.command))),m.A&&console.log(chalk.green(" hint A: ")+chalk.white(String(m.A))),m.B&&console.log(chalk.yellow(" hint B: ")+chalk.white(String(m.B))),m.C&&console.log(chalk.red(" hint C: ")+chalk.white(String(m.C))),m.submit&&console.log(chalk.magenta(" submissions: ")+chalk.white(String(m.submit))),t(a))try{const t=JSON.parse(o(a,"utf-8"));if(console.log(),console.log(chalk.gray(" Exit count: ")+chalk.white(String(t.exitCount||0))),t.totalAwaySeconds){const o=Math.round(t.totalAwaySeconds/60);console.log(chalk.gray(" Total away: ")+chalk.white(`${o} min`))}}catch{}console.log()}()})}
|
package/dist/commands/setup.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";import{input as e,select as o}from"@inquirer/prompts";import{getConfig as t,saveConfig as i}from"../lib/config.js";import{setApiKey as n}from"../lib/gemini.js";import{printSuccess as a,printError as r,printInfo as s,printHeader as c,printKeyValue as l}from"../lib/ui.js";import{logCommand as g}from"../lib/logger.js";export function registerSetupCommand(m){m.command("setup").description("Configure ICOA CLI settings").action(async()=>{g("setup");const m=t();c("ICOA CLI Setup"),console.log(),console.log(chalk.gray(" Tip: press ")+chalk.cyan("Ctrl+C")+chalk.gray(" any time to cancel setup and return to the ")+chalk.cyan("icoa>")+chalk.gray(" prompt — nothing is saved until you confirm.")),console.log(),l("CTFd URL",m.ctfdUrl||chalk.gray("Not configured")),l("CTFd Token",m.token?chalk.green("Configured"):chalk.gray("Not configured")),l("Gemini API Key",m.geminiApiKey||process.env.GEMINI_API_KEY?chalk.green("Configured"):chalk.gray("Not configured")),l("Language",m.language),l("Mode",m.mode||chalk.gray("Not set")),l("Session ID"
|
|
1
|
+
import chalk from"chalk";import{input as e,select as o}from"@inquirer/prompts";import{getConfig as t,saveConfig as i}from"../lib/config.js";import{setApiKey as n}from"../lib/gemini.js";import{printSuccess as a,printError as r,printInfo as s,printHeader as c,printKeyValue as l}from"../lib/ui.js";import{logCommand as g}from"../lib/logger.js";export function registerSetupCommand(m){m.command("setup").description("Configure ICOA CLI settings").action(async()=>{g("setup");const m=t();c("ICOA CLI Setup"),console.log(),console.log(chalk.gray(" Tip: press ")+chalk.cyan("Ctrl+C")+chalk.gray(" any time to cancel setup and return to the ")+chalk.cyan("icoa>")+chalk.gray(" prompt — nothing is saved until you confirm.")),console.log(),l("CTFd URL",m.ctfdUrl||chalk.gray("Not configured")),l("CTFd Token",m.token?chalk.green("Configured"):chalk.gray("Not configured")),l("Gemini API Key",m.geminiApiKey||process.env.GEMINI_API_KEY?chalk.green("Configured"):chalk.gray("Not configured")),l("Language",m.language),l("Mode",m.mode||chalk.gray("Not set")),l("Session ID",`${m.sessionId.substring(0,8)}...`),console.log();try{switch(await o({message:"What would you like to configure?",choices:[{name:"Switch Mode",value:"mode"},{name:"Gemini API Key",value:"gemini"},{name:"CTFd Connection",value:"ctfd"},{name:"Reset Hint Budget",value:"budget"},{name:"View All Settings",value:"view"},{name:"Exit",value:"exit"}]})){case"mode":{const e=await o({message:"Select mode:",choices:[{name:"National Selection — demo, exam, lightweight",value:"selection"},{name:"International Olympiad — Full CTF with AI assistance",value:"olympiad"},{name:"National/Regional Partner — Organizer management",value:"organizer"}]});i({mode:e}),a(`Mode switched to: ${e}. Restart ICOA CLI to apply.`);break}case"gemini":{console.log(),s("Get your API key from: https://aistudio.google.com/apikey"),console.log();const o=await e({message:"Enter Gemini API Key:"});o.trim()?(n(o.trim()),a("Gemini API key saved.")):r("No key provided.");break}case"ctfd":s('Use "join <url>" to connect to a CTFd instance.');break;case"budget":{const{confirm:e}=await import("@inquirer/prompts");if(await e({message:"Reset hint budget to defaults (A:50, B:10, C:2)? This cannot be undone.",default:!1})){const{saveBudget:e}=await import("../lib/config.js"),{DEFAULT_BUDGET:o}=await import("../types/index.js");e({...o}),a("Hint budget reset to defaults.")}break}case"view":{console.log(),c("Full Configuration");const e=t();for(const[o,t]of Object.entries(e))l(o,"token"===o&&t||"geminiApiKey"===o&&t?`${t.toString().substring(0,8)}...`:String(t??"null"));console.log();break}}console.log(),console.log(chalk.gray(" Setup complete. You are back at the ")+chalk.cyan("icoa>")+chalk.gray(" prompt.")),console.log()}catch(e){if("ExitPromptError"===e?.name)return console.log(),console.log(chalk.gray(" Setup cancelled. You are back at the ")+chalk.cyan("icoa>")+chalk.gray(" prompt. Nothing was saved.")),void console.log();throw e}})}
|
package/dist/commands/theme.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import chalk from"chalk";import{getConfig as o,saveConfig as e}from"../lib/config.js";import{logCommand as n}from"../lib/logger.js";const a=["dark","high-contrast"];export function registerThemeCommand(l){l.command("theme [variant]").description("Switch color scheme (dark | high-contrast)").action(l=>{n(`theme ${l||""}`);const r="high-contrast"===o().themeVariant?"high-contrast":"dark";return l?a.includes(l)?(e({themeVariant:l}),console.log(),console.log(chalk.green(` ✓ Theme set to: ${l}`)),console.log(chalk.gray(" Restart ")+chalk.cyan("icoa")+chalk.gray(" to see the new colors.")),void console.log()):(console.log(),console.log(chalk.red(` Unknown theme: ${l}`)),console.log(chalk.gray(" Valid: ")+chalk.white(a.join(", "))),void console.log()):(console.log(),console.log(chalk.gray(" Current theme: ")+chalk.white(r)),console.log(),console.log(chalk.gray(" Available themes:")),console.log(
|
|
1
|
+
import chalk from"chalk";import{getConfig as o,saveConfig as e}from"../lib/config.js";import{logCommand as n}from"../lib/logger.js";const a=["dark","high-contrast"];export function registerThemeCommand(l){l.command("theme [variant]").description("Switch color scheme (dark | high-contrast)").action(l=>{n(`theme ${l||""}`);const r="high-contrast"===o().themeVariant?"high-contrast":"dark";return l?a.includes(l)?(e({themeVariant:l}),console.log(),console.log(chalk.green(` ✓ Theme set to: ${l}`)),console.log(chalk.gray(" Restart ")+chalk.cyan("icoa")+chalk.gray(" to see the new colors.")),void console.log()):(console.log(),console.log(chalk.red(` Unknown theme: ${l}`)),console.log(chalk.gray(" Valid: ")+chalk.white(a.join(", "))),void console.log()):(console.log(),console.log(chalk.gray(" Current theme: ")+chalk.white(r)),console.log(),console.log(chalk.gray(" Available themes:")),console.log(` ${chalk.white("dark ")}${chalk.gray("Darcula — gray on dark gray (default)")}`),console.log(` ${chalk.white("high-contrast ")}${chalk.gray("Pure white on pure black — low vision / projectors")}`),console.log(),console.log(chalk.gray(" Switch: ")+chalk.cyan("theme <name>")+chalk.gray(" (applies on next ")+chalk.cyan("icoa")+chalk.gray(" launch)")),console.log(chalk.gray(' No "back" needed — you are still at the ')+chalk.cyan("icoa>")+chalk.gray(" prompt.")),void console.log())})}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Command as o}from"commander";import chalk from"chalk";import{registerCtfCommands as e}from"./commands/ctf.js";import{registerRefCommand as n}from"./commands/ref.js";import{registerShellCommand as r}from"./commands/shell.js";import{registerFilesCommand as s}from"./commands/files.js";import{registerConnectCommand as t}from"./commands/connect.js";import{registerNoteCommand as
|
|
2
|
+
import{Command as o}from"commander";import chalk from"chalk";import{registerCtfCommands as e}from"./commands/ctf.js";import{registerRefCommand as n}from"./commands/ref.js";import{registerShellCommand as r}from"./commands/shell.js";import{registerFilesCommand as s}from"./commands/files.js";import{registerConnectCommand as t}from"./commands/connect.js";import{registerNoteCommand as l}from"./commands/note.js";import{registerLogCommand as i}from"./commands/log.js";import{registerLangCommand as a}from"./commands/lang.js";import{registerSetupCommand as c}from"./commands/setup.js";import{registerEnvCommand as m}from"./commands/env.js";import{registerAi4ctfCommand as g}from"./commands/ai4ctf.js";import{registerExamCommand as d}from"./commands/exam.js";import{registerCtf4aiDemoCommand as p}from"./commands/ctf4ai-demo.js";import{registerThemeCommand as y}from"./commands/theme.js";import{getConfig as h,saveConfig as u}from"./lib/config.js";import{startRepl as f}from"./repl.js";import{setTerminalTheme as w}from"./lib/theme.js";import{checkForUpdates as b}from"./lib/update-check.js";import{detectIcoaInstalls as T}from"./lib/platform.js";import{readFileSync as $}from"node:fs";import{fileURLToPath as v}from"node:url";import{dirname as A,join as C}from"node:path";const _=A(v(import.meta.url)),E=JSON.parse($(C(_,"..","package.json"),"utf-8")).version,I=chalk.cyan(" ─────────────────────────────────────────────────────"),j=`\n${I}\n\n ${chalk.bold.white("██╗ ██████╗ ██████╗ █████╗")}\n ${chalk.bold.white("██║██╔════╝██╔═══██╗██╔══██╗")}\n ${chalk.bold.white("██║██║ ██║ ██║███████║")}\n ${chalk.bold.white("██║██║ ██║ ██║██╔══██║")}\n ${chalk.bold.white("██║╚██████╗╚██████╔╝██║ ██║")}\n ${chalk.bold.white("╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝")}\n\n ${chalk.yellow("International Cyber Olympiad in AI 2026")}\n ${chalk.bold.magenta("The World's First AI-Native CLI Operating System")}\n ${chalk.bold.magenta("for Cybersecurity & AI Security Competition")}\n ${chalk.bold.magenta("and Olympiad for K-12")}\n\n ${chalk.green.bold("AI4CTF")}${chalk.gray("[Day 1]")} ${chalk.white("AI as your teammate")}\n ${chalk.red.bold("CTF4AI")}${chalk.gray("[Day 2]")} ${chalk.white("Challenge & evaluate AI systems")}\n ${chalk.bold.yellow("AI is your ally. AI is your target.")}\n\n ${chalk.white("Sydney, Australia")} ${chalk.gray("Jun 27 - Jul 2, 2026")}\n ${chalk.cyan.underline("https://icoa2026.au")}\n\n ${chalk.gray(`CLI-Native Competition Terminal v${E}`)}\n\n${I}\n`;process.on("uncaughtException",o=>{"__REPL_NO_EXIT__"!==o.message&&(console.error(chalk.red("Error:"),o.message),process.exit(1))}),process.on("unhandledRejection",o=>{const e=o instanceof Error?o.message:String(o);"__REPL_NO_EXIT__"!==e&&(console.error(chalk.red("Error:"),e),process.exit(1))});const S=new o;if(S.name("icoa").version(E).description("ICOA CLI — CLI-Native CTF Competition Terminal").option("--resume","Resume previous session").action(async o=>{const e=h();w("high-contrast"===e.themeVariant?"high-contrast":"dark"),b(),function(){const o=T();if(o.length<=1)return;const e=o[0];if([...o.map(o=>o.version||"0.0.0")].sort((o,e)=>function(o,e){const n=o.split(".").map(o=>parseInt(o,10)||0),r=e.split(".").map(o=>parseInt(o,10)||0);for(let o=0;o<3;o++)if((n[o]||0)!==(r[o]||0))return(n[o]||0)-(r[o]||0);return 0}(e,o))[0]!==e.version){console.log(),console.log(chalk.yellow.bold(" ⚠ Multiple icoa installations detected on PATH:"));for(let e=0;e<o.length;e++){const n=o[e],r=n.version?`v${n.version}`:"(version unreadable)",s=0===e?chalk.yellow("→"):" ",t=0===e?chalk.gray(" ← currently running (older — shadowing newer install)"):chalk.gray(" ← shadowed");console.log(` ${s} ${chalk.cyan(n.path.padEnd(28))} ${chalk.white(r)}${t}`)}console.log(),console.log(chalk.yellow(" The first install on PATH wins. To use the newer one, remove the older:")),console.log(),e.pkgDir?console.log(` ${chalk.bold.cyan(`sudo rm -rf ${e.pkgDir} ${e.path}`)}`):console.log(` ${chalk.bold.cyan(`sudo rm -rf ${e.path} # also delete its node_modules dir`)}`),console.log(),console.log(chalk.gray(" Then re-run icoa to confirm version banner shows the newer release.")),console.log()}}();const n=process.env.LANG||process.env.LC_ALL||process.env.LC_CTYPE||"";if(!/UTF-?8/i.test(n))if("win32"===process.platform){let o="";try{const{execFileSync:e}=await import("node:child_process");o=e("chcp.com",[],{encoding:"utf-8",timeout:1500,stdio:["ignore","pipe","ignore"]}).trim()}catch{}o.includes("65001")||(console.log(chalk.yellow(`⚠ Windows terminal is not using UTF-8 (current: ${o||"unknown"}).`)),console.log(chalk.gray(' Non-English text (Ukrainian, Chinese, Japanese, etc.) may show as "?" or garbled glyphs.')),console.log(chalk.gray(" Fix (run before ")+chalk.cyan("icoa")+chalk.gray("): ")+chalk.cyan("chcp 65001")),console.log(chalk.gray(" Or stay in English inside the CLI: ")+chalk.cyan("lang en")),console.log())}else console.log(chalk.yellow(`⚠ Your terminal locale is not UTF-8 (LANG=${n||"(unset)"}).`)),console.log(chalk.gray(' Non-English text and box characters may display as "?" or garbled glyphs.')),console.log(chalk.gray(" Fix: ")+chalk.cyan("export LANG=en_US.UTF-8")+chalk.gray(" (or your locale, e.g. ")+chalk.cyan("zh_CN.UTF-8")+chalk.gray(", ")+chalk.cyan("uk_UA.UTF-8")+chalk.gray(")")),console.log();if(console.log(j),process.argv.length<=2||o.resume)return o.resume||await async function(){const o=process.stdin;if(o.isTTY&&"function"==typeof o.setRawMode)return new Promise(e=>{let n=!1;const r=()=>{if(!n){n=!0,o.removeListener("data",s);try{o.setRawMode(!1)}catch{}o.pause(),e()}},s=()=>r();o.setRawMode(!0),o.resume(),o.once("data",s),console.log(chalk.gray(" (press any key to continue...)")),setTimeout(r,3e3)});await new Promise(o=>setTimeout(o,3e3))}(),void f(S,!!o.resume)}),e(S),n(S),r(S),s(S),t(S),l(S),i(S),a(S),c(S),m(S),g(S),d(S),p(S),y(S),S.command("model",{hidden:!0}).argument("[name]","model name to switch to").action(o=>{const e=h().geminiModel||"gemini-2.5-flash";o?(u({geminiModel:o}),console.log(),console.log(chalk.green(" Model switched: ")+chalk.gray(e)+chalk.white(" -> ")+chalk.bold.white(o)),console.log()):(console.log(),console.log(chalk.gray(" Current model: ")+chalk.white(e)),console.log(),console.log(chalk.gray(" Available models:")),console.log(chalk.bold.white(" Gemini 3.x (Latest)")),console.log(chalk.white(" model gemini-3.1-pro-preview ")+chalk.gray("Most powerful, paid")),console.log(chalk.white(" model gemini-3-flash-preview ")+chalk.gray("Fast, free tier")),console.log(chalk.bold.white(" Gemini 2.5 (Stable)")),console.log(chalk.white(" model gemini-2.5-flash ")+chalk.gray("Fast, free tier (default)")),console.log(chalk.white(" model gemini-2.5-pro ")+chalk.gray("Strong reasoning, paid")),console.log(chalk.bold.white(" Open Source")),console.log(chalk.white(" model gemma-4-31b-it ")+chalk.gray("Free, open-source")),console.log(chalk.white(" model <any-model-id> ")+chalk.gray("Custom model")),console.log(),console.log(chalk.gray(" Translation uses gemini-3.1-pro-preview for best quality.")),console.log())}),"1"===process.env.ICOA_RESET_STATE)try{const{clearExamState:o}=await import("./lib/exam-state.js");o(),console.log(chalk.yellow("⚠ ICOA_RESET_STATE=1 — local exam state wiped.")),console.log(chalk.gray(" (Token NOT revoked server-side. Re-enter a fresh token with `exam <token>`.)")),console.log()}catch(o){console.log(chalk.red("⚠ ICOA_RESET_STATE: could not clear state — ")+chalk.gray(String(o)))}S.parse();
|
package/dist/lib/access.d.ts
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
1
|
export declare function isFirstRunOrUpgrade(currentVersion: string): boolean;
|
|
2
2
|
export declare function markVersionSeen(version: string): void;
|
|
3
|
-
export declare function validateToken(token: string): boolean;
|
|
4
3
|
export declare function isActivated(): boolean;
|
|
5
4
|
export declare function getDeviceFingerprint(): string;
|
|
6
|
-
|
|
5
|
+
type ActivateResult = 'ok' | 'invalid' | 'already_bound';
|
|
7
6
|
export declare function activateToken(token: string): ActivateResult;
|
|
8
7
|
export declare function isDeviceMatch(): boolean;
|
|
9
8
|
export declare function isFreeCommand(cmd: string): boolean;
|
|
10
|
-
interface SessionState {
|
|
11
|
-
startedAt: string;
|
|
12
|
-
lastExitAt: string | null;
|
|
13
|
-
lastResumeAt: string | null;
|
|
14
|
-
exitCount: number;
|
|
15
|
-
totalAwaySeconds: number;
|
|
16
|
-
}
|
|
17
|
-
export declare function getSession(): SessionState;
|
|
18
|
-
export declare function saveSession(session: Partial<SessionState>): void;
|
|
19
9
|
export declare function recordExit(): void;
|
|
20
10
|
export declare function recordResume(): {
|
|
21
11
|
awaySeconds: number;
|
package/dist/lib/access.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createHash as e}from"node:crypto";import{execSync as c}from"node:child_process";import{hostname as a,platform as f,arch as d,networkInterfaces as b}from"node:os";import{getConfig as t,saveConfig as n,getIcoaDir as
|
|
1
|
+
import{createHash as e}from"node:crypto";import{execSync as c}from"node:child_process";import{hostname as a,platform as f,arch as d,networkInterfaces as b}from"node:os";import{getConfig as t,saveConfig as n,getIcoaDir as r}from"./config.js";import{readFileSync as o,writeFileSync as i,existsSync as s}from"node:fs";import{join as u}from"node:path";const p=new Set(["93490bf664679467b4ab5af12e240175364b7af988b72cb7d000e309b7c68c15","cba7a6f075a4bd8c2fc973336cc0b1966669fc481381a065ce670730bbe0b7d3","f6773f614341acb8d2c35e8f466f2876095a2c8026a207e77de621050fa64559","d060ab36056fdac449a561fd3340c4b79ad4cde10c9a21c3397aa74574583a2b","523c11774e103298c15eb9d138872295194078f2d29486d0a6bd210436a592c3","1e16864067470f0a97088f067d8df4b655753ff498a91353b5f9b75989b6be39","f1031aac52879c776d4db5ba43eb25bb40f1a841b91304f8adf65a838fb8c666","bcd9e25014633ec6556345ab43d0cd213c454bb10d4ea0bd5be5ba02e1a6622c","9239ba9c3db74eadbb6dd05949c2a3cbe1b22e14e375ab3ca93d050e2e2ce38b","80fc0a899ed4d37138ae3172dafd466de2d3b1ce9630d67e6b00bec9315964d7","33ed35ce028824519d3a013ca326d6a58b1ca3723ec182527a7367e1ffc2712b","7e90933e760ce5e8df48bdb6bab0e95962bdc1098fceedecf617c0dac37b2a11","33fa6e3639a691b217ed0f927a8e2e2aef5eaa15eab7bd90c57874c2df2a28ac","67013663fb4536a47f6ea46b434fb7a00574bd4fc4f579e9439ba07f748db31b","10c83ad26fc965fcb1d228018f6b54d331e4a2e6087603920b75b500e191c5e4","3079268d847dc7eb196ce8cdc0af8a8506b9636539a1b7cce53dc5cb3f4cbe15","985eb5309c546d2f9d5018976973d6da06a374b897c73c830fc9d1305e78efa0","3a7326d4a7f40cc356b17769f4c5fc0d832d926aff9f067efd60f34c053bf9ef","129f408a98ad6c1b13924cf2c831c752b0c726bf1975d6b46f212a178ced52e1","ec44bc0b4eb6a929b969caaa561492527c369cc07efa50e40c2c6c16162f0c2c","07610942d9da62dd5b91c0ba48602bc44973a9d374ed10ab6c708151677ec626","8ce3165bb8e23f3b67ac53b39191aca3e2f0a620b7f932fe6a43e2c42315efcc","69938b605e652afc1cf8bc7c0dcf6a99820878367dfb3b2ec954ba90b36014f5","660377ce87d378285ab7263fdad86208c8563367d2cc36b4c1402520e45dd5f0","bef8a3727dffc94861a1dfa7d6a336ceac30aab9f0679dcab662cb9f682079dd","02318a3597b64a2c7de56b01fc25f33cc4142c76315136186df680f490681294","b7179114b42b90e9557399ae505050111b6afc55f9295d3ee84d5db6873470c5","e227ec0a6b22773500b35bee46f21bc6bc6a00fb001d54e71479f15b8fdfd72b","1c9802ae1e932d55e73994ce173a996d170e4d274aeccb1b784c9d9b69913f3d","5ed51694773bd1b46a085d10e9a369786a997c50b72ec2bdb6e68921ffca5c51","478e457279a41e9b76a3dc5894418e38246d2af31e0f4776dda8aa89c74c88c8","a7ec7e047c0e62862e9be64b0a65d3d7894d41efe6eb16ca6cfca45d8007f16f","88cf2b75604486ac5f1a0db9afbab28e8e866f9ea00f3e4b4353a7ecc81f6b55","dc4daa9ef4149f122979ffd5a8b23bca3cbb83b24a87ba99a1d00dc09e39936e","cffd9a3637b5ae30c27cae4ae42dc21374caa27dd818b5d361f112406bf3c0e7","33b3e85a964f606e857280ba379de46508f9343cbe1d2722abebbed21b82a106","a3754074df08f9c9407c73e680eac1868175cee3beac3f0e7d2bd211bdf51f58","f9eb5fd82f3360e22ad45d4797f3a95b907a766ef5a61d856bf959b60a4930dc","1c53d3145cb38501a2422bf70f55a0c206b68c14e46da31e0e3dc63c7bee78d6","20bc5f2dedbf6423a543ba25183fab3c7268cdd2762e080f6d0b3387c4b3cad8","654faf29e2a177dba2409db4942007630674b86d3ff87d2b11d428e8b4d0db7f","c27979d019266887a1b86225d3c85b2d4553e85814ab2d791547ffc3f9dc664a","74f982351453468e39aff8373cb2649d57344b8002b85fc9c0df118d051817fc","891e59d7896f29a9af2bd1a9aec98ae2ebcc57d9685027e3f07067734e603923","4a8a234bbad11dc98aa27871ed69d351385f9476af0c15b8b426cfa5630cc3db","e42c967f02a434a061152d86d68fa0a3da70235f6f8262a792abeeace14fc419","c1560c78197abd62252f8932abf73d5f7506fcd157e98390230aec81fdf5e5ac","afa2cc8fe8b9b8785921f2e649d90d239f729223b2ce7460c0f22d79427c7b5e","d574f129616c989797b3a4a7988e9d4ca3afb59ed1de67136d459307b9a1807a","94b1c1e2fab8c51bb76d9d7bc07d23f8b509919009feb141f12562cab696d49a","79bf74936c7274d1ee529fb888f30fbf2a33a15308bedb8c77c1c50793790b42","2ee8962250f82c51fdbbf170c6c7fa9b69dca336434714ba607c6f816034cb08","6e8e665df73c6934d5b432f5aa898ba33cc9a40d630b049404a23fa5d2776298","3561d1bd9ae8c916eace5bdd1eb788cd743ad461cff58acf58ed5b2064b30b7b","46a48aa8aef9f09dabb9cf340dacc1889cf3eeebc49c6d2f26ff017cd087857c","561e0536b3cf214a4bf41b2bfb2209c8d659990a871e44bef9bc0136613a4ab9","7c1f3e331c8f59cc6dab7e310d9ad151628f72e5ca3db5a6ff592c8c976c1974","fb9d9d484edfa2c0a531a3e08074f52b6988f1082606f1eecaf0c58f117dbf3d","2810e928a4ce6e2ae016751a7e8b73496de47fe1926abe6ff486bc0eed23e3d7","723c3ede92f08e9ac12b7dc0b9e6a857ab0426d3418524f4eb50ad69f0228ccd","66a07cdf0c9d56558ad4f853203071b56adfa990a4fb065b49c10108f4996fe7","dc826253950962dbc2e5699dc5c138490c4233a23b39a5cf471a931cbbc689b8","ec29a6c60c70a25a5a238ba1d03407dadafee78c01bc9f921549da428c4fbc03","0564c17382a344aa22b5452989d2950f514bbd366e7b8289a458a4604c987641","157ca17a0c1fc9faed3c2557cdf83323210a40020e72497e8e48e1d0055e54c7","e643beaf8a92e574da88b9efa424852d34261be06496ca78d08acd84c38861b7","df982789a1598f53bbcf44f1b2991c86bfd7713b36b04a06cf52640cfc9c579d","e135e4dd1aebe24484909d56fb47619eb975f9790ef4541fdc87f63a3478ecf4","823235bafe4f9e523cc4ffeb16789a2e2d6b246ab5c248335eba364d54474241","a314bda9257484bbbabc1159b957d3aae1de0bca777bcd1462fdc52abf5a62fe","65f6699712af969820645bbe271f66fe92f5da41fd834e329dadeae7801bc112","17e812f25ef6d73de97b3464ca3fd2109433d9b5bc567e4e74feda35bf660492","37ff30e4c4384b810bd86e4472e9ada4b447cd6057daf8df3f923fac3b8b3d77","c55f13e79ce2bb698606f787551591e050e33e576cb845a2f40b03edac56f819","2124d3353d3f105c3780c937f1eaf377f756e59c6ab6a97ef780a718f3a47c7b","e581535019c839a7c383d0eca3de97a6a5701025d4a24ea56f01932f910cb99c","eb089f8a27da4b94b79e5d125ed6205849b1909d0a6c1d0f51c351be2d5b8466","36ee22c148c0282e112950f8e9939fed4b95d2f8a52c9cd2fd3744070df54333","c81b6509d23e889c5b5ec79fdfaf64fed1bf00ced8f4b224aa37b5a37821ccfe","b529c4de4a2fff86266d9b0318f15246ed30f4641b4156778ff9f3fca3099e32","647c2aa42b094baf52537a8f7ffb015b7aa212c67ca779853d263d420760d8b6","8043ea259b8bbb2bf488643376c2a2b9a3864e5617ea9ac5dc72966e289c07c2","bba0397ebaf1a6093a8adb117e84198bb5d2dcf9e8e7792221af9215cda8c10b","978ec66df04567ef8e4f02aa67b903535859f7bf24a5b63298cae1c938a72ab7","124befb26711b599a311b0b0d7352b6e980a594cd56c8447e32c1aba26818fd1","990ddff1fd05882796d2081ebc0d966a39c04af7120a9440562d71db2f48d65d","f396be48a42c9cca6b6fec0a8ee7f5d15f93a57fba3baf04ab5a7d86af68bea5","4f2487f0af254ea067e853bb6874d80d05c46f6322ca6e3a8815b37931533aea","dbacdcc317d0cc436dd93c8be4d1471c712bcc567bda8e9a47dc0c1543d33e19","6658cb8824afdf853e9cf3e667ac9d6bf566d54cc0f3b81ad62f7d7eeadff530","b2c4ea7f091911a1c3733e42a52c75d3ee1666ceb62faa4e8ddb15c8a4b12b14","0f29f04bebfe26d9b0ef7b802e3b2c725e85cdc03407915a3d5753436977f24e","7eadf05c33f72431c7414e60e4068051143154d647452e3888de4c61316950ea","45237d7cbb04c768d25f2dd13708f448f3c50af5262015d468ccbdb51fe0c090","21f14816d480bc5c2e5f0a3387a0897d928e926a4341df3d32d65416b6e387f1","a39bd710b9489e8bef21362c8df87ed76008939e89dbf8e6708a2c574f0727e0","cf365a79503e1457c23cd1ea8c9fa8c398d168288f90b5c1920af798508c2b56","d123f90b3a932edf596fd561e74fc8cc0adc481c3be118a7fa6a5834cd7b787c","cde02309ad295648d86fa4acefdf7224809abb2ab9b025f14ae41e021512c5ed","39835a1337c2afd9a9690cb9946752899a110df312d9d3aa68e10f85fad49dea"]);export function isFirstRunOrUpgrade(e){return t().lastVersion!==e}export function markVersionSeen(e){n({lastVersion:e})}const l=new Set(["ref","help","exit","quit","q","clear","cls","activate"]);function m(c){return e("sha256").update(c.trim().toUpperCase()).digest("hex")}export function isActivated(){const e=t();return!(!e.accessToken||!p.has(m(e.accessToken)))}export function getDeviceFingerprint(){const t=[a(),f(),d()];try{if("darwin"===f()){const e=c("ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID",{encoding:"utf-8"}).match(/"([A-F0-9-]+)"/);e&&t.push(e[1])}else if("linux"===f())s("/etc/machine-id")&&t.push(o("/etc/machine-id","utf-8").trim());else if("win32"===f()){const e=c('powershell -Command "(Get-CimInstance Win32_ComputerSystemProduct).UUID"',{encoding:"utf-8"});t.push(e.trim())}}catch{const e=b();for(const c of Object.values(e))if(c){for(const e of c)if(!e.internal&&"00:00:00:00:00:00"!==e.mac){t.push(e.mac);break}if(t.length>3)break}}return e("sha256").update(t.join("|")).digest("hex")}export function activateToken(e){if(!function(e){return p.has(m(e))}(e))return"invalid";const c=getDeviceFingerprint(),a=u(r(),"token-bindings.json");let f={};if(s(a))try{f=JSON.parse(o(a,"utf-8"))}catch{}const d=m(e),b=f[d];return b&&b!==c?"already_bound":(f[d]=c,i(a,JSON.stringify(f,null,2)),n({accessToken:e.trim().toUpperCase(),deviceFingerprint:c}),"ok")}export function isDeviceMatch(){const e=t();return!e.deviceFingerprint||e.deviceFingerprint===getDeviceFingerprint()}export function isFreeCommand(e){return l.has(e.toLowerCase())}const g=()=>u(r(),"session-state.json");function h(){return{startedAt:(new Date).toISOString(),lastExitAt:null,lastResumeAt:null,exitCount:0,totalAwaySeconds:0}}function x(){const e=g();if(!s(e))return h();try{return{...h(),...JSON.parse(o(e,"utf-8"))}}catch{return h()}}function S(e){const c={...x(),...e};i(g(),JSON.stringify(c,null,2))}export function recordExit(){const e=x();e.lastExitAt=(new Date).toISOString(),e.exitCount++,S(e)}export function recordResume(){const e=x();if(!e.lastExitAt)return null;const c=new Date(e.lastExitAt).getTime(),a=Date.now(),f=Math.round((a-c)/1e3);return e.lastResumeAt=(new Date).toISOString(),e.totalAwaySeconds+=f,S(e),{awaySeconds:f,exitCount:e.exitCount}}
|
package/dist/lib/budget.d.ts
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
import type { HintLevel } from '../types/index.js';
|
|
2
|
-
export declare function checkBudget(level: HintLevel): {
|
|
3
|
-
allowed: boolean;
|
|
4
|
-
remaining: number;
|
|
5
|
-
};
|
|
6
|
-
export declare function deductBudget(level: HintLevel, tokensUsed: number): void;
|
|
7
|
-
export declare function getBudgetDisplay(): string;
|
|
8
|
-
export declare function isTokenCapReached(): boolean;
|
|
9
1
|
export declare function addTokenUsage(tokensUsed: number): void;
|
|
10
|
-
export declare function getTokenUsage(): {
|
|
11
|
-
used: number;
|
|
12
|
-
cap: number;
|
|
13
|
-
remaining: number;
|
|
14
|
-
};
|
package/dist/lib/budget.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getBudget as
|
|
1
|
+
import{getBudget as o,saveBudget as n}from"./config.js";export function addTokenUsage(e){const s=o();s.tokensUsed+=e,n(s)}
|
package/dist/lib/colors.d.ts
CHANGED
|
@@ -9,7 +9,3 @@ export declare const c: {
|
|
|
9
9
|
orange: (s: string) => string;
|
|
10
10
|
white: (s: string) => string;
|
|
11
11
|
};
|
|
12
|
-
export declare const DARCULA_BG_HEX = "#2B2B2B";
|
|
13
|
-
export declare const DARCULA_FG_HEX = "#A9B7C6";
|
|
14
|
-
export declare const DARCULA_BG_RGB: readonly [43, 43, 43];
|
|
15
|
-
export declare const DARCULA_FG_RGB: readonly [169, 183, 198];
|
package/dist/lib/colors.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e=(e,o
|
|
1
|
+
const e=(e,n,o)=>t=>`[38;2;${e};${n};${o}m${t}[39m`;export const c={fg:e(169,183,198),muted:e(85,85,85),red:e(255,107,104),green:e(168,192,35),yellow:e(214,191,85),blue:e(126,174,241),cyan:e(40,123,222),orange:e(204,120,50),white:e(255,255,255)};
|
package/dist/lib/ctfd-client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createWriteStream as e,mkdirSync as t}from"node:fs";import{join as s}from"node:path";import{pipeline as o}from"node:stream/promises";import{Readable as a}from"node:stream";export class CTFdClient{baseUrl;token;sessionCookie;csrfNonce;constructor(e,t,s,o){this.baseUrl=e.replace(/\/+$/,""),this.token=t,this.sessionCookie=s||"",this.csrfNonce=o||""}getAuthHeaders(){if(this.token)return{Authorization:`Token ${this.token}`};if(this.sessionCookie){const e={Cookie:this.sessionCookie};return this.csrfNonce&&(e["CSRF-Token"]=this.csrfNonce),e}return{}}async fetchCsrfNonce(){if(this.csrfNonce)return this.csrfNonce;try{const e=await fetch(this.baseUrl,{headers:this.sessionCookie?{Cookie:this.sessionCookie}:{}}),t=(await e.text()).match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);if(t)return this.csrfNonce=t[1],this.csrfNonce}catch{}return""}async request(e,t,s){this.sessionCookie&&!this.csrfNonce&&await this.fetchCsrfNonce();const o=`${this.baseUrl}/api/v1${t}`,a={...this.getAuthHeaders(),"Content-Type":"application/json"},n=await fetch(o,{method:e,headers:a,body:s?JSON.stringify(s):void 0});if(!n.ok){const e=await n.text().catch(()=>"Unknown error");throw new Error(`CTFd API error (${n.status}): ${e}`)}const r=await n.json();if(!1===r.success)throw new Error(`CTFd error: ${r.errors?.join(", ")||"Unknown error"}`);return r.data}async testConnection(){try{return await this.request("GET","/users/me")}catch(e){if(this.sessionCookie&&e.message?.includes("403"))return this.testConnectionViaProfile();throw e}}async testConnectionViaProfile(){const e=await fetch(`${this.baseUrl}/settings`,{headers:{Cookie:this.sessionCookie}});if(!e.ok)throw new Error("Session expired or invalid.");const t=await e.text(),s=t.match(/name="name"[^>]*value="([^"]+)"/)||t.match(/<input[^>]*id="name"[^>]*value="([^"]+)"/),o=s?.[1]||"User",a=t.match(/user_id['":\s]+(\d+)/)||t.match(/userId['":\s]+(\d+)/),n=a?parseInt(a[1],10):0,r=t.match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);return r&&(this.csrfNonce=r[1]),{id:n,name:o,score:0,team_id:0,country:""}}async getChallenges(){return this.request("GET","/challenges")}async getChallenge(e){return this.request("GET",`/challenges/${e}`)}async submitFlag(e,t){const s=await fetch(`${this.baseUrl}/api/v1/challenges/attempt`,{method:"POST",headers:{...this.getAuthHeaders(),"Content-Type":"application/json"},body:JSON.stringify({challenge_id:e,submission:t})});if(!s.ok){const e=await s.text().catch(()=>"Unknown error");throw new Error(`CTFd API error (${s.status}): ${e}`)}return(await s.json()).data}async getScoreboard(){return this.request("GET","/scoreboard")}async getTeam(){return this.request("GET","/teams/me")}async getCompetitionMeta(){const e=await fetch(this.baseUrl),t=await e.text(),s=t.match(/'start'\s*:\s*(\d+)/),o=t.match(/'end'\s*:\s*(\d+)/),a=t.match(/'userMode'\s*:\s*"([^"]+)"/),n=t.match(/'csrfNonce'\s*:\s*"([^"]+)"/);return{start:s?parseInt(s[1]):null,end:o?parseInt(o[1]):null,userMode:a?.[1]||"users",csrfNonce:n?.[1]||""}}async getChallengeFiles(e){return(await this.getChallenge(e)).files||[]}async downloadFile(n,r){t(r,{recursive:!0});const i=n.startsWith("http")?n:`${this.baseUrl}/${n.replace(/^\//,"")}`,c=await fetch(i,{headers:this.getAuthHeaders(),redirect:"follow"});if(!c.ok||!c.body)throw new Error(`Failed to download: ${i}`);const h=(n.split("/").pop()||"file").split("?")[0],l=s(r,h),d=e(l);return await o(a.fromWeb(c.body),d),l}async getTokenViaIcoaApi(e,t){const s=JSON.stringify({name:e,password:t}),o={"Content-Type":"application/json"};try{const e=await fetch(`${this.baseUrl}/api/icoa/token`,{method:"POST",headers:o,body:s,signal:AbortSignal.timeout(5e3)});if(e.ok){const t=await e.json();if(t.success&&t.data?.token)return t.data.token}}catch{}try{const e=await fetch(`${this.baseUrl}:9090/api/icoa/token`,{method:"POST",headers:o,body:s,signal:AbortSignal.timeout(5e3)});if(e.ok){const t=await e.json();if(t.success&&t.data?.token)return t.data.token}}catch{}return null}async loginWithCredentials(e,t){const s=await this.getTokenViaIcoaApi(e,t);if(s)return{token:s,session:"",csrf:""};const o=await fetch(`${this.baseUrl}/login`),a=await o.text(),n=a.match(/name="nonce"[^>]*value="([^"]+)"/)||a.match(/value="([^"]+)"[^>]*name="nonce"/)||a.match(/id="nonce"[^>]*value="([^"]+)"/)||a.match(/csrfNonce['":\s]+['"]([^'"]+)['"]/),r=n?.[1]||"";if(!r)throw new Error("Could not extract CSRF nonce from login page.");const i=o.headers.getSetCookie?.()||[],c=i.map(e=>e.split(";")[0]).join("; "),h=await fetch(`${this.baseUrl}/login`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Cookie:c},body:new URLSearchParams({name:e,password:t,nonce:r,_submit:"Submit"}),redirect:"manual"}),l=h.headers.getSetCookie?.()||[],d=[...i,...l].map(e=>e.split(";")[0]).join("; ");if((h.headers.get("location")||"").includes("/login"))throw new Error("Invalid username or password.");try{const e=await fetch(`${this.baseUrl}/settings`,{headers:{Cookie:d}}),t=(await e.text()).match(/csrfNonce['":\s]+['"]([^'"]+)['"]/),s=t?.[1]||r,o=await fetch(`${this.baseUrl}/api/v1/tokens`,{method:"POST",headers:{"Content-Type":"application/json",Cookie:d,"CSRF-Token":s},body:JSON.stringify({expiration:"2026-12-31T23:59:59+00:00"})});if(o.ok){const e=await o.json();if(e.success&&e.data?.value)return{token:e.data.value,session:"",csrf:""}}}catch{}let f="";try{const e=await fetch(`${this.baseUrl}/challenges`,{headers:{Cookie:d}}),t=(await e.text()).match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);t&&(f=t[1])}catch{}return{token:"",session:d,csrf:f}}}
|
|
1
|
+
import{createWriteStream as e,mkdirSync as t}from"node:fs";import{join as s}from"node:path";import{pipeline as o}from"node:stream/promises";import{Readable as a}from"node:stream";export class CTFdClient{baseUrl;token;sessionCookie;csrfNonce;constructor(e,t,s,o){this.baseUrl=e.replace(/\/+$/,""),this.token=t,this.sessionCookie=s||"",this.csrfNonce=o||""}getAuthHeaders(){if(this.token)return{Authorization:`Token ${this.token}`};if(this.sessionCookie){const e={Cookie:this.sessionCookie};return this.csrfNonce&&(e["CSRF-Token"]=this.csrfNonce),e}return{}}async fetchCsrfNonce(){if(this.csrfNonce)return this.csrfNonce;try{const e=await fetch(this.baseUrl,{headers:this.sessionCookie?{Cookie:this.sessionCookie}:{}}),t=(await e.text()).match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);if(t)return this.csrfNonce=t[1],this.csrfNonce}catch{}return""}async request(e,t,s){this.sessionCookie&&!this.csrfNonce&&await this.fetchCsrfNonce();const o=`${this.baseUrl}/api/v1${t}`,a={...this.getAuthHeaders(),"Content-Type":"application/json"},n=await fetch(o,{method:e,headers:a,body:s?JSON.stringify(s):void 0});if(!n.ok){const e=await n.text().catch(()=>"Unknown error");throw new Error(`CTFd API error (${n.status}): ${e}`)}const r=await n.json();if(!1===r.success)throw new Error(`CTFd error: ${r.errors?.join(", ")||"Unknown error"}`);return r.data}async testConnection(){try{return await this.request("GET","/users/me")}catch(e){if(this.sessionCookie&&e.message?.includes("403"))return this.testConnectionViaProfile();throw e}}async testConnectionViaProfile(){const e=await fetch(`${this.baseUrl}/settings`,{headers:{Cookie:this.sessionCookie}});if(!e.ok)throw new Error("Session expired or invalid.");const t=await e.text(),s=t.match(/name="name"[^>]*value="([^"]+)"/)||t.match(/<input[^>]*id="name"[^>]*value="([^"]+)"/),o=s?.[1]||"User",a=t.match(/user_id['":\s]+(\d+)/)||t.match(/userId['":\s]+(\d+)/),n=a?parseInt(a[1],10):0,r=t.match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);return r&&(this.csrfNonce=r[1]),{id:n,name:o,score:0,team_id:0,country:""}}async getChallenges(){return this.request("GET","/challenges")}async getChallenge(e){return this.request("GET",`/challenges/${e}`)}async submitFlag(e,t){const s=await fetch(`${this.baseUrl}/api/v1/challenges/attempt`,{method:"POST",headers:{...this.getAuthHeaders(),"Content-Type":"application/json"},body:JSON.stringify({challenge_id:e,submission:t})});if(!s.ok){const e=await s.text().catch(()=>"Unknown error");throw new Error(`CTFd API error (${s.status}): ${e}`)}return(await s.json()).data}async getScoreboard(){return this.request("GET","/scoreboard")}async getTeam(){return this.request("GET","/teams/me")}async getCompetitionMeta(){const e=await fetch(this.baseUrl),t=await e.text(),s=t.match(/'start'\s*:\s*(\d+)/),o=t.match(/'end'\s*:\s*(\d+)/),a=t.match(/'userMode'\s*:\s*"([^"]+)"/),n=t.match(/'csrfNonce'\s*:\s*"([^"]+)"/);return{start:s?parseInt(s[1],10):null,end:o?parseInt(o[1],10):null,userMode:a?.[1]||"users",csrfNonce:n?.[1]||""}}async getChallengeFiles(e){return(await this.getChallenge(e)).files||[]}async downloadFile(n,r){t(r,{recursive:!0});const i=n.startsWith("http")?n:`${this.baseUrl}/${n.replace(/^\//,"")}`,c=await fetch(i,{headers:this.getAuthHeaders(),redirect:"follow"});if(!c.ok||!c.body)throw new Error(`Failed to download: ${i}`);const h=(n.split("/").pop()||"file").split("?")[0],l=s(r,h),d=e(l);return await o(a.fromWeb(c.body),d),l}async getTokenViaIcoaApi(e,t){const s=JSON.stringify({name:e,password:t}),o={"Content-Type":"application/json"};try{const e=await fetch(`${this.baseUrl}/api/icoa/token`,{method:"POST",headers:o,body:s,signal:AbortSignal.timeout(5e3)});if(e.ok){const t=await e.json();if(t.success&&t.data?.token)return t.data.token}}catch{}try{const e=await fetch(`${this.baseUrl}:9090/api/icoa/token`,{method:"POST",headers:o,body:s,signal:AbortSignal.timeout(5e3)});if(e.ok){const t=await e.json();if(t.success&&t.data?.token)return t.data.token}}catch{}return null}async loginWithCredentials(e,t){const s=await this.getTokenViaIcoaApi(e,t);if(s)return{token:s,session:"",csrf:""};const o=await fetch(`${this.baseUrl}/login`),a=await o.text(),n=a.match(/name="nonce"[^>]*value="([^"]+)"/)||a.match(/value="([^"]+)"[^>]*name="nonce"/)||a.match(/id="nonce"[^>]*value="([^"]+)"/)||a.match(/csrfNonce['":\s]+['"]([^'"]+)['"]/),r=n?.[1]||"";if(!r)throw new Error("Could not extract CSRF nonce from login page.");const i=o.headers.getSetCookie?.()||[],c=i.map(e=>e.split(";")[0]).join("; "),h=await fetch(`${this.baseUrl}/login`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Cookie:c},body:new URLSearchParams({name:e,password:t,nonce:r,_submit:"Submit"}),redirect:"manual"}),l=h.headers.getSetCookie?.()||[],d=[...i,...l].map(e=>e.split(";")[0]).join("; ");if((h.headers.get("location")||"").includes("/login"))throw new Error("Invalid username or password.");try{const e=await fetch(`${this.baseUrl}/settings`,{headers:{Cookie:d}}),t=(await e.text()).match(/csrfNonce['":\s]+['"]([^'"]+)['"]/),s=t?.[1]||r,o=await fetch(`${this.baseUrl}/api/v1/tokens`,{method:"POST",headers:{"Content-Type":"application/json",Cookie:d,"CSRF-Token":s},body:JSON.stringify({expiration:"2026-12-31T23:59:59+00:00"})});if(o.ok){const e=await o.json();if(e.success&&e.data?.value)return{token:e.data.value,session:"",csrf:""}}}catch{}let f="";try{const e=await fetch(`${this.baseUrl}/challenges`,{headers:{Cookie:d}}),t=(await e.text()).match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);t&&(f=t[1])}catch{}return{token:"",session:d,csrf:f}}}
|
package/dist/lib/demo-exam.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { ExamQuestion, ExamSession } from '../types/index.js';
|
|
2
2
|
export declare const DEMO_POOL_SIZE = 30;
|
|
3
3
|
export declare const DEMO_PICK_SIZE = 10;
|
|
4
|
-
export declare const DEMO_SESSION: ExamSession;
|
|
5
4
|
export declare const DEMO_ANSWERS: Record<number, 'A' | 'B' | 'C' | 'D'>;
|
|
6
5
|
export declare const DEMO_EXPLANATIONS: Record<number, string>;
|
|
7
6
|
export declare const DEMO_QUESTIONS: ExamQuestion[];
|
package/dist/lib/demo-exam.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as e,readFileSync as t}from"node:fs";import{join as r,dirname as i}from"node:path";import{fileURLToPath as n}from"node:url";import{getConfig as o}from"./config.js";const s=i(n(import.meta.url));export const DEMO_POOL_SIZE=30;export const DEMO_PICK_SIZE=10;export const DEMO_SESSION={examId:"demo-free",examName:"ICOA Demo Exam — Free Practice",startedAt:"",durationMinutes:0,questionCount:10,country:"ALL"};export const DEMO_ANSWERS={1:"B",2:"B",3:"C",4:"B",5:"C",6:"B",7:"B",8:"C",9:"C",10:"B",11:"C",12:"B",13:"B",14:"B",15:"B",16:"D",17:"D",18:"C",19:"C",20:"B",21:"A",22:"D",23:"C",24:"B",25:"A",26:"B",27:"D",28:"C",29:"A",30:"B"};export const DEMO_EXPLANATIONS={1:"RSA is an asymmetric (public-key) cipher. AES, DES, and Blowfish are all symmetric ciphers that use the same key for encryption and decryption.",2:"SQL injection occurs when user input is inserted directly into database queries without proper sanitization, allowing attackers to manipulate the query.",3:"HTTP 403 means Forbidden — the server understood the request but refuses to authorize it. 401 is Unauthorized, 404 is Not Found, 500 is Internal Server Error.",4:"A nonce (number used once) is a random value used in cryptographic protocols to prevent replay attacks — ensuring each request or message is unique and cannot be reused by an attacker.",5:"Wireshark is the standard tool for capturing and analyzing network packets. Burp Suite is for web testing, John the Ripper for password cracking, Ghidra for reverse engineering.",6:"XSS stands for Cross-Site Scripting — a vulnerability where attackers inject malicious scripts into web pages viewed by other users.",7:"A firewall filters network traffic based on security rules, blocking unauthorized access while allowing legitimate communication. It does not encrypt, scan for viruses, or speed up connections.",8:"A Trojan disguises itself as legitimate software to trick users into installing it. Unlike worms, Trojans do not self-replicate.",9:"HTTPS (HTTP Secure) uses TLS/SSL to encrypt web traffic, protecting data from eavesdropping and tampering. HTTP, FTP, and SMTP are not secure by default.",10:"A cryptographic hash is a one-way function that produces a fixed-size digest. It cannot be reversed, unlike encryption.",11:"Ghidra is a reverse engineering and binary analysis tool developed by the NSA. Nmap scans ports, SQLMap tests SQL injection, Nikto scans web servers.",12:"DNS Spoofing (cache poisoning) manipulates DNS responses to redirect victims to attacker-controlled servers. It is distinct from phishing, SQLi, and brute force.",13:"SSH (Secure Shell) runs on port 22 by default. Port 21 is FTP, 80 is HTTP, 443 is HTTPS.",14:"Two-factor authentication (2FA) requires two distinct types of credentials — something you know (password) and something you have (phone/token) or are (biometrics).",15:'The command "netstat -tulpn" shows all listening TCP/UDP ports with process info. "ls -la" lists files, "chmod" changes permissions, "cat /etc/passwd" shows user accounts.',16:"A Man-in-the-Middle (MitM) attack intercepts and potentially modifies communications between two parties who believe they are communicating directly with each other.",17:"SHA-256 is a cryptographic hash function. AES-256 is a symmetric cipher, RSA-2048 is an asymmetric cipher, and Diffie-Hellman is a key exchange protocol.",18:"The principle of least privilege means granting users only the minimum permissions necessary to perform their tasks, reducing the attack surface.",19:"Nmap is the standard port scanning tool. Wireshark captures packets, Metasploit is an exploitation framework, and Hashcat is a password cracker.",20:"Ransomware encrypts the victim's files and demands payment (usually cryptocurrency) in exchange for the decryption key.",21:"Symmetric encryption uses a single shared key for both encrypting and decrypting. Asymmetric encryption uses a key pair — a public key to encrypt and a private key to decrypt.",22:"Remote Code Execution (RCE) allows an attacker to run arbitrary code on a target server, often leading to full system compromise. CSRF, clickjacking, and open redirect have different impacts.",23:"OWASP (Open Web Application Security Project) is a non-profit organization that publishes widely-used web security standards and guides, including the OWASP Top 10.",24:"The chmod command changes file permissions in Linux. chown changes ownership, chgrp changes group, and passwd changes user passwords.",25:"An SSL/TLS certificate is a digital document issued by a trusted Certificate Authority that verifies a website's identity and enables encrypted HTTPS connections.",26:"Phishing is a social engineering attack that tricks people into revealing sensitive information. Buffer overflow, SQL injection, and port scanning are technical attacks.",27:"The grep command searches for text patterns in files using regular expressions. It is one of the most commonly used Linux text processing tools.",28:"A VPN (Virtual Private Network) creates an encrypted tunnel for internet traffic, protecting data from interception and masking the user's IP address.",29:"CSRF (Cross-Site Request Forgery) tricks a logged-in user's browser into performing unwanted actions on a trusted site, such as changing account settings or transferring funds.",30:"Passwords should be stored as salted cryptographic hashes. Plain text and Base64 are insecure, and AES encryption is reversible if the key is compromised. Salted hashing is one-way and resistant to rainbow tables."};export const DEMO_QUESTIONS=[{number:1,text:"Which algorithm is NOT a symmetric cipher?",category:"Cryptography",options:{A:"AES",B:"RSA",C:"DES",D:"Blowfish"}},{number:2,text:"What does SQL injection exploit?",category:"Web Security",options:{A:"Buffer overflow in web server",B:"Unsanitized user input in database queries",C:"Weak encryption algorithms",D:"Misconfigured firewall rules"}},{number:3,text:'Which HTTP status code indicates "Forbidden"?',category:"Web Security",options:{A:"401",B:"404",C:"403",D:"500"}},{number:4,text:"What is the primary purpose of a nonce in cryptography?",category:"Cryptography",options:{A:"Encrypt data at rest",B:"Prevent replay attacks",C:"Generate random passwords",D:"Compress data before encryption"}},{number:5,text:"Which tool is commonly used for network packet capture?",category:"Network",options:{A:"Burp Suite",B:"Ghidra",C:"Wireshark",D:"John the Ripper"}},{number:6,text:"What does XSS stand for in cybersecurity?",category:"Web Security",options:{A:"Extended Security System",B:"Cross-Site Scripting",C:"XML Secure Socket",D:"Cross-Server Sharing"}},{number:7,text:"What is the primary function of a firewall?",category:"Network",options:{A:"Encrypt network data",B:"Filter network traffic based on security rules",C:"Detect viruses in files",D:"Speed up internet connection"}},{number:8,text:"Which type of malware disguises itself as legitimate software?",category:"Malware",options:{A:"Worm",B:"Ransomware",C:"Trojan",D:"Adware"}},{number:9,text:"Which protocol provides secure communication on the web?",category:"Network",options:{A:"HTTP",B:"FTP",C:"HTTPS",D:"SMTP"}},{number:10,text:"What is a cryptographic hash?",category:"Cryptography",options:{A:"A reversible encryption key",B:"A one-way function producing a fixed-size digest",C:"An authentication protocol",D:"A type of digital signature"}},{number:11,text:"Which tool is used for binary analysis?",category:"Reverse Engineering",options:{A:"Nmap",B:"SQLMap",C:"Ghidra",D:"Nikto"}},{number:12,text:"Which attack manipulates DNS requests to redirect traffic?",category:"Network",options:{A:"Phishing",B:"DNS Spoofing",C:"SQL Injection",D:"Brute Force"}},{number:13,text:"What is the standard port for SSH?",category:"Network",options:{A:"21",B:"22",C:"80",D:"443"}},{number:14,text:"What is two-factor authentication (2FA)?",category:"Authentication",options:{A:"Using two different passwords",B:"Verifying identity with two distinct types of credentials",C:"Encrypting data twice",D:"Connecting through two networks"}},{number:15,text:"Which Linux command shows open ports on a system?",category:"Linux",options:{A:"ls -la",B:"netstat -tulpn",C:"chmod 777",D:"cat /etc/passwd"}},{number:16,text:"What is a Man-in-the-Middle (MitM) attack?",category:"Network",options:{A:"Accessing a server without authorization",B:"Guessing passwords by brute force",C:"Sending multiple requests to overload a server",D:"Intercepting and modifying communications between two parties"}},{number:17,text:"Which of these is a hash algorithm?",category:"Cryptography",options:{A:"AES-256",B:"Diffie-Hellman",C:"RSA-2048",D:"SHA-256"}},{number:18,text:"What is the principle of least privilege?",category:"Security",options:{A:"Give root access to all users",B:"Use the shortest password possible",C:"Grant only the permissions necessary to perform a task",D:"Disable all firewalls"}},{number:19,text:"Which tool is commonly used for port scanning?",category:"Network",options:{A:"Wireshark",B:"Metasploit",C:"Nmap",D:"Hashcat"}},{number:20,text:"What is ransomware?",category:"Malware",options:{A:"Software that shows unwanted ads",B:"Software that encrypts files and demands payment to decrypt",C:"Software that records keystrokes",D:"Software that replicates across networks"}},{number:21,text:"What is the difference between symmetric and asymmetric encryption?",category:"Cryptography",options:{A:"Symmetric uses the same key to encrypt and decrypt; asymmetric uses two different keys",B:"Symmetric is slower than asymmetric",C:"Asymmetric only works with small files",D:"There is no significant difference"}},{number:22,text:"Which vulnerability allows arbitrary code execution on a web server?",category:"Web Security",options:{A:"CSRF",B:"Open Redirect",C:"Clickjacking",D:"Remote Code Execution (RCE)"}},{number:23,text:"What is OWASP?",category:"Security",options:{A:"A security operating system",B:"A type of firewall",C:"An organization that publishes web security standards and guides",D:"A programming language for security"}},{number:24,text:"Which Linux command changes file permissions?",category:"Linux",options:{A:"chown",B:"chmod",C:"chgrp",D:"passwd"}},{number:25,text:"What is an SSL/TLS certificate?",category:"Cryptography",options:{A:"A digital document that verifies a website identity",B:"A file containing malware",C:"A private key for SSH",D:"A type of encrypted database"}},{number:26,text:"Which of the following is a social engineering attack?",category:"Security",options:{A:"Buffer overflow",B:"Phishing",C:"SQL Injection",D:"Port scanning"}},{number:27,text:'What does the Linux command "grep" do?',category:"Linux",options:{A:"Compresses files",B:"Configures the network",C:"Shows active processes",D:"Searches for text patterns in files"}},{number:28,text:"What is a VPN?",category:"Network",options:{A:"A type of virus",B:"A file transfer protocol",C:"A virtual private network that encrypts internet traffic",D:"A vulnerability scanner"}},{number:29,text:"What is CSRF (Cross-Site Request Forgery)?",category:"Web Security",options:{A:"An attack that forces a user browser to perform unauthorized actions",B:"A data encryption method",C:"A type of network scanner",D:"A file compression technique"}},{number:30,text:"What is the best practice for storing passwords in a database?",category:"Security",options:{A:"Plain text",B:"Hashed with salt",C:"Encrypted with AES",D:"Encoded in Base64"}}];function a(e){const t=[...e];for(let e=t.length-1;e>0;e--){const r=Math.floor(Math.random()*(e+1));[t[e],t[r]]=[t[r],t[e]]}return t}export function pickDemoQuestions(e=10){const t=getLocalizedDemoQuestions(),r=getLocalizedExplanations();return a(t.map(e=>({...e,answer:DEMO_ANSWERS[e.number],explanation:r[e.number]}))).slice(0,e).map((e,t)=>{const r=function(e){if(!e.answer)return e;const t=["A","B","C","D"],r=a(t),i={A:e.options[r[0]],B:e.options[r[1]],C:e.options[r[2]],D:e.options[r[3]]},n=t[r.indexOf(e.answer)];return{...e,options:i,answer:n,sourceOrder:r}}(e);return{...r,sourceNumber:e.number,number:t+1}})}export function getLocalizedExplanations(){const i=o().language;if(!i||"en"===i)return DEMO_EXPLANATIONS;const n=r(s,"..","..","translations",i,"demo-explanations.json");if(!e(n))return DEMO_EXPLANATIONS;try{const e=JSON.parse(t(n,"utf-8"));if(e&&"object"==typeof e)return{...DEMO_EXPLANATIONS,...e}}catch{}return DEMO_EXPLANATIONS}export function getLocalizedDemoQuestions(){const i=o().language;if(!i||"en"===i)return DEMO_QUESTIONS;const n=r(s,"..","..","translations",i,"demo.json");if(!e(n))return DEMO_QUESTIONS;try{const e=JSON.parse(t(n,"utf-8"));return Array.isArray(e)?DEMO_QUESTIONS.map(t=>{const r=e.find(e=>e.number===t.number);return r&&r.options?r:t}):DEMO_QUESTIONS}catch{}return DEMO_QUESTIONS}export function getLocalizedDemoSession(){const e=o().language;return e&&"en"!==e?{...DEMO_SESSION,examName:{zh:"ICOA 模拟考试 — 免费练习",ja:"ICOA デモ試験 — 無料練習",ko:"ICOA 데모 시험 — 무료 연습",es:"ICOA Examen Demo — Práctica Gratis",ar:"اختبار ICOA التجريبي — تدريب مجاني",fr:"ICOA Examen Démo — Pratique Gratuite",pt:"ICOA Exame Demo — Prática Gratuita",ru:"ICOA Демо Экзамен — Бесплатная Практика",hi:"ICOA डेमो परीक्षा — निःशुल्क अभ्यास",de:"ICOA Demo-Prüfung — Kostenlose Übung",id:"ICOA Ujian Demo — Latihan Gratis",th:"ICOA สอบทดลอง — ฝึกฟรี",vi:"ICOA Thi Thử — Luyện Tập Miễn Phí",tr:"ICOA Demo Sınav — Ücretsiz Uygulama",uk:"ICOA Демо Екзамен — Безплатна Практика",ht:"ICOA Egzamen Demo — Pratik Gratis"}[e]||DEMO_SESSION.examName,startedAt:""}:{...DEMO_SESSION,startedAt:""}}
|
|
1
|
+
import{existsSync as e,readFileSync as t}from"node:fs";import{join as r,dirname as i}from"node:path";import{fileURLToPath as n}from"node:url";import{getConfig as o}from"./config.js";const s=i(n(import.meta.url));export const DEMO_POOL_SIZE=30;export const DEMO_PICK_SIZE=10;const a={examId:"demo-free",examName:"ICOA Demo Exam — Free Practice",startedAt:"",durationMinutes:0,questionCount:10,country:"ALL"};export const DEMO_ANSWERS={1:"B",2:"B",3:"C",4:"B",5:"C",6:"B",7:"B",8:"C",9:"C",10:"B",11:"C",12:"B",13:"B",14:"B",15:"B",16:"D",17:"D",18:"C",19:"C",20:"B",21:"A",22:"D",23:"C",24:"B",25:"A",26:"B",27:"D",28:"C",29:"A",30:"B"};export const DEMO_EXPLANATIONS={1:"RSA is an asymmetric (public-key) cipher. AES, DES, and Blowfish are all symmetric ciphers that use the same key for encryption and decryption.",2:"SQL injection occurs when user input is inserted directly into database queries without proper sanitization, allowing attackers to manipulate the query.",3:"HTTP 403 means Forbidden — the server understood the request but refuses to authorize it. 401 is Unauthorized, 404 is Not Found, 500 is Internal Server Error.",4:"A nonce (number used once) is a random value used in cryptographic protocols to prevent replay attacks — ensuring each request or message is unique and cannot be reused by an attacker.",5:"Wireshark is the standard tool for capturing and analyzing network packets. Burp Suite is for web testing, John the Ripper for password cracking, Ghidra for reverse engineering.",6:"XSS stands for Cross-Site Scripting — a vulnerability where attackers inject malicious scripts into web pages viewed by other users.",7:"A firewall filters network traffic based on security rules, blocking unauthorized access while allowing legitimate communication. It does not encrypt, scan for viruses, or speed up connections.",8:"A Trojan disguises itself as legitimate software to trick users into installing it. Unlike worms, Trojans do not self-replicate.",9:"HTTPS (HTTP Secure) uses TLS/SSL to encrypt web traffic, protecting data from eavesdropping and tampering. HTTP, FTP, and SMTP are not secure by default.",10:"A cryptographic hash is a one-way function that produces a fixed-size digest. It cannot be reversed, unlike encryption.",11:"Ghidra is a reverse engineering and binary analysis tool developed by the NSA. Nmap scans ports, SQLMap tests SQL injection, Nikto scans web servers.",12:"DNS Spoofing (cache poisoning) manipulates DNS responses to redirect victims to attacker-controlled servers. It is distinct from phishing, SQLi, and brute force.",13:"SSH (Secure Shell) runs on port 22 by default. Port 21 is FTP, 80 is HTTP, 443 is HTTPS.",14:"Two-factor authentication (2FA) requires two distinct types of credentials — something you know (password) and something you have (phone/token) or are (biometrics).",15:'The command "netstat -tulpn" shows all listening TCP/UDP ports with process info. "ls -la" lists files, "chmod" changes permissions, "cat /etc/passwd" shows user accounts.',16:"A Man-in-the-Middle (MitM) attack intercepts and potentially modifies communications between two parties who believe they are communicating directly with each other.",17:"SHA-256 is a cryptographic hash function. AES-256 is a symmetric cipher, RSA-2048 is an asymmetric cipher, and Diffie-Hellman is a key exchange protocol.",18:"The principle of least privilege means granting users only the minimum permissions necessary to perform their tasks, reducing the attack surface.",19:"Nmap is the standard port scanning tool. Wireshark captures packets, Metasploit is an exploitation framework, and Hashcat is a password cracker.",20:"Ransomware encrypts the victim's files and demands payment (usually cryptocurrency) in exchange for the decryption key.",21:"Symmetric encryption uses a single shared key for both encrypting and decrypting. Asymmetric encryption uses a key pair — a public key to encrypt and a private key to decrypt.",22:"Remote Code Execution (RCE) allows an attacker to run arbitrary code on a target server, often leading to full system compromise. CSRF, clickjacking, and open redirect have different impacts.",23:"OWASP (Open Web Application Security Project) is a non-profit organization that publishes widely-used web security standards and guides, including the OWASP Top 10.",24:"The chmod command changes file permissions in Linux. chown changes ownership, chgrp changes group, and passwd changes user passwords.",25:"An SSL/TLS certificate is a digital document issued by a trusted Certificate Authority that verifies a website's identity and enables encrypted HTTPS connections.",26:"Phishing is a social engineering attack that tricks people into revealing sensitive information. Buffer overflow, SQL injection, and port scanning are technical attacks.",27:"The grep command searches for text patterns in files using regular expressions. It is one of the most commonly used Linux text processing tools.",28:"A VPN (Virtual Private Network) creates an encrypted tunnel for internet traffic, protecting data from interception and masking the user's IP address.",29:"CSRF (Cross-Site Request Forgery) tricks a logged-in user's browser into performing unwanted actions on a trusted site, such as changing account settings or transferring funds.",30:"Passwords should be stored as salted cryptographic hashes. Plain text and Base64 are insecure, and AES encryption is reversible if the key is compromised. Salted hashing is one-way and resistant to rainbow tables."};export const DEMO_QUESTIONS=[{number:1,text:"Which algorithm is NOT a symmetric cipher?",category:"Cryptography",options:{A:"AES",B:"RSA",C:"DES",D:"Blowfish"}},{number:2,text:"What does SQL injection exploit?",category:"Web Security",options:{A:"Buffer overflow in web server",B:"Unsanitized user input in database queries",C:"Weak encryption algorithms",D:"Misconfigured firewall rules"}},{number:3,text:'Which HTTP status code indicates "Forbidden"?',category:"Web Security",options:{A:"401",B:"404",C:"403",D:"500"}},{number:4,text:"What is the primary purpose of a nonce in cryptography?",category:"Cryptography",options:{A:"Encrypt data at rest",B:"Prevent replay attacks",C:"Generate random passwords",D:"Compress data before encryption"}},{number:5,text:"Which tool is commonly used for network packet capture?",category:"Network",options:{A:"Burp Suite",B:"Ghidra",C:"Wireshark",D:"John the Ripper"}},{number:6,text:"What does XSS stand for in cybersecurity?",category:"Web Security",options:{A:"Extended Security System",B:"Cross-Site Scripting",C:"XML Secure Socket",D:"Cross-Server Sharing"}},{number:7,text:"What is the primary function of a firewall?",category:"Network",options:{A:"Encrypt network data",B:"Filter network traffic based on security rules",C:"Detect viruses in files",D:"Speed up internet connection"}},{number:8,text:"Which type of malware disguises itself as legitimate software?",category:"Malware",options:{A:"Worm",B:"Ransomware",C:"Trojan",D:"Adware"}},{number:9,text:"Which protocol provides secure communication on the web?",category:"Network",options:{A:"HTTP",B:"FTP",C:"HTTPS",D:"SMTP"}},{number:10,text:"What is a cryptographic hash?",category:"Cryptography",options:{A:"A reversible encryption key",B:"A one-way function producing a fixed-size digest",C:"An authentication protocol",D:"A type of digital signature"}},{number:11,text:"Which tool is used for binary analysis?",category:"Reverse Engineering",options:{A:"Nmap",B:"SQLMap",C:"Ghidra",D:"Nikto"}},{number:12,text:"Which attack manipulates DNS requests to redirect traffic?",category:"Network",options:{A:"Phishing",B:"DNS Spoofing",C:"SQL Injection",D:"Brute Force"}},{number:13,text:"What is the standard port for SSH?",category:"Network",options:{A:"21",B:"22",C:"80",D:"443"}},{number:14,text:"What is two-factor authentication (2FA)?",category:"Authentication",options:{A:"Using two different passwords",B:"Verifying identity with two distinct types of credentials",C:"Encrypting data twice",D:"Connecting through two networks"}},{number:15,text:"Which Linux command shows open ports on a system?",category:"Linux",options:{A:"ls -la",B:"netstat -tulpn",C:"chmod 777",D:"cat /etc/passwd"}},{number:16,text:"What is a Man-in-the-Middle (MitM) attack?",category:"Network",options:{A:"Accessing a server without authorization",B:"Guessing passwords by brute force",C:"Sending multiple requests to overload a server",D:"Intercepting and modifying communications between two parties"}},{number:17,text:"Which of these is a hash algorithm?",category:"Cryptography",options:{A:"AES-256",B:"Diffie-Hellman",C:"RSA-2048",D:"SHA-256"}},{number:18,text:"What is the principle of least privilege?",category:"Security",options:{A:"Give root access to all users",B:"Use the shortest password possible",C:"Grant only the permissions necessary to perform a task",D:"Disable all firewalls"}},{number:19,text:"Which tool is commonly used for port scanning?",category:"Network",options:{A:"Wireshark",B:"Metasploit",C:"Nmap",D:"Hashcat"}},{number:20,text:"What is ransomware?",category:"Malware",options:{A:"Software that shows unwanted ads",B:"Software that encrypts files and demands payment to decrypt",C:"Software that records keystrokes",D:"Software that replicates across networks"}},{number:21,text:"What is the difference between symmetric and asymmetric encryption?",category:"Cryptography",options:{A:"Symmetric uses the same key to encrypt and decrypt; asymmetric uses two different keys",B:"Symmetric is slower than asymmetric",C:"Asymmetric only works with small files",D:"There is no significant difference"}},{number:22,text:"Which vulnerability allows arbitrary code execution on a web server?",category:"Web Security",options:{A:"CSRF",B:"Open Redirect",C:"Clickjacking",D:"Remote Code Execution (RCE)"}},{number:23,text:"What is OWASP?",category:"Security",options:{A:"A security operating system",B:"A type of firewall",C:"An organization that publishes web security standards and guides",D:"A programming language for security"}},{number:24,text:"Which Linux command changes file permissions?",category:"Linux",options:{A:"chown",B:"chmod",C:"chgrp",D:"passwd"}},{number:25,text:"What is an SSL/TLS certificate?",category:"Cryptography",options:{A:"A digital document that verifies a website identity",B:"A file containing malware",C:"A private key for SSH",D:"A type of encrypted database"}},{number:26,text:"Which of the following is a social engineering attack?",category:"Security",options:{A:"Buffer overflow",B:"Phishing",C:"SQL Injection",D:"Port scanning"}},{number:27,text:'What does the Linux command "grep" do?',category:"Linux",options:{A:"Compresses files",B:"Configures the network",C:"Shows active processes",D:"Searches for text patterns in files"}},{number:28,text:"What is a VPN?",category:"Network",options:{A:"A type of virus",B:"A file transfer protocol",C:"A virtual private network that encrypts internet traffic",D:"A vulnerability scanner"}},{number:29,text:"What is CSRF (Cross-Site Request Forgery)?",category:"Web Security",options:{A:"An attack that forces a user browser to perform unauthorized actions",B:"A data encryption method",C:"A type of network scanner",D:"A file compression technique"}},{number:30,text:"What is the best practice for storing passwords in a database?",category:"Security",options:{A:"Plain text",B:"Hashed with salt",C:"Encrypted with AES",D:"Encoded in Base64"}}];function c(e){const t=[...e];for(let e=t.length-1;e>0;e--){const r=Math.floor(Math.random()*(e+1));[t[e],t[r]]=[t[r],t[e]]}return t}export function pickDemoQuestions(e=10){const t=getLocalizedDemoQuestions(),r=getLocalizedExplanations();return c(t.map(e=>({...e,answer:DEMO_ANSWERS[e.number],explanation:r[e.number]}))).slice(0,e).map((e,t)=>{const r=function(e){if(!e.answer)return e;const t=["A","B","C","D"],r=c(t),i={A:e.options[r[0]],B:e.options[r[1]],C:e.options[r[2]],D:e.options[r[3]]},n=t[r.indexOf(e.answer)];return{...e,options:i,answer:n,sourceOrder:r}}(e);return{...r,sourceNumber:e.number,number:t+1}})}export function getLocalizedExplanations(){const i=o().language;if(!i||"en"===i)return DEMO_EXPLANATIONS;const n=r(s,"..","..","translations",i,"demo-explanations.json");if(!e(n))return DEMO_EXPLANATIONS;try{const e=JSON.parse(t(n,"utf-8"));if(e&&"object"==typeof e)return{...DEMO_EXPLANATIONS,...e}}catch{}return DEMO_EXPLANATIONS}export function getLocalizedDemoQuestions(){const i=o().language;if(!i||"en"===i)return DEMO_QUESTIONS;const n=r(s,"..","..","translations",i,"demo.json");if(!e(n))return DEMO_QUESTIONS;try{const e=JSON.parse(t(n,"utf-8"));return Array.isArray(e)?DEMO_QUESTIONS.map(t=>{const r=e.find(e=>e.number===t.number);return r?.options?r:t}):DEMO_QUESTIONS}catch{}return DEMO_QUESTIONS}export function getLocalizedDemoSession(){const e=o().language;return e&&"en"!==e?{...a,examName:{zh:"ICOA 模拟考试 — 免费练习",ja:"ICOA デモ試験 — 無料練習",ko:"ICOA 데모 시험 — 무료 연습",es:"ICOA Examen Demo — Práctica Gratis",ar:"اختبار ICOA التجريبي — تدريب مجاني",fr:"ICOA Examen Démo — Pratique Gratuite",pt:"ICOA Exame Demo — Prática Gratuita",ru:"ICOA Демо Экзамен — Бесплатная Практика",hi:"ICOA डेमो परीक्षा — निःशुल्क अभ्यास",de:"ICOA Demo-Prüfung — Kostenlose Übung",id:"ICOA Ujian Demo — Latihan Gratis",th:"ICOA สอบทดลอง — ฝึกฟรี",vi:"ICOA Thi Thử — Luyện Tập Miễn Phí",tr:"ICOA Demo Sınav — Ücretsiz Uygulama",uk:"ICOA Демо Екзамен — Безплатна Практика",ht:"ICOA Egzamen Demo — Pratik Gratis"}[e]||a.examName,startedAt:""}:{...a,startedAt:""}}
|
package/dist/lib/demo-stats.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExamQuestion } from '../types/index.js';
|
|
2
|
-
|
|
2
|
+
interface DemoStats {
|
|
3
3
|
best: number;
|
|
4
4
|
bestPercentage: number;
|
|
5
5
|
attempts: number;
|
|
@@ -14,3 +14,4 @@ export declare function recordDemoAttempt(score: number, total: number): DemoSta
|
|
|
14
14
|
export declare function saveRetryQueue(questions: ExamQuestion[]): void;
|
|
15
15
|
export declare function getRetryQueue(): ExamQuestion[] | null;
|
|
16
16
|
export declare function clearRetryQueue(): void;
|
|
17
|
+
export {};
|
package/dist/lib/exam-setup.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
interface ExamSetupState {
|
|
2
2
|
completedAt: string;
|
|
3
3
|
pythonVersion: string;
|
|
4
4
|
installedPackages: string[];
|
|
@@ -7,3 +7,4 @@ export interface ExamSetupState {
|
|
|
7
7
|
export declare function getExamSetup(): ExamSetupState | null;
|
|
8
8
|
export declare function saveExamSetup(state: ExamSetupState): void;
|
|
9
9
|
export declare function isExamSetupComplete(): boolean;
|
|
10
|
+
export {};
|
package/dist/lib/exam-state.d.ts
CHANGED
|
@@ -22,7 +22,5 @@ export declare function getDemoState(): ExamState | null;
|
|
|
22
22
|
export declare function getExamState(): ExamState | null;
|
|
23
23
|
export declare function saveExamState(state: ExamState): void;
|
|
24
24
|
export declare function clearExamState(examId?: string): void;
|
|
25
|
-
export declare function isExamActive(): boolean;
|
|
26
25
|
export declare function getExamDeadline(): Date | null;
|
|
27
|
-
export declare function addInteraction(interaction: import('../types/index.js').ExamInteraction): void;
|
|
28
26
|
export declare function refetchQuestionDataIfStale(): Promise<ExamState | null>;
|
package/dist/lib/exam-state.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readFileSync as t,writeFileSync as e,existsSync as n,unlinkSync as o,mkdirSync as r}from"node:fs";import{execFileSync as a}from"node:child_process";import{join as i}from"node:path";import{homedir as s}from"node:os";import{getIcoaDir as c}from"./config.js";export function materializeAttachedData(t,n){if(!n)return null;let o;try{const a=i(s(),"icoa-workspace");r(a,{recursive:!0}),o=i(a,`q${t}.txt`),e(o,n,"utf-8")}catch{return null}try{return a("docker",["cp",o,`icoa-sandbox:/home/competitor/challenges/q${t}.txt`],{stdio:"ignore",timeout:3e3}),`challenges/q${t}.txt`}catch{return o}}function u(){return i(c(),"exam-state.json")}function f(){return i(c(),"demo-state.json")}function l(t){return"demo-free"===t?f():u()}export function getRealExamState(){const e=u();if(!n(e))return null;try{const n=JSON.parse(t(e,"utf-8"));if("demo-free"===n?.session?.examId){try{o(e)}catch{}return null}return n}catch{return null}}export function getDemoState(){const e=f();if(!n(e))return null;try{return JSON.parse(t(e,"utf-8"))}catch{return null}}export function getExamState(){return getRealExamState()||getDemoState()}export function saveExamState(t){const n=l(t.session.examId);e(n,JSON.stringify(t,null,2))}export function clearExamState(t){if(t){const e=l(t);n(e)&&o(e)}else{const t=getExamState();if(t){const e=l(t.session.examId);n(e)&&o(e)}}}export function
|
|
1
|
+
import{readFileSync as t,writeFileSync as e,existsSync as n,unlinkSync as o,mkdirSync as r}from"node:fs";import{execFileSync as a}from"node:child_process";import{join as i}from"node:path";import{homedir as s}from"node:os";import{getIcoaDir as c}from"./config.js";export function materializeAttachedData(t,n){if(!n)return null;let o;try{const a=i(s(),"icoa-workspace");r(a,{recursive:!0}),o=i(a,`q${t}.txt`),e(o,n,"utf-8")}catch{return null}try{return a("docker",["cp",o,`icoa-sandbox:/home/competitor/challenges/q${t}.txt`],{stdio:"ignore",timeout:3e3}),`challenges/q${t}.txt`}catch{return o}}function u(){return i(c(),"exam-state.json")}function f(){return i(c(),"demo-state.json")}function l(t){return"demo-free"===t?f():u()}export function getRealExamState(){const e=u();if(!n(e))return null;try{const n=JSON.parse(t(e,"utf-8"));if("demo-free"===n?.session?.examId){try{o(e)}catch{}return null}return n}catch{return null}}export function getDemoState(){const e=f();if(!n(e))return null;try{return JSON.parse(t(e,"utf-8"))}catch{return null}}export function getExamState(){return getRealExamState()||getDemoState()}export function saveExamState(t){const n=l(t.session.examId);e(n,JSON.stringify(t,null,2))}export function clearExamState(t){if(t){const e=l(t);n(e)&&o(e)}else{const t=getExamState();if(t){const e=l(t.session.examId);n(e)&&o(e)}}}export function getExamDeadline(){const t=getExamState();if(!t)return null;if(!t.session.durationMinutes)return null;const{deadlineServerMs:e,clockOffsetMs:n}=t.session;if("number"==typeof e&&"number"==typeof n)return new Date(e-n);const o=t.session.confirmedAt||t.session.startedAt,r=new Date(o).getTime();return new Date(r+60*t.session.durationMinutes*1e3)}let m=!1;export async function refetchQuestionDataIfStale(){const t=getRealExamState();if(!t)return null;if(m)return t;const e=t.session.token;if(!e)return t;if(!t.questions.some(t=>{if("ai4ctf"!==t.type&&"ctf4ai"!==t.type)return!1;const e=String(t.description||"");return(e.includes("attachedData")||/base64 blob/i.test(e))&&!t.attachedData}))return t;m=!0;try{const{getConfig:n}=await import("./config.js"),o=n(),r=o.ctfdUrl||"https://practice.icoa2026.au",a=o.language||"en",i=await fetch(`${r}/api/icoa/exam-token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:e,deviceHash:"",lang:a}),signal:AbortSignal.timeout(8e3)});if(!i.ok)return t;const s=await i.json(),c=s?.data?.questions||[];if(0===c.length)return t;const u=new Map;for(const t of c)u.set(t.number,t);let f=0;for(const e of t.questions){const t=u.get(e.number);t&&(t.attachedData&&!e.attachedData&&(e.attachedData=t.attachedData,f++),t.description&&(!e.description||t.description.length>e.description.length)&&(e.description=t.description))}return f>0&&saveExamState(t),t}catch{return t}}
|
package/dist/lib/gemini.d.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare function filterFlagPatterns(text: string): string;
|
|
3
|
-
/**
|
|
4
|
-
* @deprecated Local hint generation is disabled as of 2026-04-24.
|
|
5
|
-
* All AI interaction goes through the server gateway. Use `ai4ctf` chat
|
|
6
|
-
* or the integrated `help` / `hint` commands inside `exam` mode instead.
|
|
7
|
-
*/
|
|
8
|
-
export declare function generateHint(_level: HintLevel, _question: string, _context?: ChallengeContext): Promise<{
|
|
9
|
-
text: string;
|
|
10
|
-
tokensUsed: number;
|
|
11
|
-
}>;
|
|
1
|
+
import type { ChallengeContext } from '../types/index.js';
|
|
12
2
|
/**
|
|
13
3
|
* @deprecated Runtime translation is disabled. Translations are pre-baked
|
|
14
4
|
* at build time via `panda/translate-*.js` scripts and shipped in
|
package/dist/lib/gemini.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{GoogleGenAI as e}from"@google/genai";import chalk from"chalk";import{readFileSync as t}from"node:fs";import{dirname as o,join as n}from"node:path";import{fileURLToPath as r}from"node:url";import{getConfig as
|
|
1
|
+
import{GoogleGenAI as e}from"@google/genai";import chalk from"chalk";import{readFileSync as t}from"node:fs";import{dirname as o,join as n}from"node:path";import{fileURLToPath as r}from"node:url";import{getConfig as s,saveConfig as a}from"./config.js";import{getRealExamState as i}from"./exam-state.js";const c=o(r(import.meta.url));let l=null;function p(){if(l)return l;try{const e=JSON.parse(t(n(c,"..","..","package.json"),"utf-8"));l=e.version||"unknown"}catch{l="unknown"}return l}export async function translateText(e,t){throw new Error("Local AI model path has been disabled (2026-04-24). ICOA provides all AI server-side — no API key setup needed. Use `ai4ctf` (chat) or `exam` (with integrated help/hint) instead.")}export function setApiKey(e){a({geminiApiKey:e})}export async function createChatSession(e,t){const o=s();let n=t||"You are an AI teammate in the ICOA cybersecurity CTF competition (International Cyber Olympiad in AI 2026, Sydney).\n\nYou're a friendly, knowledgeable cybersecurity partner — like a fellow competitor sitting next to the user. Be conversational, encouraging, and collaborative.\n\nRULES:\n- Help the competitor think through challenges, brainstorm approaches, explain concepts\n- You MAY discuss vulnerability types, tools, techniques, and methodologies\n- You MAY suggest approaches and help debug code\n- Do NOT provide complete working exploits or full solution scripts\n- Do NOT provide flags or flag fragments\n- Never output anything matching flag format: icoa{...}\n- If you don't know something, say so honestly\n- Keep responses concise unless the user asks for detail\n- When the user opens a challenge, use the context to give relevant advice";e&&(n+=`\n\nThe competitor is currently working on:\nChallenge: ${e.name}\nCategory: ${e.category}`);const r=o.ctfdUrl||"https://practice.icoa2026.au",a=o.deviceFingerprint||"",c=o.geminiModel||"gemini-2.5-flash",l=[];return{async sendMessage(e){l.push({role:"user",text:e});const t=i(),o={systemPrompt:n,messages:l,model:c,maxTokens:2048,deviceFingerprint:a};t?.session?.token&&(o.examToken=t.session.token);const s=await fetch(`${r}/api/icoa/ai/chat`,{method:"POST",headers:{"Content-Type":"application/json","User-Agent":`icoa-cli/${p()}`},body:JSON.stringify(o),signal:AbortSignal.timeout(6e4)});if(!s.ok){const e=(await s.json().catch(()=>({message:"AI proxy error"}))).message||`AI proxy returned ${s.status}`;if(401===s.status)throw new Error(`${chalk.yellow("⚠ ")}Exam token expired. Re-enter via \`exam <token>\`.`);if(403===s.status)throw new Error(chalk.yellow("⚠ ")+e);if(429===s.status)throw new Error(chalk.yellow("⏳ ")+e);throw new Error(e)}const u=await s.json(),m=function(e){return e.replace(/icoa\{[^}]*\}/gi,"[FLAG REDACTED]")}(u.data?.text||""),h=u.data?.tokensUsed||0;return l.push({role:"model",text:m}),{text:m,tokensUsed:h}}}}
|