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: permissions:
contents: write contents: write
id-token: write
jobs: jobs:
build: build:
@ -58,6 +59,22 @@ jobs:
fi fi
echo "VERSION=$VERSION" >> "$GITHUB_OUTPUT" 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 - name: Setup pnpm
uses: pnpm/action-setup@v5 uses: pnpm/action-setup@v5
@ -98,7 +115,31 @@ jobs:
- name: Package & Publish - name: Package & Publish
shell: bash 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 working-directory: surfsense_desktop
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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: 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) rid = _get_request_id(request)
should_sanitize = exc.status_code == 500
# Structured dict details (e.g. {"code": "CAPTCHA_REQUIRED", "message": "..."}) # Structured dict details (e.g. {"code": "CAPTCHA_REQUIRED", "message": "..."})
# are preserved so the frontend can parse them. # are preserved so the frontend can parse them.
@ -130,9 +141,9 @@ def _http_exception_handler(request: Request, exc: HTTPException) -> JSONRespons
exc.status_code, exc.status_code,
message, message,
) )
if exc.status_code == 500: if should_sanitize:
message = GENERIC_5XX_MESSAGE message = GENERIC_5XX_MESSAGE
err_code = "INTERNAL_ERROR" err_code = "INTERNAL_ERROR"
body = { body = {
"error": { "error": {
"code": err_code, "code": err_code,
@ -159,8 +170,8 @@ def _http_exception_handler(request: Request, exc: HTTPException) -> JSONRespons
exc.status_code, exc.status_code,
detail, detail,
) )
if exc.status_code == 500: if should_sanitize:
detail = GENERIC_5XX_MESSAGE detail = GENERIC_5XX_MESSAGE
code = _status_to_code(exc.status_code, detail) code = _status_to_code(exc.status_code, detail)
return _build_error_response(exc.status_code, detail, code=code, request_id=rid) return _build_error_response(exc.status_code, detail, code=code, request_id=rid)

View file

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

View file

@ -70,6 +70,20 @@ def _make_test_app():
async def raise_http_500(): async def raise_http_500():
raise HTTPException(status_code=500, detail="secret db password leaked") 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") @app.get("/surfsense-connector")
async def raise_connector(): async def raise_connector():
raise ConnectorError("GitHub API returned 401") raise ConnectorError("GitHub API returned 401")
@ -184,6 +198,20 @@ class TestHTTPExceptionHandler:
assert body["error"]["message"] == GENERIC_5XX_MESSAGE assert body["error"]["message"] == GENERIC_5XX_MESSAGE
assert body["error"]["code"] == "INTERNAL_ERROR" 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 # SurfSenseError hierarchy

View file

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

View file

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

View file

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

View file

@ -41,7 +41,7 @@ async function getAllModels(): Promise<AnonModel[]> {
function buildSeoTitle(model: AnonModel): string { function buildSeoTitle(model: AnonModel): string {
if (model.seo_title) return model.seo_title; 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 { 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"; import { BACKEND_URL } from "@/lib/env-config";
export const metadata: Metadata = { 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: 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.", "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: [ keywords: [
@ -67,7 +67,7 @@ export const metadata: Metadata = {
canonical: "https://surfsense.com/free", canonical: "https://surfsense.com/free",
}, },
openGraph: { openGraph: {
title: "ChatGPT Free Online Without Login | Claude AI Free No Login | SurfSense", title: "Free AI Chat, No Login Required | SurfSense",
description: description:
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and 100+ AI models. Open source NotebookLM alternative.", "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", url: "https://surfsense.com/free",
@ -84,7 +84,7 @@ export const metadata: Metadata = {
}, },
twitter: { twitter: {
card: "summary_large_image", 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: description:
"Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more. No sign-up needed.", "Use ChatGPT free online without login. Chat with GPT-4, Claude AI, Gemini and more. No sign-up needed.",
images: ["/og-image.png"], images: ["/og-image.png"],

View file

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

View file

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