Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 20 additions & 11 deletions backend/apps/system/api/assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,53 +40,62 @@ async def info(request: Request, response: Response, session: SessionDep, trans:
# 校验 SQLBOT-EMBEDDED-SIGN 请求头
sign_header = request.headers.get("SQLBOT-EMBEDDED-SIGN")
if not sign_header:
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=''))
raise RuntimeError(trans('i18n_embedded.missing_sign'))

sign_data = await decrypt_embedded_sign(sign_header)
try:
sign_data = await decrypt_embedded_sign(sign_header)
except Exception:
raise RuntimeError(trans('i18n_embedded.invalid_sign'))

# 校验 assistant_id 与 id 参数一致
if str(sign_data.get("assistant_id")) != str(id):
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=''))
raise RuntimeError(trans('i18n_embedded.assistant_id_mismatch'))

# 校验 target(来源域名)是否合法
target = sign_data.get("target", "")

request_origin = request.headers.get("origin") or get_origin_from_referer(request)
if not request_origin:
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=request_origin or ''))
raise RuntimeError(trans('i18n_embedded.missing_origin'))
request_origin = request_origin.rstrip('/')
if not target or target == "null":
target = request_origin
elif target != request_origin:
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or ''))
raise RuntimeError(trans('i18n_embedded.target_origin_mismatch'))

if not origin_match_domain(target, db_model.domain):
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or ''))

# 校验 sign_time 是否在 10 秒内
sign_time_str = sign_data.get("sign_time", "")
sign_time = datetime.fromisoformat(sign_time_str)
try:
sign_time = datetime.fromisoformat(sign_time_str)
except (ValueError, TypeError):
raise RuntimeError(trans('i18n_embedded.sign_time_invalid'))
now_utc = datetime.now(timezone.utc)
sign_time_utc = sign_time.astimezone(timezone.utc)
if abs((now_utc - sign_time_utc).total_seconds()) > 10:
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or ''))
raise RuntimeError(trans('i18n_embedded.sign_time_expired'))

# 校验是否为真实浏览器请求(非自动化工具)
if sign_data.get("webdriver", False):
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or ''))
raise RuntimeError(trans('i18n_embedded.automation_tool_detected'))

# 校验 User-Agent 一致性(签名中的 navigator.userAgent 与请求头一致)
sign_user_agent = sign_data.get("user_agent", "")
request_user_agent = request.headers.get("User-Agent", "")
if sign_user_agent != request_user_agent:
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or ''))
raise RuntimeError(trans('i18n_embedded.user_agent_mismatch'))

# 校验 timezone 与 sign_time 偏移一致性(防时区伪造)
tz_name = sign_data.get("timezone", "")
tz = ZoneInfo(tz_name)
try:
tz = ZoneInfo(tz_name)
except Exception:
raise RuntimeError(trans('i18n_embedded.timezone_mismatch'))
sign_time_naive = sign_time.replace(tzinfo=None)
if tz.utcoffset(sign_time_naive) != sign_time.utcoffset():
raise RuntimeError(trans('i18n_embedded.invalid_origin', origin=target or ''))
raise RuntimeError(trans('i18n_embedded.timezone_mismatch'))

origin = target.rstrip('/')

