mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-16 16:45:07 +00:00
7264c44fd7
app.blade.php is the root template for all three interfaces, and it opened
with two lines pointing at a third party:
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet" />
Every visitor to /login, /register, /forgot-password, /s/{token} and every
public listing page therefore made a request to a host the operator did
not choose and could not switch off, before they had done anything at all
-- handing it their IP address, their user agent, and through Origin the
hostname of the installation they were visiting. On the signed-out pages
that is a visitor who has agreed to nothing, and an operator who often has
told their own users that this server is where their files live.
There was no self-hosted copy in the repository, no setting, no mention in
INSTALL.md, DOCKER.md or SECURITY.md, and no SRI on the tag.
The font now ships with the application, through @fontsource/instrument-sans
-- the same font, the same three weights the URL asked for, from a
versioned dependency rather than binaries pasted into the repository.
Vite fingerprints and emits them like any other asset.
Cost, measured on this build: twelve files, 192 KB on disk. A browser
fetches only woff2 and only the subsets it needs, which is 73 KB for all
six woff2 files together and typically 41 KB (latin, three weights) for a
page in English. Against that, every page load loses a DNS lookup, a TLS
handshake and a round trip to another origin, so signed-out pages get
faster rather than slower.
This is a privacy change rather than a vulnerability fix, and worth saying
plainly: the share token does not leak this way. Referrer-Policy:
strict-origin-when-cross-origin is set in both nginx configs and in the
INSTALL.md snippet, so the path never travelled in the Referer. What
travelled was the visit itself.
Not changed: public/.htaccess still sets no security headers at all, so an
Apache installation has no Referrer-Policy. That is a real gap and a
separate change.
Verified: `npm run build` succeeds and emits the faces; no reference to
the CDN survives anywhere in public/build; `tsc --noEmit` and prettier are
clean. No test asserts on the font, before or after.
70 lines
3.6 KiB
PHP
70 lines
3.6 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
@if($page['props']['noindex'] ?? false)
|
|
<meta name="robots" content="noindex">
|
|
@endif
|
|
|
|
{{-- Name only, never the version: this tag is served to anyone
|
|
who asks, and publishing the exact release tells a scanner
|
|
which advisories apply to this installation. What it buys
|
|
is ecosystem visibility — surveys like BuiltWith count
|
|
ProjectSend installs from this and nothing else. --}}
|
|
@if(app(\App\Modules\Platform\Attribution\Attribution::class)->visible())
|
|
<meta name="generator" content="ProjectSend">
|
|
@endif
|
|
|
|
{{-- The same name app.tsx suffixes every page title with, from the
|
|
same place: the site name in the shared props. Taking it from
|
|
APP_NAME instead would show one name in the tab until Inertia
|
|
hydrates and a different one after, on any installation whose
|
|
administrator renamed the site. --}}
|
|
<title inertia>{{ $page['props']['name'] ?? config('app.name', 'ProjectSend') }}</title>
|
|
|
|
{{-- Which cookie holds this installation's CSRF token. Named after
|
|
the installation rather than the framework, so a neighbouring
|
|
Laravel app on the same hostname cannot overwrite it — and
|
|
there is nothing on the client that could work the name out. --}}
|
|
<meta name="xsrf-cookie" content="{{ \App\Http\Middleware\ValidateCsrfToken::cookieName() }}">
|
|
|
|
<link rel="icon" href="/favicon.ico" sizes="48x48">
|
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
|
|
@routes
|
|
@viteReactRefresh
|
|
@php
|
|
// A page component may be shipped by an installed package
|
|
// under vendor/<vendor>/<name>/resources/js/pages/ instead of
|
|
// this app's own resources/js/pages/. This must stay in step
|
|
// with the client-side glob in resources/js/app.tsx — they
|
|
// are two halves of one lookup, and when they disagreed the
|
|
// page built cleanly, type-checked cleanly, and then answered
|
|
// 500 with "Unable to locate file in Vite manifest".
|
|
$pageComponentPath = "resources/js/pages/{$page['component']}.tsx";
|
|
if (! file_exists(base_path($pageComponentPath))) {
|
|
$packageMatch = glob(base_path("vendor/*/*/resources/js/pages/{$page['component']}.tsx"))[0] ?? null;
|
|
if ($packageMatch !== null) {
|
|
$pageComponentPath = ltrim(str_replace(base_path(), '', $packageMatch), '/');
|
|
}
|
|
}
|
|
@endphp
|
|
@vite(['resources/js/app.tsx', $pageComponentPath])
|
|
@inertiaHead
|
|
|
|
{{-- Operator-authored snippets (Community only). Raw by design —
|
|
see CustomAssetsBridge, which returns '' unless the edition
|
|
grants the capability and the module is actually installed.
|
|
This is the one root view for all three surfaces (public,
|
|
portal, staff), so it is the only place they need wiring. --}}
|
|
{!! app(\App\Modules\Platform\CustomAssets\CustomAssetsBridge::class)->render('head') !!}
|
|
</head>
|
|
<body class="font-sans antialiased">
|
|
{!! app(\App\Modules\Platform\CustomAssets\CustomAssetsBridge::class)->render('body_top') !!}
|
|
@inertia
|
|
{!! app(\App\Modules\Platform\CustomAssets\CustomAssetsBridge::class)->render('body_bottom') !!}
|
|
</body>
|
|
</html>
|