Repository navigation
Expand file tree
/
Copy pathTorChatPage.html
More file actions
2092 lines (1999 loc) · 97.8 KB
/
Copy pathTorChatPage.html
File metadata and controls
2092 lines (1999 loc) · 97.8 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
<!DOCTYPE html>
<html lang="en" itemscope itemtype="https://schema.org/WebPage">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Tor Chat - Self-hosted E2EE chat over Tor Hidden Service</title>
<meta name="description"
content="OffCode Tor Chat: self-hosted Tor chat with browser WebCrypto AES-GCM. Password rooms are server-blind E2EE; open rooms are public encrypted channels. SignalR relay, SQLCipher, Server Manager, optional Full Vanguards.">
<meta name="author" content="OffCrypt">
<meta name="last-modified" content="2026-10-19">
<meta name="keywords"
content="Tor Chat, TorChat, torchat, onion chat, .onion links, E2EE chat, Tor Hidden Service, SignalR, WebCrypto, AES-GCM, SQLCipher, Vanguards, OffCode, anonymous chat">
<meta name="robots" content="index, follow">
<meta property="og:type" content="website">
<meta property="og:locale" content="en_US">
<meta property="og:url" content="https://www.dev-offcode.com/TorChatPage.html">
<meta property="og:title" content="Tor Chat - Self-hosted E2EE chat over Tor">
<meta property="og:description"
content="Self-hosted Tor chat: password rooms are server-blind E2EE; open rooms are public encrypted channels. .onion HS, WebCrypto AES-GCM, Server Manager.">
<meta property="og:image" content="https://www.dev-offcode.com/TorChat1.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="OffCode Tor Chat interface">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@OffCryptAndroid">
<meta name="twitter:creator" content="@OffCryptAndroid">
<meta name="twitter:url" content="https://www.dev-offcode.com/TorChatPage.html">
<meta name="twitter:title" content="Tor Chat - Self-hosted E2EE chat over Tor">
<meta name="twitter:description"
content="Tor-only chat: password-protected server-blind E2EE, open public rooms, WebCrypto, SQLCipher, Server Manager, optional Full Vanguards.">
<meta name="twitter:image" content="https://www.dev-offcode.com/TorChat1.png">
<meta property="og:see_also" content="https://www.instagram.com/off.crypt86/">
<meta property="og:see_also" content="https://www.youtube.com/@OffCrypt">
<meta name="instagram:site" content="@off.crypt86">
<meta name="youtube:channel" content="@OffCrypt">
<link rel="canonical" href="https://www.dev-offcode.com/TorChatPage.html">
<link rel="alternate" hreflang="en" href="https://www.dev-offcode.com/TorChatPage.html">
<link rel="alternate" hreflang="x-default" href="https://www.dev-offcode.com/TorChatPage.html">
<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<link rel="preload" href="styles-automation.css?v=3.1.8" as="style">
<link rel="preload" href="styles-at-extras.css?v=3.2.7" as="style">
<link rel="preload" href="TorChat1.png" as="image">
<link rel="preload" href="TorManager1.png" as="image">
<link rel="preload" href="TorManager2.png" as="image">
<meta name="theme-color" content="#c45c26">
<meta name="msapplication-TileColor" content="#c45c26">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"@id": "https://www.dev-offcode.com/TorChatPage.html#software",
"name": "OffCode Tor Chat",
"applicationCategory": "CommunicationApplication, SecurityApplication",
"operatingSystem": "Windows, macOS, Ubuntu Desktop (host); Docker (host); Tor Browser (clients)",
"description": "Self-hosted Tor Hidden Service chat. Browser WebCrypto AES-GCM with PBKDF2-v3 (600k). Password rooms are server-blind E2EE; open rooms use a room-id-derived key. v3 onion intro DoS and Equi-X Proof-of-Work on Tor 0.4.8+. Creator Settings (close/max members/read-only/slow mode/disable attachments/pin/lock/delete), SignalR relay, SQLCipher, Server Manager or Docker.",
"url": "https://www.dev-offcode.com/TorChatPage.html",
"image": "https://www.dev-offcode.com/TorChat1.png",
"offers": [
{
"@type": "Offer",
"name": "Free binary release",
"availability": "https://schema.org/ComingSoon",
"price": "0",
"priceCurrency": "USD"
},
{
"@type": "Offer",
"name": "Developer License - Source Code",
"availability": "https://schema.org/ComingSoon",
"price": "49",
"priceCurrency": "USD"
}
],
"inLanguage": ["en"],
"author": {
"@type": "Person",
"@id": "https://www.dev-offcode.com/#author",
"name": "EmptyCode0x86",
"url": "https://www.dev-offcode.com",
"sameAs": [
"https://twitter.com/OffCryptAndroid",
"https://www.instagram.com/off.crypt86/",
"https://www.youtube.com/@OffCrypt",
"https://github.com/emptycode0x86",
"https://bsky.app/profile/off-crypt86.bsky.social",
"https://www.linkedin.com/in/off-crypt-b94175382/"
]
},
"publisher": {
"@type": "Organization",
"@id": "https://www.dev-offcode.com/#organization",
"name": "OffCrypt",
"url": "https://www.dev-offcode.com",
"logo": {
"@type": "ImageObject",
"url": "https://www.dev-offcode.com/TorChat1.png"
},
"sameAs": [
"https://twitter.com/OffCryptAndroid",
"https://www.instagram.com/off.crypt86/",
"https://www.youtube.com/@OffCrypt",
"https://bsky.app/profile/off-crypt86.bsky.social",
"https://www.linkedin.com/in/off-crypt-b94175382/"
]
},
"datePublished": "2026-07-12",
"dateModified": "2026-10-19"
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "Can the host read password-protected chats?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. The password and room key stay in the browser (separate v3 KDF domains for join proof vs message key). ChatServer sees ciphertext and a join-proof hash only. Open rooms are public encrypted channels by design."
}
},
{
"@type": "Question",
"name": "Do I host Tor Chat myself?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Server Manager (Windows, macOS, Ubuntu Desktop) starts ChatServer (health check) then a Tor v3 Hidden Service. You can create public rooms, cap or Disable Room Creation on the onion site, copy the Admin key into Profile Restore, and optionally start with Windows. Optional Full Vanguards hardens long-lived onions. Docker compose (ChatServer + Tor, no host ports) is also supported. There is no OffCode-hosted chat cloud; you control the onion URL and the stack."
}
},
{
"@type": "Question",
"name": "How does end-to-end encryption work?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Clients encrypt with WebCrypto (PBKDF2-HMAC-SHA256 at 600000 iterations, then AES-256-GCM) before sending. Locked rooms: join proof and room key use separate v3 KDF domains so the server cannot derive the message key; password never leaves the browser. The server stores a second PBKDF2 hash of the join proof (250000 iterations, iter:salt:hash). Open rooms encrypt too (pbkdf2-v3) but derive the key from the room id, so anyone who knows the id (including the host) can decrypt. 1:1 chats can use ECDH hybrid encryption plus a safety number (eight groups of five digits) for out-of-band peer verification."
}
},
{
"@type": "Question",
"name": "Are open rooms private from the server?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. Open rooms are public encrypted channels: the key is derived from the room id. Use a password (and optionally a hidden room with invite link) for server-blind E2EE."
}
},
{
"@type": "Question",
"name": "How does Tor Chat relate to EU Chat Control 1.0?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Chat Control 1.0 lets major messaging providers voluntarily scan private communications. Tor Chat is a different model: self-hosted on Tor, not a centralized big-tech messenger. You avoid that scanned platform path by hosting your own Hidden Service with browser E2EE. This is architectural privacy, not legal advice."
}
},
{
"@type": "Question",
"name": "Do clients need Tor Browser?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Guests open http://ADDRESS.onion/ in Tor Browser. Server Manager binds ChatServer to loopback only; Docker listens inside the compose network with no host ports. Tor publishes the Hidden Service. The UI is Tor Browser-friendly: self-hosted fonts, SRI scripts, and no third-party CDNs."
}
},
{
"@type": "Question",
"name": "Can the host delete rooms or wipe chat data?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. Server Manager can remove a room in realtime with secure_delete/VACUUM for that room’s ciphertext. Remove SQL data overwrites then deletes the SQLCipher DB and db.key.dpapi (optional: also wipe Tor HS keys / .onion). It does not delete the Admin key (operator recovery). Generate new address securely rotates the Hidden Service identity. Tor clients cannot call the admin delete API."
}
},
{
"@type": "Question",
"name": "What are hidden rooms and invite links?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Hidden rooms stay off the public list. Guests join with an invite link (?invite=); add a password if the room is locked. Close room to new members blocks all new joins, including invites; people already in stay. The creator can rejoin with recovery or manage token."
}
},
{
"@type": "Question",
"name": "How long do messages stay?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Message expiry is required: 30 min to 1 week (default 8 h). There is no forever option. Each room keeps at most 500 messages (oldest trimmed). A creator-pinned message is kept until it expires or the room is wiped. Expired rows are removed with secure_delete."
}
},
{
"@type": "Question",
"name": "Is Tor Chat free? Does it need port forwarding?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Yes. The prebuilt binary release is free (self-hosted, Server Manager on Windows, macOS or Ubuntu Desktop, or Docker). No port forwarding needed: Tor Hidden Service handles inbound connections via rendezvous; the host only needs outbound Tor access. Works behind NAT, CGNAT, and firewalls."
}
},
{
"@type": "Question",
"name": "My .onion address won’t open: how do I fix it?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Ensure the Hidden Service is running in Server Manager, copy the full .onion link again, and open it only in Tor Browser. If the page fails to load, wait for Tor to bootstrap, try a new circuit, and verify the host PC is online with ChatServer still running."
}
},
{
"@type": "Question",
"name": "Does Tor Chat protect the onion service from DoS?",
"acceptedAnswer": {
"@type": "Answer",
"text": "The Hidden Service enables Tor intro-point rate limits and Equi-X Proof-of-Work on Tor 0.4.8 or newer (man-page defaults; PoW stays idle until overload), plus per-circuit stream limits. This follows Tor Project onion DoS guidance. It does not use client IPs. Tor Browser includes the PoW client. A well-funded network-wide attack can still hurt availability."
}
},
{
"@type": "Question",
"name": "Which operating systems can I host Tor Chat on?",
"acceptedAnswer": {
"@type": "Answer",
"text": "Tor Chat runs on Windows, macOS and Ubuntu Desktop. Ubuntu Server support is in progress and will come later. Guests only need Tor Browser to open the chat."
}
},
{
"@type": "Question",
"name": "Is the public Feed encrypted?",
"acceptedAnswer": {
"@type": "Answer",
"text": "No. The optional Feed is public plain text. Anyone with the .onion address can read it, so do not post secrets. It is off by default, stored encrypted on the server disk, and moderated by the server owner. Chat rooms are different: their messages are end-to-end encrypted."
}
}
]
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://www.dev-offcode.com/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Tor Chat",
"item": "https://www.dev-offcode.com/TorChatPage.html"
}
]
}
</script>
<!-- Video Schema: product overview -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "VideoObject",
"name": "OffCode Tor Chat product overview",
"description": "Product overview of OffCode Tor Chat: self-hosted Tor Hidden Service chat with browser WebCrypto AES-GCM, password E2EE rooms, and Server Manager.",
"thumbnailUrl": "https://img.youtube.com/vi/BIefF56ShPc/maxresdefault.jpg",
"uploadDate": "2026-07-22",
"embedUrl": "https://www.youtube.com/embed/BIefF56ShPc",
"contentUrl": "https://www.youtube.com/watch?v=BIefF56ShPc"
}
</script>
<!-- Video Schema: setup walkthrough -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "VideoObject",
"name": "Set up OffCode Tor Chat on the Tor network",
"description": "Here's an example of how easy it is for anyone to set up their own OffCode Tor Chat server on the Tor network, with absolutely no technical knowledge required.",
"thumbnailUrl": "https://img.youtube.com/vi/wLQqCrwd-NM/maxresdefault.jpg",
"uploadDate": "2026-10-10",
"embedUrl": "https://www.youtube.com/embed/wLQqCrwd-NM",
"contentUrl": "https://www.youtube.com/watch?v=wLQqCrwd-NM"
}
</script>
<link rel="stylesheet" href="styles-automation.css?v=3.1.8">
<link rel="stylesheet" href="styles-at-extras.css?v=3.2.7">
<link rel="stylesheet" href="gallery-simple.css?v=3.0.2">
<link rel="stylesheet" href="boot-gate.css?v=1">
<link rel="stylesheet" href="torchat-gate.css?v=1">
<link rel="stylesheet" href="boot-code.css?v=7">
</head>
<body class="at-page">
<noscript>
<style>
#origin-warning { display: none !important; }
html { overflow: auto !important; }
</style>
</noscript>
<div id="origin-warning" class="boot-block origin-warning boot-gate force-motion" role="alertdialog" aria-modal="true" aria-labelledby="gate-tagline" aria-describedby="boot-block-title">
<div class="boot-code force-motion" hidden aria-hidden="true">
<div class="boot-code__snip s1">
<span class="n40">async function deriveRoomKey(pw, salt) {</span>
<span class="n44"> const mat = await crypto.subtle.importKey(</span>
<span class="n42"> "raw", te.encode(pw.normalize("NFC")),</span>
<span class="n35"> "PBKDF2", false, ["deriveKey"])</span>
<span class="n48"> return crypto.subtle.deriveKey({name:"PBKDF2",</span>
<span class="n50"> hash:"SHA-256", iterations:600000, salt}, mat)</span>
</div>
<div class="boot-code__snip s2">
<span class="n39">async function encryptText(key, text) {</span>
<span class="n55"> const iv = crypto.getRandomValues(new Uint8Array(12))</span>
<span class="n42"> const box = await crypto.subtle.encrypt(</span>
<span class="n46"> {name:"AES-GCM", iv, additionalData}, key,</span>
<span class="n35"> new TextEncoder().encode(text))</span>
<span class="n26"> return {iv, cipher: box}</span>
</div>
<div class="boot-code__snip s3">
<span class="n38">async function generateEcdhKeyPair() {</span>
<span class="n47"> const pair = await crypto.subtle.generateKey(</span>
<span class="n45"> {name:"ECDH", namedCurve:"P-256"}, false,</span>
<span class="n19"> ["deriveBits"])</span>
<span class="n52"> const spki = await crypto.subtle.exportKey("spki",</span>
<span class="n19"> pair.publicKey)</span>
</div>
<div class="boot-code__snip s4">
<span class="n42">async function decryptText(key, buf, iv) {</span>
<span class="n45"> if (iv.byteLength !== 12) throw Error("iv")</span>
<span class="n41"> const pt = await crypto.subtle.decrypt(</span>
<span class="n51"> {name:"AES-GCM", iv, additionalData}, key, buf)</span>
<span class="n37"> return new TextDecoder().decode(pt)</span>
<span class="n1">}</span>
</div>
<div class="boot-code__snip s5">
<span class="n41">async function deriveJoinProof(rid, pw) {</span>
<span class="n50"> const mat = await crypto.subtle.importKey("raw",</span>
<span class="n52"> te.encode(pw.normalize("NFC")), "PBKDF2", false,</span>
<span class="n19"> ["deriveBits"])</span>
<span class="n49"> return crypto.subtle.deriveBits({name:"PBKDF2",</span>
<span class="n59"> hash:"SHA-256", iterations:600000, salt:rid}, mat, 256)</span>
</div>
<div class="boot-code__snip s6">
<span class="n52">public static string Encrypt(byte[] key, string p) {</span>
<span class="n49"> var nonce = RandomNumberGenerator.GetBytes(12);</span>
<span class="n25"> var tag = new byte[16];</span>
<span class="n36"> using var g = new AesGcm(key, 16);</span>
<span class="n55"> g.Encrypt(nonce, Encoding.UTF8.GetBytes(p), ct, tag);</span>
<span class="n37"> return Convert.ToBase64String(buf);</span>
</div>
<div class="boot-code__snip s7">
<span class="n54">public static (byte[] pkcs8, byte[] spki) Generate() {</span>
<span class="n48"> var seed = RandomNumberGenerator.GetBytes(32);</span>
<span class="n53"> var priv = new X25519PrivateKeyParameters(seed, 0);</span>
<span class="n37"> var pub = priv.GeneratePublicKey();</span>
<span class="n33"> return (pkInfo.GetDerEncoded(),</span>
<span class="n26"> spki.GetDerEncoded());</span>
</div>
<div class="boot-code__snip s8">
<span class="n52">public static (byte[] Ct, byte[] Tag) AesGcmEncrypt(</span>
<span class="n55"> byte[] key, byte[] nonce, byte[] plain, byte[] aad) {</span>
<span class="n32"> using var g = new AesGcm(key);</span>
<span class="n40"> g.Encrypt(nonce, plain, ct, tag, aad);</span>
<span class="n19"> return (ct, tag);</span>
<span class="n1">}</span>
</div>
</div>
<div class="gate-stage" aria-hidden="true">
<div class="gate-tilt">
<span class="gate-orbit gate-orbit--1"></span>
<span class="gate-orbit gate-orbit--2"></span>
<span class="gate-orbit gate-orbit--3"></span>
<span class="gate-orbit gate-orbit--4"></span>
<span class="gate-orbit gate-orbit--5"></span>
<div class="gate-onion">
<span class="gate-layer gate-layer--3"></span>
<span class="gate-layer gate-layer--2"></span>
<span class="gate-layer gate-layer--1"></span>
<span class="gate-core"></span>
<span class="gate-tie gate-tie--1"></span>
<span class="gate-tie gate-tie--2"></span>
<span class="gate-tie gate-tie--3"></span>
<span class="gate-tie gate-tie--4"></span>
<span class="gate-tie gate-tie--5"></span>
<span class="gate-spark gate-spark--1"></span>
<span class="gate-spark gate-spark--2"></span>
<span class="gate-spark gate-spark--3"></span>
<span class="gate-blip gate-blip--1" aria-hidden="true"></span>
<span class="gate-blip gate-blip--2" aria-hidden="true"></span>
<span class="gate-blip gate-blip--3" aria-hidden="true"></span>
<span class="gate-blip gate-blip--4" aria-hidden="true"></span>
<span class="gate-blip gate-blip--5" aria-hidden="true"></span>
<span class="gate-blip gate-blip--6" aria-hidden="true"></span>
<span class="gate-blip gate-blip--7" aria-hidden="true"></span>
<span class="gate-blip gate-blip--8" aria-hidden="true"></span>
</div>
</div>
</div>
<p id="gate-tagline" class="gate-tagline">Privacy isn't suspect. It's necessary.</p>
<p id="boot-block-title" class="gate-sub">Only runs in Tor Browser, where your identity belongs to you.</p>
<button type="button" id="gate-continue" class="gate-continue">Continue</button>
</div>
<script>
(function () {
var gate = document.getElementById("origin-warning");
var skip = false;
try {
skip = sessionStorage.getItem("tc-gate") === "1";
} catch (e) {}
var hash = location.hash;
if (skip || (hash && hash.length > 1)) {
if (gate) gate.hidden = true;
return;
}
var btn = document.getElementById("gate-continue");
if (btn) {
btn.addEventListener("click", function () {
if (gate) gate.hidden = true;
try {
sessionStorage.setItem("tc-gate", "1");
} catch (e2) {}
});
}
})();
</script>
<button class="mobile-menu-toggle" aria-label="Toggle Menu">
<span class="hamburger"></span>
</button>
<header class="at-nav">
<div class="at-nav__inner">
<aside class="sidebar">
<div class="sidebar-header">
<div class="sidebar-title">OffCode</div>
</div>
<nav class="sidebar-nav">
<ul class="sidebar-menu">
<li class="menu-item">
<a href="index.html" class="menu-link">
<span class="menu-icon at-icon at-icon--home" aria-hidden="true"></span>
<span class="menu-text">Home</span>
</a>
</li>
<li class="menu-item">
<a href="OffCryptAndroid.html" class="menu-link">
<span class="menu-icon at-icon at-icon--phone" aria-hidden="true"></span>
<span class="menu-text">Android app</span>
</a>
</li>
<li class="menu-item">
<a href="OffCryptDesktop.html" class="menu-link">
<span class="menu-icon at-icon at-icon--monitor" aria-hidden="true"></span>
<span class="menu-text">Desktop App</span>
</a>
</li>
<li class="menu-item">
<a href="ScreenLockBuilder.html" class="menu-link">
<span class="menu-icon at-icon at-icon--lock" aria-hidden="true"></span>
<span class="menu-text">Screen Lock Builder</span>
</a>
</li>
<li class="menu-item">
<a href="ScreenLockBuilderPremium.html" class="menu-link">
<span class="menu-icon at-icon at-icon--star" aria-hidden="true"></span>
<span class="menu-text">Premium Builder</span>
</a>
</li>
<li class="menu-item">
<a href="AutomationTool.html" class="menu-link">
<span class="menu-icon at-icon at-icon--cpu" aria-hidden="true"></span>
<span class="menu-text">Automation Tool</span>
</a>
</li>
<li class="menu-item">
<a href="RemoteControl.html" class="menu-link">
<span class="menu-icon at-icon at-icon--cast" aria-hidden="true"></span>
<span class="menu-text">Web Remote Control</span>
</a>
</li>
<li class="menu-item active">
<a href="TorChatPage.html" class="menu-link">
<span class="menu-icon at-icon at-icon--layers" aria-hidden="true"></span>
<span class="menu-text">Tor Chat</span>
</a>
</li>
<li class="menu-item">
<a href="Community.html" class="menu-link">
<span class="menu-icon at-icon at-icon--message" aria-hidden="true"></span>
<span class="menu-text">Community</span>
</a>
</li>
<li class="menu-item">
<a href="https://ko-fi.com/emptyc0de" class="menu-link" target="_blank" rel="noopener noreferrer">
<span class="menu-icon at-icon at-icon--heart" aria-hidden="true"></span>
<span class="menu-text">Donate</span>
</a>
</li>
<li class="menu-item">
<a href="PrivacyPolicy.html" class="menu-link">
<span class="menu-icon at-icon at-icon--shield" aria-hidden="true"></span>
<span class="menu-text">Privacy Policy</span>
</a>
</li>
</ul>
</nav>
<div class="sidebar-footer">
<p class="footer-text">Social media</p>
<div class="sidebar-social-divider"></div>
<div class="sidebar-social">
<a href="https://x.com/OffCryptAndroid" target="_blank" rel="noopener noreferrer"
aria-label="Follow OffCrypt on X">
<span class="sidebar-social-icon at-icon at-icon--x" aria-hidden="true"></span>
<span class="sidebar-social-label">X</span>
</a>
<a href="https://www.instagram.com/off.crypt86/" target="_blank" rel="noopener noreferrer"
aria-label="Follow OffCrypt on Instagram">
<span class="sidebar-social-icon at-icon at-icon--camera" aria-hidden="true"></span>
<span class="sidebar-social-label">Instagram</span>
</a>
<a href="https://www.tiktok.com/@offcrypt0" target="_blank" rel="noopener noreferrer"
aria-label="Follow OffCrypt on TikTok">
<span class="sidebar-social-icon at-icon at-icon--music" aria-hidden="true"></span>
<span class="sidebar-social-label">TikTok</span>
</a>
<a href="https://www.youtube.com/@OffCrypt" target="_blank" rel="noopener noreferrer"
aria-label="Follow OffCrypt on YouTube">
<span class="sidebar-social-icon at-icon at-icon--play" aria-hidden="true"></span>
<span class="sidebar-social-label">YouTube</span>
</a>
<a href="https://bsky.app/profile/off-crypt86.bsky.social" target="_blank" rel="noopener noreferrer"
aria-label="Follow OffCrypt on Bluesky">
<span class="sidebar-social-icon at-icon at-icon--butterfly" aria-hidden="true"></span>
<span class="sidebar-social-label">Bluesky</span>
</a>
<a href="https://www.linkedin.com/in/off-crypt-b94175382/" target="_blank" rel="noopener noreferrer"
aria-label="Follow OffCrypt on LinkedIn">
<span class="sidebar-social-icon at-icon at-icon--briefcase" aria-hidden="true"></span>
<span class="sidebar-social-label">LinkedIn</span>
</a>
</div>
</div>
</aside>
</div>
</header>
<div class="main-wrapper">
<main class="at-main">
<section class="at-hero">
<span class="at-hero__eyebrow">Tor Hidden Service Chat</span>
<h1 class="at-hero__title">OffCode Tor Chat</h1>
<p class="at-hero__subtitle">Self-hosted E2EE chat over Tor Hidden Service</p>
<div class="at-hero__rule" aria-hidden="true"></div>
<p class="at-hero__lead">
Setting up your own self-hosted chat on the <strong>Tor network</strong> has never been this easy.
Guests open your <strong>.onion</strong> link in <strong>Tor Browser</strong>: no port forwarding,
no cloud account, and no complicated setup. OffCode Tor Chat is designed to make secure Tor
Hidden Service hosting simple.
</p>
<p class="at-hero__lead">
Host with <strong>Server Manager</strong> on Windows, macOS or Ubuntu Desktop, or with <strong>Docker</strong>.
Copy the <strong>.onion</strong> URL and send it to your guests. They join in Tor Browser
with a display name only: no accounts, no phone numbers, nothing to install on their side.
</p>
<p class="at-hero__body">
<strong>Password rooms</strong> stay private: the host cannot read the messages.
<strong>Open rooms</strong> are public channels by design. <strong>Hidden rooms</strong>
stay off the public list and use invite links. Creators get room settings in the app
(details under Features). Messages are encrypted in the browser before they reach your
server. How it works and Features cover the encryption design.
</p>
<div class="at-hero__cta-row">
<a href="#pricing" class="at-btn at-btn--primary">Download</a>
<a href="#faq" class="at-btn at-btn--ghost">FAQ</a>
<a href="#security" class="at-btn at-btn--ghost">Security Architecture</a>
<a href="#why-torchat" class="at-btn at-btn--ghost">Why Tor Chat</a>
</div>
<div class="at-hero__chips">
<span class="at-chip"><span class="at-icon at-icon--layers" aria-hidden="true"></span> Tor Hidden Service</span>
<span class="at-chip"><span class="at-icon at-icon--shield" aria-hidden="true"></span> Password E2EE</span>
<span class="at-chip"><span class="at-icon at-icon--repeat" aria-hidden="true"></span> Safety numbers</span>
<span class="at-chip"><span class="at-icon at-icon--database" aria-hidden="true"></span> SQLCipher at rest</span>
<span class="at-chip"><span class="at-icon at-icon--monitor" aria-hidden="true"></span> Server Manager</span>
</div>
</section>
<div id="screenshots" class="torchat-screens">
<p class="features-carousel-hint">Browse screenshots with the arrows or by swiping</p>
<div class="features-carousel" id="torchat-screens-carousel" data-features-carousel>
<button type="button" class="features-carousel-btn prev"
aria-label="Previous screenshot"
aria-controls="torchat-screens-track">
<span aria-hidden="true">‹</span>
</button>
<button type="button" class="features-carousel-btn next"
aria-label="Next screenshot"
aria-controls="torchat-screens-track">
<span aria-hidden="true">›</span>
</button>
<div class="features-carousel-track" id="torchat-screens-track" role="region"
aria-roledescription="carousel" aria-label="Tor Chat screenshots" tabindex="0">
<figure class="at-feature-card screenshot-card">
<div class="screenshot-frame">
<img src="TorChat1.png" width="1200" height="630"
alt="OffCode TorChat lobby, rooms list, create room, and Copper/Forge UI"
loading="eager" decoding="async"
data-full="TorChat1.png"
data-title="Lobby: rooms list, create/join, Copper/Forge UI">
</div>
<figcaption>Lobby: rooms list, create/join, Copper/Forge UI
<span class="screenshot-zoom-hint">Click to enlarge</span>
</figcaption>
</figure>
<figure class="at-feature-card screenshot-card">
<div class="screenshot-frame">
<img src="MyRoomsTorChat.png" width="1200" height="630"
alt="OffCode TorChat My rooms: hidden rooms bound to creator recovery"
loading="lazy" decoding="async"
data-full="MyRoomsTorChat.png"
data-title="My rooms: hidden rooms via creator recovery">
</div>
<figcaption>My rooms: hidden rooms via creator recovery
<span class="screenshot-zoom-hint">Click to enlarge</span>
</figcaption>
</figure>
<figure class="at-feature-card screenshot-card">
<div class="screenshot-frame">
<img src="ProfileTorChat.png" width="1200" height="630"
alt="OffCode TorChat Profile: recovery code and display settings"
loading="lazy" decoding="async"
data-full="ProfileTorChat.png"
data-title="Profile: recovery code and display settings">
</div>
<figcaption>Profile: recovery code and display settings
<span class="screenshot-zoom-hint">Click to enlarge</span>
</figcaption>
</figure>
<figure class="at-feature-card screenshot-card">
<div class="screenshot-frame">
<img src="TorPrivateChat.png" width="1200" height="630"
alt="OffCode TorChat private password room: encrypted messaging"
loading="lazy" decoding="async"
data-full="TorPrivateChat.png"
data-title="Private chat: password room E2EE messaging">
</div>
<figcaption>Private chat: password room E2EE messaging
<span class="screenshot-zoom-hint">Click to enlarge</span>
</figcaption>
</figure>
<figure class="at-feature-card screenshot-card">
<div class="screenshot-frame">
<img src="RoomSettings.png" width="1200" height="630"
alt="OffCode TorChat room Settings: close joins, disable attachments, slow mode, lock later, delete"
loading="lazy" decoding="async"
data-full="RoomSettings.png"
data-title="Room Settings: creator controls">
</div>
<figcaption>Room Settings: close joins, disable attachments, slow mode, lock / delete
<span class="screenshot-zoom-hint">Click to enlarge</span>
</figcaption>
</figure>
<figure class="at-feature-card screenshot-card">
<div class="screenshot-frame">
<img src="RoomSettings2.png" width="1200" height="630"
alt="OffCode TorChat room Settings: max members, announcement read-only, and more creator options"
loading="lazy" decoding="async"
data-full="RoomSettings2.png"
data-title="Room Settings: max members and announcement mode">
</div>
<figcaption>Room Settings: max members, announcement / read-only, more options
<span class="screenshot-zoom-hint">Click to enlarge</span>
</figcaption>
</figure>
</div>
<div class="features-carousel-dots" role="tablist"
aria-label="Screenshot slides"></div>
</div>
</div>
<div class="product-videos">
<div class="at-hero__video">
<iframe src="https://www.youtube.com/embed/BIefF56ShPc"
title="OffCode Tor Chat product overview"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen></iframe>
</div>
<figure class="torchat-setup-video">
<div class="at-hero__video">
<iframe src="https://www.youtube.com/embed/wLQqCrwd-NM"
title="Set up OffCode Tor Chat on the Tor network"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen></iframe>
</div>
<figcaption class="torchat-setup-video__caption">Here's an example of how easy it is for anyone to
set up their own OffCode Tor Chat server on the Tor network, with absolutely no
technical knowledge required.</figcaption>
</figure>
</div>
<section id="how-it-works" class="at-section">
<div class="at-section__header">
<h2 class="at-section__title">How it works</h2>
<p class="at-section__subtitle">Browser encrypts first: password rooms stay server-blind; open rooms are public by design</p>
</div>
<div class="hiw-demo force-motion is-playing" id="hiw-demo" data-hiw-demo aria-label="Animated explanation of TorChat encryption flow">
<p class="hiw-demo__kicker">See it in motion</p>
<p class="hiw-demo__question">What leaves your browser: plaintext or ciphertext?</p>
<div class="hiw-stage" aria-hidden="true">
<div class="hiw-zone hiw-zone--client">
<span class="hiw-zone__badge">Client</span>
<h4>Tor Browser</h4>
<div class="hiw-msg-slot">
<div class="hiw-bubble hiw-bubble--plain">“Hello from Tor”</div>
<div class="hiw-bubble hiw-bubble--cipher">a8f3…c91e · AES-GCM</div>
</div>
<div class="hiw-chips">
<span class="hiw-chip hiw-chip--kdf">PBKDF2-v3 · 600k</span>
<span class="hiw-chip hiw-chip--aes">AES-256-GCM</span>
</div>
</div>
<div class="hiw-tor">
<div class="hiw-tor__rail"></div>
<div class="hiw-packet"></div>
<div class="hiw-tor__pill">Tor Network<small>.onion path</small></div>
</div>
<div class="hiw-zone hiw-zone--host">
<span class="hiw-zone__badge">Host</span>
<h4>ChatServer</h4>
<ul class="hiw-host-items">
<li><strong>Receives</strong> ciphertext only</li>
<li><strong>Stores</strong> in SQLCipher</li>
<li><strong>Broadcasts</strong> same blob</li>
<li><strong>Cannot</strong> read locked rooms</li>
</ul>
</div>
</div>
<div class="hiw-caption" aria-live="polite">
<p class="hiw-caption__step">
<strong>1 · Encrypt in the browser.</strong>
Password → <em>PBKDF2-v3 (600k)</em> → room key + join proof.
Message → <em>AES-256-GCM</em>. Plaintext never leaves Tor Browser.
</p>
<p class="hiw-caption__step">
<strong>2 · Travel over Tor.</strong>
Only the ciphertext envelope crosses the <em>.onion</em> path.
The host never sees readable chat text from password rooms.
</p>
<p class="hiw-caption__step hiw-caption__step--host">
<strong>3 · Blind relay + store.</strong>
ChatServer <em>receives</em>, <em>stores</em> (SQLCipher), and
<em>broadcasts</em> the same ciphertext. Locked-room keys stay client-side.
</p>
<p class="hiw-caption__step">
<strong>4 · Room types matter.</strong>
Password rooms = server-blind E2EE.
Open rooms still encrypt, but the key is <em>f(room id)</em>: public by design.
</p>
</div>
</div>
</section>
<section id="features" class="at-section">
<div class="at-section__inner">
<h2 class="at-section__title">Features</h2>
<div class="product-screens">
<p class="features-carousel-hint">Browse features with the arrows or by swiping</p>
<div class="features-carousel" id="torchat-features-carousel" data-features-carousel>
<button type="button" class="features-carousel-btn prev"
aria-label="Previous feature"
aria-controls="torchat-features-track">
<span aria-hidden="true">‹</span>
</button>
<button type="button" class="features-carousel-btn next"
aria-label="Next feature"
aria-controls="torchat-features-track">
<span aria-hidden="true">›</span>
</button>
<div class="features-carousel-track" id="torchat-features-track" role="region"
aria-roledescription="carousel" aria-label="Tor Chat features" tabindex="0">
<div class="at-feature-card">
<div class="feature-icon at-icon at-icon--layers" aria-hidden="true"></div>
<h3>Tor-only hosting</h3>
<p><strong>Server Manager:</strong> ChatServer binds <code>127.0.0.1</code> only.
Tor Expert Bundle publishes a v3 <code>.onion</code> and forwards VirtualPort 80
(and the app port) to localhost. <strong>Docker:</strong> ChatServer listens on
<code>0.0.0.0:8080</code> inside the compose network only (no host <code>ports:</code>);
Tor HS is the only public path.</p>
<ul class="at-feature-list">
<li><code>HiddenServiceVersion 3</code>, <code>ClientOnly 1</code>, <code>SafeLogging 1</code></li>
<li>SOCKS disabled on the HS process (<code>SOCKSPort 0</code>)</li>
<li>Intro DoS + HS Proof-of-Work when Tor is ≥ 0.4.8 (<code>HiddenServiceEnableIntroDoSDefense</code>, <code>HiddenServicePoWDefensesEnabled</code>; man-page rates; PoW idle until overload)</li>
<li>Open in Tor Browser: <code>http://ADDRESS.onion/</code></li>
<li>Fail-closed overlay unless the host is loopback (<code>localhost</code> / <code>127.0.0.1</code> / <code>::1</code>) or a v3 <code>.onion</code>, with <code>isSecureContext</code> + <code>crypto.subtle</code></li>
</ul>
</div>
<div class="at-feature-card">
<div class="feature-icon at-icon at-icon--shield" aria-hidden="true"></div>
<h3>Browser encryption (pbkdf2-v3)</h3>
<p>WebCrypto PBKDF2-HMAC-SHA256 (<strong>600 000</strong> iterations) → AES-256-GCM.
Separate v3 KDF domains for join proof vs room key on locked rooms so the server cannot
derive the message key from join material.</p>
<ul class="at-feature-list">
<li>AAD binds <code>encryptionVersion|roomId|senderId|nonce|clientSentAtUnixMs</code></li>
<li><strong>Locked:</strong> password stays in the browser: server-blind E2EE</li>
<li><strong>Open:</strong> key from <code>torchat-open-material-v3:{roomId}</code>: public by design</li>
<li>Server stores join-proof hash as <code>250000:salt:hash</code> (250k PBKDF2)</li>
<li>1:1 ECDH hybrid + safety number (8×5 decimal, 128-bit) for peer verify / TOFU</li>
<li>No Double Ratchet yet; prefer short message retention if passwords may leak</li>
</ul>
</div>
<div class="at-feature-card">
<div class="feature-icon at-icon at-icon--message" aria-hidden="true"></div>
<h3>Rooms, invites & TTL</h3>
<p>Open, password-protected, and/or hidden rooms with invite links and required per-message lifetime.</p>
<ul class="at-feature-list">
<li>No accounts: guests join with a display name only</li>
<li>Hidden rooms: not listed; join via <code>?invite=</code> (+ password if locked)</li>
<li><strong>Message expiry required:</strong> 30 min … 1 week (default 8 h). No forever option</li>
<li>E2EE FileSeal attachments (max 32 MiB): chunked AES-GCM PUT + HMAC
<code>X-Transfer-Token</code>; compact v3 manifest via <code>SendEncrypted</code>.
Images strip EXIF/XMP in the browser before encrypt (no canvas when possible)</li>
<li>Profile: hide date/time stamps; pick message time zone (Tor often shows UTC)</li>
<li>Unique <code>senderId</code> per room; server binds send identity to the session</li>
</ul>
</div>
<div class="at-feature-card">
<div class="feature-icon at-icon at-icon--cpu" aria-hidden="true"></div>
<h3>Creator Settings</h3>
<p>Only the room creator sees Settings (manage token and/or Profile recovery code).</p>
<ul class="at-feature-list">
<li><strong>Close room to new members</strong>: blocks all joins (including invites); members stay; the creator can rejoin with recovery / manage token</li>
<li><strong>Max members</strong>: optional cap (2–50); new joins rejected when full; lowering keeps people already in</li>
<li><strong>Announcement / read-only</strong>: only the creator can send messages and files; members see a locked composer</li>
<li><strong>Disable file and image sending</strong>: text only when on; already-received attachments stay visible</li>
<li><strong>Slow mode</strong>: send cooldown 1 min … 4 h (applies to everyone, including creator)</li>
<li><strong>Lock later</strong>: set a password once on an open room; wipes open history</li>
<li><strong>Pin message</strong>: creator pins one ciphertext envelope (in-app menu); TTL still applies; 500-cap keeps the pinned row</li>
<li><strong>Delete this room</strong>: same wipe path as Server Manager room delete</li>
<li><strong>Creator recovery code</strong>: binds rooms you create; <strong>My rooms</strong> lists hidden; Copy / Restore</li>
</ul>
</div>
<div class="at-feature-card">
<div class="feature-icon at-icon at-icon--database" aria-hidden="true"></div>
<h3>Ciphertext storage</h3>
<p>SQLCipher database under LocalAppData (Windows) or Docker volume. DPAPI / env key.
Parameterized queries. Plaintext is never written to disk.</p>
<ul class="at-feature-list">
<li>Rooms + ciphertext envelopes only (<code>encryption_version</code>: pbkdf2-v3 / ecdh-hybrid-v3)</li>
<li>Max 500 messages per room (oldest trimmed; pinned row kept until TTL or wipe)</li>
<li>Expired rows: <code>secure_delete</code> + WAL checkpoint / VACUUM</li>
<li>Replay fingerprints + restart-gap reject via <code>server_meta</code></li>
</ul>
</div>
<div class="at-feature-card">
<div class="feature-icon at-icon at-icon--shield" aria-hidden="true"></div>
<h3>Tor Browser-friendly UI</h3>
<p>Copper/Forge web UI designed for onion use: no third-party CDNs, self-hosted fonts, SRI
on scripts.</p>
<ul class="at-feature-list">
<li>Strict CSP (<code>script-src</code> / <code>connect-src 'self'</code>)</li>
<li>CORS locked to your onion (<code>TORCHAT_ALLOWED_ONION</code> + live admin POST). Fail-closed: empty allowlist denies every <code>.onion</code> origin until locked</li>
<li><code>tools/verify-sri.ps1</code> checks <code>index.html</code> hashes vs wwwroot JS</li>
<li>No IndexedDB / WebRTC; creator recovery may use <code>localStorage</code> (not an E2EE secret)</li>
<li>Fail-closed boot overlay unless loopback or v3 <code>.onion</code> (plus Web Crypto)</li>
<li>Three self-hosted fonts; gzip HTML/CSS/JS only</li>
<li>Leave / ForceLeave clears the on-screen transcript (not a logout; recovery codes stay)</li>
<li>DOM built with <code>textContent</code> / <code>createElement</code></li>
</ul>
</div>
</div>
<div class="features-carousel-dots" role="tablist"
aria-label="Feature slides"></div>
</div>
</div>
</div>
</section>
<section id="server-manager" class="at-section">
<div class="at-section__inner">
<h2 class="at-section__title">Server Manager</h2>
<p class="at-section__subtitle">Host console for your Tor chat stack (Windows, macOS, Ubuntu Desktop)</p>
<p class="at-prose">
<strong>Server Manager</strong> is the host-side control panel: it starts and stops ChatServer
plus a Tor v3 Hidden Service, shows the <code>.onion</code> URL to share, optionally enables
Full Vanguards, and lets you administer rooms and storage. The host never receives plaintext from
clients; password-room keys stay in browsers. Open-room keys are derivable from the room id by design.
</p>
<div class="product-screens">
<p class="features-carousel-hint">Browse screenshots with the arrows or by swiping</p>
<div class="features-carousel" id="torchat-manager-carousel" data-features-carousel>
<button type="button" class="features-carousel-btn prev"
aria-label="Previous Server Manager screenshot"
aria-controls="torchat-manager-track">
<span aria-hidden="true">‹</span>
</button>
<button type="button" class="features-carousel-btn next"
aria-label="Next Server Manager screenshot"
aria-controls="torchat-manager-track">
<span aria-hidden="true">›</span>
</button>
<div class="features-carousel-track" id="torchat-manager-track" role="region"
aria-roledescription="carousel" aria-label="Server Manager screenshots" tabindex="0">
<figure class="at-feature-card screenshot-card">
<div class="screenshot-frame">
<img src="TorManager1.png" width="960" height="540"
alt="TorChat Server Manager - Dashboard with ChatServer start and onion URL"
loading="lazy" decoding="async"
data-full="TorManager1.png"
data-title="Dashboard: start/stop stack and copy onion URL">
</div>
<figcaption>Dashboard: start/stop stack and copy onion URL
<span class="screenshot-zoom-hint">Click to enlarge</span>
</figcaption>
</figure>
<figure class="at-feature-card screenshot-card">
<div class="screenshot-frame">
<img src="TorManager2.png" width="960" height="540"
alt="TorChat Server Manager - Settings with live room list and remove room"
loading="lazy" decoding="async"
data-full="TorManager2.png"
data-title="Settings: public rooms, Admin key, live rooms, wipe SQL data">
</div>
<figcaption>Settings: public rooms, Admin key, live rooms, wipe SQL data
<span class="screenshot-zoom-hint">Click to enlarge</span>
</figcaption>
</figure>
<figure class="at-feature-card screenshot-card">
<div class="screenshot-frame">
<img src="TorManagerFeed.png" width="960" height="540"
alt="TorChat Server Manager - Feed tab with Enable feed and post moderation"
loading="lazy" decoding="async"
data-full="TorManagerFeed.png"
data-title="Feed: Enable feed and remove selected post">
</div>
<figcaption>Feed: Enable feed and remove selected post
<span class="screenshot-zoom-hint">Click to enlarge</span>
</figcaption>
</figure>
</div>
<div class="features-carousel-dots" role="tablist"
aria-label="Server Manager screenshot slides"></div>
</div>
</div>
<ul class="at-prose">
<li>Start ChatServer first (Production, waits for <code>/api/health</code>), then enable Tor</li>
<li>Copy and share the published <code>.onion</code> URL</li>
<li>Optional Full Vanguards checkbox (Python + Stem) for long-lived HS hardening</li>
<li>Locks CORS to your onion: <code>TORCHAT_ALLOWED_ONION</code> on start + live
<code>POST /api/admin/cors-onion</code> (no ChatServer restart). Fail-closed until the live
hostname is set (loopback always allowed)</li>
<li><strong>Create room:</strong> add an open public room by name. You own it with the
<strong>Admin key</strong> (copy into onion Profile Restore; the <strong>i</strong> button
explains this). The key is not stored in <code>config.ini</code></li>
<li><strong>Maximum Number of Rooms:</strong> caps how many public rooms people can create
from the onion page</li>
<li><strong>Disable Room Creation:</strong> checkbox; when on, nobody can create rooms from
the onion site. You can still Create room from Server Manager</li>
<li><strong>Start server on startup:</strong> Save writes Windows logon start (HKCU Run).
ChatServer (and Tor if you saved it on) then start without extra dialogs</li>
<li><strong>Enable feed:</strong> optional public Feed tab on the onion site (off by default).
Moderate with <strong>Remove selected post</strong></li>
<li>Live room list (room id, locked flag, online count only, no message content)</li>
<li>Remove a selected room in realtime: connected users are forced back to the lobby;
ciphertext for that room is deleted with <code>secure_delete</code> + VACUUM</li>
<li><strong>Remove SQL data:</strong> securely overwrites (then deletes) the SQLCipher DB, WAL/SHM,
and <code>db.key.dpapi</code>; optional checkbox also wipes Tor Hidden Service keys
(<code>.onion</code> identity). Does not delete the Admin key</li>
<li><strong>Generate new address:</strong> securely wipes HS keys and publishes a new
<code>.onion</code> (chat DB unchanged). Works only while Tor and Vanguards are stopped; also clears Tor's saved state and Vanguards' state. Does not start Tor automatically</li>
<li><strong>Reset guard data:</strong> Connection tab. Only when Tor and Vanguards are both stopped. Clears Tor's saved entry-point data (a backup is kept). Optional checkbox also clears Vanguards' data. Your .onion address and keys stay the same</li>