Expand Down
12 changes: 11 additions & 1 deletion backend/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,17 @@
"i18n_ds_invalid": "Datasource connection is invalid",
"i18n_ds_upload_error": "Upload Failed",
"i18n_embedded": {
"invalid_origin": "Domain name validation failed【{origin}】"
"invalid_origin": "Domain name validation failed【{origin}】",
"missing_sign": "Missing embedded signature (SQLBOT-EMBEDDED-SIGN); cannot verify the embedding party's identity.",
"assistant_id_mismatch": "The assistant ID in the signature does not match the current request; cannot verify the embedding party's identity.",
"missing_origin": "The current page is missing source information (Origin/Referer); cannot verify the embedding party's identity.",
"target_origin_mismatch": "The source domain in the signature does not match the current page domain; cannot verify the embedding party's identity.",
"invalid_sign": "The embedded signature is invalid or corrupted; cannot verify the embedding party's identity.",
"sign_time_expired": "The synchronization delay between the server (SQLBot) and the current page (embedding site) exceeds 10 seconds, causing the connection to be interrupted.",
"sign_time_invalid": "Invalid signature time format; cannot verify the embedding party's identity.",
"automation_tool_detected": "Automated tool access detected. Embedding has been blocked.",
"user_agent_mismatch": "The browser identifier in the signature does not match the current request. Embedding has been blocked.",
"timezone_mismatch": "The timezone information in the signature does not match the signing time. Embedding has been blocked."
},
"i18n_chat": {
"record_id_in_mcp": "Answer ID: "
Expand Down
12 changes: 11 additions & 1 deletion backend/locales/ko-KR.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,17 @@
"i18n_ds_invalid": "데이터 소스 연결이 무효합니다",
"i18n_ds_upload_error": "파일 업로드 실패",
"i18n_embedded": {
"invalid_origin": "도메인 이름 검증 실패 【{origin}】"
"invalid_origin": "도메인 이름 검증 실패 【{origin}】",
"missing_sign": "임베드 서명(SQLBOT-EMBEDDED-SIGN)이 누락되어 임베드 당사자의 신원을 확인할 수 없습니다.",
"assistant_id_mismatch": "서명의 어시스턴트 ID가 현재 요청과 일치하지 않아 임베드 당사자의 신원을 확인할 수 없습니다.",
"missing_origin": "현재 페이지에 출처 정보(Origin/Referer)가 없어 임베드 당사자의 신원을 확인할 수 없습니다.",
"target_origin_mismatch": "서명의 출처 도메인이 현재 페이지 도메인과 일치하지 않아 임베드 당사자의 신원을 확인할 수 없습니다.",
"invalid_sign": "임베드 서명이 유효하지 않거나 손상되어 임베드 당사자의 신원을 확인할 수 없습니다.",
"sign_time_expired": "서버(SQLBot)와 현재 페이지(임베드 사이트) 간 동기화 지연이 10초를 초과하여 연결이 중단되었습니다.",
"sign_time_invalid": "서명 시간 형식이 유효하지 않아 임베드 당사자의 신원을 확인할 수 없습니다.",
"automation_tool_detected": "자동화 도구 접근이 감지되어 임베드를 차단했습니다.",
"user_agent_mismatch": "서명의 브라우저 식별 정보가 현재 요청과 일치하지 않아 임베드를 차단했습니다.",
"timezone_mismatch": "서명의 시간대 정보가 서명 시간과 일치하지 않아 임베드를 차단했습니다."
},
"i18n_chat": {
"record_id_in_mcp": "응답 ID: "
Expand Down
12 changes: 11 additions & 1 deletion backend/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,17 @@
"i18n_ds_invalid": "数据源连接无效",
"i18n_ds_upload_error": "上传文件失败",
"i18n_embedded": {
"invalid_origin": "域名校验失败【{origin}】"
"invalid_origin": "域名校验失败【{origin}】",
"missing_sign": "缺少嵌入签名(SQLBOT-EMBEDDED-SIGN),无法验证嵌入方身份。",
"assistant_id_mismatch": "签名中的助手标识与当前请求不一致,无法验证嵌入方身份。",
"missing_origin": "当前页面缺少来源信息(Origin/Referer),无法验证嵌入方身份。",
"target_origin_mismatch": "签名中的来源域名与当前页面域名不一致,无法验证嵌入方身份。",
"invalid_sign": "嵌入签名无效或已损坏,无法验证嵌入方身份。",
"sign_time_expired": "服务端(SQLBot)与当前页面(嵌入端)的同步延迟超过10秒,导致连接中断。",
"sign_time_invalid": "签名时间格式无效,无法验证嵌入方身份。",
"automation_tool_detected": "检测到自动化工具访问,已阻止嵌入。",
"user_agent_mismatch": "签名中的浏览器标识与当前请求不一致,已阻止嵌入。",
"timezone_mismatch": "签名中的时区信息与签名时间不一致,已阻止嵌入。"
},
"i18n_chat": {
"record_id_in_mcp": "响应ID: "
Expand Down
12 changes: 11 additions & 1 deletion backend/locales/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,17 @@
"i18n_ds_invalid": "數據源連接無效",
"i18n_ds_upload_error": "上傳文件失敗",
"i18n_embedded": {
"invalid_origin": "網域校驗失敗【{origin}】"
"invalid_origin": "網域校驗失敗【{origin}】",
"missing_sign": "缺少嵌入簽名(SQLBOT-EMBEDDED-SIGN),無法驗證嵌入方身分。",
"assistant_id_mismatch": "簽名中的助手標識與當前請求不一致,無法驗證嵌入方身分。",
"missing_origin": "當前頁面缺少來源資訊(Origin/Referer),無法驗證嵌入方身分。",
"target_origin_mismatch": "簽名中的來源網域與當前頁面網域不一致,無法驗證嵌入方身分。",
"invalid_sign": "嵌入簽名無效或已損壞,無法驗證嵌入方身分。",
"sign_time_expired": "服務端(SQLBot)與當前頁面(嵌入端)的同步延遲超過10秒,導致連線中斷。",
"sign_time_invalid": "簽名時間格式無效,無法驗證嵌入方身分。",
"automation_tool_detected": "偵測到自動化工具存取,已阻止嵌入。",
"user_agent_mismatch": "簽名中的瀏覽器標識與當前請求不一致,已阻止嵌入。",
"timezone_mismatch": "簽名中的時區資訊與簽名時間不一致,已阻止嵌入。"
},
"i18n_chat": {
"record_id_in_mcp": "響應ID: "
Expand Down
Loading