Repository navigation
Expand file tree
/
Copy pathapp.html
More file actions
2244 lines (2190 loc) · 228 KB
/
Copy pathapp.html
File metadata and controls
2244 lines (2190 loc) · 228 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<title>codeLearn</title>
<meta name="theme-color" content="#0D1015">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="codeLearn">
<link rel="manifest" href="manifest.json">
<link rel="apple-touch-icon" href="icons/apple-touch-icon.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@600;700&family=IBM+Plex+Mono:wght@400;600&display=swap">
<style>
:root{
--bg:#0D1015; --panel:#141A21; --panel2:#1B222C; --line:#28313D;
--ink:#E9E6DE; --mut:#94A0AE; --amber:#F2A63B; --amber-dim:#8a5f22;
--violet:#A18BFA; --good:#3FC98C; --bad:#E2594F; --teal:#2DD4BF;
--kw:#7FB2F0; --str:#95CE84; --com:#5B6B7B; --num:#D89AF0; --vr:#EFA48B; --fn:#E8CE7A;
--disp:'Chakra Petch',system-ui,sans-serif;
--mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0; background:var(--bg); color:var(--ink);
font:15px/1.55 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
-webkit-tap-highlight-color:transparent; overscroll-behavior-y:contain;
}
button{font:inherit;color:inherit;background:none;border:none;padding:0;cursor:pointer}
.wrap{max-width:560px;margin:0 auto;padding:0 16px 104px}
/* ---------- app bar ---------- */
.appbar{
position:sticky; top:env(safe-area-inset-top,0px); z-index:20;
background:linear-gradient(180deg,var(--bg) 82%,rgba(13,16,21,0));
padding:10px 0 12px;
}
.appbar .row{display:flex;align-items:center;gap:10px}
.logo{font-family:var(--disp);font-weight:700;font-size:19px;letter-spacing:.02em}
.logo em{font-style:normal;color:var(--amber)}
.logo .cursor{display:inline-block;width:9px;height:17px;background:var(--amber);margin-left:3px;vertical-align:-2px;animation:blink 1.2s steps(2) infinite}
@keyframes blink{50%{opacity:0}}
@media (prefers-reduced-motion:reduce){.logo .cursor{animation:none}}
.spacer{flex:1}
.streak{display:flex;align-items:center;gap:5px;font-family:var(--disp);font-weight:600;font-size:14px;color:var(--amber);background:var(--panel);border:1px solid var(--line);border-radius:999px;padding:4px 12px}
.streak.cold{color:var(--mut)}
.xpline{display:flex;align-items:center;gap:10px;margin-top:9px}
.rank{font-family:var(--disp);font-weight:600;font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--violet);white-space:nowrap}
.xpbar{flex:1;height:6px;border-radius:3px;background:var(--panel2);overflow:hidden}
.xpbar i{display:block;height:100%;border-radius:3px;background:linear-gradient(90deg,var(--violet),var(--amber));transition:width .5s ease}
.xpnum{font-family:var(--mono);font-size:11px;color:var(--mut);white-space:nowrap;font-variant-numeric:tabular-nums}
/* ---------- bottom nav ---------- */
.tabbar{
position:fixed;left:0;right:0;bottom:0;z-index:30;
background:rgba(20,26,33,.96);backdrop-filter:blur(10px);
border-top:1px solid var(--line);
padding:8px 8px calc(8px + env(safe-area-inset-bottom,0px));
}
.tabbar .inner{max-width:560px;margin:0 auto;display:flex}
.tab{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;color:var(--mut);font-family:var(--disp);font-size:11px;font-weight:600;letter-spacing:.05em;padding:4px 0;border-radius:10px}
.tab svg{width:22px;height:22px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
.tab.on{color:var(--amber)}
/* ---------- generic ---------- */
h1.pg{font-family:var(--disp);font-size:24px;font-weight:700;margin:14px 0 4px;text-wrap:balance}
.sub{color:var(--mut);margin:0 0 18px;max-width:46ch}
.sect{font-family:var(--disp);font-size:12px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--mut);margin:26px 0 10px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:16px;margin-bottom:12px}
.tap{display:block;width:100%;text-align:left;transition:transform .08s ease}
.tap:active{transform:scale(.985)}
.chip{display:inline-block;font-family:var(--mono);font-size:10.5px;padding:2px 8px;border-radius:999px;border:1px solid var(--line);color:var(--mut);background:var(--panel2)}
.chip.amber{color:var(--amber);border-color:var(--amber-dim)}
.chip.violet{color:var(--violet);border-color:#4a3f86}
.chip.good{color:var(--good);border-color:#1e5f44}
.chip.bad{color:var(--bad);border-color:#6e2b26}
.btn{
display:block;width:100%;text-align:center;font-family:var(--disp);font-weight:700;font-size:15px;
letter-spacing:.03em;background:var(--amber);color:#171208;border-radius:12px;padding:14px 18px;margin-top:14px;
}
.btn:active{transform:scale(.98)}
.btn.ghost{background:var(--panel2);color:var(--ink);border:1px solid var(--line)}
.btn.danger{background:none;border:1px solid #6e2b26;color:var(--bad)}
.btn[disabled]{opacity:.4;pointer-events:none}
.back{display:inline-flex;align-items:center;gap:6px;color:var(--mut);font-family:var(--disp);font-weight:600;font-size:13px;margin-top:12px}
.pbar{height:5px;border-radius:3px;background:var(--panel2);overflow:hidden}
.pbar i{display:block;height:100%;background:var(--amber);border-radius:3px;transition:width .4s}
.pbar.v i{background:var(--violet)}
/* ---------- track / lesson lists ---------- */
.trackcard{display:flex;align-items:center;gap:14px}
.tricon{
width:44px;height:44px;border-radius:11px;flex:none;display:flex;align-items:center;justify-content:center;
font-family:var(--mono);font-weight:600;font-size:13px;border:1px solid var(--line);background:var(--panel2);
}
.tricon.accent{color:var(--good)}
.tricon svg{width:23px;height:23px;display:block}
.trmeta{flex:1;min-width:0}
.trname{font-family:var(--disp);font-weight:700;font-size:16px}
.trsub{font-size:12.5px;color:var(--mut);margin:1px 0 7px}
.count{font-family:var(--mono);font-size:11px;color:var(--mut);white-space:nowrap;font-variant-numeric:tabular-nums}
.lrow{display:flex;align-items:center;gap:12px}
.lstate{width:34px;height:34px;border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;border:1px solid var(--line);background:var(--panel2);font-family:var(--mono);font-size:12px;color:var(--mut)}
.lstate.done{background:#153526;border-color:#1e5f44;color:var(--good)}
.lstate.next{background:#33270f;border-color:var(--amber-dim);color:var(--amber)}
.lrow.locked{opacity:.45}
.lname{font-weight:600;font-size:14.5px}
.lxp{font-family:var(--mono);font-size:11px;color:var(--mut)}
/* ---------- code ---------- */
.codebox{
background:#0A0D12;border:1px solid var(--line);border-radius:10px;
margin:12px 0 4px;overflow-x:auto;
}
.codebox pre{margin:0;padding:12px 14px;font:12.5px/1.65 var(--mono);white-space:pre;min-width:min-content}
.codebox .lang{display:block;font:10px/1 var(--mono);color:var(--com);padding:8px 14px 0;letter-spacing:.1em;text-transform:uppercase}
.tk-kw{color:var(--kw)} .tk-str{color:var(--str)} .tk-com{color:var(--com)}
.tk-num{color:var(--num)} .tk-var{color:var(--vr)} .tk-fn{color:var(--fn)}
code.inl{font:12.5px var(--mono);background:var(--panel2);border:1px solid var(--line);border-radius:5px;padding:1px 5px;color:var(--amber)}
/* hunt code lines */
.hline{display:flex;width:100%;text-align:left;font:12.5px/1.7 var(--mono);white-space:pre;border-left:3px solid transparent}
.hline .no{flex:none;width:34px;padding-left:10px;color:#3d4856;user-select:none;font-variant-numeric:tabular-nums}
.hline .src{padding-right:14px}
.hline:active{background:#161d26}
.hline.hit{background:#15301f;border-left-color:var(--good)}
.hline.miss{background:#331a17;border-left-color:var(--bad)}
/* ---------- quiz ---------- */
.opt{
display:block;width:100%;text-align:left;background:var(--panel);border:1px solid var(--line);
border-radius:12px;padding:13px 14px;margin-bottom:10px;font-size:14px;
}
.opt .key{font-family:var(--mono);font-size:11px;color:var(--mut);margin-right:8px}
.opt.right{border-color:var(--good);background:#12291d}
.opt.wrong{border-color:var(--bad);background:#2c1714}
.opt.dim{opacity:.5}
.explain{border-left:3px solid var(--amber);background:var(--panel);border-radius:0 12px 12px 0;padding:12px 14px;font-size:13.5px;color:var(--ink);margin-top:4px}
.explain b{color:var(--amber)}
.qmeta{display:flex;align-items:center;gap:10px;margin:16px 0 10px}
.qmeta .pbar{flex:1}
.hearts{font-family:var(--mono);font-size:13px;color:var(--bad);letter-spacing:2px}
/* ---------- results / profile ---------- */
.bignum{font-family:var(--disp);font-weight:700;font-size:44px;color:var(--amber);line-height:1.1}
.center{text-align:center}
.resultcard{padding:28px 16px}
.badgegrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px}
.badge{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:12px}
.badge.off{opacity:.38}
.badge .bi{font-size:20px}
.badge .bn{font-family:var(--disp);font-weight:600;font-size:13px;margin:6px 0 2px}
.badge .bd{font-size:11.5px;color:var(--mut)}
.statrow{display:flex;gap:10px}
.stat{flex:1;background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:12px;text-align:center}
.stat .v{font-family:var(--disp);font-weight:700;font-size:22px;color:var(--amber)}
.stat .k{font-size:11px;color:var(--mut);letter-spacing:.05em;text-transform:uppercase}
/* toast */
#toast{
position:fixed;left:50%;bottom:calc(84px + env(safe-area-inset-bottom,0px));transform:translateX(-50%) translateY(20px);
background:var(--panel2);border:1px solid var(--amber-dim);color:var(--ink);
font-family:var(--disp);font-weight:600;font-size:14px;border-radius:999px;padding:10px 20px;
opacity:0;pointer-events:none;transition:all .3s ease;z-index:50;white-space:nowrap;max-width:92vw;
}
#toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
#updatebar{
position:fixed;left:0;right:0;top:0;z-index:60;transform:translateY(-120%);transition:transform .35s ease;
background:var(--amber);color:#171208;font-family:var(--disp);font-weight:700;font-size:13.5px;letter-spacing:.02em;
text-align:center;padding:calc(11px + env(safe-area-inset-top,0px)) 16px 11px;cursor:pointer;
box-shadow:0 6px 20px rgba(0,0,0,.4);
}
#updatebar.show{transform:translateY(0)}
#backupModal{position:fixed;inset:0;z-index:70;background:rgba(5,7,10,.72);display:flex;align-items:flex-end;justify-content:center}
#backupModal .sheet{width:100%;max-width:560px;background:var(--panel);border:1px solid var(--line);border-radius:18px 18px 0 0;padding:20px 16px calc(20px + env(safe-area-inset-bottom,0px));max-height:88vh;overflow-y:auto}
.sheettitle{font-family:var(--disp);font-weight:700;font-size:18px;margin-bottom:6px}
.backupta{width:100%;min-height:118px;background:#0A0D12;border:1px solid var(--line);border-radius:10px;color:var(--ink);font:12px/1.5 var(--mono);padding:10px;resize:vertical;-webkit-user-select:text;user-select:text}
.sheetbtns{display:flex;flex-wrap:wrap;gap:8px}
.sheetbtns .btn{flex:1;min-width:130px}
.refs{margin-top:14px;border:1px solid var(--line);border-radius:12px;padding:12px 14px;background:var(--panel)}
.refslabel{font-family:var(--disp);font-size:11px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--violet);margin-bottom:8px}
.reflink{display:flex;align-items:center;justify-content:space-between;gap:10px;text-decoration:none;color:var(--ink);font-size:13.5px;padding:9px 0;border-top:1px solid var(--line)}
.reflink:first-of-type{border-top:none}
.reflink .arw{color:var(--mut);font-size:14px;flex:none}
.reflink:active{color:var(--amber)}
.flink{display:inline-flex;align-items:center;gap:6px;color:var(--violet);font-size:13px;font-weight:600;text-decoration:none;padding:4px 2px;margin:2px 0 12px;cursor:pointer;border-bottom:1px dashed rgba(161,139,250,.45)}
.flink:active{color:var(--amber);border-bottom-color:rgba(242,166,59,.5)}
.codebox.corr{border-color:#1e5f44}
.codebox.corr .lang{color:var(--good)}
.trackrefs{margin:30px 2px 4px;padding-top:18px;border-top:1px solid var(--line)}
.trackrefs-h{font-family:var(--disp);font-size:12px;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--mut);margin-bottom:12px}
.treflist{list-style:none;margin:0;padding:0}
.treflist li{position:relative;padding:7px 0 7px 20px;font-size:13.5px;line-height:1.45}
.treflist li+li{border-top:1px dashed var(--line)}
.treflist li::before{content:"↗";position:absolute;left:2px;top:7px;color:var(--mut);font-size:11px}
.treflist a{color:var(--violet);text-decoration:none}
.treflist a:active{color:var(--amber)}
/* language-basics grid tiles (teal theme, distinct from the security tracks) */
.basicsgrid{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:4px}
.basicscard{display:block;text-align:left;border-radius:14px;padding:14px;border:1px solid #1f5f63;background:linear-gradient(150deg,#0e3033,#0b2124);color:var(--ink);transition:transform .08s ease}
.basicscard:active{transform:scale(.98)}
.bc-icon{font-family:var(--mono);font-weight:600;font-size:13px;width:38px;height:38px;border-radius:10px;display:flex;align-items:center;justify-content:center;background:rgba(45,212,191,.16);border:1px solid #1f5f63;color:var(--teal);margin-bottom:10px}
.bc-icon .bc-img{width:24px;height:24px;object-fit:contain;display:block}
.bc-name{font-family:var(--disp);font-weight:700;font-size:15px;line-height:1.15}
.bc-count{font-family:var(--mono);font-size:11px;color:var(--mut);margin:3px 0 9px;font-variant-numeric:tabular-nums}
.bc-bar{height:5px;border-radius:3px;background:rgba(0,0,0,.32);overflow:hidden}
.bc-bar i{display:block;height:100%;border-radius:3px;background:linear-gradient(90deg,var(--teal),#7fe3c0)}
/* language fact file */
.factfile{border:1px solid #1f5f63;background:linear-gradient(150deg,#0e2e31,var(--panel));border-radius:14px;padding:14px 16px;margin:2px 0 20px}
.ff-note{font-size:13px;color:var(--ink);line-height:1.5;margin-bottom:12px}
.ff-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px}
.ff-cell{background:rgba(0,0,0,.22);border-radius:9px;padding:8px 10px;min-width:0}
.ff-k{font-family:var(--disp);font-size:9.5px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--teal)}
.ff-v{font-family:var(--mono);font-size:11.5px;color:var(--ink);margin-top:3px;line-height:1.3}
.ff-link{display:inline-block;margin-top:12px;font-size:12.5px;color:var(--teal);text-decoration:none}
.ff-link:active{color:var(--amber)}
.daily{border:1px solid var(--amber-dim);background:linear-gradient(160deg,#1d1608,var(--panel))}
.dailytag{font-family:var(--disp);font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--amber);margin-bottom:6px}
kbd,strong{color:var(--ink)}
p{margin:.5em 0}
</style>
</head>
<body>
<header class="appbar"><div class="wrap" style="padding-bottom:0">
<div class="row">
<div class="logo">code<em>Learn</em><span class="cursor"></span></div>
<div class="spacer"></div>
<div class="streak" id="streakpill">⚑ 0</div>
</div>
<div class="xpline">
<span class="rank" id="rankname">Script Kiddie</span>
<div class="xpbar"><i id="xpfill" style="width:0%"></i></div>
<span class="xpnum" id="xpnum">0 XP</span>
</div>
</div></header>
<main class="wrap" id="view"></main>
<nav class="tabbar"><div class="inner">
<button class="tab" data-tab="learn" aria-label="Learn">
<svg viewBox="0 0 24 24"><path d="M4 19V6a2 2 0 0 1 2-2h13v13H6a2 2 0 0 0-2 2zm0 0a2 2 0 0 0 2 2h13"/><path d="M9 8h6M9 11h4"/></svg>
LEARN</button>
<button class="tab" data-tab="hunt" aria-label="Hunt">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="7"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4"/><circle cx="12" cy="12" r="1.2" fill="currentColor"/></svg>
HUNT</button>
<button class="tab" data-tab="profile" aria-label="Rank">
<svg viewBox="0 0 24 24"><path d="M12 3l2.1 4.6 5 .6-3.7 3.4 1 4.9L12 14l-4.4 2.5 1-4.9L4.9 8.2l5-.6z"/></svg>
RANK</button>
</div></nav>
<div id="updatebar" onclick="applyUpdate()">↻ New version available — tap to update</div>
<div id="toast"></div>
<div id="backupModal" hidden onclick="if(event.target===this)closeBackup()">
<div class="sheet">
<div class="sheettitle" id="backupTitle">Backup</div>
<div class="lxp" id="backupDesc" style="margin-bottom:10px"></div>
<textarea id="backupText" class="backupta" spellcheck="false" autocapitalize="off" autocomplete="off"></textarea>
<div class="lxp" id="backupStatus" style="min-height:16px;margin:8px 0"></div>
<input type="file" id="backupFile" accept="application/json,.json,.txt" hidden onchange="handleBackupFile(this)">
<div class="sheetbtns">
<button class="btn ghost" id="backupCopy" onclick="copyBackup()" style="margin:0">Copy code</button>
<button class="btn ghost" id="backupDownload" onclick="downloadBackup()" style="margin:0">Download file</button>
<button class="btn ghost" id="backupFileBtn" onclick="document.getElementById('backupFile').click()" style="margin:0">Load file</button>
<button class="btn" id="backupRestore" onclick="restoreBackup()" style="margin:0">Restore</button>
</div>
<button class="btn ghost" onclick="closeBackup()" style="margin-top:10px">Close</button>
</div>
</div>
<script>
'use strict';
/* =========================================================
codeLearn — teach a pentester to read code & spot vulns
========================================================= */
/* ---------- tiny syntax highlighter ---------- */
const KW = {
php:'php echo print include include_once require require_once if else elseif foreach as function return while for new class public private protected true false null die exit isset empty use namespace extends static global try catch switch case break',
csharp:'using namespace class public private protected static void string int var bool return if else for foreach in new null true false async await try catch throw get set readonly interface override',
cpp:'include define int char void return if else for while new delete sizeof struct class public private const unsigned long double float bool true false nullptr using namespace std size_t uint16_t uint32_t',
js:'const let var function return if else for while new class this document window true false null undefined async await try catch import from export typeof require module',
python:'def return if elif else for in while import from as class with try except raise True False None print lambda pass not and or is global del yield',
java:'import public private protected class static final void String int long boolean var return if else for while do new null true false try catch finally throw throws this extends implements interface package abstract',
sql:'select from where and or order by limit insert into values update set delete join inner left on union all as group having create table drop like',
go:'package import func var const type struct interface map chan go defer return if else for range switch case break continue default nil true false string int int64 byte error bool make new append len cap',
bash:'if then else elif fi for while do done case esac function in echo export local return exit read then set unset source'
};
function esc(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');}
function hl(src,lang){
lang = lang||'js';
const kws = (KW[lang]||KW.js).split(' ');
const kwRe = kws.join('|');
const flags = lang==='sql' ? 'gi' : 'g';
const re = new RegExp(
'(\\/\\*[\\s\\S]*?\\*\\/|\\/\\/[^\\n]*|#[^\\n]*|--[^\\n]*)'+ // 1 comment
'|("(?:[^"\\\\\\n]|\\\\.)*"|\'(?:[^\'\\\\\\n]|\\\\.)*\')'+ // 2 string
'|(\\$\\w+|@\\w+)'+ // 3 var/attr
'|\\b(\\d[\\d._]*)\\b'+ // 4 number
'|\\b('+kwRe+')\\b'+ // 5 keyword
'|\\b(\\w+)(?=\\()' // 6 fn call
,flags);
return esc(src).replace(re,(m,com,str,vr,num,kw,fn)=>{
if(com!==undefined){
// '#include' in C++ is not a comment; '#' comments only for php/python/bash
const hashLang = (lang==='php'||lang==='python'||lang==='bash');
if(com[0]==='#' && !hashLang) return m;
if(com.slice(0,2)==='--' && lang!=='sql') return m;
if(com[0]==='#' && hashLang) return '<span class="tk-com">'+com+'</span>';
if(com.slice(0,2)==='--' && lang==='sql') return '<span class="tk-com">'+com+'</span>';
return '<span class="tk-com">'+com+'</span>';
}
if(str!==undefined) return '<span class="tk-str">'+str+'</span>';
if(vr !==undefined) return '<span class="tk-var">'+vr+'</span>';
if(num!==undefined) return '<span class="tk-num">'+num+'</span>';
if(kw !==undefined) return '<span class="tk-kw">'+kw+'</span>';
if(fn !==undefined) return '<span class="tk-fn">'+fn+'</span>';
return m;
});
}
function codebox(src,lang,label){
return '<div class="codebox"><span class="lang">'+(label||lang)+'</span><pre>'+hl(src,lang)+'</pre></div>';
}
/* like codebox but with line numbers down the left (static, non-interactive) */
function codeboxNumbered(src,lang,label,extra){
const rows=src.split('\n').map((ln,i)=>
'<div class="hline"><span class="no">'+(i+1)+'</span><span class="src">'+hl(ln,lang)+'</span></div>').join('');
return '<div class="codebox'+(extra?' '+extra:'')+'"><span class="lang">'+(label||langName(lang))+'</span>'+
'<pre style="padding:6px 0 10px">'+rows+'</pre></div>';
}
/* ---------- ranks & badges ---------- */
const RANKS = [
[0,'Script Kiddie'],[120,'Recruit'],[300,'Code Reader'],[600,'Analyst'],
[1000,'Bug Hunter'],[1500,'Exploit Dev'],[2200,'Red Teamer'],[3000,'APT Operator']
];
/* monochrome line icons for the non-basics track rows (inherit .tricon color) */
const TRACK_SVG = {
webpat:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="12" cy="12" r="7"/><circle cx="12" cy="12" r="2.5"/><path d="M12 1.5v3.5M12 19v3.5M1.5 12h3.5M19 12h3.5"/></svg>',
crypto:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="10.5" width="14" height="9.5" rx="2"/><path d="M8 10.5V8a4 4 0 0 1 8 0v2.5"/></svg>',
audit:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="2.8"/></svg>',
breach:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg>',
exploit:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2 4.5 13H11l-1 9 8.5-11.5H12z"/></svg>'
};
const GREEN_GROUPS = {'Attack patterns & real-world':1, 'Exploitation':1};
const BASICS_ICONS = {
phpbasics:'icons/language_icons/php-32px.png',
pybasics:'icons/language_icons/python-32px.png',
jsbasics:'icons/language_icons/javascript-32px.png',
nodebasics:'icons/language_icons/nodedotjs-32px.png',
cbasics:'icons/language_icons/c-32px.png'
};
const BADGES = [
{id:'first', ic:'🩸', n:'First Blood', d:'Complete your first lesson'},
{id:'recon', ic:'🔭', n:'Recon Done', d:'Finish the Code Recon track'}, {id:'polyglot',ic:'🌐', n:'Polyglot', d:'Finish a lesson in 3 languages'},
{id:'eagle', ic:'🎯', n:'Eagle Eye', d:'Ace a code review with no misses'},
{id:'five', ic:'🔎', n:'Reviewer', d:'Clear 5 review challenges'},
{id:'allhunt',ic:'🏆', n:'Lead Auditor', d:'Clear every review challenge'},
{id:'week', ic:'⚑', n:'Week Warrior', d:'Keep a 7-day streak'},
{id:'grind', ic:'⚡', n:'Grinder', d:'Earn 1,000 XP'},
{id:'scholar',ic:'🎓', n:'Scholar', d:'Complete every lesson'},
{id:'daily', ic:'📅', n:'On Target', d:'Clear a Daily Target challenge'}
];
/* ---------- version ---------- */
const APP_VERSION='1.12.1';
/* ---------- persistent state ---------- */
const KEY='codelearn.v1';
let S = {xp:0, done:{}, hunts:{}, streak:{last:'',n:0}, badges:{}, daily:''};
try{ const raw=localStorage.getItem(KEY); if(raw) S=Object.assign(S,JSON.parse(raw)); }catch(e){}
function save(){ try{ localStorage.setItem(KEY,JSON.stringify(S)); }catch(e){} }
function today(){ const d=new Date(); return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); }
function yesterday(){ const d=new Date(Date.now()-864e5); return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); }
function rankInfo(xp){
let cur=RANKS[0], next=null;
for(const r of RANKS){ if(xp>=r[0]) cur=r; else { next=r; break; } }
return {name:cur[1], base:cur[0], next};
}
function addXP(n){
const before=rankInfo(S.xp).name;
S.xp+=n;
const t=today();
if(S.streak.last!==t){
S.streak.n = (S.streak.last===yesterday()) ? S.streak.n+1 : 1;
S.streak.last=t;
}
const after=rankInfo(S.xp).name;
save(); renderHeader();
if(after!==before) toast('▲ Rank up: '+after);
checkBadges();
}
function grant(id){
if(S.badges[id]) return;
S.badges[id]=Date.now(); save();
const b=BADGES.find(x=>x.id===id);
if(b) setTimeout(()=>toast(b.ic+' Badge: '+b.n),700);
}
function checkBadges(){
const doneIds=Object.keys(S.done);
if(doneIds.length>0) grant('first');
const langsDone=new Set();
for(const t of TRACKS){
const fin=t.lessons.filter(l=>S.done[l.id]).length;
if(fin>0 && t.id!=='recon') langsDone.add(t.id);
if(fin===t.lessons.length && t.id==='recon') grant('recon');
}
if(langsDone.size>=3) grant('polyglot');
const solved=Object.values(S.hunts).filter(h=>h.solved);
if(solved.length>=5) grant('five');
if(solved.length===HUNTS.length) grant('allhunt');
if(solved.some(h=>h.perfect)) grant('eagle');
if(S.streak.n>=7) grant('week');
if(S.xp>=1000) grant('grind');
if(TRACKS.every(t=>t.lessons.every(l=>S.done[l.id]))) grant('scholar');
}
/* ---------- toast ---------- */
let toastT=null;
function toast(msg){
const el=document.getElementById('toast');
el.textContent=msg; el.classList.add('show');
clearTimeout(toastT); toastT=setTimeout(()=>el.classList.remove('show'),2400);
}
/* ---------- header ---------- */
function renderHeader(){
const r=rankInfo(S.xp);
document.getElementById('rankname').textContent=r.name;
document.getElementById('xpnum').textContent=S.xp+' XP';
const span=r.next? (r.next[0]-r.base) : 1;
const pct=r.next? Math.min(100,Math.round((S.xp-r.base)/span*100)) : 100;
document.getElementById('xpfill').style.width=pct+'%';
const sp=document.getElementById('streakpill');
const active=(S.streak.last===today());
sp.textContent='⚑ '+S.streak.n;
sp.classList.toggle('cold',!active);
}
/* ---------- router ---------- */
let view={name:'learn'};
function go(v){ view=v; render(); window.scrollTo(0,0); }
function setTab(name){
document.querySelectorAll('.tab').forEach(b=>b.classList.toggle('on',b.dataset.tab===name));
}
document.querySelectorAll('.tab').forEach(b=>{
b.addEventListener('click',()=>go({name:b.dataset.tab}));
});
function render(){
const root=document.getElementById('view');
if(view.name==='learn'){ setTab('learn'); root.innerHTML=vHome(); }
else if(view.name==='track'){ setTab('learn'); root.innerHTML=vTrack(view.trackId); }
else if(view.name==='lesson'){ setTab('learn'); root.innerHTML=vLesson(); }
else if(view.name==='hunt'){ setTab('hunt'); root.innerHTML=vHuntList(); }
else if(view.name==='huntplay'){ setTab('hunt'); root.innerHTML=vHuntPlay(); }
else if(view.name==='profile'){ setTab('profile'); root.innerHTML=vProfile(); }
bindView(root);
}
/* delegate clicks via data-act attributes */
function bindView(root){
root.querySelectorAll('[data-act]').forEach(el=>{
el.addEventListener('click',()=>handleAct(el));
});
}
/* =========================================================
CURRICULUM
Each lesson: id, name, xp, cards[] (a card = teach step)
card types: {t:'read', lang, code, note} -> annotated code
{t:'md', html} -> explanation
{t:'quiz', q, opts[], a, why} -> single MCQ
========================================================= */
const TRACKS = [
{
id:'recon', name:'Code Recon', tag:'START HERE', icon:'{ }', accent:'amber',
sub:'Read any code even if you have never programmed.',
lessons:[
{id:'r1', name:'What code actually is', xp:20, cards:[
{t:'md',html:'<p>Code is a <b>list of instructions</b> a computer runs top to bottom. As a pentester you rarely need to <i>write</i> it — you need to answer three questions:</p><p>1. Where does <b>input</b> come from?<br>2. What does the code <b>do</b> with it?<br>3. Where does it <b>end up</b> (a database, a file, the page, a shell)?</p><p>That path — <b>source → processing → sink</b> — is the whole game. A vulnerability is almost always <b>untrusted input reaching a dangerous sink without being cleaned.</b></p>'},
{t:'read',lang:'php',label:'PHP — read it like a sentence',code:'$name = $_GET["name"]; // SOURCE: from the URL\n$greeting = "Hello " . $name; // processing: glue strings\necho $greeting; // SINK: printed to the page',note:'<code class="inl">$_GET["name"]</code> is user input from the URL. It is glued into a string, then <code class="inl">echo</code> writes it to the page. Input flows straight to output untouched — remember that shape.'},
{t:'quiz',q:'In the code above, which part is the <b>source</b> of untrusted data?',
opts:['The <code class="inl">echo</code> statement','<code class="inl">$_GET["name"]</code>','The string "Hello "','The variable <code class="inl">$greeting</code>'],a:1,
why:'<b>$_GET</b> reads values straight from the URL query string. Anything a user can control is a source — and the URL is fully attacker-controlled.'}
]},
{id:'r2', name:'Variables, strings & concatenation', xp:20, cards:[
{t:'md',html:'<p>A <b>variable</b> is a labeled box holding a value. Different languages mark them differently — spotting them is step one to reading unfamiliar code:</p><p>• PHP: <code class="inl">$user</code> (dollar sign)<br>• C# / C++: <code class="inl">string user</code> (type then name)<br>• Python / JS: <code class="inl">user = ...</code> (just a name)</p><p><b>Concatenation</b> means joining strings. Watch for it around commands and queries — that is often where attacker input gets welded into something dangerous.</p>'},
{t:'read',lang:'csharp',label:'C# — same idea, different syntax',code:'string user = Request.Query["u"]; // source\nstring msg = "Welcome, " + user; // + joins strings\nResponse.Write(msg); // sink'},
{t:'quiz',q:'Across PHP, C#, and Python, what is the common tell that a value is a <b>variable</b> holding data rather than a fixed keyword?',
opts:['It is always UPPERCASE','It gets assigned a value and is reused by name','It is inside quotes','It ends in a semicolon'],a:1,
why:'A variable is <b>named and assigned</b>, then referred to later by that name. The exact syntax varies, but the "store a value, reuse it by name" pattern is universal.'}
]},
{id:'r3', name:'Functions & what they return', xp:25, cards:[
{t:'md',html:'<p>A <b>function</b> is a named block of reusable code. You <i>call</i> it by name with <code class="inl">( )</code>, optionally passing <b>arguments</b> inside. It may <b>return</b> a value back.</p><p>For a pentester, functions are the vocabulary. You do not memorize a language — you learn which function <b>names</b> are dangerous. <code class="inl">system()</code>, <code class="inl">eval()</code>, <code class="inl">query()</code>: the name tells you what sink you are looking at.</p>'},
{t:'read',lang:'python',label:'Python — defining and calling',code:'def make_query(uid): # define, uid is the argument\n return "SELECT * WHERE id=" + uid\n\nsql = make_query(user_input) # call it; sql holds the result\ndb.execute(sql) # dangerous sink: runs the SQL'},
{t:'quiz',q:'You see <code class="inl">run_command(user_data)</code> in unfamiliar code. Before reading anything else, what should raise your eyebrow?',
opts:['Nothing, function names are arbitrary','A function whose name implies running commands is being fed user data','It uses parentheses','The variable is named user_data'],a:1,
why:'Function <b>names</b> describe intent. A name like <code class="inl">run_command</code>, <code class="inl">exec</code>, or <code class="inl">query</code> receiving user-controlled data is a classic source→sink red flag worth tracing.'}
]},
{id:'r4', name:'Control flow: if, loops & branches', xp:25, cards:[
{t:'md',html:'<p><b>Control flow</b> decides which lines run. <code class="inl">if</code> runs a block only when a condition is true; loops (<code class="inl">for</code>, <code class="inl">while</code>, <code class="inl">foreach</code>) repeat a block.</p><p>Pentest angle: security checks are <b>if statements</b>. Find the <code class="inl">if</code> that guards a sensitive action, then ask: <i>can I make that condition go the way I want?</i> A missing or weak check is an access-control bug.</p>'},
{t:'read',lang:'php',label:'PHP — an auth check',code:'if ($user["role"] == "admin") { // the gate\n deleteAllUsers(); // sensitive action\n} else {\n echo "Access denied";\n}'},
{t:'quiz',q:'The gate is <code class="inl">if ($user["role"] == "admin")</code>. As an attacker, what is your first question?',
opts:['How is $user["role"] set, and can I influence it?','What color is the button?','How fast does the loop run?','Is the code indented correctly?'],a:0,
why:'Access control lives in conditions. If <b>role</b> comes from a cookie, hidden form field, or unsigned token you can tamper with, the gate is bypassable. Always trace where the checked value originates.'}
]}
]
},
{
id:'phpbasics', name:'PHP Basics', tag:'BASICS', icon:'php', accent:'violet',
sub:'From zero: run PHP, use the interactive CLI shell, embed it in HTML, and read the syntax.',
meta:{released:'1995',current:'PHP 8.4 (Nov 2024)',eol:'PHP 8.0 and earlier',note:'Created by Rasmus Lerdorf; maintained by The PHP Group. It powers WordPress, Laravel and much of the web.',link:'https://www.php.net/supported-versions.php'},
lessons:[
{id:'pb1', name:'What PHP is & how it runs', xp:15, cards:[
{t:'md',html:'<p><b>PHP</b> is a server-side scripting language that powers a huge share of the web (WordPress, Laravel, and a great deal of legacy code). It is <b>interpreted</b>, not compiled: you do not build an executable — the <b>PHP interpreter</b> reads your <code class="inl">.php</code> file and runs it directly.</p><p>On a website the flow is: a browser requests a page → the web server hands the <code class="inl">.php</code> file to PHP → PHP runs it and produces <b>HTML</b> → that HTML is sent back. The visitor never sees the PHP source, only its output.</p>'},
{t:'read',lang:'php',label:'hello.php',code:'<?php\necho "Hello from PHP!";\n// no build step — the interpreter runs this file directly',note:'Run it and PHP prints the text. Unlike C or Java there is no separate compile step — you hand the source straight to the interpreter.'},
{t:'quiz',q:'PHP is best described as which kind of language?',
opts:['Compiled to a standalone .exe before running','Interpreted — the PHP engine runs the source directly','A database','A styling language like CSS'],a:1,
why:'PHP is <b>interpreted</b>: the engine reads and executes your source at request time. There is no manual compile step (PHP 8\'s JIT is internal and automatic).'}
]},
{id:'pb2', name:'Running PHP from the command line', xp:20, cards:[
{t:'md',html:'<p>You do not need a web server to run PHP — the <code class="inl">php</code> command does it all:</p><p>• <code class="inl">php file.php</code> — run a script<br>• <code class="inl">php -a</code> — an <b>interactive shell</b> (REPL): type PHP, press enter, see the result<br>• <code class="inl">php -r \'code\'</code> — run a one-liner with no file<br>• <code class="inl">php -S localhost:8000</code> — a built-in <b>web server</b> for quick testing<br>• <code class="inl">php -v</code> / <code class="inl">php -m</code> — version and loaded modules</p>'},
{t:'read',lang:'php',label:'terminal',code:'$ php -v\nPHP 8.3.2 (cli)\n\n$ php -a # interactive shell (REPL)\nphp > echo 2 + 2;\n4\nphp > $name = "world";\nphp > echo "hi $name";\nhi world\n\n$ php -S localhost:8000 # built-in dev web server',note:'<code class="inl">php -a</code> is the interactive shell — ideal for testing a snippet. <code class="inl">php -S localhost:8000</code> spins up a throwaway web server so you can open the page in a browser.'},
{t:'quiz',q:'Which command drops you into an interactive PHP shell (REPL) in the terminal?',
opts:['<code class="inl">php -S</code>','<code class="inl">php -a</code>','<code class="inl">php -v</code>','<code class="inl">php --compile</code>'],a:1,
why:'<code class="inl">php -a</code> opens the interactive shell. <code class="inl">php -S</code> starts a dev web server, <code class="inl">php -v</code> prints the version, and there is no <code class="inl">--compile</code> — PHP is interpreted.'}
]},
{id:'pb3', name:'Mixing PHP into HTML', xp:20, cards:[
{t:'md',html:'<p>PHP\'s original superpower is embedding directly inside HTML. Anything between <code class="inl"><?php</code> and <code class="inl">?></code> runs as PHP; everything outside those tags is sent as plain HTML.</p><p><code class="inl"><?= $x ?></code> is shorthand for <code class="inl"><?php echo $x; ?></code> — a quick way to print a value into the page.</p>'},
{t:'read',lang:'php',label:'page.php',code:'<!DOCTYPE html>\n<html>\n<body>\n <h1>Welcome</h1>\n <?php $user = "Sam"; ?>\n <p>Hello, <?= $user ?>!</p>\n <?php if ($user === "Sam") { ?>\n <p>You are an admin.</p>\n <?php } ?>\n</body>\n</html>',note:'The HTML is sent as-is; the <code class="inl"><?php ... ?></code> islands run on the server and their output is spliced in. This templating is also why <b>unescaped output</b> here becomes XSS — a direct link to the security track.'},
{t:'quiz',q:'What does <code class="inl"><?= $user ?></code> do in an HTML page?',
opts:['Declares a variable','Short for <code class="inl"><?php echo $user; ?></code> — prints the value','Imports a file','Starts a loop'],a:1,
why:'<code class="inl"><?= ... ?></code> is the <b>short echo tag</b> — it prints the expression into the HTML, identical to <code class="inl"><?php echo ...; ?></code>.'}
]},
{id:'pb4', name:'Variables, types & echo', xp:20, cards:[
{t:'md',html:'<p>PHP variables always start with <code class="inl">$</code> and need no type declaration. The core types: <b>string</b>, <b>int</b>, <b>float</b>, <b>bool</b>, <b>array</b>, and <b>null</b>.</p><p>Print with <code class="inl">echo</code>. Join strings with the <b>dot</b> operator <code class="inl">.</code>. Inside <b>double quotes</b> variables are expanded; inside <b>single quotes</b> they stay literal.</p>'},
{t:'read',lang:'php',label:'types.php',code:'<?php\n$name = "Ada"; // string\n$age = 36; // int\n$pi = 3.14; // float\n$ok = true; // bool\n\necho "Name: " . $name; // -> Name: Ada\necho "Hi $name, age $age"; // -> Hi Ada, age 36\necho \'Hi $name\'; // -> Hi $name (literal)',note:'Double quotes interpolate variables; single quotes do not. The <code class="inl">.</code> operator concatenates — and, as the security track shows, concatenating user input into SQL or HTML is where bugs begin.'},
{t:'quiz',q:'What prints from <code class="inl">echo \'Hello $name\';</code> when <code class="inl">$name = "Sam"</code>?',
opts:['Hello Sam','Hello $name','An error','Hello'],a:1,
why:'<b>Single quotes</b> do not interpolate — the text is literal, so it prints <code class="inl">Hello $name</code>. Double quotes would expand it to <code class="inl">Hello Sam</code>.'}
]},
{id:'pb5', name:'Arrays, loops & functions', xp:25, cards:[
{t:'md',html:'<p>Two array styles: <b>indexed</b> (<code class="inl">[10, 20, 30]</code>) and <b>associative</b> (key ⇒ value). Loop with <code class="inl">foreach</code>, branch with <code class="inl">if/else</code>, and package reusable logic in a <code class="inl">function</code>.</p>'},
{t:'read',lang:'php',label:'basics.php',code:'<?php\n$user = ["name" => "Ada", "role" => "admin"]; // associative array\n\nforeach ($user as $key => $value) {\n echo "$key = $value\\n";\n}\n\nif ($user["role"] === "admin") {\n echo "Access granted";\n}\n\nfunction greet($who) {\n return "Hello " . $who;\n}\necho greet($user["name"]);',note:'This is the exact shape you decode when auditing: an <code class="inl">if</code> gate on <code class="inl">$user["role"]</code>, a <code class="inl">function</code> that returns a value, arrays holding request data. Reading it is the whole skill.'},
{t:'quiz',q:'Which construct loops over every key/value pair in a PHP array?',
opts:['<code class="inl">switch</code>','<code class="inl">foreach ($arr as $k => $v)</code>','<code class="inl">echo</code>','<code class="inl">function</code>'],a:1,
why:'<code class="inl">foreach</code> iterates arrays; the <code class="inl">$k => $v</code> form gives you both the key and the value on each pass.'}
]},
{id:'pb6', name:'Composer — packages & autoloading', xp:20, cards:[
{t:'md',html:'<p><b>Composer</b> is PHP\'s package manager (like npm for Node or pip for Python). It reads a <code class="inl">composer.json</code> file, downloads libraries from <b>Packagist</b> into a <code class="inl">vendor/</code> folder, and writes a <code class="inl">composer.lock</code> pinning the exact versions.</p><p>One line — <code class="inl">require "vendor/autoload.php";</code> — then loads all of them automatically.</p>'},
{t:'read',lang:'js',label:'composer.json',code:'{\n "require": {\n "monolog/monolog": "^3.0",\n "guzzlehttp/guzzle": "^7.8"\n }\n}',note:'Run <code class="inl">composer install</code> to fetch these into <code class="inl">vendor/</code>, or <code class="inl">composer require vendor/pkg</code> to add one. For a pentester, <code class="inl">composer.lock</code> is gold: it lists every dependency and exact version to check against known CVEs.'},
{t:'quiz',q:'What is Composer to a PHP project?',
opts:['A web server','The package manager — it installs the libraries listed in composer.json','A database engine','A debugger'],a:1,
why:'<b>Composer</b> manages dependencies: it reads <code class="inl">composer.json</code>, pulls packages from Packagist into <code class="inl">vendor/</code>, and pins them in <code class="inl">composer.lock</code> — the file you audit for vulnerable versions.'}
]},
{id:'pb7', name:'From syntax to security', xp:20, cards:[
{t:'md',html:'<p>You can now read PHP. Here is how the basics map straight onto the security skills:</p><p>• <code class="inl">echo</code> into HTML → <b>XSS</b> if the value is untrusted<br>• string concatenation into a query → <b>SQL injection</b><br>• <code class="inl">$_GET</code> / <code class="inl">$_POST</code> superglobals → the <b>sources</b> of attacker input<br>• <code class="inl">include</code> with a variable path → <b>file inclusion</b><br>• <code class="inl">composer.lock</code> → the <b>dependency</b> audit surface</p><p>Next stop: the <b>PHP</b> security track, to hunt these for real.</p>'},
{t:'quiz',q:'You can now read PHP well enough to audit it. Which basic feature becomes XSS when misused?',
opts:['A foreach loop','Echoing untrusted input into HTML without encoding','Defining a function','Using Composer'],a:1,
why:'Any <code class="inl">echo</code> of untrusted data into a page is the XSS sink — the exact pattern from the PHP security track. The basics and the bugs are the same code, read two ways.'}
]},
{id:'pb8', name:'Deprecated & removed functions', xp:25, cards:[
{t:'md',html:'<p>Languages evolve — old functions get <b>deprecated</b> (marked for removal) and eventually <b>removed</b>. Spotting them quickly dates a codebase and flags risk: legacy functions often predate modern security features.</p><p>The biggest PHP example is the old <code class="inl">mysql_*</code> extension (note: no <code class="inl">i</code>). It was <b>deprecated in PHP 5.5 (2013)</b> and <b>removed in PHP 7.0 (2015)</b> because it encouraged string-built queries and never properly supported prepared statements — a SQL-injection factory. Its replacements are <code class="inl">mysqli</code> and <code class="inl">PDO</code>.</p>'},
{t:'read',lang:'php',label:'Legacy code = instant red flag',code:'// removed in PHP 7.0 — if you see this, the code is pre-2015\n$r = mysql_query("SELECT * FROM users WHERE id = " . $_GET["id"]);\n\n// other removed relics:\n// ereg() , split() -> removed 7.0 (use preg_* instead)\n// create_function() -> removed 8.0 (code-injection risk)\n// each() -> removed 8.0\n// magic_quotes_gpc -> removed 5.4',note:'<code class="inl">mysql_*</code>, <code class="inl">ereg</code>, <code class="inl">create_function</code> and <code class="inl">magic_quotes</code> are all gone. Their presence means unmaintained code running on an old PHP — worth a closer look and a version check.'},
{t:'quiz',q:'You see <code class="inl">mysql_query()</code> (no "i") in a codebase. What does it tell you?',
opts:['It is the modern, recommended API','It is a removed function (gone since PHP 7.0, 2015) — legacy, likely insecure code','It is faster than mysqli','Nothing useful'],a:1,
why:'The <code class="inl">mysql_*</code> extension was removed in <b>PHP 7.0 (2015)</b> for encouraging injection-prone queries. Seeing it means old, unmaintained code — check the PHP version and audit those queries. Use <code class="inl">mysqli</code>/<code class="inl">PDO</code> with prepared statements.'}
]}
]
},
{
id:'pybasics', name:'Python Basics', tag:'BASICS', icon:'py', accent:'violet',
sub:'From zero: run Python, use the REPL, pip & virtual environments, and read the syntax.',
meta:{released:'1991',current:'Python 3.13 (Oct 2024)',eol:'Python 2 (ended Jan 1, 2020)',note:'Created by Guido van Rossum. Everywhere in web, data science, automation and security tooling.',link:'https://devguide.python.org/versions/'},
lessons:[
{id:'pyb1', name:'What Python is & how it runs', xp:15, cards:[
{t:'md',html:'<p><b>Python</b> is a high-level, general-purpose language famous for clean, readable syntax where <b>indentation</b> defines structure. It is <b>interpreted</b> and <b>dynamically typed</b>: you run source directly with the <code class="inl">python</code> command, with no compile step and no type declarations.</p><p>It runs everywhere — web backends (Django, Flask), data science, automation, and most security tooling.</p>'},
{t:'read',lang:'python',label:'hello.py',code:'# hello.py — run it with: python hello.py\nprint("Hello from Python!")\n# interpreted: the interpreter runs this directly, no build step',note:'Like PHP, Python is interpreted — hand the source to the <code class="inl">python</code> command and it runs. No separate compile step.'},
{t:'quiz',q:'How is Python code executed?',
opts:['Compiled to a native .exe first','Interpreted — the python interpreter runs the source directly','It runs only inside a browser','It must be converted to C'],a:1,
why:'Python is <b>interpreted</b>: <code class="inl">python file.py</code> runs the source directly. (It compiles to bytecode internally and automatically, but there is no manual build step.)'}
]},
{id:'pyb2', name:'Running Python from the command line', xp:20, cards:[
{t:'md',html:'<p>The <code class="inl">python</code> command (sometimes <code class="inl">python3</code>) is the hub:</p><p>• <code class="inl">python file.py</code> — run a script<br>• <code class="inl">python</code> — the <b>interactive shell</b> (REPL), prompt <code class="inl">>>></code><br>• <code class="inl">python -c "code"</code> — a one-liner<br>• <code class="inl">python -m module</code> — run a module as a tool (e.g. <code class="inl">http.server</code>, <code class="inl">venv</code>, <code class="inl">pip</code>)<br>• <code class="inl">python --version</code></p>'},
{t:'read',lang:'python',label:'terminal',code:'$ python --version\nPython 3.13.1\n\n$ python # interactive shell (REPL)\n>>> 2 + 2\n4\n>>> name = "world"\n>>> print(f"hi {name}")\nhi world\n\n$ python -m http.server 8000 # serve the current folder\n$ python -m venv .venv # create a virtual environment',note:'Bare <code class="inl">python</code> opens the REPL. <code class="inl">python -m</code> runs a module as a command — <code class="inl">http.server</code> for a quick server, <code class="inl">venv</code> for environments.'},
{t:'quiz',q:'How do you open Python\'s interactive shell (REPL)?',
opts:['python -c','Run <code class="inl">python</code> with no script argument','python --compile','python -S'],a:1,
why:'Running <code class="inl">python</code> with no file drops you into the REPL (prompt <code class="inl">>>></code>). <code class="inl">python -m</code> runs a module such as <code class="inl">http.server</code> or <code class="inl">venv</code>.'}
]},
{id:'pyb3', name:'Variables, types & f-strings', xp:20, cards:[
{t:'md',html:'<p>Variables are just names — no <code class="inl">$</code>, no type keywords. Core types: <b>int</b>, <b>float</b>, <b>str</b>, <b>bool</b>, <b>list</b>, <b>dict</b>, <b>None</b>. Print with <code class="inl">print()</code>. Modern string building uses <b>f-strings</b>: <code class="inl">f"...{var}..."</code>.</p>'},
{t:'read',lang:'python',label:'types.py',code:'name = "Ada" # str\nage = 36 # int\npi = 3.14 # float\nok = True # bool\nnums = [1, 2, 3] # list\n\nprint("Name: " + name) # concatenation\nprint(f"Hi {name}, age {age}") # f-string (preferred)',note:'No sigils and no declared types — a variable is a name bound to a value. <b>f-strings</b> interpolate variables directly and are the readable default.'},
{t:'quiz',q:'What does <code class="inl">f"Hi {name}"</code> produce when <code class="inl">name = "Sam"</code>?',
opts:['The literal text Hi {name}','Hi Sam — the f-string inserts the variable value','An error','Hi'],a:1,
why:'An <b>f-string</b> (the <code class="inl">f</code> prefix) evaluates <code class="inl">{name}</code> and inserts its value, giving <code class="inl">Hi Sam</code>.'}
]},
{id:'pyb4', name:'Data structures & control flow', xp:25, cards:[
{t:'md',html:'<p>Everyday containers: <b>list</b> <code class="inl">[...]</code>, <b>dict</b> <code class="inl">{key: value}</code>, plus tuples and sets. Branch with <code class="inl">if/elif/else</code>, loop with <code class="inl">for</code>/<code class="inl">while</code>, and define reusable logic with <code class="inl">def</code>. <b>Indentation</b> — not braces — marks each block.</p>'},
{t:'read',lang:'python',label:'basics.py',code:'user = {"name": "Ada", "role": "admin"} # dict\n\nfor key, value in user.items():\n print(f"{key} = {value}")\n\nif user["role"] == "admin":\n print("Access granted")\n\ndef greet(who):\n return "Hello " + who\n\nprint(greet(user["name"]))',note:'This is the shape you decode when auditing Python: a dict holding request data, an <code class="inl">if</code> gate on a role, a <code class="inl">def</code> that returns a value.'},
{t:'quiz',q:'What marks a block of code in Python (an if body, a loop body, a function body)?',
opts:['Curly braces {}','Indentation (leading whitespace)','Semicolons','An end keyword'],a:1,
why:'Python uses <b>indentation</b> to define blocks — consistent leading whitespace, not braces. Mismatched indentation is itself a syntax error.'}
]},
{id:'pyb5', name:'pip & virtual environments', xp:20, cards:[
{t:'md',html:'<p><b>pip</b> is Python\'s package installer; it fetches libraries from <b>PyPI</b>. Projects list their dependencies in <code class="inl">requirements.txt</code>. A <b>virtual environment</b> (<code class="inl">venv</code>) gives each project its own isolated set of packages so they do not collide.</p>'},
{t:'read',lang:'python',label:'terminal',code:'$ pip install requests # install a package from PyPI\n$ pip freeze > requirements.txt # pin the installed versions\n$ python -m venv .venv # create an isolated environment\n$ source .venv/bin/activate # activate it (macOS/Linux)',note:'<code class="inl">requirements.txt</code> is the dependency surface you audit for vulnerable versions — the Python equivalent of composer.lock or package-lock.json.'},
{t:'quiz',q:'What is pip to a Python project?',
opts:['A web server','The package installer — it fetches libraries from PyPI','A code editor','The interpreter itself'],a:1,
why:'<b>pip</b> installs packages from <b>PyPI</b>; projects record them in <code class="inl">requirements.txt</code>, which — like composer.lock — is the dependency list you audit for known CVEs.'}
]},
{id:'pyb6', name:'Deprecated & removed (Python 2 vs 3)', xp:25, cards:[
{t:'md',html:'<p><b>Python 2 reached end-of-life on January 1, 2020</b> — it receives no security fixes. Spotting Python 2 syntax instantly dates code and flags risk. Tells: <code class="inl">print</code> used as a statement (no parentheses), <code class="inl">xrange</code>, <code class="inl">raw_input</code>, and old <code class="inl">except Exception, e:</code> syntax.</p><p>Even within Python 3, modules get removed: <code class="inl">distutils</code> (removed 3.12) and <code class="inl">cgi</code>/<code class="inl">cgitb</code> (removed 3.13) are recent examples.</p>'},
{t:'read',lang:'python',label:'Python 2 tells (all invalid in Python 3)',code:'print "hello" # py2 statement; py3 needs print("hello")\nexcept Exception, e: # py2 syntax; py3 uses except Exception as e\nxrange(10) # py2 only; py3 uses range()\nraw_input("name? ") # py2 only; py3 uses input()',note:'Any of these means <b>Python 2</b> — unmaintained since 2020. Flag it: the runtime itself is unpatched, on top of whatever the app code does.'},
{t:'quiz',q:'You see <code class="inl">print "hello"</code> (no parentheses). What does it tell you?',
opts:['Modern, idiomatic Python 3','It is Python 2 — end-of-life since Jan 1, 2020 and no longer patched','A performance optimization','A type annotation'],a:1,
why:'<code class="inl">print</code> as a statement is <b>Python 2</b>, which reached end-of-life on Jan 1, 2020. Unpatched Python 2 in production is a finding in itself.'}
]}
]
},
{
id:'jsbasics', name:'JavaScript Basics', tag:'BASICS', icon:'js', accent:'violet',
sub:'The language itself (ECMAScript): how it runs, its syntax, and its legacy features.',
meta:{released:'1995',current:'ECMAScript 2026 (ES2026)',eol:'None — the spec is additive',note:'Created by Brendan Eich at Netscape. The language is standardized as ECMAScript; it runs inside a host (a browser, or a runtime like Node.js) on an engine such as V8.',link:'https://tc39.es/ecma262/'},
lessons:[
{id:'jsb1', name:'What JavaScript is (the language)', xp:15, cards:[
{t:'md',html:'<p><b>JavaScript</b> is a high-level, dynamically typed language standardized as <b>ECMAScript</b> — a spec updated every year (the current edition is <b>ES2026</b>). It is <b>interpreted</b> (JIT-compiled internally), with no separate build step.</p><p>JavaScript never runs on its own: it runs inside a <b>host</b> that provides a JS <b>engine</b>. The two big hosts are the <b>browser</b> (engines like V8 and SpiderMonkey) and <b>Node.js</b> (a server runtime — its own track). The <i>language</i> is identical in both; what changes is what the host lets it touch.</p>'},
{t:'read',lang:'js',label:'Pure ECMAScript — runs in any host',code:'const greet = (name) => `Hello ${name}`;\nconsole.log(greet("world")); // Hello world\n// nothing here is browser- or Node-specific: it is just the language',note:'This snippet is plain ECMAScript — it behaves the same in a browser console or under Node. The browser adds the DOM; Node adds files and networks; the core language stays the same.'},
{t:'quiz',q:'How is the JavaScript language formally standardized?',
opts:['As Node.js','As ECMAScript, updated yearly (current: ES2026)','As HTML5','It has no standard'],a:1,
why:'The language is <b>ECMAScript</b>, maintained by TC39 with a new edition each year. "JavaScript" is the common name; <b>Node.js</b> is a runtime that executes it, not the language itself.'}
]},
{id:'jsb2', name:'Running JavaScript', xp:20, cards:[
{t:'md',html:'<p>You run JavaScript wherever there is an engine:</p><p>• <b>In a browser</b> — load it with a <code class="inl"><script></code> tag, or open <b>DevTools → Console</b>, which is a live REPL.<br>• <b>On your machine</b> — install <b>Node.js</b> and run <code class="inl">node file.js</code> or the <code class="inl">node</code> REPL.</p><p>The browser console is the quickest way to try a snippet; the Node runtime is covered in its own track.</p>'},
{t:'read',lang:'js',label:'browser DevTools console',code:'> 2 + 2\n4\n> const name = "Ada"\n> `hi ${name}`\n\'hi Ada\'\n// the Console tab evaluates each line, like a REPL',note:'Open DevTools (F12 / right-click → Inspect) and pick the <b>Console</b> tab. It runs the page\'s JavaScript and anything you type — perfect for experimenting.'},
{t:'quiz',q:'What is the quickest way to run a JavaScript snippet with no setup?',
opts:['Compile it with gcc','The browser DevTools Console (a built-in REPL)','Upload it to a server','You cannot run JS without Node'],a:1,
why:'Every browser ships a JavaScript engine and a <b>Console</b> (DevTools) that acts as a REPL — no install needed. For running JS outside the browser, you use the Node.js runtime.'}
]},
{id:'jsb3', name:'Variables, types & template literals', xp:20, cards:[
{t:'md',html:'<p>Declare variables with <code class="inl">const</code> (cannot be reassigned) or <code class="inl">let</code> (can). The old <code class="inl">var</code> is legacy. Types are dynamic: <b>string</b>, <b>number</b>, <b>boolean</b>, <b>object</b>, <b>array</b>, <code class="inl">null</code>, <code class="inl">undefined</code>.</p><p>Build strings with <b>template literals</b> — backticks with <code class="inl">${...}</code> — instead of clunky <code class="inl">+</code> concatenation.</p>'},
{t:'read',lang:'js',label:'types.js',code:'const name = "Ada"; // string\nlet age = 36; // number\nconst ok = true; // boolean\nconst nums = [1, 2, 3]; // array\n\nconsole.log("Hi " + name); // concatenation\nconsole.log(`Hi ${name}, age ${age}`); // template literal (preferred)',note:'Prefer <code class="inl">const</code> by default, <code class="inl">let</code> when you must reassign, and avoid <code class="inl">var</code>. Template literals read better than <code class="inl">+</code> and are the modern default.'},
{t:'quiz',q:'What does <code class="inl">`Hi ${name}`</code> do when <code class="inl">name = "Sam"</code>?',
opts:['Prints the literal Hi ${name}','Produces "Hi Sam" — a template literal inserts the value','Throws an error','Defines a function'],a:1,
why:'A <b>template literal</b> (backticks) evaluates <code class="inl">${name}</code> and inserts its value, giving <code class="inl">Hi Sam</code>.'}
]},
{id:'jsb4', name:'Objects, arrays & functions', xp:25, cards:[
{t:'md',html:'<p><b>Objects</b> hold key/value data <code class="inl">{ }</code>; <b>arrays</b> hold ordered lists <code class="inl">[ ]</code>. Branch with <code class="inl">if/else</code>, loop with <code class="inl">for...of</code>, and define logic with functions — classic <code class="inl">function</code> or short <b>arrow functions</b> <code class="inl">=></code>.</p>'},
{t:'read',lang:'js',label:'basics.js',code:'const user = { name: "Ada", role: "admin" }; // object\n\nfor (const [key, value] of Object.entries(user)) {\n console.log(`${key} = ${value}`);\n}\n\nif (user.role === "admin") {\n console.log("Access granted");\n}\n\nconst greet = (who) => "Hello " + who; // arrow function\nconsole.log(greet(user.name));',note:'This is the shape you audit in JS code: an object holding data, an <code class="inl">if</code> gate on a role, an arrow function returning a value. Use <code class="inl">===</code> (strict), not <code class="inl">==</code>.'},
{t:'quiz',q:'What is an arrow function <code class="inl">(x) => x + 1</code>?',
opts:['A comparison operator','A shorter syntax for defining a function','An array','A loop'],a:1,
why:'Arrow functions (<code class="inl">=></code>) are a compact way to write functions. Here it takes <code class="inl">x</code> and returns <code class="inl">x + 1</code>.'}
]},
{id:'jsb5', name:'Deprecated & legacy JavaScript', xp:25, cards:[
{t:'md',html:'<p>Legacy language patterns are tells of aging code:</p><p>• <code class="inl">var</code> instead of <code class="inl">let</code>/<code class="inl">const</code> — function-scoped with hoisting surprises.<br>• Loose <code class="inl">==</code> instead of strict <code class="inl">===</code> — does type coercion (<code class="inl">0 == ""</code> is true).<br>• The <code class="inl">with</code> statement — forbidden in strict mode.<br>• Old patterns like <code class="inl">arguments</code> instead of rest params <code class="inl">(...args)</code>.</p>'},
{t:'read',lang:'js',label:'Legacy tells',code:'var x = 1; // legacy: prefer let / const\nif (a == b) { } // loose equality: prefer ===\nwith (obj) { y = 1; } // forbidden in strict mode\n\n"use strict"; // opts into safer semantics; ES modules are strict by default',note:'Lots of <code class="inl">var</code> and <code class="inl">==</code>, or a <code class="inl">with</code> block, mark old, unmodernised JavaScript. Modern code uses <code class="inl">let</code>/<code class="inl">const</code>, <code class="inl">===</code>, and strict mode.'},
{t:'quiz',q:'Why prefer <code class="inl">===</code> over <code class="inl">==</code> in JavaScript?',
opts:['=== is faster to type','=== compares without type coercion, avoiding surprises like <code class="inl">0 == ""</code> being true','== is removed from the language','They are identical'],a:1,
why:'Loose <code class="inl">==</code> coerces types before comparing, producing surprises (<code class="inl">0 == ""</code>, <code class="inl">"" == false</code>). Strict <code class="inl">===</code> checks type and value, which is predictable.'}
]}
]
},
{
id:'nodebasics', name:'Node.js Basics', tag:'RUNTIME', icon:'node', accent:'violet',
sub:'The runtime that runs JavaScript on the server: the CLI, modules, npm, and its pitfalls.',
meta:{released:'2009',current:'Node.js 24 LTS',eol:'Node.js 18 and earlier',note:'Created by Ryan Dahl. A runtime, not a language — it runs JavaScript outside the browser on Google\'s V8 engine, using a non-blocking, event-driven model.',link:'https://nodejs.org/en/about/previous-releases'},
lessons:[
{id:'nb1', name:'What Node.js is', xp:20, cards:[
{t:'md',html:'<p><b>Node.js is a runtime, not a language.</b> It runs JavaScript outside the browser on Google\'s <b>V8</b> engine, with a non-blocking <b>event loop</b> that handles many connections without a thread per request.</p><p>Crucially, Node gives JavaScript the powers the browser sandbox withholds: the filesystem (<code class="inl">fs</code>), the network (<code class="inl">http</code>, <code class="inl">net</code>), and processes (<code class="inl">child_process</code>). That is exactly why server-side JS bugs — command injection, path traversal, SSRF — live in Node code.</p>'},
{t:'read',lang:'js',label:'server.js — a Node HTTP server',code:'const http = require("http"); // a Node built-in, not in browsers\nhttp.createServer((req, res) => {\n res.end("Hello from Node");\n}).listen(3000);\n// run it: node server.js',note:'The <code class="inl">http</code>, <code class="inl">fs</code> and <code class="inl">child_process</code> modules exist only in Node — the browser has no such access. Node is where JavaScript touches the machine.'},
{t:'quiz',q:'Node.js is best described as…',
opts:['A new programming language','A runtime that executes JavaScript outside the browser, with file and network access','A web browser','A CSS framework'],a:1,
why:'<b>Node.js</b> is a <b>runtime</b> built on V8. It runs the JavaScript language on servers and gives it OS access (files, network, processes) that browsers deliberately withhold.'}
]},
{id:'nb2', name:'Running Node & the CLI', xp:20, cards:[
{t:'md',html:'<p>The <code class="inl">node</code> command runs JavaScript on your machine:</p><p>• <code class="inl">node file.js</code> — run a script<br>• <code class="inl">node</code> — the interactive <b>REPL</b><br>• <code class="inl">node -e "code"</code> — a one-liner<br>• <code class="inl">npx pkg</code> — run a package without installing it globally<br>• <code class="inl">node --version</code></p>'},
{t:'read',lang:'js',label:'terminal',code:'$ node --version\nv24.4.0\n\n$ node // interactive REPL\n> 2 + 2\n4\n> require("os").platform()\n\'darwin\'\n\n$ node app.js // run a script\n$ npx cowsay hello // run a package without installing it',note:'Bare <code class="inl">node</code> opens the REPL, where you can even <code class="inl">require()</code> built-in modules. <code class="inl">npx</code> runs a package on demand without a global install.',},
{t:'quiz',q:'How do you open the Node.js interactive shell (REPL)?',
opts:['node --repl','Run <code class="inl">node</code> with no script argument','node -c','npm start'],a:1,
why:'Running <code class="inl">node</code> with no file starts the REPL. <code class="inl">node file.js</code> runs a script; <code class="inl">npx</code> runs a package without a global install.'}
]},
{id:'nb3', name:'Modules: CommonJS vs ES modules', xp:25, cards:[
{t:'md',html:'<p>Node has <b>two</b> module systems, and you will read both:</p><p>• <b>CommonJS</b> (the classic) — <code class="inl">const x = require("./x")</code> and <code class="inl">module.exports = ...</code>.<br>• <b>ES modules</b> (modern) — <code class="inl">import x from "./x.js"</code> and <code class="inl">export</code>, enabled by <code class="inl">"type": "module"</code> in package.json or an <code class="inl">.mjs</code> extension.</p><p>Knowing which one a file uses tells you how it loads code and what an attacker-controlled path could pull in.</p>'},
{t:'read',lang:'js',label:'Two ways to import',code:'// CommonJS\nconst fs = require("fs");\nmodule.exports = { helper };\n\n// ES modules ("type":"module" or .mjs)\nimport fs from "fs";\nexport { helper };',note:'Same runtime, two syntaxes. <code class="inl">require()</code> with a user-controlled path is a classic footgun — it can load unexpected local files or modules.'},
{t:'quiz',q:'What are the two module systems you see in Node.js code?',
opts:['HTML and CSS','CommonJS (<code class="inl">require</code>) and ES modules (<code class="inl">import</code>)','SQL and NoSQL','GET and POST'],a:1,
why:'Node supports <b>CommonJS</b> (<code class="inl">require</code>/<code class="inl">module.exports</code>) and <b>ES modules</b> (<code class="inl">import</code>/<code class="inl">export</code>). ESM is selected via <code class="inl">"type":"module"</code> or the <code class="inl">.mjs</code> extension.'}
]},
{id:'nb4', name:'npm & package.json', xp:20, cards:[
{t:'md',html:'<p><b>npm</b> is Node\'s package manager. A project\'s <code class="inl">package.json</code> lists its dependencies; <code class="inl">npm install</code> downloads them into <code class="inl">node_modules/</code> and pins exact versions in <code class="inl">package-lock.json</code>.</p><p>Security tie: <code class="inl">package-lock.json</code> is the dependency surface to audit, and <code class="inl">npm audit</code> flags known-vulnerable versions automatically.</p>'},
{t:'read',lang:'js',label:'package.json',code:'{\n "type": "module",\n "dependencies": {\n "express": "^4.19.2",\n "lodash": "^4.17.21"\n }\n}',note:'Run <code class="inl">npm install</code> to fetch these, <code class="inl">npm install pkg</code> to add one, and <code class="inl">npm audit</code> to check every dependency against the advisory database.'},
{t:'quiz',q:'What is npm to a Node project?',
opts:['A web browser','The package manager — it installs the libraries listed in package.json','The JS engine','A linter'],a:1,
why:'<b>npm</b> installs packages from the npm registry into <code class="inl">node_modules/</code>, recording exact versions in <code class="inl">package-lock.json</code> — the file you audit with <code class="inl">npm audit</code>.'}
]},
{id:'nb5', name:'Node deprecations & pitfalls', xp:25, cards:[
{t:'md',html:'<p>Node-specific legacy to recognise:</p><p>• <code class="inl">new Buffer()</code> is <b>deprecated</b> — it could return <b>uninitialized memory</b> (an information leak). Use <code class="inl">Buffer.alloc()</code> (zero-filled) or <code class="inl">Buffer.from()</code>.<br>• The old <code class="inl">domain</code> module is deprecated.<br>• Legacy <code class="inl">url.parse()</code> has quirks the WHATWG <code class="inl">new URL()</code> avoids.</p>'},
{t:'read',lang:'js',label:'Buffer: unsafe vs safe',code:'new Buffer(10); // DEPRECATED: may expose leftover memory\nBuffer.alloc(10); // safe: zero-filled\nBuffer.from(userInput); // safe: explicit conversion',note:'<code class="inl">new Buffer(size)</code> handed back memory that was not zeroed, so it could leak whatever data was there before. Seeing it marks old Node code that needs updating.'},
{t:'quiz',q:'Why was Node\'s <code class="inl">new Buffer()</code> constructor deprecated?',
opts:['It was slow','It could return uninitialized memory, leaking data — replaced by Buffer.alloc()/from()','It only worked in browsers','It used too much disk'],a:1,
why:'<code class="inl">new Buffer(size)</code> returned memory that was not zeroed, so it could expose leftover data. The safe replacements are <code class="inl">Buffer.alloc()</code> (zero-filled) and <code class="inl">Buffer.from()</code>.'}
]}
]
},
{
id:'cbasics', name:'C Basics', tag:'BASICS', icon:'C', accent:'violet',
sub:'The compiled systems language: how it compiles, pointers, memory, and dangerous functions.',
meta:{released:'1972',current:'C23 (ISO/IEC 9899:2024)',eol:'K&R C / C89 (legacy)',note:'Created by Dennis Ritchie at Bell Labs. A compiled systems language behind operating systems, embedded devices and much of the software stack.',link:'https://en.cppreference.com/w/c'},
lessons:[
{id:'cb1', name:'What C is & how it compiles', xp:15, cards:[
{t:'md',html:'<p><b>C</b> is a <b>compiled</b>, statically typed systems language — the opposite of PHP/Python/JS. It is not interpreted: a <b>compiler</b> translates your <code class="inl">.c</code> source into a machine-code <b>executable</b> that the CPU runs directly.</p><p>That is why C is fast and close to the hardware — and why its bugs (memory corruption) execute against real memory. The build has stages: <b>preprocess → compile → link</b> → executable.</p>'},
{t:'read',lang:'cpp',label:'hello.c',code:'#include <stdio.h>\n\nint main(void) {\n printf("Hello, C!\\n");\n return 0;\n}',note:'Every C program has a <code class="inl">main()</code>. But this file cannot run yet — C is compiled, so it must first be turned into an executable.'},
{t:'read',lang:'bash',label:'compile & run',code:'$ gcc hello.c -o hello # preprocess -> compile -> link\n$ ./hello # run the executable\nHello, C!',note:'<code class="inl">gcc</code> (or <code class="inl">clang</code>) turns <code class="inl">hello.c</code> into a native binary. There is no interpreter at run time — the CPU runs the compiled machine code directly.'},
{t:'quiz',q:'How is a C program run, compared with PHP or Python?',
opts:['It is interpreted line by line','It is compiled by gcc/clang into a machine-code executable that runs directly','It runs in a browser','It needs a virtual machine'],a:1,
why:'C is <b>compiled</b>: a compiler produces a native executable. That is why it is fast and low-level — and why memory bugs execute against real memory.'}
]},
{id:'cb2', name:'The compile toolchain', xp:20, cards:[
{t:'md',html:'<p>Compiling is really several steps: the <b>preprocessor</b> expands <code class="inl">#include</code> and <code class="inl">#define</code>, the <b>compiler</b> produces object files, and the <b>linker</b> combines them (plus libraries) into one executable.</p><p>Useful flags: <code class="inl">-Wall</code> (warnings), <code class="inl">-g</code> (debug info), <code class="inl">-O2</code> (optimize). Larger projects automate builds with <b>make</b> and a <code class="inl">Makefile</code>.</p>'},
{t:'read',lang:'bash',label:'toolchain',code:'$ gcc -Wall -g main.c utils.c -o app # warnings + debug info\n$ clang -O2 main.c -o app # optimized build\n\n$ make # build via a Makefile\n$ make clean # remove build artifacts',note:'<code class="inl">-Wall</code> surfaces bugs early, <code class="inl">-g</code> adds debug symbols, <code class="inl">-O2</code> optimizes. <code class="inl">#include</code> pulls in header declarations; the linker resolves them against the real library code.'},
{t:'quiz',q:'What does the linker do in the C toolchain?',
opts:['Runs the program','Combines compiled object files and libraries into a single executable','Formats the source code','Downloads packages'],a:1,
why:'The <b>linker</b> stitches object files together with the libraries they call, producing the final executable. The preprocessor and compiler run before it.'}
]},
{id:'cb3', name:'Variables, types & pointers', xp:20, cards:[
{t:'md',html:'<p>C requires you to declare a <b>type</b>: <code class="inl">int</code>, <code class="inl">char</code>, <code class="inl">float</code>, <code class="inl">double</code>, plus arrays. Its signature feature is the <b>pointer</b> — a variable that holds a <b>memory address</b>.</p><p><code class="inl">&x</code> means "address of x"; <code class="inl">*p</code> means "the value stored at p". Pointers are powerful and the root of most C vulnerabilities.</p>'},
{t:'read',lang:'cpp',label:'pointers.c',code:'int x = 42; // an integer\nint *p = &x; // p holds the ADDRESS of x\nprintf("%d\\n", *p); // *p = the value AT that address = 42\n\nchar name[16]; // a fixed 16-byte buffer',note:'A pointer is just an address. Reading or writing through a wrong or unchecked pointer is exactly how buffer overflows and use-after-free happen — the C/C++ security track drills these.'},
{t:'quiz',q:'In C, what is a pointer?',
opts:['A kind of loop','A variable that holds a memory address','A comment','A function name'],a:1,
why:'A <b>pointer</b> stores a memory address. <code class="inl">&x</code> takes an address; <code class="inl">*p</code> follows one. Mishandled pointers cause C\'s memory-corruption bugs.'}
]},
{id:'cb4', name:'Control flow & functions', xp:20, cards:[
{t:'md',html:'<p>C has the usual <code class="inl">if/else</code>, <code class="inl">for</code>, <code class="inl">while</code>, and <code class="inl">switch</code>, with blocks in <code class="inl">{ }</code>. Functions declare a return type and typed parameters.</p>'},
{t:'read',lang:'cpp',label:'flow.c',code:'int add(int a, int b) { // returns an int\n return a + b;\n}\n\nint main(void) {\n for (int i = 0; i < 3; i++) {\n if (i % 2 == 0)\n printf("%d is even\\n", i);\n }\n return add(2, 3);\n}',note:'The same control-flow shapes as other languages, but with explicit types. You do not need to write C to audit it — reading it is the skill.'},
{t:'quiz',q:'What must a C function declare that a Python function does not?',
opts:['Its colour','The types of its parameters and its return value','A password','A URL'],a:1,
why:'C is statically typed: every function states its return type and the type of each parameter. The compiler checks these before the program runs.'}
]},
{id:'cb5', name:'The standard library & memory', xp:25, cards:[
{t:'md',html:'<p>C\'s standard library gives you I/O (<code class="inl">printf</code>/<code class="inl">scanf</code>, <code class="inl">stdio.h</code>), strings (<code class="inl">string.h</code>: <code class="inl">strlen</code>, <code class="inl">strcpy</code>), and <b>manual memory</b> (<code class="inl">stdlib.h</code>: <code class="inl">malloc</code>/<code class="inl">free</code>).</p><p>There is <b>no garbage collector</b> — you allocate and free memory yourself. Forgetting to free (leak), using after free, or writing past the end are all vulnerabilities.</p>'},
{t:'read',lang:'cpp',label:'memory.c',code:'#include <stdlib.h>\n#include <string.h>\n\nchar *buf = malloc(64); // ask the OS for 64 bytes\nstrcpy(buf, "hello"); // copy a string in\n// ... use buf ...\nfree(buf); // give the memory back',note:'Manual memory is C\'s power and its peril: forgetting <code class="inl">free</code> leaks; using <code class="inl">buf</code> after <code class="inl">free</code> is use-after-free; copying more than 64 bytes overflows it.'},
{t:'quiz',q:'How is memory managed in C?',
opts:['Automatically by a garbage collector','Manually with malloc/free — the programmer owns it','By the browser','It is not needed'],a:1,
why:'C has <b>no garbage collector</b>. You call <code class="inl">malloc</code> to allocate and <code class="inl">free</code> to release. Mismanaging that is the source of leaks and use-after-free bugs.'}
]},
{id:'cb6', name:'Deprecated & dangerous functions', xp:25, cards:[
{t:'md',html:'<p>Some C standard functions are so unsafe they were removed or are universally flagged. The worst is <code class="inl">gets()</code> — it reads input with <b>no length limit</b> and was <b>removed from the C standard in C11</b>. The unsafe string family (<code class="inl">strcpy</code>, <code class="inl">strcat</code>, <code class="inl">sprintf</code>) has no bounds checking.</p>'},
{t:'read',lang:'cpp',label:'dangerous vs bounded',code:'gets(buf); // REMOVED in C11 — never bounds-checked\nstrcpy(buf, input); // no length limit -> overflow\nsprintf(buf, "%s", input); // same problem\n\nsnprintf(buf, sizeof buf, "%s", input); // bounded: safer',note:'<code class="inl">gets()</code> is gone from the language for good reason. <code class="inl">strcpy</code>/<code class="inl">sprintf</code> on untrusted input are classic overflow findings — prefer the sized versions.'},
{t:'quiz',q:'Why was C\'s <code class="inl">gets()</code> function removed from the language?',
opts:['It was slow','It had no way to limit input length, so it always risked a buffer overflow','It only worked on Windows','It was too new'],a:1,
why:'<code class="inl">gets()</code> read input with <b>no bound</b>, so any input longer than the buffer overflowed it. It was removed in C11; use <code class="inl">fgets</code> with a size instead.'}
]}
]
},
{
id:'php', name:'PHP', tag:'WEB', icon:'php', accent:'violet',
sub:'The classic web-app language. Powers huge legacy attack surface.',
lessons:[
{id:'p1', name:'Reading PHP superglobals', xp:25, cards:[
{t:'md',html:'<p>PHP hands attacker input through <b>superglobals</b> — arrays available everywhere. Memorize these; each is a source:</p><p>• <code class="inl">$_GET</code> — URL query params<br>• <code class="inl">$_POST</code> — form body<br>• <code class="inl">$_REQUEST</code> — GET + POST + cookies (sloppy, dangerous)<br>• <code class="inl">$_COOKIE</code>, <code class="inl">$_SERVER</code> — headers, cookies<br>• <code class="inl">$_FILES</code> — uploads</p><p>Any data touching these is <b>untrusted</b> until proven otherwise.</p>'},
{t:'read',lang:'php',code:'$page = $_GET["page"];\ninclude($page . ".php"); // whatever is in ?page= gets included'},
{t:'quiz',q:'Why is <code class="inl">$_REQUEST</code> considered more dangerous to trust than <code class="inl">$_POST</code>?',
opts:['It is slower','It merges GET, POST and cookies, so input can arrive from unexpected places','It is encrypted','It only works on admin pages'],a:1,
why:'<b>$_REQUEST</b> blurs the source. A value you assumed came from a POST form could be supplied via the URL, defeating assumptions and some weak defenses.'}
]},
{id:'p2', name:'SQL injection in PHP', xp:35, cards:[
{t:'md',html:'<p><b>SQL injection</b>: user input is glued into a database query, so an attacker can change the query\'s meaning. The tell is <b>string concatenation into a query</b> with no parameters.</p>'},
{t:'read',lang:'php',label:'Vulnerable',code:'$id = $_GET["id"];\n$sql = "SELECT * FROM users WHERE id = " . $id;\n$result = mysqli_query($conn, $sql);',note:'Input <code class="inl">$id</code> is concatenated directly into SQL. Sending <code class="inl">?id=1 OR 1=1</code> returns every row; <code class="inl">?id=1; DROP TABLE users</code> can be catastrophic.'},
{t:'read',lang:'php',label:'Safe — parameterized',code:'$stmt = $conn->prepare("SELECT * FROM users WHERE id = ?");\n$stmt->bind_param("i", $_GET["id"]); // input stays DATA, never code\n$stmt->execute();',note:'The <code class="inl">?</code> placeholder keeps input as a value, so it can never change the query structure. This is the fix you are looking for.'},
{t:'quiz',q:'Which single detail most reliably signals possible SQL injection?',
opts:['The word SELECT appears','User input is concatenated into the query string instead of bound as a parameter','The query touches a users table','mysqli is used at all'],a:1,
why:'Parameterized queries (prepared statements) separate code from data. <b>Concatenating input into the SQL text</b> is the root cause — the function or table name is secondary.'}
]},
{id:'p3', name:'Command injection & RCE', xp:35, cards:[
{t:'md',html:'<p>Some functions hand a string to the operating system shell. If user input reaches them, an attacker can run OS commands (Remote Code Execution). PHP\'s dangerous shell sinks:</p><p><code class="inl">system()</code>, <code class="inl">exec()</code>, <code class="inl">shell_exec()</code>, <code class="inl">passthru()</code>, <code class="inl">popen()</code>, and backticks <code class="inl">`...`</code>.</p>'},
{t:'read',lang:'php',label:'Vulnerable',code:'$host = $_GET["host"];\nsystem("ping -c 1 " . $host); // ?host=8.8.8.8;id runs `id` too',note:'Shell metacharacters like <code class="inl">;</code> <code class="inl">|</code> <code class="inl">&&</code> <code class="inl">$( )</code> let the attacker chain their own commands after the intended one.'},
{t:'quiz',q:'Input reaches <code class="inl">shell_exec($cmd)</code>. Which payload character is the classic way to append an extra command?',
opts:['A space','A semicolon ; (or | and &&)','A period .','A capital letter'],a:1,
why:'Shell metacharacters — <b>; | && $( )</b> — terminate or chain commands. If input containing them reaches a shell sink unescaped, that is command injection.'}
]},
{id:'p4', name:'XSS & dangerous output', xp:30, cards:[
{t:'md',html:'<p><b>Cross-site scripting (XSS)</b>: user input is written into an HTML page without encoding, so the browser runs it as script. The sink is any <code class="inl">echo</code>/<code class="inl">print</code> of untrusted data into HTML.</p>'},
{t:'read',lang:'php',label:'Vulnerable',code:'$q = $_GET["q"];\necho "You searched for: " . $q; // ?q=<\script>steal()<\/script>',note:'The fix is contextual output encoding — <code class="inl">htmlspecialchars($q)</code> turns <code class="inl"><</code> into <code class="inl">&lt;</code> so the browser shows it instead of running it.'},
{t:'quiz',q:'What makes reflected XSS possible in the code above?',
opts:['Using $_GET at all','Echoing untrusted input into HTML without encoding it','The word "searched"','Using double quotes'],a:1,
why:'XSS is an <b>output-encoding</b> failure. Input is only dangerous when it reaches an HTML sink un-encoded; <code class="inl">htmlspecialchars()</code> neutralizes it.'}
]},
{id:'p5', name:'Type juggling & loose comparisons', xp:35, cards:[
{t:'md',html:'<p>PHP has two equality operators: <code class="inl">==</code> (loose — converts types first) and <code class="inl">===</code> (strict — type and value must match). Loose <code class="inl">==</code> causes surprising, security-relevant behaviour called <b>type juggling</b>.</p><p>Classic traps: two strings that look like numbers in scientific notation (<code class="inl">"0e123"</code>) compare <b>equal</b> to each other and to <code class="inl">0</code>; before PHP 8, <code class="inl">"abc" == 0</code> was true; and passing an array where a string is expected can break functions like <code class="inl">strcmp</code>.</p>'},
{t:'read',lang:'php',label:'Vulnerable comparison',code:'$hash = md5($_POST["password"]);\nif ($hash == $stored_hash) { // loose == !\n login();\n}\n// "magic hashes" like 240610708 md5 to 0e462... which == 0',note:'If two password hashes both look like <code class="inl">0e[digits]</code>, loose <code class="inl">==</code> treats them as the number 0 and they match. Always use <code class="inl">===</code> or <code class="inl">hash_equals()</code> for hash/token comparison.'},
{t:'quiz',q:'Why can <code class="inl">if ($a == $b)</code> be dangerous when comparing password hashes?',
opts:['== is slower than ===','Loose == juggles types, so two different "0e..." strings both become 0 and match','== does not work on strings','It always returns true'],a:1,
why:'Loose <code class="inl">==</code> converts operands before comparing. Two distinct "magic hash" strings both coerce to <code class="inl">0</code> and compare equal. Use <b>===</b> (strict) or <code class="inl">hash_equals()</code>.'}
]}
]
},
{
id:'dotnet', name:'ASP.NET / C#', tag:'WEB', icon:'C#', accent:'violet',
sub:'Enterprise web stack. Strong typing, but plenty of footguns.',
lessons:[
{id:'d1', name:'Reading C# & request input', xp:25, cards:[
{t:'md',html:'<p>C# is <b>statically typed</b>: variables declare a type (<code class="inl">string</code>, <code class="inl">int</code>, <code class="inl">var</code>). In ASP.NET, attacker input arrives through:</p><p>• <code class="inl">Request.Query["x"]</code> / <code class="inl">Request.Form["x"]</code><br>• Controller action <b>parameters</b> (model binding)<br>• <code class="inl">Request.Cookies</code>, <code class="inl">Request.Headers</code></p><p>Model binding is sneaky: a method argument can be silently populated from the request.</p>'},
{t:'read',lang:'csharp',code:'public IActionResult Search(string term) { // term is bound from the request\n var results = _db.Find(term);\n return View(results);\n}'},
{t:'quiz',q:'In an ASP.NET controller, where does the value of a method parameter like <code class="inl">string term</code> typically come from?',
opts:['It is always empty','Model binding fills it automatically from the query string, form, or route','Only from a database','From another C# file'],a:1,
why:'ASP.NET <b>model binding</b> auto-populates action parameters from request data. That makes plain-looking arguments an untrusted source you must trace.'}
]},
{id:'d2', name:'SQL injection in ADO.NET', xp:35, cards:[
{t:'md',html:'<p>Same root cause as PHP: string-built SQL. In C# watch for <code class="inl">SqlCommand</code> built with <code class="inl">+</code> or string interpolation (<code class="inl">$"...{x}..."</code>).</p>'},
{t:'read',lang:'csharp',label:'Vulnerable',code:'var sql = "SELECT * FROM Users WHERE Name = \'" + name + "\'";\nvar cmd = new SqlCommand(sql, conn);',note:'String interpolation <code class="inl">$"...WHERE Name=\'{name}\'"</code> is the same bug in disguise — the input still becomes part of the SQL text.'},
{t:'read',lang:'csharp',label:'Safe — parameters',code:'var cmd = new SqlCommand("SELECT * FROM Users WHERE Name = @n", conn);\ncmd.Parameters.AddWithValue("@n", name); // input bound as data'},
{t:'quiz',q:'You see <code class="inl">$"SELECT * FROM T WHERE x = {input}"</code> passed to SqlCommand. Verdict?',
opts:['Safe, because C# is type-checked','Vulnerable — interpolation builds the query from raw input, same as concatenation','Safe, because it uses SqlCommand','Only vulnerable if input is a string'],a:1,
why:'String interpolation and concatenation both weld input into the query. Only <b>parameters</b> (<code class="inl">@n</code> + <code class="inl">AddWithValue</code>) or an ORM with parameterization keep input as data.'}
]},
{id:'d3', name:'Insecure deserialization', xp:35, cards:[
{t:'md',html:'<p>Deserialization rebuilds objects from bytes/text. If the data is attacker-controlled, some deserializers can be tricked into constructing dangerous objects → RCE. In .NET the historically dangerous names are <code class="inl">BinaryFormatter</code>, <code class="inl">LosFormatter</code>, <code class="inl">SoapFormatter</code>, and <code class="inl">TypeNameHandling.All</code> in Json.NET.</p>'},
{t:'read',lang:'csharp',label:'Dangerous',code:'var fmt = new BinaryFormatter();\nvar obj = fmt.Deserialize(userStream); // untrusted bytes -> objects',note:'<code class="inl">BinaryFormatter</code> is deprecated by Microsoft precisely because it is unsafe on untrusted input. Its mere presence on attacker data is a finding.'},
{t:'quiz',q:'Which is the strongest single red flag for an insecure deserialization bug in .NET?',
opts:['Any use of JSON','BinaryFormatter.Deserialize (or TypeNameHandling.All) applied to attacker-controlled data','A class with many fields','Using async/await'],a:1,
why:'<b>BinaryFormatter</b> and <code class="inl">TypeNameHandling.All</code> allow type information in the payload to control object creation. On untrusted input that is a well-known RCE path.'}
]},
{id:'d4', name:'XML external entities (XXE)', xp:35, cards:[
{t:'md',html:'<p>XML parsers can be told to resolve <b>external entities</b> — references that pull in other files or URLs. If an app parses attacker-supplied XML with entity resolution enabled, the attacker can read local files (<code class="inl">file:///etc/passwd</code>) or make the server fetch internal URLs (SSRF). This is <b>XXE</b>.</p><p>In .NET the danger is an <code class="inl">XmlDocument</code>/<code class="inl">XmlReader</code> with a non-null <code class="inl">XmlResolver</code> or <code class="inl">DtdProcessing.Parse</code> on untrusted XML.</p>'},
{t:'read',lang:'csharp',label:'Vulnerable vs safe',code:'// vulnerable: external entities are resolved\nvar doc = new XmlDocument();\ndoc.XmlResolver = new XmlUrlResolver(); // resolves external entities\ndoc.LoadXml(userXml);\n\n// safe: refuse DTDs and disable the resolver\nvar settings = new XmlReaderSettings {\n DtdProcessing = DtdProcessing.Prohibit, XmlResolver = null };',note:'Modern .NET defaults are safer, but legacy code that sets an <code class="inl">XmlResolver</code> or <code class="inl">DtdProcessing.Parse</code> on untrusted XML is vulnerable. Disable DTDs and set <code class="inl">XmlResolver = null</code>.'},
{t:'quiz',q:'What does an XXE attack abuse?',
opts:['A SQL query','An XML parser that resolves external entities, letting an attacker read files or trigger SSRF','A CSS import','A missing password'],a:1,
why:'<b>XXE</b> abuses XML external-entity resolution to read local files or reach internal systems. Disable DTD processing and null the resolver when parsing untrusted XML.'}
]},
{id:'d5', name:'Mass assignment / overposting', xp:35, cards:[
{t:'md',html:'<p>ASP.NET model binding auto-fills an object\'s properties from request fields. If a controller binds a request straight onto a <b>domain model</b>, an attacker can set properties you never exposed — like <code class="inl">IsAdmin</code> — just by adding them to the request. This is <b>mass assignment</b> (a.k.a. overposting).</p>'},
{t:'read',lang:'csharp',label:'Vulnerable vs safe',code:'// vulnerable: binds the whole User entity, including IsAdmin\npublic IActionResult Update(User user) {\n _db.Update(user); // attacker posts IsAdmin=true\n return Ok();\n}\n\n// safe: bind a DTO exposing only the fields you allow\npublic IActionResult Update(UpdateProfileDto dto) { /* map dto -> user */ }',note:'Bind to a view model / DTO containing only editable fields, or use an explicit <code class="inl">[Bind(nameof(...))]</code> allowlist. Never bind untrusted input onto an entity with sensitive properties.'},
{t:'quiz',q:'How does a mass-assignment (overposting) bug let a user become admin?',
opts:['By guessing the password','Model binding sets a sensitive property (e.g. IsAdmin) from an extra request field the developer never intended','By SQL injection','By XSS'],a:1,
why:'Binding a request directly onto a model lets the attacker supply extra fields. Bind to a DTO with only allowed properties, or use a <code class="inl">[Bind]</code> allowlist.'}
]}
]
},
{
id:'cpp', name:'C / C++', tag:'NATIVE', icon:'C++', accent:'amber',
sub:'Memory is manual here. Whole vuln classes that web devs never see.',
lessons:[
{id:'c1', name:'Pointers & memory, gently', xp:25, cards:[
{t:'md',html:'<p>C/C++ let you touch memory directly. A <b>pointer</b> holds an address; a <b>buffer</b> is a fixed-size block (often <code class="inl">char buf[64]</code>). The language will <b>not</b> stop you writing past the end of a buffer — that is the source of memory-corruption bugs.</p><p>You do not need to write C to audit it. You need to spot <b>fixed-size buffers</b> meeting <b>unbounded copies</b>.</p>'},
{t:'read',lang:'cpp',code:'char buf[64]; // room for 64 bytes\nstrcpy(buf, user_input); // copies until a null byte — no size limit!',note:'If <code class="inl">user_input</code> is longer than 64 bytes, <code class="inl">strcpy</code> writes past <code class="inl">buf</code> — a <b>buffer overflow</b>, corrupting adjacent memory.'},
{t:'quiz',q:'What is the fundamental danger of <code class="inl">char buf[64]</code> combined with an unbounded copy?',
opts:['It uses too much memory','Writing more than 64 bytes overflows into adjacent memory','64 is an odd size','Pointers are slow'],a:1,
why:'A fixed buffer + a copy with no length limit means input longer than the buffer <b>corrupts neighbouring memory</b>, which can crash the program or hijack execution.'}
]},
{id:'c2', name:'Dangerous C functions', xp:35, cards:[
{t:'md',html:'<p>Some C functions are dangerous because they do not take a size limit. Memorize this hit-list — seeing them on untrusted data is an instant finding:</p><p><code class="inl">strcpy</code>, <code class="inl">strcat</code>, <code class="inl">sprintf</code>, <code class="inl">gets</code>, <code class="inl">scanf("%s")</code>.</p><p>Their bounded cousins are safer: <code class="inl">strncpy</code>, <code class="inl">snprintf</code>, <code class="inl">fgets</code> — but even these need the length used correctly.</p>'},
{t:'read',lang:'cpp',label:'Vulnerable',code:'char name[32];\nprintf("Enter name: ");\ngets(name); // gets() has NO bound — removed from the C standard',note:'<code class="inl">gets()</code> is so unsafe it was removed from C11. If you ever see it, it is a finding, full stop.'},
{t:'quiz',q:'Why are <code class="inl">strcpy</code> and <code class="inl">gets</code> flagged while <code class="inl">snprintf</code> is preferred?',
opts:['They are older','They perform no bounds checking, so oversized input overflows the buffer','They only work on Linux','They are slower'],a:1,
why:'The unsafe functions copy until a terminator with <b>no maximum length</b>. <code class="inl">snprintf</code>/<code class="inl">strncpy</code> take a size argument, capping the write to the buffer.'}
]},
{id:'c3', name:'Integer overflow & use-after-free', xp:35, cards:[
{t:'md',html:'<p>Two more native-code classics:</p><p><b>Integer overflow</b> — a number wraps past its max (e.g. a <code class="inl">uint16_t</code> over 65535 rolls to 0). If it feeds a size calculation, you can allocate too little and overflow.</p><p><b>Use-after-free</b> — memory is <code class="inl">free()</code>d, then used again. The freed block may be reused by an attacker to control what that pointer now sees.</p>'},
{t:'read',lang:'cpp',label:'Use-after-free',code:'char* p = (char*)malloc(64);\nfree(p); // memory released\nstrcpy(p, data); // BUG: p is dangling, used after free'},
{t:'quiz',q:'A size is computed as <code class="inl">len * count</code> in an <code class="inl">int</code>, then used for <code class="inl">malloc</code>. What class of bug should you check for?',
opts:['XSS','Integer overflow — the multiplication can wrap to a tiny value, under-allocating','SQL injection','CSRF'],a:1,
why:'If <code class="inl">len * count</code> overflows the integer type, <code class="inl">malloc</code> gets a small number while the code then writes the full amount — a heap overflow via <b>integer overflow</b>.'}
]},
{id:'c4', name:'Format string vulnerabilities', xp:35, cards:[
{t:'md',html:'<p>Functions in the <code class="inl">printf</code> family read a <b>format string</b> with placeholders (<code class="inl">%s</code>, <code class="inl">%x</code>, <code class="inl">%n</code>). If <b>user input becomes the format string</b> itself, the attacker controls those placeholders — leaking memory with <code class="inl">%x</code>/<code class="inl">%s</code>, or even writing to memory with <code class="inl">%n</code>.</p>'},
{t:'read',lang:'cpp',label:'Vulnerable vs safe',code:'printf(user_input); // BUG: input IS the format string\nprintf("%s", user_input); // safe: input is just data',note:'If <code class="inl">user_input</code> is <code class="inl">"%x %x %x"</code> the first line dumps stack memory; <code class="inl">%n</code> can write to it. The fix is to always pass a fixed format string and put user data in an argument.'},
{t:'quiz',q:'What makes <code class="inl">printf(user_input)</code> a vulnerability?',
opts:['printf is deprecated','User input is used as the format string, so %x/%n let the attacker read or write memory','It prints too slowly','It needs a trailing newline'],a:1,
why:'When attacker input is the <b>format string</b>, placeholders like <code class="inl">%x</code> (read the stack) and <code class="inl">%n</code> (write) execute. Always use a literal format: <code class="inl">printf("%s", input)</code>.'}
]}
]
},
{
id:'web', name:'JS & Web Logic', tag:'WEB', icon:'JS', accent:'violet',
sub:'Client and server JavaScript, plus the logic flaws scanners miss.',
lessons:[
{id:'j1', name:'DOM XSS & dangerous sinks', xp:30, cards:[
{t:'md',html:'<p>In browser JS, some properties/functions treat strings as HTML or code. Feed them untrusted data and you get DOM-based XSS. Dangerous sinks:</p><p><code class="inl">innerHTML</code>, <code class="inl">outerHTML</code>, <code class="inl">document.write</code>, <code class="inl">eval</code>, <code class="inl">setTimeout("string")</code>, <code class="inl">location</code>. Common sources: <code class="inl">location.hash</code>, <code class="inl">location.search</code>, <code class="inl">document.referrer</code>.</p>'},
{t:'read',lang:'js',code:'const q = location.hash.slice(1); // source: URL fragment\ndocument.getElementById("out").innerHTML = q; // sink: parsed as HTML',note:'<code class="inl">textContent</code> would render it as plain text safely. <code class="inl">innerHTML</code> parses tags and event handlers — that is the bug.'},
{t:'quiz',q:'Which assignment is the XSS sink here?',
opts:['location.hash.slice(1)','.innerHTML = q','getElementById("out")','const q'],a:1,
why:'<b>innerHTML</b> parses its string as HTML. Assigning untrusted data to it lets injected markup and scripts run. <code class="inl">textContent</code> is the safe sibling.'}
]},
{id:'j2', name:'eval, prototype & Node sinks', xp:35, cards:[
{t:'md',html:'<p>Server-side (Node.js) JavaScript adds OS-level sinks: <code class="inl">child_process.exec()</code>, <code class="inl">eval()</code>, <code class="inl">Function()</code>, and unsafe <code class="inl">fs</code> paths. And <code class="inl">exec</code> (unlike <code class="inl">execFile</code>) runs a full shell.</p>'},
{t:'read',lang:'js',label:'Vulnerable',code:'const { exec } = require("child_process");\napp.get("/dns", (req,res) => {\n exec("nslookup " + req.query.domain); // shell injection\n});',note:'<code class="inl">exec</code> spawns <code class="inl">/bin/sh -c</code>, so shell metacharacters in <code class="inl">req.query.domain</code> run commands. <code class="inl">execFile("nslookup",[domain])</code> avoids the shell.'},
{t:'quiz',q:'Why is <code class="inl">child_process.exec(userInput)</code> riskier than <code class="inl">execFile("cmd",[userInput])</code>?',
opts:['exec is deprecated','exec runs input through a shell, so metacharacters chain commands; execFile passes args directly','execFile is faster','No difference'],a:1,
why:'<b>exec</b> interprets the string in a shell, exposing <code class="inl">; | && $()</code>. <b>execFile</b>/<b>spawn</b> pass arguments as a list to the binary, with no shell to abuse.'}
]},
{id:'j3', name:'Prototype pollution', xp:35, cards:[
{t:'md',html:'<p>In JavaScript every object inherits from <code class="inl">Object.prototype</code>. If code copies attacker-controlled keys into an object — a deep <b>merge</b> of JSON, or <code class="inl">obj[key] = value</code> with <code class="inl">key</code> from input — an attacker can set <code class="inl">__proto__</code> (or <code class="inl">constructor.prototype</code>) and change properties on <b>every</b> object at once. Impact ranges from logic bypass and denial of service to, in some stacks, RCE.</p>'},
{t:'read',lang:'js',label:'Vulnerable deep merge',code:'function merge(target, src) {\n for (const k in src) {\n if (typeof src[k] === "object") merge(target[k] = target[k] || {}, src[k]);\n else target[k] = src[k]; // no guard when k === "__proto__"\n }\n}\nmerge({}, JSON.parse(req.body)); // {"__proto__":{"isAdmin":true}} pollutes all objects',note:'The merge never rejects <code class="inl">__proto__</code>, so attacker JSON writes onto the shared prototype. Guard the keys (skip <code class="inl">__proto__</code>, <code class="inl">constructor</code>, <code class="inl">prototype</code>) or use a null-prototype object / Map.'},
{t:'quiz',q:'What makes a recursive object <code class="inl">merge()</code> vulnerable to prototype pollution?',
opts:['It is recursive','It copies a key like <code class="inl">__proto__</code> from attacker input onto objects, changing the shared prototype','It uses JSON','It runs on Node'],a:1,
why:'Copying attacker-controlled keys such as <code class="inl">__proto__</code> writes onto <code class="inl">Object.prototype</code>, affecting all objects. Reject those keys or use a Map / null-prototype object.'}
]}
]
},
{
id:'python', name:'Python', tag:'BACKEND', icon:'py', accent:'violet',
sub:'Web backends, scripts and automation. Clean syntax, sharp edges.',
lessons:[
{id:'py1', name:'Reading Python & where input enters', xp:25, cards:[
{t:'md',html:'<p>Python has no <code class="inl">$</code> sigils and no type declarations — a variable is just a name being assigned, and <b>indentation</b> (not braces) defines blocks. So you spot untrusted input by the library that produced it.</p><p>Common sources:</p><p>• Flask: <code class="inl">request.args</code>, <code class="inl">request.form</code>, <code class="inl">request.json</code><br>• Django: <code class="inl">request.GET</code>, <code class="inl">request.POST</code><br>• Scripts & CLI: <code class="inl">sys.argv</code>, <code class="inl">input()</code>, environment variables</p>'},
{t:'read',lang:'python',label:'Flask — a request handler',code:'from flask import request\n\n@app.route("/hello")\ndef hello():\n name = request.args.get("name") # SOURCE: from ?name=\n return "<h1>Hi " + name + "</h1>" # SINK: reflected into HTML',note:'Input from <code class="inl">request.args</code> is concatenated into HTML and returned — reflected <b>XSS</b> unless escaped. Jinja templates auto-escape; raw string building like this does not.'},
{t:'quiz',q:'In this Python web handler, which line is the untrusted <b>source</b>?',
opts:['The <code class="inl">@app.route</code> decorator','<code class="inl">request.args.get("name")</code>','The string <code class="inl">"Hi "</code>','The function name <code class="inl">hello</code>'],a:1,
why:'<b>request.args</b> reads the URL query string — fully attacker-controlled. The decorator and function name are structure, not data.'}
]},
{id:'py2', name:'SQL injection in Python', xp:30, cards:[
{t:'md',html:'<p>Every Python database driver (sqlite3, psycopg2, MySQLdb, SQLAlchemy Core) accepts <b>parameters</b>. The bug appears when code builds the query string itself with an <b>f-string</b>, <code class="inl">%</code>, <code class="inl">.format()</code>, or <code class="inl">+</code>.</p>'},
{t:'read',lang:'python',label:'Vulnerable',code:'name = request.args.get("name")\ncur.execute(f"SELECT * FROM users WHERE name = \'{name}\'")',note:'The f-string drops <code class="inl">name</code> straight into the SQL text — <code class="inl">?name=\' OR \'1\'=\'1</code> returns every row. f-strings and <code class="inl">.format()</code> are SQL injection in disguise.'},
{t:'read',lang:'python',label:'Safe — parameterized',code:'cur.execute("SELECT * FROM users WHERE name = %s", (name,))',note:'The <code class="inl">%s</code> here is a <b>driver placeholder</b>, not string formatting — the value is bound separately and can never alter the query. (sqlite3 uses <code class="inl">?</code>.)'},
{t:'quiz',q:'Which call is <b>safe</b> from SQL injection?',
opts:['<code class="inl">cur.execute(f"...WHERE id={uid}")</code>','<code class="inl">cur.execute("...WHERE id=%s", (uid,))</code>','<code class="inl">cur.execute("...WHERE id=" + uid)</code>','<code class="inl">cur.execute("...WHERE id={}".format(uid))</code>'],a:1,
why:'Only the <b>parameterized</b> call keeps the value as data. f-strings, <code class="inl">+</code>, and <code class="inl">.format()</code> all build the query from raw input.'}
]},
{id:'py3', name:'Command injection in Python', xp:30, cards:[
{t:'md',html:'<p>Python reaches the OS shell through <code class="inl">os.system()</code>, <code class="inl">os.popen()</code>, and <code class="inl">subprocess</code> calls with <code class="inl">shell=True</code>. The danger sign is a <b>built command string</b> handed to any of these.</p>'},
{t:'read',lang:'python',label:'Vulnerable',code:'import subprocess\nhost = request.args.get("host")\nsubprocess.run("ping -c1 " + host, shell=True) # ?host=8.8.8.8;id',note:'<code class="inl">shell=True</code> runs the string in <code class="inl">/bin/sh</code>, so <code class="inl">;</code> <code class="inl">|</code> <code class="inl">$()</code> chain attacker commands.'},
{t:'read',lang:'python',label:'Safe — argument list',code:'subprocess.run(["ping", "-c1", host]) # list form, no shell',note:'An <b>argument list</b> with no <code class="inl">shell=True</code> sends <code class="inl">host</code> as a single argument to ping — the shell never parses it.'},
{t:'quiz',q:'What single detail makes <code class="inl">subprocess.run(cmd, shell=True)</code> dangerous?',
opts:['It imports subprocess','<code class="inl">shell=True</code> runs the built string through a shell that interprets metacharacters','run() is deprecated','It returns output'],a:1,
why:'<b>shell=True</b> plus a concatenated string is command injection. The list form with no shell is the safe pattern.'}
]},
{id:'py4', name:'Server-Side Template Injection', xp:35, cards:[
{t:'md',html:'<p>Template engines (Jinja2, Twig, Freemarker) evaluate expressions inside markers like <code class="inl">{{ }}</code>. If <b>user input is placed into the template itself</b> — not just passed as a value — the input is evaluated as template code, often leading to RCE. The Python tell is <code class="inl">render_template_string()</code> built from input.</p>'},
{t:'read',lang:'python',label:'Vulnerable',code:'from flask import render_template_string, request\nname = request.args.get("name")\ntpl = "<h1>Hello " + name + "</h1>"\nreturn render_template_string(tpl) # ?name={{7*7}} -> 49',note:'Because <code class="inl">name</code> is concatenated into the <i>template source</i>, <code class="inl">{{7*7}}</code> renders as <code class="inl">49</code> — proof of <b>SSTI</b>. Payloads escalate to reading files and running commands.'},
{t:'read',lang:'python',label:'Safe — fixed template, bound value',code:'return render_template_string("<h1>Hello {{ name }}</h1>", name=name)',note:'The template is <b>fixed</b> and <code class="inl">name</code> is passed as a bound variable — escaped, never evaluated as template code.'},
{t:'quiz',q:'In a Flask app, <code class="inl">?name={{7*7}}</code> shows <code class="inl">49</code> on the page. What does that prove?',
opts:['A math bug','Server-Side Template Injection — input is evaluated as template code','A caching issue','Nothing, that is normal'],a:1,
why:'User input being <b>evaluated</b> (7*7 → 49) means it reached the template engine as code. That is <b>SSTI</b>, a common path to RCE.'}
]},
{id:'py5', name:'Insecure deserialization (pickle)', xp:35, cards:[
{t:'md',html:'<p>Python\'s <code class="inl">pickle</code> can serialize almost any object — and <b>deserializing</b> attacker-controlled pickle data can execute code, because a pickle stream can invoke arbitrary constructors. The same risk applies to <code class="inl">yaml.load()</code> with an unsafe loader.</p>'},
{t:'read',lang:'python',label:'Dangerous',code:'import pickle\ndata = request.get_data() # untrusted bytes\nobj = pickle.loads(data) # RCE: pickle can run code on load',note:'Never <code class="inl">pickle.loads()</code> untrusted input — a crafted pickle can run commands as your app. Use JSON, or <code class="inl">yaml.safe_load()</code> for YAML.'},
{t:'quiz',q:'You see <code class="inl">pickle.loads(request.get_data())</code>. Verdict?',
opts:['Fine — pickle is just serialization','Critical — deserializing untrusted pickle can execute code (RCE)','Only a problem if the object is large','Safe if wrapped in try/except'],a:1,
why:'<b>pickle.loads</b> on attacker data is a textbook RCE. The fix is a safe format (JSON) or <code class="inl">yaml.safe_load</code>; try/except does not help.'}
]}
]
},
{
id:'go', name:'Go', tag:'BACKEND', icon:'Go', accent:'amber',
sub:'Google\'s compiled language for servers and cloud. Clean syntax, familiar bug classes.',
lessons:[
{id:'go1', name:'Reading Go & where input enters', xp:25, cards:[
{t:'md',html:'<p><b>Go</b> is statically typed and compiled, with a clean syntax: <code class="inl">:=</code> declares and infers a variable, and errors are returned explicitly. In web services (<code class="inl">net/http</code>, Gin, Echo) untrusted input arrives via <code class="inl">r.URL.Query().Get()</code>, <code class="inl">r.FormValue()</code>, <code class="inl">r.Header.Get()</code>, and path variables.</p>'},
{t:'read',lang:'go',label:'net/http handler',code:'func handler(w http.ResponseWriter, r *http.Request) {\n name := r.URL.Query().Get("name") // source: ?name=\n fmt.Fprintf(w, "<h1>%s</h1>", name) // reflected into HTML\n}',note:'Go\'s <code class="inl">html/template</code> auto-escapes, but writing straight to the response with <code class="inl">Fprintf</code> does not — reflected <b>XSS</b>. <code class="inl">:=</code> declares and infers the variable type.'},
{t:'quiz',q:'Where does <code class="inl">r.URL.Query().Get("name")</code> get its value?',
opts:['A constant','The HTTP request query string — untrusted input','Another Go file','The compiler'],a:1,
why:'It reads the URL query string from the request. Like all request data it is attacker-controlled and must be treated as a source.'}
]},
{id:'go2', name:'SQL injection in Go', xp:30, cards:[
{t:'md',html:'<p>Go\'s <code class="inl">database/sql</code> supports <b>placeholders</b> (<code class="inl">?</code> for MySQL, <code class="inl">$1</code> for Postgres). Building the query string with <code class="inl">fmt.Sprintf</code> instead is injectable.</p>'},
{t:'read',lang:'go',label:'Vulnerable',code:'q := fmt.Sprintf("SELECT * FROM users WHERE name = \'%s\'", name)\nrows, _ := db.Query(q) // SQL injection',note:'<code class="inl">fmt.Sprintf</code> welds input into the SQL text, exactly like concatenation in other languages.'},
{t:'read',lang:'go',label:'Safe — placeholder',code:'rows, _ := db.Query("SELECT * FROM users WHERE name = ?", name) // bound',note:'The <code class="inl">?</code> placeholder keeps <code class="inl">name</code> as data. This is the fix you look for.'},
{t:'quiz',q:'Which Go database call is safe from SQL injection?',
opts:['<code class="inl">db.Query(fmt.Sprintf("... %s", name))</code>','<code class="inl">db.Query("... = ?", name)</code> with a placeholder','Concatenating with +','Running it twice'],a:1,
why:'A parameterized query with a <b>placeholder</b> (<code class="inl">?</code> / <code class="inl">$1</code>) separates code from data. <code class="inl">fmt.Sprintf</code> into the query is the injectable pattern.'}
]},
{id:'go3', name:'Command injection in Go', xp:30, cards:[
{t:'md',html:'<p>Go runs OS commands with <code class="inl">os/exec</code>. <code class="inl">exec.Command("sh", "-c", str)</code> runs a <b>shell</b> and is injectable; <code class="inl">exec.Command("ping", "-c1", host)</code> passes arguments directly with no shell.</p>'},
{t:'read',lang:'go',label:'Vulnerable vs safe',code:'// vulnerable: a shell parses the built string\nexec.Command("sh", "-c", "ping -c1 "+host).Run()\n\n// safe: arguments passed directly, no shell\nexec.Command("ping", "-c1", host).Run()',note:'Routing through <code class="inl">sh -c</code> exposes <code class="inl">;</code> <code class="inl">|</code> <code class="inl">$( )</code>. The argument-list form sends <code class="inl">host</code> as a single argument to the binary.'},
{t:'quiz',q:'What makes <code class="inl">exec.Command("sh","-c", "ping "+host)</code> injectable?',
opts:['Using Go','User input is concatenated into a command run through a shell','ping is missing','It returns an error'],a:1,
why:'A concatenated string run via <code class="inl">sh -c</code> lets shell metacharacters chain commands. Pass an argument list with no shell instead.'}
]},
{id:'go4', name:'SSRF & path traversal in Go', xp:30, cards:[
{t:'md',html:'<p>Two more familiar classes in Go: <b>SSRF</b> — <code class="inl">http.Get(userURL)</code> fetches an attacker-chosen URL (including cloud metadata) — and <b>path traversal</b> — joining a user filename onto a base path and opening it lets <code class="inl">../</code> escape.</p>'},
{t:'read',lang:'go',label:'Two sinks',code:'resp, _ := http.Get(r.URL.Query().Get("url")) // SSRF\n\np := filepath.Join("/data", r.URL.Query().Get("f"))\nf, _ := os.Open(p) // path traversal via ../',note:'<code class="inl">filepath.Join</code> does not stop <code class="inl">../</code> escaping the base — call <code class="inl">filepath.Clean</code> and verify the result stays inside <code class="inl">/data</code>. Allowlist URLs and block internal ranges for the fetch.'},
{t:'quiz',q:'Why is <code class="inl">http.Get(userURL)</code> in a server risky?',
opts:['It is slow','SSRF — the server can reach internal systems (like cloud metadata) the user cannot','It uses too much memory','Nothing'],a:1,
why:'Fetching a user-supplied URL server-side is <b>SSRF</b>: it abuses the server\'s network position to reach internal-only resources such as the cloud metadata endpoint.'}
]}
]
},
{
id:'java', name:'Java & the JVM', tag:'JVM', icon:'☕', accent:'violet',
sub:'Enterprise backends and Android — strong typing, classic injection and deserialization bugs.',
lessons:[
{id:'jv1', name:'Reading Java & where input enters', xp:25, cards:[
{t:'md',html:'<p>Java is statically typed and object-oriented; every method lives in a class. In web apps (Spring, Jakarta EE) untrusted input arrives through:</p><p>• Spring: <code class="inl">@RequestParam</code>, <code class="inl">@PathVariable</code>, <code class="inl">@RequestBody</code><br>• Servlets: <code class="inl">request.getParameter()</code>, <code class="inl">getHeader()</code><br>• CLI: the <code class="inl">String[] args</code> of <code class="inl">main</code></p>'},
{t:'read',lang:'java',label:'Spring controller',code:'@GetMapping("/user")\npublic String user(@RequestParam String name) { // bound from ?name=\n return "<h1>" + name + "</h1>"; // reflected into HTML\n}',note:'<code class="inl">@RequestParam</code> auto-binds the query parameter into <code class="inl">name</code>. Returned straight into HTML, it is reflected <b>XSS</b> unless the view escapes it.'},
{t:'quiz',q:'In a Spring controller, where does a <code class="inl">@RequestParam String name</code> value come from?',
opts:['A constant in the code','The HTTP request (query string, form, etc.) — untrusted input','Another class','The database only'],a:1,
why:'<b>@RequestParam</b> binds request data into the argument. Like all request-bound values it is attacker-controlled and must be treated as a source.'}
]},
{id:'jv2', name:'SQL injection in JDBC', xp:30, cards:[
{t:'md',html:'<p>JDBC runs SQL through <code class="inl">Statement</code> or <code class="inl">PreparedStatement</code>. Building a query by <b>concatenation</b> and running it via <code class="inl">Statement</code> is injectable; <code class="inl">PreparedStatement</code> with <code class="inl">?</code> placeholders is safe.</p>'},
{t:'read',lang:'java',label:'Vulnerable',code:'String sql = "SELECT * FROM users WHERE name = \'" + name + "\'";\nStatement st = conn.createStatement();\nResultSet rs = st.executeQuery(sql); // SQL injection',note:'Input is welded into the SQL text. An ORM like Hibernate/JPA is injectable too if you build HQL/JPQL by concatenation.'},
{t:'read',lang:'java',label:'Safe — PreparedStatement',code:'PreparedStatement ps = conn.prepareStatement(\n "SELECT * FROM users WHERE name = ?");\nps.setString(1, name); // bound as data\nResultSet rs = ps.executeQuery();',note:'The <code class="inl">?</code> placeholder keeps input as a value, never code. This is the fix you look for.'},
{t:'quiz',q:'Which JDBC pattern prevents SQL injection?',
opts:['Statement with a concatenated query','PreparedStatement with ? placeholders and setString()','Running the query twice','Catching SQLException'],a:1,
why:'<b>PreparedStatement</b> with bound parameters separates code from data. Concatenating into a <code class="inl">Statement</code> is the classic injectable pattern.'}
]},
{id:'jv3', name:'Command injection in Java', xp:30, cards:[
{t:'md',html:'<p>Java runs OS commands via <code class="inl">Runtime.getRuntime().exec()</code> and <code class="inl">ProcessBuilder</code>. Passing a <b>single built string</b> — especially through a shell (<code class="inl">sh -c</code>) — lets attacker metacharacters chain commands.</p>'},
{t:'read',lang:'java',label:'Vulnerable',code:'String cmd = "ping -c1 " + host;\nRuntime.getRuntime().exec(new String[]{"sh","-c", cmd}); // shell = injectable',note:'Routing through <code class="inl">sh -c</code> with a concatenated string exposes <code class="inl">;</code> <code class="inl">|</code> <code class="inl">$( )</code>. Prefer <code class="inl">new ProcessBuilder("ping","-c1",host)</code> with no shell and validated args.'},
{t:'quiz',q:'What makes the exec() call above injectable?',
opts:['Using Java at all','User input is concatenated into a command run through a shell (sh -c)','ping is not installed','exec() returns a Process'],a:1,
why:'A concatenated string run via <code class="inl">sh -c</code> lets shell metacharacters chain commands. Pass an argument array with no shell instead.'}
]},
{id:'jv4', name:'Insecure deserialization in Java', xp:35, cards:[
{t:'md',html:'<p>Java can turn bytes back into objects with <code class="inl">ObjectInputStream.readObject()</code>. On <b>attacker-controlled</b> bytes this is dangerous: crafted data can trigger "gadget chains" in libraries already on the classpath and reach <b>remote code execution</b> (the technique behind tools like ysoserial).</p>'},
{t:'read',lang:'java',label:'Dangerous',code:'ObjectInputStream in = new ObjectInputStream(request.getInputStream());\nMyObject obj = (MyObject) in.readObject(); // untrusted bytes -> objects',note:'<code class="inl">readObject()</code> on untrusted input is a well-known RCE path. Prefer a safe data format (JSON with a strict schema) and never deserialize untrusted Java objects.'},
{t:'quiz',q:'Why is <code class="inl">ObjectInputStream.readObject()</code> on request data a critical finding?',
opts:['It is slow','Crafted bytes can trigger gadget chains and reach remote code execution','It only reads strings','It needs a database'],a:1,
why:'Java deserialization of attacker data can chain existing classes into <b>RCE</b>. It is the same class of bug behind many Java CVEs.'}