Merge remote-tracking branch 'upstream/dev' into feat/obsidian-plugin

This commit is contained in:
Anish Sarkar 2026-04-21 23:35:22 +05:30
commit 16ea8e2401
12 changed files with 100 additions and 20 deletions

View file

@ -22,6 +22,7 @@ on:
permissions:
contents: write
id-token: write
jobs:
build:
@ -58,6 +59,22 @@ jobs:
fi
echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT"
- name: Detect Windows signing eligibility
id: sign
shell: bash
run: |
# Sign Windows builds only on production v* tags (not beta-v*, not workflow_dispatch).
# This matches the single OIDC federated credential configured in Entra ID.
if [ "${{ matrix.os }}" = "windows-latest" ] \
&& [ "${{ github.event_name }}" = "push" ] \
&& [[ "$GITHUB_REF" == refs/tags/v* ]]; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
echo "Windows signing: ENABLED (v* tag on windows-latest)"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "Windows signing: skipped"
fi
- name: Setup pnpm
uses: pnpm/action-setup@v5
@ -98,7 +115,31 @@ jobs:
- name: Package & Publish
shell: bash
run: pnpm exec electron-builder ${{ matrix.platform }} --config electron-builder.yml --publish ${{ inputs.publish || 'always' }} -c.extraMetadata.version=${{ steps.version.outputs.VERSION }}
run: |
CMD=(pnpm exec electron-builder ${{ matrix.platform }} \
--config electron-builder.yml \
--publish "${{ inputs.publish || 'always' }}" \
-c.extraMetadata.version="${{ steps.version.outputs.VERSION }}")
if [ "${{ steps.sign.outputs.enabled }}" = "true" ]; then
CMD+=(-c.win.azureSignOptions.publisherName="$WINDOWS_PUBLISHER_NAME")
CMD+=(-c.win.azureSignOptions.endpoint="$AZURE_CODESIGN_ENDPOINT")
CMD+=(-c.win.azureSignOptions.codeSigningAccountName="$AZURE_CODESIGN_ACCOUNT")
CMD+=(-c.win.azureSignOptions.certificateProfileName="$AZURE_CODESIGN_PROFILE")
fi
"${CMD[@]}"
working-directory: surfsense_desktop
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WINDOWS_PUBLISHER_NAME: ${{ vars.WINDOWS_PUBLISHER_NAME }}
AZURE_CODESIGN_ENDPOINT: ${{ vars.AZURE_CODESIGN_ENDPOINT }}
AZURE_CODESIGN_ACCOUNT: ${{ vars.AZURE_CODESIGN_ACCOUNT }}
AZURE_CODESIGN_PROFILE: ${{ vars.AZURE_CODESIGN_PROFILE }}
# Service principal credentials for Azure.Identity EnvironmentCredential used by the
# TrustedSigning PowerShell module. Only populated when signing is enabled.
# electron-builder 26 does not yet support OIDC federated tokens for Azure signing,
# so we fall back to client-secret auth. Rotate AZURE_CLIENT_SECRET before expiry.
AZURE_TENANT_ID: ${{ steps.sign.outputs.enabled == 'true' && secrets.AZURE_TENANT_ID || '' }}
AZURE_CLIENT_ID: ${{ steps.sign.outputs.enabled == 'true' && secrets.AZURE_CLIENT_ID || '' }}
AZURE_CLIENT_SECRET: ${{ steps.sign.outputs.enabled == 'true' && secrets.AZURE_CLIENT_SECRET || '' }}

View file

@ -1 +1 @@
0.0.16
0.0.19

View file

