Skip to content

Commit 13b6e00

Browse files
Merge branch 'main' into pr-1099
2 parents 5969ef9 + f28228c commit 13b6e00

23 files changed

Lines changed: 3027 additions & 811 deletions

‎backend/app.py‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import sys
55
import os
66
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
7+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
78

89
from flask import Flask, request, jsonify, redirect, url_for
910
from flask_cors import CORS
@@ -778,13 +779,6 @@ def _validate_jwt_secret_startup():
778779

779780
_validate_jwt_secret_startup()
780781

781-
@app.route('/api/v1/config', methods=['GET'])
782-
def get_config():
783-
"""Serve public configuration values like Google Books API Key."""
784-
return jsonify({
785-
"google_books_key": os.getenv('GOOGLE_BOOKS_API_KEY', ''),
786-
"google_books_key_secondary": os.getenv('GOOGLE_BOOKS_API_KEY_SECONDARY', '')
787-
})
788782

789783
# =====================================================================
790784
# ENDPOINT: CSRF Token Retrieval

‎backend/sanitizer.py‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,17 @@
1111
from markupsafe import escape
1212
from typing import Any, Dict, List, Optional, Union
1313
import bleach
14+
import unicodedata
15+
16+
1417

1518
logger = logging.getLogger(__name__)
1619

