icoa-cli 2.19.99 → 2.19.101
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.js +1 -700
- package/dist/commands/connect.js +1 -66
- package/dist/commands/ctf.js +1 -620
- package/dist/commands/ctf4ai-demo.js +1 -525
- package/dist/commands/env.js +1 -737
- package/dist/commands/exam.js +1 -2353
- package/dist/commands/files.js +1 -52
- package/dist/commands/hint.js +1 -119
- package/dist/commands/lang.js +1 -155
- package/dist/commands/log.js +1 -163
- package/dist/commands/note.js +1 -32
- package/dist/commands/ref.js +1 -63
- package/dist/commands/setup.js +1 -103
- package/dist/commands/shell.js +1 -55
- package/dist/commands/theme.js +1 -50
- package/dist/index.js +1 -225
- package/dist/lib/access.js +1 -246
- package/dist/lib/budget.js +1 -42
- package/dist/lib/colors.js +1 -21
- package/dist/lib/config.js +1 -60
- package/dist/lib/ctfd-client.js +1 -274
- package/dist/lib/demo-exam.js +1 -249
- package/dist/lib/demo-flags.js +1 -27
- package/dist/lib/demo-stats.js +1 -65
- package/dist/lib/exam-client.js +1 -57
- package/dist/lib/exam-setup.js +1 -23
- package/dist/lib/exam-state.js +1 -112
- package/dist/lib/gemini.js +1 -235
- package/dist/lib/i18n.js +1 -273
- package/dist/lib/log-sync.js +1 -110
- package/dist/lib/logger.js +1 -59
- package/dist/lib/paper-upgrade.js +1 -117
- package/dist/lib/platform.js +1 -86
- package/dist/lib/sandbox.js +1 -93
- package/dist/lib/terminal.js +1 -49
- package/dist/lib/theme.js +1 -108
- package/dist/lib/translation.js +1 -66
- package/dist/lib/ui.js +1 -80
- package/dist/lib/update-check.js +1 -102
- package/dist/postinstall.js +1 -48
- package/dist/repl.js +1 -1259
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.js +1 -38
- package/package.json +6 -2
- package/translations/sw/i18n-snippet.ts +1 -0
package/dist/lib/ctfd-client.js
CHANGED
|
@@ -1,274 +1 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { pipeline } from 'node:stream/promises';
|
|
4
|
-
import { Readable } from 'node:stream';
|
|
5
|
-
export class CTFdClient {
|
|
6
|
-
baseUrl;
|
|
7
|
-
token;
|
|
8
|
-
sessionCookie;
|
|
9
|
-
csrfNonce;
|
|
10
|
-
constructor(baseUrl, token, sessionCookie, csrfNonce) {
|
|
11
|
-
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
12
|
-
this.token = token;
|
|
13
|
-
this.sessionCookie = sessionCookie || '';
|
|
14
|
-
this.csrfNonce = csrfNonce || '';
|
|
15
|
-
}
|
|
16
|
-
getAuthHeaders() {
|
|
17
|
-
if (this.token) {
|
|
18
|
-
return { Authorization: `Token ${this.token}` };
|
|
19
|
-
}
|
|
20
|
-
if (this.sessionCookie) {
|
|
21
|
-
const headers = { Cookie: this.sessionCookie };
|
|
22
|
-
if (this.csrfNonce) {
|
|
23
|
-
headers['CSRF-Token'] = this.csrfNonce;
|
|
24
|
-
}
|
|
25
|
-
return headers;
|
|
26
|
-
}
|
|
27
|
-
return {};
|
|
28
|
-
}
|
|
29
|
-
async fetchCsrfNonce() {
|
|
30
|
-
if (this.csrfNonce)
|
|
31
|
-
return this.csrfNonce;
|
|
32
|
-
try {
|
|
33
|
-
const res = await fetch(this.baseUrl, {
|
|
34
|
-
headers: this.sessionCookie ? { Cookie: this.sessionCookie } : {},
|
|
35
|
-
});
|
|
36
|
-
const html = await res.text();
|
|
37
|
-
const match = html.match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);
|
|
38
|
-
if (match) {
|
|
39
|
-
this.csrfNonce = match[1];
|
|
40
|
-
return this.csrfNonce;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
catch { /* ignore */ }
|
|
44
|
-
return '';
|
|
45
|
-
}
|
|
46
|
-
async request(method, path, body) {
|
|
47
|
-
// Auto-fetch CSRF nonce for session-based auth
|
|
48
|
-
if (this.sessionCookie && !this.csrfNonce) {
|
|
49
|
-
await this.fetchCsrfNonce();
|
|
50
|
-
}
|
|
51
|
-
const url = `${this.baseUrl}/api/v1${path}`;
|
|
52
|
-
const headers = {
|
|
53
|
-
...this.getAuthHeaders(),
|
|
54
|
-
'Content-Type': 'application/json',
|
|
55
|
-
};
|
|
56
|
-
const res = await fetch(url, {
|
|
57
|
-
method,
|
|
58
|
-
headers,
|
|
59
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
60
|
-
});
|
|
61
|
-
if (!res.ok) {
|
|
62
|
-
const text = await res.text().catch(() => 'Unknown error');
|
|
63
|
-
throw new Error(`CTFd API error (${res.status}): ${text}`);
|
|
64
|
-
}
|
|
65
|
-
const json = (await res.json());
|
|
66
|
-
if (json.success === false) {
|
|
67
|
-
throw new Error(`CTFd error: ${json.errors?.join(', ') || 'Unknown error'}`);
|
|
68
|
-
}
|
|
69
|
-
return json.data;
|
|
70
|
-
}
|
|
71
|
-
async testConnection() {
|
|
72
|
-
try {
|
|
73
|
-
return await this.request('GET', '/users/me');
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
// Session mode fallback: API may return 403, try scraping profile page
|
|
77
|
-
if (this.sessionCookie && err.message?.includes('403')) {
|
|
78
|
-
return this.testConnectionViaProfile();
|
|
79
|
-
}
|
|
80
|
-
throw err;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
async testConnectionViaProfile() {
|
|
84
|
-
const res = await fetch(`${this.baseUrl}/settings`, {
|
|
85
|
-
headers: { Cookie: this.sessionCookie },
|
|
86
|
-
});
|
|
87
|
-
if (!res.ok)
|
|
88
|
-
throw new Error('Session expired or invalid.');
|
|
89
|
-
const html = await res.text();
|
|
90
|
-
// Extract user name from settings page
|
|
91
|
-
const nameMatch = html.match(/name="name"[^>]*value="([^"]+)"/) ||
|
|
92
|
-
html.match(/<input[^>]*id="name"[^>]*value="([^"]+)"/);
|
|
93
|
-
const name = nameMatch?.[1] || 'User';
|
|
94
|
-
// Extract user ID from page
|
|
95
|
-
const idMatch = html.match(/user_id['":\s]+(\d+)/) ||
|
|
96
|
-
html.match(/userId['":\s]+(\d+)/);
|
|
97
|
-
const id = idMatch ? parseInt(idMatch[1], 10) : 0;
|
|
98
|
-
// Update CSRF nonce from settings page
|
|
99
|
-
const csrfMatch = html.match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);
|
|
100
|
-
if (csrfMatch)
|
|
101
|
-
this.csrfNonce = csrfMatch[1];
|
|
102
|
-
return { id, name, score: 0, team_id: 0, country: '' };
|
|
103
|
-
}
|
|
104
|
-
async getChallenges() {
|
|
105
|
-
return this.request('GET', '/challenges');
|
|
106
|
-
}
|
|
107
|
-
async getChallenge(id) {
|
|
108
|
-
return this.request('GET', `/challenges/${id}`);
|
|
109
|
-
}
|
|
110
|
-
async submitFlag(challengeId, submission) {
|
|
111
|
-
const res = await fetch(`${this.baseUrl}/api/v1/challenges/attempt`, {
|
|
112
|
-
method: 'POST',
|
|
113
|
-
headers: {
|
|
114
|
-
...this.getAuthHeaders(),
|
|
115
|
-
'Content-Type': 'application/json',
|
|
116
|
-
},
|
|
117
|
-
body: JSON.stringify({ challenge_id: challengeId, submission }),
|
|
118
|
-
});
|
|
119
|
-
if (!res.ok) {
|
|
120
|
-
const text = await res.text().catch(() => 'Unknown error');
|
|
121
|
-
throw new Error(`CTFd API error (${res.status}): ${text}`);
|
|
122
|
-
}
|
|
123
|
-
const json = (await res.json());
|
|
124
|
-
return json.data;
|
|
125
|
-
}
|
|
126
|
-
async getScoreboard() {
|
|
127
|
-
return this.request('GET', '/scoreboard');
|
|
128
|
-
}
|
|
129
|
-
async getTeam() {
|
|
130
|
-
return this.request('GET', '/teams/me');
|
|
131
|
-
}
|
|
132
|
-
async getCompetitionMeta() {
|
|
133
|
-
const res = await fetch(this.baseUrl);
|
|
134
|
-
const html = await res.text();
|
|
135
|
-
const startMatch = html.match(/'start'\s*:\s*(\d+)/);
|
|
136
|
-
const endMatch = html.match(/'end'\s*:\s*(\d+)/);
|
|
137
|
-
const modeMatch = html.match(/'userMode'\s*:\s*"([^"]+)"/);
|
|
138
|
-
const csrfMatch = html.match(/'csrfNonce'\s*:\s*"([^"]+)"/);
|
|
139
|
-
return {
|
|
140
|
-
start: startMatch ? parseInt(startMatch[1]) : null,
|
|
141
|
-
end: endMatch ? parseInt(endMatch[1]) : null,
|
|
142
|
-
userMode: modeMatch?.[1] || 'users',
|
|
143
|
-
csrfNonce: csrfMatch?.[1] || '',
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
async getChallengeFiles(id) {
|
|
147
|
-
const challenge = await this.getChallenge(id);
|
|
148
|
-
return challenge.files || [];
|
|
149
|
-
}
|
|
150
|
-
async downloadFile(filePath, destDir) {
|
|
151
|
-
mkdirSync(destDir, { recursive: true });
|
|
152
|
-
const url = filePath.startsWith('http')
|
|
153
|
-
? filePath
|
|
154
|
-
: `${this.baseUrl}/${filePath.replace(/^\//, '')}`;
|
|
155
|
-
const res = await fetch(url, {
|
|
156
|
-
headers: this.getAuthHeaders(),
|
|
157
|
-
redirect: 'follow',
|
|
158
|
-
});
|
|
159
|
-
if (!res.ok || !res.body) {
|
|
160
|
-
throw new Error(`Failed to download: ${url}`);
|
|
161
|
-
}
|
|
162
|
-
const rawName = filePath.split('/').pop() || 'file';
|
|
163
|
-
const fileName = rawName.split('?')[0];
|
|
164
|
-
const destPath = join(destDir, fileName);
|
|
165
|
-
const fileStream = createWriteStream(destPath);
|
|
166
|
-
await pipeline(Readable.fromWeb(res.body), fileStream);
|
|
167
|
-
return destPath;
|
|
168
|
-
}
|
|
169
|
-
async getTokenViaIcoaApi(username, password) {
|
|
170
|
-
const body = JSON.stringify({ name: username, password });
|
|
171
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
172
|
-
// Try via nginx proxy first (same origin, no port)
|
|
173
|
-
try {
|
|
174
|
-
const res = await fetch(`${this.baseUrl}/api/icoa/token`, {
|
|
175
|
-
method: 'POST', headers, body,
|
|
176
|
-
signal: AbortSignal.timeout(5000),
|
|
177
|
-
});
|
|
178
|
-
if (res.ok) {
|
|
179
|
-
const json = await res.json();
|
|
180
|
-
if (json.success && json.data?.token)
|
|
181
|
-
return json.data.token;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
catch { /* proxy not available */ }
|
|
185
|
-
// Fallback: direct port 9090
|
|
186
|
-
try {
|
|
187
|
-
const res = await fetch(`${this.baseUrl}:9090/api/icoa/token`, {
|
|
188
|
-
method: 'POST', headers, body,
|
|
189
|
-
signal: AbortSignal.timeout(5000),
|
|
190
|
-
});
|
|
191
|
-
if (res.ok) {
|
|
192
|
-
const json = await res.json();
|
|
193
|
-
if (json.success && json.data?.token)
|
|
194
|
-
return json.data.token;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
catch { /* API not available */ }
|
|
198
|
-
return null;
|
|
199
|
-
}
|
|
200
|
-
async loginWithCredentials(username, password) {
|
|
201
|
-
// Try ICOA token API first (fastest path)
|
|
202
|
-
const icoaToken = await this.getTokenViaIcoaApi(username, password);
|
|
203
|
-
if (icoaToken) {
|
|
204
|
-
return { token: icoaToken, session: '', csrf: '' };
|
|
205
|
-
}
|
|
206
|
-
// Fallback: standard CTFd login flow
|
|
207
|
-
// Step 1: GET /login to get nonce
|
|
208
|
-
const loginPageRes = await fetch(`${this.baseUrl}/login`);
|
|
209
|
-
const loginHtml = await loginPageRes.text();
|
|
210
|
-
const nonceMatch = loginHtml.match(/name="nonce"[^>]*value="([^"]+)"/) ||
|
|
211
|
-
loginHtml.match(/value="([^"]+)"[^>]*name="nonce"/) ||
|
|
212
|
-
loginHtml.match(/id="nonce"[^>]*value="([^"]+)"/) ||
|
|
213
|
-
loginHtml.match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);
|
|
214
|
-
const nonce = nonceMatch?.[1] || '';
|
|
215
|
-
if (!nonce) {
|
|
216
|
-
throw new Error('Could not extract CSRF nonce from login page.');
|
|
217
|
-
}
|
|
218
|
-
const cookies = loginPageRes.headers.getSetCookie?.() || [];
|
|
219
|
-
const cookieStr = cookies.map((c) => c.split(';')[0]).join('; ');
|
|
220
|
-
// Step 2: POST /login with credentials
|
|
221
|
-
const loginRes = await fetch(`${this.baseUrl}/login`, {
|
|
222
|
-
method: 'POST',
|
|
223
|
-
headers: {
|
|
224
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
225
|
-
Cookie: cookieStr,
|
|
226
|
-
},
|
|
227
|
-
body: new URLSearchParams({ name: username, password, nonce, _submit: 'Submit' }),
|
|
228
|
-
redirect: 'manual',
|
|
229
|
-
});
|
|
230
|
-
const loginCookies = loginRes.headers.getSetCookie?.() || [];
|
|
231
|
-
const allCookies = [...cookies, ...loginCookies].map((c) => c.split(';')[0]).join('; ');
|
|
232
|
-
const location = loginRes.headers.get('location') || '';
|
|
233
|
-
if (location.includes('/login')) {
|
|
234
|
-
throw new Error('Invalid username or password.');
|
|
235
|
-
}
|
|
236
|
-
// Step 3: Try to generate API token (some CTFd instances allow it)
|
|
237
|
-
try {
|
|
238
|
-
const settingsRes = await fetch(`${this.baseUrl}/settings`, {
|
|
239
|
-
headers: { Cookie: allCookies },
|
|
240
|
-
});
|
|
241
|
-
const settingsHtml = await settingsRes.text();
|
|
242
|
-
const csrfMatch = settingsHtml.match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);
|
|
243
|
-
const csrfNonce = csrfMatch?.[1] || nonce;
|
|
244
|
-
const tokenRes = await fetch(`${this.baseUrl}/api/v1/tokens`, {
|
|
245
|
-
method: 'POST',
|
|
246
|
-
headers: {
|
|
247
|
-
'Content-Type': 'application/json',
|
|
248
|
-
Cookie: allCookies,
|
|
249
|
-
'CSRF-Token': csrfNonce,
|
|
250
|
-
},
|
|
251
|
-
body: JSON.stringify({ expiration: '2026-12-31T23:59:59+00:00' }),
|
|
252
|
-
});
|
|
253
|
-
if (tokenRes.ok) {
|
|
254
|
-
const tokenJson = (await tokenRes.json());
|
|
255
|
-
if (tokenJson.success && tokenJson.data?.value) {
|
|
256
|
-
return { token: tokenJson.data.value, session: '', csrf: '' };
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
catch { /* Token generation not available, use session */ }
|
|
261
|
-
// Fallback: use session cookie + CSRF nonce
|
|
262
|
-
// Get CSRF nonce from the main page
|
|
263
|
-
let csrf = '';
|
|
264
|
-
try {
|
|
265
|
-
const mainRes = await fetch(`${this.baseUrl}/challenges`, { headers: { Cookie: allCookies } });
|
|
266
|
-
const mainHtml = await mainRes.text();
|
|
267
|
-
const csrfMatch = mainHtml.match(/csrfNonce['":\s]+['"]([^'"]+)['"]/);
|
|
268
|
-
if (csrfMatch)
|
|
269
|
-
csrf = csrfMatch[1];
|
|
270
|
-
}
|
|
271
|
-
catch { /* ignore */ }
|
|
272
|
-
return { token: '', session: allCookies, csrf };
|
|
273
|
-
}
|
|
274
|
-
}
|
|
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}}}
|
package/dist/lib/demo-exam.js
CHANGED
|
@@ -1,249 +1 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { join, dirname } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { getConfig } from './config.js';
|
|
5
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
export const DEMO_POOL_SIZE = 30;
|
|
7
|
-
export const DEMO_PICK_SIZE = 10;
|
|
8
|
-
export const DEMO_SESSION = {
|
|
9
|
-
examId: 'demo-free',
|
|
10
|
-
examName: 'ICOA Demo Exam — Free Practice',
|
|
11
|
-
startedAt: '',
|
|
12
|
-
durationMinutes: 0,
|
|
13
|
-
questionCount: DEMO_PICK_SIZE,
|
|
14
|
-
country: 'ALL',
|
|
15
|
-
};
|
|
16
|
-
// Answer key for the 30-question pool (balanced A=7 B=7 C=8 D=8)
|
|
17
|
-
// Q16-30 option order was rebalanced from translate-demo.js to distribute answers.
|
|
18
|
-
export const DEMO_ANSWERS = {
|
|
19
|
-
1: 'B', 2: 'B', 3: 'C', 4: 'B', 5: 'C', 6: 'B', 7: 'B', 8: 'C',
|
|
20
|
-
9: 'C', 10: 'B', 11: 'C', 12: 'B', 13: 'B', 14: 'B', 15: 'B',
|
|
21
|
-
16: 'D', 17: 'D', 18: 'C', 19: 'C', 20: 'B', 21: 'A', 22: 'D', 23: 'C',
|
|
22
|
-
24: 'B', 25: 'A', 26: 'B', 27: 'D', 28: 'C', 29: 'A', 30: 'B',
|
|
23
|
-
};
|
|
24
|
-
export const DEMO_EXPLANATIONS = {
|
|
25
|
-
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.',
|
|
26
|
-
2: 'SQL injection occurs when user input is inserted directly into database queries without proper sanitization, allowing attackers to manipulate the query.',
|
|
27
|
-
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.',
|
|
28
|
-
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.',
|
|
29
|
-
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.',
|
|
30
|
-
6: 'XSS stands for Cross-Site Scripting — a vulnerability where attackers inject malicious scripts into web pages viewed by other users.',
|
|
31
|
-
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.',
|
|
32
|
-
8: 'A Trojan disguises itself as legitimate software to trick users into installing it. Unlike worms, Trojans do not self-replicate.',
|
|
33
|
-
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.',
|
|
34
|
-
10: 'A cryptographic hash is a one-way function that produces a fixed-size digest. It cannot be reversed, unlike encryption.',
|
|
35
|
-
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.',
|
|
36
|
-
12: 'DNS Spoofing (cache poisoning) manipulates DNS responses to redirect victims to attacker-controlled servers. It is distinct from phishing, SQLi, and brute force.',
|
|
37
|
-
13: 'SSH (Secure Shell) runs on port 22 by default. Port 21 is FTP, 80 is HTTP, 443 is HTTPS.',
|
|
38
|
-
14: 'Two-factor authentication (2FA) requires two distinct types of credentials — something you know (password) and something you have (phone/token) or are (biometrics).',
|
|
39
|
-
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.',
|
|
40
|
-
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.',
|
|
41
|
-
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.',
|
|
42
|
-
18: 'The principle of least privilege means granting users only the minimum permissions necessary to perform their tasks, reducing the attack surface.',
|
|
43
|
-
19: 'Nmap is the standard port scanning tool. Wireshark captures packets, Metasploit is an exploitation framework, and Hashcat is a password cracker.',
|
|
44
|
-
20: 'Ransomware encrypts the victim\'s files and demands payment (usually cryptocurrency) in exchange for the decryption key.',
|
|
45
|
-
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.',
|
|
46
|
-
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.',
|
|
47
|
-
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.',
|
|
48
|
-
24: 'The chmod command changes file permissions in Linux. chown changes ownership, chgrp changes group, and passwd changes user passwords.',
|
|
49
|
-
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.',
|
|
50
|
-
26: 'Phishing is a social engineering attack that tricks people into revealing sensitive information. Buffer overflow, SQL injection, and port scanning are technical attacks.',
|
|
51
|
-
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.',
|
|
52
|
-
28: 'A VPN (Virtual Private Network) creates an encrypted tunnel for internet traffic, protecting data from interception and masking the user\'s IP address.',
|
|
53
|
-
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.',
|
|
54
|
-
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.',
|
|
55
|
-
};
|
|
56
|
-
// 30-question pool. Q16-30 options were rebalanced from the original translate-demo.js layout
|
|
57
|
-
// to achieve an even answer distribution across A/B/C/D.
|
|
58
|
-
// Translation files (translations/<lang>/demo.json) must match this option order.
|
|
59
|
-
export const DEMO_QUESTIONS = [
|
|
60
|
-
{ number: 1, text: 'Which algorithm is NOT a symmetric cipher?', category: 'Cryptography',
|
|
61
|
-
options: { A: 'AES', B: 'RSA', C: 'DES', D: 'Blowfish' } },
|
|
62
|
-
{ number: 2, text: 'What does SQL injection exploit?', category: 'Web Security',
|
|
63
|
-
options: { A: 'Buffer overflow in web server', B: 'Unsanitized user input in database queries', C: 'Weak encryption algorithms', D: 'Misconfigured firewall rules' } },
|
|
64
|
-
{ number: 3, text: 'Which HTTP status code indicates "Forbidden"?', category: 'Web Security',
|
|
65
|
-
options: { A: '401', B: '404', C: '403', D: '500' } },
|
|
66
|
-
{ number: 4, text: 'What is the primary purpose of a nonce in cryptography?', category: 'Cryptography',
|
|
67
|
-
options: { A: 'Encrypt data at rest', B: 'Prevent replay attacks', C: 'Generate random passwords', D: 'Compress data before encryption' } },
|
|
68
|
-
{ number: 5, text: 'Which tool is commonly used for network packet capture?', category: 'Network',
|
|
69
|
-
options: { A: 'Burp Suite', B: 'Ghidra', C: 'Wireshark', D: 'John the Ripper' } },
|
|
70
|
-
{ number: 6, text: 'What does XSS stand for in cybersecurity?', category: 'Web Security',
|
|
71
|
-
options: { A: 'Extended Security System', B: 'Cross-Site Scripting', C: 'XML Secure Socket', D: 'Cross-Server Sharing' } },
|
|
72
|
-
{ number: 7, text: 'What is the primary function of a firewall?', category: 'Network',
|
|
73
|
-
options: { A: 'Encrypt network data', B: 'Filter network traffic based on security rules', C: 'Detect viruses in files', D: 'Speed up internet connection' } },
|
|
74
|
-
{ number: 8, text: 'Which type of malware disguises itself as legitimate software?', category: 'Malware',
|
|
75
|
-
options: { A: 'Worm', B: 'Ransomware', C: 'Trojan', D: 'Adware' } },
|
|
76
|
-
{ number: 9, text: 'Which protocol provides secure communication on the web?', category: 'Network',
|
|
77
|
-
options: { A: 'HTTP', B: 'FTP', C: 'HTTPS', D: 'SMTP' } },
|
|
78
|
-
{ number: 10, text: 'What is a cryptographic hash?', category: 'Cryptography',
|
|
79
|
-
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' } },
|
|
80
|
-
{ number: 11, text: 'Which tool is used for binary analysis?', category: 'Reverse Engineering',
|
|
81
|
-
options: { A: 'Nmap', B: 'SQLMap', C: 'Ghidra', D: 'Nikto' } },
|
|
82
|
-
{ number: 12, text: 'Which attack manipulates DNS requests to redirect traffic?', category: 'Network',
|
|
83
|
-
options: { A: 'Phishing', B: 'DNS Spoofing', C: 'SQL Injection', D: 'Brute Force' } },
|
|
84
|
-
{ number: 13, text: 'What is the standard port for SSH?', category: 'Network',
|
|
85
|
-
options: { A: '21', B: '22', C: '80', D: '443' } },
|
|
86
|
-
{ number: 14, text: 'What is two-factor authentication (2FA)?', category: 'Authentication',
|
|
87
|
-
options: { A: 'Using two different passwords', B: 'Verifying identity with two distinct types of credentials', C: 'Encrypting data twice', D: 'Connecting through two networks' } },
|
|
88
|
-
{ number: 15, text: 'Which Linux command shows open ports on a system?', category: 'Linux',
|
|
89
|
-
options: { A: 'ls -la', B: 'netstat -tulpn', C: 'chmod 777', D: 'cat /etc/passwd' } },
|
|
90
|
-
{ number: 16, text: 'What is a Man-in-the-Middle (MitM) attack?', category: 'Network',
|
|
91
|
-
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' } },
|
|
92
|
-
{ number: 17, text: 'Which of these is a hash algorithm?', category: 'Cryptography',
|
|
93
|
-
options: { A: 'AES-256', B: 'Diffie-Hellman', C: 'RSA-2048', D: 'SHA-256' } },
|
|
94
|
-
{ number: 18, text: 'What is the principle of least privilege?', category: 'Security',
|
|
95
|
-
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' } },
|
|
96
|
-
{ number: 19, text: 'Which tool is commonly used for port scanning?', category: 'Network',
|
|
97
|
-
options: { A: 'Wireshark', B: 'Metasploit', C: 'Nmap', D: 'Hashcat' } },
|
|
98
|
-
{ number: 20, text: 'What is ransomware?', category: 'Malware',
|
|
99
|
-
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' } },
|
|
100
|
-
{ number: 21, text: 'What is the difference between symmetric and asymmetric encryption?', category: 'Cryptography',
|
|
101
|
-
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' } },
|
|
102
|
-
{ number: 22, text: 'Which vulnerability allows arbitrary code execution on a web server?', category: 'Web Security',
|
|
103
|
-
options: { A: 'CSRF', B: 'Open Redirect', C: 'Clickjacking', D: 'Remote Code Execution (RCE)' } },
|
|
104
|
-
{ number: 23, text: 'What is OWASP?', category: 'Security',
|
|
105
|
-
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' } },
|
|
106
|
-
{ number: 24, text: 'Which Linux command changes file permissions?', category: 'Linux',
|
|
107
|
-
options: { A: 'chown', B: 'chmod', C: 'chgrp', D: 'passwd' } },
|
|
108
|
-
{ number: 25, text: 'What is an SSL/TLS certificate?', category: 'Cryptography',
|
|
109
|
-
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' } },
|
|
110
|
-
{ number: 26, text: 'Which of the following is a social engineering attack?', category: 'Security',
|
|
111
|
-
options: { A: 'Buffer overflow', B: 'Phishing', C: 'SQL Injection', D: 'Port scanning' } },
|
|
112
|
-
{ number: 27, text: 'What does the Linux command "grep" do?', category: 'Linux',
|
|
113
|
-
options: { A: 'Compresses files', B: 'Configures the network', C: 'Shows active processes', D: 'Searches for text patterns in files' } },
|
|
114
|
-
{ number: 28, text: 'What is a VPN?', category: 'Network',
|
|
115
|
-
options: { A: 'A type of virus', B: 'A file transfer protocol', C: 'A virtual private network that encrypts internet traffic', D: 'A vulnerability scanner' } },
|
|
116
|
-
{ number: 29, text: 'What is CSRF (Cross-Site Request Forgery)?', category: 'Web Security',
|
|
117
|
-
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' } },
|
|
118
|
-
{ number: 30, text: 'What is the best practice for storing passwords in a database?', category: 'Security',
|
|
119
|
-
options: { A: 'Plain text', B: 'Hashed with salt', C: 'Encrypted with AES', D: 'Encoded in Base64' } },
|
|
120
|
-
];
|
|
121
|
-
/** Fisher-Yates shuffle, returns a new array. */
|
|
122
|
-
function shuffle(arr) {
|
|
123
|
-
const out = [...arr];
|
|
124
|
-
for (let i = out.length - 1; i > 0; i--) {
|
|
125
|
-
const j = Math.floor(Math.random() * (i + 1));
|
|
126
|
-
[out[i], out[j]] = [out[j], out[i]];
|
|
127
|
-
}
|
|
128
|
-
return out;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Shuffle the four options within a single question; recompute the answer letter
|
|
132
|
-
* and record `sourceOrder` so the shuffle can be replayed against a different
|
|
133
|
-
* translation of the same question (used by lang-switch to keep answers).
|
|
134
|
-
*/
|
|
135
|
-
function shuffleQuestionOptions(q) {
|
|
136
|
-
if (!q.answer)
|
|
137
|
-
return q;
|
|
138
|
-
const keys = ['A', 'B', 'C', 'D'];
|
|
139
|
-
const sourceOrder = shuffle(keys);
|
|
140
|
-
const newOptions = {
|
|
141
|
-
A: q.options[sourceOrder[0]],
|
|
142
|
-
B: q.options[sourceOrder[1]],
|
|
143
|
-
C: q.options[sourceOrder[2]],
|
|
144
|
-
D: q.options[sourceOrder[3]],
|
|
145
|
-
};
|
|
146
|
-
const newAnswer = keys[sourceOrder.indexOf(q.answer)];
|
|
147
|
-
return { ...q, options: newOptions, answer: newAnswer, sourceOrder };
|
|
148
|
-
}
|
|
149
|
-
/**
|
|
150
|
-
* Pick `n` random questions from the pool, shuffle each question's options,
|
|
151
|
-
* renumber 1..n. Returns fully self-contained questions with answer + explanation
|
|
152
|
-
* populated, so downstream code does not need to look up DEMO_ANSWERS by number.
|
|
153
|
-
*/
|
|
154
|
-
export function pickDemoQuestions(n = DEMO_PICK_SIZE) {
|
|
155
|
-
const translatedPool = getLocalizedDemoQuestions();
|
|
156
|
-
const explanations = getLocalizedExplanations();
|
|
157
|
-
// Enrich pool with answer + explanation keyed by ORIGINAL number
|
|
158
|
-
const enriched = translatedPool.map((q) => ({
|
|
159
|
-
...q,
|
|
160
|
-
answer: DEMO_ANSWERS[q.number],
|
|
161
|
-
explanation: explanations[q.number],
|
|
162
|
-
}));
|
|
163
|
-
// Shuffle pool, pick n, shuffle each question's options, renumber 1..n.
|
|
164
|
-
// Keep the original pool number as `sourceNumber` so lang-switch can re-translate
|
|
165
|
-
// this exact question without losing the user's answer.
|
|
166
|
-
const picked = shuffle(enriched).slice(0, n);
|
|
167
|
-
return picked.map((q, i) => {
|
|
168
|
-
const shuffled = shuffleQuestionOptions(q);
|
|
169
|
-
return { ...shuffled, sourceNumber: q.number, number: i + 1 };
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Get localized explanations for demo questions.
|
|
174
|
-
* Reads from translations/<lang>/demo-explanations.json.
|
|
175
|
-
* Falls back to English for any missing entries.
|
|
176
|
-
*/
|
|
177
|
-
export function getLocalizedExplanations() {
|
|
178
|
-
const lang = getConfig().language;
|
|
179
|
-
if (!lang || lang === 'en')
|
|
180
|
-
return DEMO_EXPLANATIONS;
|
|
181
|
-
const path = join(__dirname, '..', '..', 'translations', lang, 'demo-explanations.json');
|
|
182
|
-
if (!existsSync(path))
|
|
183
|
-
return DEMO_EXPLANATIONS;
|
|
184
|
-
try {
|
|
185
|
-
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
186
|
-
if (data && typeof data === 'object') {
|
|
187
|
-
// Merge: translated entries override English, missing entries stay English
|
|
188
|
-
return { ...DEMO_EXPLANATIONS, ...data };
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
catch { }
|
|
192
|
-
return DEMO_EXPLANATIONS;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Get demo questions translated to user's language.
|
|
196
|
-
* Reads from translations/<lang>/demo.json (bundled static file).
|
|
197
|
-
* Per-question fallback: missing entries use the English source.
|
|
198
|
-
*/
|
|
199
|
-
export function getLocalizedDemoQuestions() {
|
|
200
|
-
const lang = getConfig().language;
|
|
201
|
-
if (!lang || lang === 'en')
|
|
202
|
-
return DEMO_QUESTIONS;
|
|
203
|
-
const path = join(__dirname, '..', '..', 'translations', lang, 'demo.json');
|
|
204
|
-
if (!existsSync(path))
|
|
205
|
-
return DEMO_QUESTIONS;
|
|
206
|
-
try {
|
|
207
|
-
const data = JSON.parse(readFileSync(path, 'utf-8'));
|
|
208
|
-
if (!Array.isArray(data))
|
|
209
|
-
return DEMO_QUESTIONS;
|
|
210
|
-
// Per-question merge: use translated where available, English fallback otherwise.
|
|
211
|
-
return DEMO_QUESTIONS.map((eng) => {
|
|
212
|
-
const tr = data.find((d) => d.number === eng.number);
|
|
213
|
-
return tr && tr.options ? tr : eng;
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
catch { }
|
|
217
|
-
return DEMO_QUESTIONS;
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Get localized demo session name.
|
|
221
|
-
*/
|
|
222
|
-
export function getLocalizedDemoSession() {
|
|
223
|
-
const lang = getConfig().language;
|
|
224
|
-
if (!lang || lang === 'en')
|
|
225
|
-
return { ...DEMO_SESSION, startedAt: '' };
|
|
226
|
-
const names = {
|
|
227
|
-
zh: 'ICOA 模拟考试 — 免费练习',
|
|
228
|
-
ja: 'ICOA デモ試験 — 無料練習',
|
|
229
|
-
ko: 'ICOA 데모 시험 — 무료 연습',
|
|
230
|
-
es: 'ICOA Examen Demo — Práctica Gratis',
|
|
231
|
-
ar: 'اختبار ICOA التجريبي — تدريب مجاني',
|
|
232
|
-
fr: 'ICOA Examen Démo — Pratique Gratuite',
|
|
233
|
-
pt: 'ICOA Exame Demo — Prática Gratuita',
|
|
234
|
-
ru: 'ICOA Демо Экзамен — Бесплатная Практика',
|
|
235
|
-
hi: 'ICOA डेमो परीक्षा — निःशुल्क अभ्यास',
|
|
236
|
-
de: 'ICOA Demo-Prüfung — Kostenlose Übung',
|
|
237
|
-
id: 'ICOA Ujian Demo — Latihan Gratis',
|
|
238
|
-
th: 'ICOA สอบทดลอง — ฝึกฟรี',
|
|
239
|
-
vi: 'ICOA Thi Thử — Luyện Tập Miễn Phí',
|
|
240
|
-
tr: 'ICOA Demo Sınav — Ücretsiz Uygulama',
|
|
241
|
-
uk: 'ICOA Демо Екзамен — Безплатна Практика',
|
|
242
|
-
ht: 'ICOA Egzamen Demo — Pratik Gratis',
|
|
243
|
-
};
|
|
244
|
-
return {
|
|
245
|
-
...DEMO_SESSION,
|
|
246
|
-
examName: names[lang] || DEMO_SESSION.examName,
|
|
247
|
-
startedAt: '',
|
|
248
|
-
};
|
|
249
|
-
}
|
|
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:""}}
|
package/dist/lib/demo-flags.js
CHANGED
|
@@ -1,27 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// i18n.ts (EN) and the i18n translations must reference this constant
|
|
3
|
-
// rather than hand-copying the base64 string — hand-copying created the
|
|
4
|
-
// BUG-002 class drift where Q33 base64 was written as "ISOA{...}" in one
|
|
5
|
-
// place and "ICOA{...}" in another. This file exists so every translation
|
|
6
|
-
// picks up the correct value from one place.
|
|
7
|
-
//
|
|
8
|
-
// The matching unit test (test/demo-flags-consistency.test.js) scans
|
|
9
|
-
// compiled i18n.js for any base64-looking string that starts with
|
|
10
|
-
// "aWNvYX" (= "icoa{") and asserts they ALL equal DEMO_AI4CTF_FLAG_B64.
|
|
11
|
-
// That's the drift protection: if someone hand-edits one translation's
|
|
12
|
-
// b64, the scan finds two distinct values and the test fails.
|
|
13
|
-
export const DEMO_AI4CTF_FLAG_B64 = 'aWNvYXt3M2xjMG1lXzJfYWk0Y3RmfQ==';
|
|
14
|
-
export const DEMO_AI4CTF_FLAG_DECODED = 'icoa{w3lc0me_2_ai4ctf}';
|
|
15
|
-
/**
|
|
16
|
-
* Runtime verification that the b64 constant decodes to the expected plaintext.
|
|
17
|
-
* Catches hand-edit drift between the two constants above.
|
|
18
|
-
* Throws if they disagree. Safe to call at module load or from tests.
|
|
19
|
-
*/
|
|
20
|
-
export function verifyDemoAi4ctfFlag() {
|
|
21
|
-
const decoded = Buffer.from(DEMO_AI4CTF_FLAG_B64, 'base64').toString('utf-8');
|
|
22
|
-
if (decoded !== DEMO_AI4CTF_FLAG_DECODED) {
|
|
23
|
-
throw new Error(`Demo flag drift: base64 '${DEMO_AI4CTF_FLAG_B64}' decodes to '${decoded}' ` +
|
|
24
|
-
`but DEMO_AI4CTF_FLAG_DECODED constant is '${DEMO_AI4CTF_FLAG_DECODED}'. ` +
|
|
25
|
-
`Fix demo-flags.ts so both halves agree.`);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
1
|
+
export const DEMO_AI4CTF_FLAG_B64="aWNvYXt3M2xjMG1lXzJfYWk0Y3RmfQ==";export const DEMO_AI4CTF_FLAG_DECODED="icoa{w3lc0me_2_ai4ctf}";export function verifyDemoAi4ctfFlag(){const t=Buffer.from(DEMO_AI4CTF_FLAG_B64,"base64").toString("utf-8");if("icoa{w3lc0me_2_ai4ctf}"!==t)throw new Error(`Demo flag drift: base64 '${DEMO_AI4CTF_FLAG_B64}' decodes to '${t}' but DEMO_AI4CTF_FLAG_DECODED constant is 'icoa{w3lc0me_2_ai4ctf}'. Fix demo-flags.ts so both halves agree.`)}
|