@ -114,8 +114,19 @@ def _surfsense_error_handler(request: Request, exc: SurfSenseError) -> JSONRespo
def _http_exception_handler(request: Request, exc: HTTPException) -> JSONResponse:
"""Wrap FastAPI/Starlette HTTPExceptions into the standard envelope."""
"""Wrap FastAPI/Starlette HTTPExceptions into the standard envelope.
5xx sanitization policy:
- 500 responses are sanitized (replaced with ``GENERIC_5XX_MESSAGE``) because
they usually wrap raw internal errors and may leak sensitive info.
- Other 5xx statuses (501, 502, 503, 504, ...) are raised explicitly by
route code to communicate a specific, user-safe operational state
(e.g. 503 "Page purchases are temporarily unavailable."). Those details
are preserved so the frontend can render them, but the error is still
logged server-side.
"""
rid = _get_request_id(request)
should_sanitize = exc.status_code == 500
# Structured dict details (e.g. {"code": "CAPTCHA_REQUIRED", "message": "..."})
# are preserved so the frontend can parse them.
@ -130,9 +141,9 @@ def _http_exception_handler(request: Request, exc: HTTPException) -> JSONRespons
exc.status_code,
message,
)
if exc.status_code == 500:
message = GENERIC_5XX_MESSAGE
err_code = "INTERNAL_ERROR"
if should_sanitize:
message = GENERIC_5XX_MESSAGE
err_code = "INTERNAL_ERROR"
body = {
"error": {
"code": err_code,
@ -159,8 +170,8 @@ def _http_exception_handler(request: Request, exc: HTTPException) -> JSONRespons
exc.status_code,
detail,
)
if exc.status_code == 500:
detail = GENERIC_5XX_MESSAGE
if should_sanitize:
detail = GENERIC_5XX_MESSAGE
code = _status_to_code(exc.status_code, detail)
return _build_error_response(exc.status_code, detail, code=code, request_id=rid)

View file

@ -1,6 +1,6 @@
[project]
name = "surf-new-backend"
version = "0.0.16"
version = "0.0.19"
description = "SurfSense Backend"
requires-python = ">=3.12"
dependencies = [

View file

@ -70,6 +70,20 @@ def _make_test_app():
async def raise_http_500():
raise HTTPException(status_code=500, detail="secret db password leaked")
@app.get("/http-503")
async def raise_http_503():
raise HTTPException(
status_code=503,
detail="Page purchases are temporarily unavailable.",
)
@app.get("/http-502")
async def raise_http_502():
raise HTTPException(
status_code=502,
detail="Unable to create Stripe checkout session.",
)
@app.get("/surfsense-connector")
async def raise_connector():
raise ConnectorError("GitHub API returned 401")
@ -184,6 +198,20 @@ class TestHTTPExceptionHandler:
assert body["error"]["message"] == GENERIC_5XX_MESSAGE
assert body["error"]["code"] == "INTERNAL_ERROR"
def test_503_preserves_detail(self, client):
# Intentional 503s (e.g. feature flag off) must surface the developer
# message so the frontend can render actionable copy.
body = _assert_envelope(client.get("/http-503"), 503)
assert (
body["error"]["message"] == "Page purchases are temporarily unavailable."
)
assert body["error"]["message"] != GENERIC_5XX_MESSAGE
def test_502_preserves_detail(self, client):
body = _assert_envelope(client.get("/http-502"), 502)
assert body["error"]["message"] == "Unable to create Stripe checkout session."
assert body["error"]["message"] != GENERIC_5XX_MESSAGE
# ---------------------------------------------------------------------------
# SurfSenseError hierarchy

View file

@ -7947,7 +7947,7 @@ wheels = [
[[package]]
name = "surf-new-backend"
version = "0.0.16"
version = "0.0.19"
source = { editable = "." }
dependencies = [
{ name = "alembic" },

View file

@ -1,7 +1,7 @@
{
"name": "surfsense_browser_extension",
"displayName": "Surfsense Browser Extension",
"version": "0.0.16",
"version": "0.0.19",
"description": "Extension to collect Browsing History for SurfSense.",
"author": "https://github.com/MODSetter",
"engines": {

View file

@ -1,6 +1,6 @@
{
"name": "surfsense-desktop",
"version": "0.0.16",
"version": "0.0.19",
"description": "SurfSense Desktop App",
"main": "dist/main.js",
"scripts": {

View file

@ -41,7 +41,7 @@ async function getAllModels(): Promise<AnonModel[]> {
function buildSeoTitle(model: AnonModel): string {
if (model.seo_title) return model.seo_title;
return `${model.name} Free Online Without Login | No Sign-Up AI Chat | SurfSense`;
return `Chat with ${model.name} Free, No Login | SurfSense`;
}
function buildSeoDescription(model: AnonModel): string {

View file

@ -18,7 +18,7 @@ import type { AnonModel } from "@/contracts/types/anonymous-chat.types";
import { BACKEND_URL } from "@/lib/env-config";
export const metadata: Metadata = {
title: "ChatGPT Free Online Without Login | Chat GPT No Login, Claude AI Free | SurfSense",
title: "Free AI Chat, No Login Required | SurfSense",
description:
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more for free. No sign-up required. Open source NotebookLM alternative with free AI chat and document Q&A.",
keywords: [
@ -67,7 +67,7 @@ export const metadata: Metadata = {
canonical: "https://surfsense.com/free",
},
openGraph: {
title: "ChatGPT Free Online Without Login | Claude AI Free No Login | SurfSense",
title: "Free AI Chat, No Login Required | SurfSense",
description:
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and 100+ AI models. Open source NotebookLM alternative.",
url: "https://surfsense.com/free",
@ -84,7 +84,7 @@ export const metadata: Metadata = {
},
twitter: {
card: "summary_large_image",
title: "ChatGPT Free Online Without Login | Claude AI Free No Login | SurfSense",
title: "Free AI Chat, No Login Required | SurfSense",
description:
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more. No sign-up needed.",
images: ["/og-image.png"],

View file

@ -45,7 +45,7 @@ export const metadata: Metadata = {
alternates: {
canonical: "https://surfsense.com",
},
title: "SurfSense - NotebookLM Alternative | Free ChatGPT & Claude AI",
title: "SurfSense Open Source, Privacy-Focused NotebookLM Alternative for Teams",
description:
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.",
keywords: [
@ -87,7 +87,7 @@ export const metadata: Metadata = {
"SurfSense",
],
openGraph: {
title: "SurfSense - NotebookLM Alternative | Free ChatGPT & Claude AI",
title: "SurfSense Open Source, Privacy-Focused NotebookLM Alternative for Teams",
description:
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude, and any AI model for free.",
url: "https://surfsense.com",
@ -105,7 +105,7 @@ export const metadata: Metadata = {
},
twitter: {
card: "summary_large_image",
title: "SurfSense - NotebookLM Alternative | Free ChatGPT & Claude AI",
title: "SurfSense Open Source, Privacy-Focused NotebookLM Alternative for Teams",
description:
"Open source NotebookLM alternative for teams with no data limits. Use ChatGPT, Claude AI, and any AI model for free.",
creator: "@SurfSenseAI",

View file

@ -1,6 +1,6 @@
{
"name": "surfsense_web",
"version": "0.0.16",
"version": "0.0.19",
"private": true,
"description": "SurfSense Frontend",
"scripts": {