20+
def normalize_unicode(text: str) -> str:
21+
"""Strip homoglyph and invisible control characters."""
22+
text = unicodedata.normalize("NFC", text)
23+
return "".join(c for c in text if unicodedata.category(c) not in ("Cc", "Cf"))
24+
1725
# Patterns for potential prompt injection or malicious commands
1826
PROMPT_INJECTION_PATTERNS = [
1927
r"(?i)ignore\s+all\s+previous\s+instructions",
@@ -72,6 +80,8 @@ def sanitize_string(text: Optional[str], max_len: int = 5000, strip_html: bool =
7280

7381
# Convert to string
7482
text = str(text).strip()
83+
text = normalize_unicode(text)
84+
7585

7686
if not text:
7787
return ""
@@ -115,8 +125,7 @@ def sanitize_string(text: Optional[str], max_len: int = 5000, strip_html: bool =
115125
strip=False # Convert unknown tags to entities
116126
)
117127

118-
# Layer 3: Additional HTML escaping for extra safety
119-
text = html.escape(text)
128+
120129

121130
# Layer 4: Limit length
122131
if len(text) > max_len:
@@ -158,8 +167,7 @@ def sanitize_for_ai(text: Optional[str]) -> str:
158167
logger.warning(f"Possible prompt injection detected: {detected_patterns[:2]}")
159168
clean_text = f"[User Input - Content Flagged]: {clean_text}"
160169

161-
return clean_text
162-
170+
return f"<user_input>{clean_text}</user_input>"
163171

164172

165173
def sanitize_payload(data: Union[Dict, List, str, Any]) -> Any:
@@ -179,7 +187,7 @@ def sanitize_payload(data: Union[Dict, List, str, Any]) -> Any:
179187
Sanitized version of the payload
180188
"""
181189
if isinstance(data, dict):
182-
return {k: sanitize_payload(v) for k, v in data.items()}
190+
return {sanitize_string(str(k)): sanitize_payload(v) for k, v in data.items()}
183191
elif isinstance(data, list):
184192
return [sanitize_payload(i) for i in data]
185193
elif isinstance(data, str):
-6.03 MB
Binary file not shown.
-523 KB
Binary file not shown.

‎frontend/assets/sounds/fire.mp3‎

-13.8 MB
Binary file not shown.

‎frontend/assets/sounds/rain.mp3‎

2.97 MB
Binary file not shown.
1.15 MB
Binary file not shown.

‎frontend/css/contributing.css‎

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,3 +547,158 @@ body {
547547
padding: 1rem;
548548
}
549549
}
550+
551+
/* =========================
552+
DARK MODE
553+
========================= */
554+
555+
[data-theme="night"] {
556+
--landing-bg: #111111;
557+
--landing-surface: #1b1b1b;
558+
--landing-surface-strong: #222222;
559+
--landing-border: rgba(255,255,255,0.08);
560+
--landing-shadow: 0 10px 30px rgba(0,0,0,0.5);
561+
--landing-gold: #d4af37;
562+
--landing-wood: #f5f5f5;
563+
--landing-ink: #ffffff;
564+
--landing-muted: #d1d5db;
565+
}
566+
567+
/* BODY */
568+
569+
[data-theme="night"] body {
570+
background:
571+
radial-gradient(circle at top left, rgba(212,175,55,0.08), transparent 24%),
572+
radial-gradient(circle at top right, rgba(127,150,160,0.06), transparent 18%),
573+
#111111;
574+
color: #ffffff;
575+
}
576+
577+
/* HEADER */
578+
579+
[data-theme="night"] .landing-header {
580+
background: rgba(20,20,20,0.9);
581+
border: 1px solid rgba(255,255,255,0.06);
582+
}
583+
584+
/* HERO CARDS */
585+
586+
[data-theme="night"] .guide-hero-copy,
587+
[data-theme="night"] .guide-hero-aside,
588+
[data-theme="night"] .guide-section,
589+
[data-theme="night"] .guide-card,
590+
[data-theme="night"] .guide-list-item,
591+
[data-theme="night"] .guide-hero-stat,
592+
[data-theme="night"] .page-link-card {
593+
background: #1c1c1c !important;
594+
border: 1px solid rgba(255,255,255,0.08);
595+
box-shadow: none;
596+
}
597+
598+
/* TEXT */
599+
600+
[data-theme="night"] h1,
601+
[data-theme="night"] h2,
602+
[data-theme="night"] h3,
603+
[data-theme="night"] strong,
604+
[data-theme="night"] p,
605+
[data-theme="night"] li,
606+
[data-theme="night"] span,
607+
[data-theme="night"] a {
608+
color: #ffffff !important;
609+
}
610+
611+
/* MUTED TEXT */
612+
613+
[data-theme="night"] .guide-note,
614+
[data-theme="night"] .eyebrow-note,
615+
[data-theme="night"] .guide-hero-stat span,
616+
[data-theme="night"] .page-link-card span {
617+
color: #d1d5db !important;
618+
}
619+
620+
/* BUTTONS */
621+
622+
[data-theme="night"] .btn-primary,
623+
[data-theme="night"] .btn-ghost,
624+
[data-theme="night"] .landing-icon-btn {
625+
background: #222222;
626+
color: white;
627+
border: 1px solid rgba(255,255,255,0.08);
628+
}
629+
630+
/* NAVBAR LINKS */
631+
632+
[data-theme="night"] .landing-nav a {
633+
color: #f3f4f6 !important;
634+
}
635+
636+
[data-theme="night"] .landing-nav a:hover {
637+
background: rgba(255,255,255,0.08);
638+
}
639+
640+
/* TOC LINKS */
641+
642+
[data-theme="night"] .toc a {
643+
background: #222222;
644+
color: white;
645+
border: 1px solid rgba(255,255,255,0.08);
646+
}
647+
648+
/* PAGE LINK ICON */
649+
650+
[data-theme="night"] .page-link-card i {
651+
background: rgba(255,255,255,0.08);
652+
color: #ffffff;
653+
}
654+
655+
/* BACK BUTTON */
656+
657+
[data-theme="night"] #backToTop {
658+
background: #222222;
659+
color: white;
660+
}
661+
662+
/* THEME TOGGLE */
663+
664+
[data-theme="night"] #themeToggle {
665+
background: #222222;
666+
color: white;
667+
}
668+
669+
/* MAIN TEXT */
670+
671+
[data-theme="night"] h1,
672+
[data-theme="night"] h2,
673+
[data-theme="night"] h3,
674+
[data-theme="night"] p,
675+
[data-theme="night"] li {
676+
color: #ffffff !important;
677+
}
678+
679+
/* LINKS */
680+
681+
[data-theme="night"] a {
682+
color: #f5f5f5;
683+
}
684+
685+
/* STRONG TEXT */
686+
687+
[data-theme="night"] strong {
688+
color: #ffffff;
689+
}
690+
691+
/* BADGES / PILLS */
692+
693+
[data-theme="night"] .eyebrow-pill,
694+
[data-theme="night"] .eyebrow-note,
695+
[data-theme="night"] .guide-note {
696+
color: #111111 !important;
697+
background: #f3f4f6 !important;
698+
}
699+
700+
/* TOC BUTTONS */
701+
702+
[data-theme="night"] .toc a {
703+
color: #ffffff !important;
704+
}

‎frontend/css/style-responsive.css‎

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,38 @@ input {
3232
min-width: 44px;
3333
}
3434

35+
.main-footer a {
36+
min-height: 0;
37+
min-width: 0;
38+
}
39+
40+
.main-footer .footer-nav a,
41+
.main-footer .footer-legal a {
42+
line-height: 1.35;
43+
}
44+
45+
.main-footer .social-icons a {
46+
min-height: 40px;
47+
min-width: 40px;
48+
}
49+
3550
@media (max-width: 768px) {
3651

3752
button,
3853
a {
3954
min-height: 48px;
4055
min-width: 48px;
4156
}
57+
58+
.main-footer a {
59+
min-height: 0;
60+
min-width: 0;
61+
}
62+
63+
.main-footer .social-icons a {
64+
min-height: 40px;
65+
min-width: 40px;
66+
}
4267
}
4368

4469
/* --- Extra Large Screens (1200px+) --- */
@@ -1135,4 +1160,4 @@ input {
11351160
.sort-select {
11361161
width: 100%;
11371162
}
1138-
}
1163+
}

0 commit comments

Comments
 (0)