Skip to content
Draft
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
10 changes: 4 additions & 6 deletions apps/backend/app/core/security_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,8 @@ def frontend_content_security_policy(index_html: str) -> str:
The API's deny-all policy above blocks every script and stylesheet the page
needs, so the shell gets its own. Inline scripts in index.html are allowed by
hash, computed from the built file itself so editing that script can never
silently break the page. Third-party origins are exactly the ones the
frontend loads today: Google Fonts, and the Monaco editor from jsDelivr,
which runs its language workers from blob: URLs. Inline styles are allowed
silently break the page. Editor scripts and workers are bundled locally. Fonts use local system
fallbacks. No third-party font or executable CDN origin is permitted. Inline styles are allowed
because the graph and editor libraries inject their own.
"""

Expand All @@ -75,13 +74,12 @@ def frontend_content_security_policy(index_html: str) -> str:
"'sha256-" + base64.b64encode(hashlib.sha256(body.encode("utf-8")).digest()).decode() + "'"
for body in _inline_scripts(index_html)
),
"https://cdn.jsdelivr.net",
]
directives = [
"default-src 'self'",
"script-src " + " ".join(script_sources),
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net",
"font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net",
"style-src 'self' 'unsafe-inline'",
"font-src 'self' data:",
"img-src 'self' data: blob:",
"worker-src 'self' blob:",
"connect-src 'self'",
Expand Down
9 changes: 9 additions & 0 deletions apps/backend/tests/test_security_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,12 @@ def source(body: str) -> str:
assert source("\n b();\n") in script_src
# An external script is allowed by 'self', not by a hash of its empty body.
assert source("") not in script_src


def test_frontend_policy_excludes_executable_and_font_cdns():
from app.core.security_headers import frontend_content_security_policy

policy = frontend_content_security_policy("<html></html>")
assert "https://" not in policy
assert "worker-src 'self' blob:" in policy
assert "style-src 'self' 'unsafe-inline'" in policy
23 changes: 23 additions & 0 deletions apps/frontend/e2e/local-editor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { readFileSync } from 'node:fs';
import { expect, test } from '@playwright/test';

const fixtures = JSON.parse(readFileSync(process.env.PARTHA_VISUAL_FIXTURES ?? '/tmp/partha-e2e-fixtures.json', 'utf8'));

test('source explorer renders Monaco without third-party executable or font requests', async ({ page }, testInfo) => {
const external: string[] = [];
page.on('request', request => {
const url = new URL(request.url());
if (url.protocol.startsWith('http') && !['localhost', '127.0.0.1'].includes(url.hostname)) external.push(request.url());
});
await page.goto('/login');
await page.getByLabel(/email/i).fill(fixtures.email);
await page.getByLabel('Password', {exact:true}).fill(fixtures.password);
await page.getByRole('button', {name:/sign in|log ?in/i}).click();
await expect(page.getByRole('heading', {level:1})).toBeVisible();
const repository = fixtures.repos.find((repo: {label:string}) => repo.label === 'small');
await page.goto(`/repositories/${repository.id}?tab=Explorer&path=src/api/routes.ts`);
await expect(page.locator('.monaco-editor')).toBeVisible({timeout:20000});
await expect(page.locator('.monaco-editor .view-lines')).toContainText('export');
expect(external).toEqual([]);
await testInfo.attach('local-editor', {body:await page.screenshot(),contentType:'image/png'});
});
3 changes: 0 additions & 3 deletions apps/frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png" />
<link rel="icon" type="image/png" sizes="48x48" href="/favicon-48.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicon-180.png" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Montserrat:wght@200;300;400;500;600;700;800&family=Proza+Libre:ital,wght@0,400;0,500;0,600;1,400&family=Cormorant+Upright:ital,wght@0,400;0,500;0,600;1,500&display=swap" rel="stylesheet" />
<title>PARTHA</title>
<style>
/* Prevents a white flash behind the landing artwork before React
Expand Down
7 changes: 2 additions & 5 deletions apps/frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"framer-motion": "^13.4.0",
"html-to-image": "^1.11.13",
"lucide-react": "^0.577.0",
"monaco-editor": "0.55.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-dropzone": "^20.1.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import Editor, { type OnMount } from '@monaco-editor/react';
import './localEditor';
import { Copy, Check, FileWarning, Loader2, ShieldAlert, Target } from 'lucide-react';
import type { FileTreeNode } from '@/shared/types';
import { repositoryService } from '@/shared/services/api/repositories';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ vi.mock('@/shared/services/api/architecture', () => ({
},
}));

vi.mock('./localEditor', () => ({}));

vi.mock('@monaco-editor/react', () => ({
default: ({ value }: { value: string }) => <pre data-testid="editor-stub">{value}</pre>,
}));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { loader } from '@monaco-editor/react';
import * as monaco from 'monaco-editor';
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';

self.MonacoEnvironment = { getWorker: () => new EditorWorker() };
loader.config({ monaco });
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ export function LandingCanvas() {
<p className="-translate-x-1/2 [word-break:break-word] absolute font-display font-light font-light leading-[48px] left-[717.5px] text-[48px] text-[var(--ln-ink)] text-center top-[68px] tracking-[-1.2px] w-[761px]" data-node-id="504:8522">
Understand the system behind the code.
</p>
<p className="-translate-x-1/2 [word-break:break-word] absolute font-accent font-normal leading-[24px] left-[717.5px] not-italic text-[20px] text-[var(--ln-ink)] text-center top-[192px] w-[697px]" data-node-id="504:8542">{`PARTHA is open source and runs on your own machine. Clone the repository, point it at a codebase you need to understand, and your code never leaves your computer.`}</p>
<p className="-translate-x-1/2 [word-break:break-word] absolute font-accent font-normal leading-[24px] left-[717.5px] not-italic text-[20px] text-[var(--ln-ink)] text-center top-[192px] w-[697px]" data-node-id="504:8542">{`PARTHA runs on your installation. Automatic AI context excludes source text; your question is sent verbatim to your provider, including any code you paste.`}</p>
<div className="-translate-x-1/2 absolute content-stretch flex gap-[26px] items-center left-1/2 px-[256px] top-[292px]" data-node-id="504:8527" data-name="Container">
<Link to={nav.analyze} className="h-[43px] relative rounded-[34px] shrink-0 w-[245px] cursor-pointer block" data-node-id="504:8555" data-name="Button v1">
<div className="absolute bg-[#fa4d01] inset-0 rounded-[15px]" data-node-id="504:8556" />
Expand Down
3 changes: 0 additions & 3 deletions apps/marketing/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,12 @@
content="PARTHA turns a repository revision into one sealed, queryable model and uses it to explain architecture, dependencies, review findings, insights, and documentation. See a scripted walkthrough, or run it yourself."
/>
<title>PARTHA</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Typography as specified by the iteration-1 landing design (Figma
f1HlSxjl8pvvOm86XFPZEJ): Montserrat = display / headings / buttons,
Proza Libre = all reading text, Cormorant Upright = editorial accents.
Note this is plain Montserrat, not Montserrat Alternates: the design
file uses Montserrat throughout. Fallback rendering is treated as a
failure for approved marketing output, so real weights load here. -->
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@200;300;400;500;600;700;800&family=Proza+Libre:ital,wght@0,400;0,500;0,600;1,400&family=Cormorant+Upright:ital,wght@0,400;0,500;0,600;1,500&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<style>
/* Ported from apps/frontend/index.html (#382): prevents a white flash
behind the landing artwork before React mounts and App's own
Expand Down
2 changes: 1 addition & 1 deletion apps/marketing/src/components/landing/LandingCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ export function LandingCanvas({ onSeeHowItWorks, onAnalyzeRepository }: LandingC
<p className="-translate-x-1/2 [word-break:break-word] absolute font-display font-light font-light leading-[48px] left-[717.5px] text-[48px] text-[var(--ln-ink)] text-center top-[68px] tracking-[-1.2px] w-[761px]" data-node-id="504:8522">
Understand the system behind the code.
</p>
<p className="-translate-x-1/2 [word-break:break-word] absolute font-accent font-normal leading-[24px] left-[717.5px] not-italic text-[20px] text-[var(--ln-ink)] text-center top-[192px] w-[697px]" data-node-id="504:8542">{`PARTHA is open source and runs on your own machine. Clone the repository, point it at a codebase you need to understand, and your code never leaves your computer.`}</p>
<p className="-translate-x-1/2 [word-break:break-word] absolute font-accent font-normal leading-[24px] left-[717.5px] not-italic text-[20px] text-[var(--ln-ink)] text-center top-[192px] w-[697px]" data-node-id="504:8542">{`PARTHA runs on your installation. Automatic AI context excludes source text; your question is sent verbatim to your provider, including any code you paste.`}</p>
<div className="-translate-x-1/2 absolute content-stretch flex gap-[26px] items-center left-1/2 px-[256px] top-[292px]" data-node-id="504:8527" data-name="Container">
<a href="https://github.com/Second-Origin/PARTHA#quick-start" target="_blank" rel="noreferrer" className="h-[43px] relative rounded-[34px] shrink-0 w-[245px] cursor-pointer block" data-node-id="504:8555" data-name="Button v1">
<div className="absolute bg-[#fa4d01] inset-0 rounded-[15px]" data-node-id="504:8556" />
Expand Down
Loading