mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Harden Vite fs.deny for local logs and cache (#948)
Deny local logs, cache, and repository build output from Vite development file serving. Contribution by @blowing-a-raspberry.
This commit is contained in:
5 files changed
+68
-2
No files matched your search
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
God's Eye View is a local-first client for **public** data. It is built for exploration, demos, and learning — not as a hardened production service. This document explains the security model so you can run it safely and report issues responsibly.
|
God's Eye View is a local-first client for **public** data. It is built for exploration, demos, and learning — not as a hardened production service. This document explains the security model so you can run it safely and report issues responsibly.
|
||||||
|
|
||||||
|
Vite's development file server also denies `.gev-logs/`, `.gev-cache/`, and
|
||||||
|
the package checkout's root `dist/` directory. Dependency `dist/` directories
|
||||||
|
remain available. This filesystem deny list does not filter `public/` assets or
|
||||||
|
preview output; never put private artifacts in a public asset directory.
|
||||||
|
|
||||||
## Reporting a vulnerability
|
## Reporting a vulnerability
|
||||||
|
|
||||||
Please report security issues **privately** — do not open a public issue for anything exploitable.
|
Please report security issues **privately** — do not open a public issue for anything exploitable.
|
||||||
|
|||||||
+33
-1
@@ -1,9 +1,29 @@
|
|||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
import { applicationHtmlPlugin } from './application-html.js';
|
import { applicationHtmlPlugin } from './application-html.js';
|
||||||
import cesium from 'vite-plugin-cesium';
|
import cesium from 'vite-plugin-cesium';
|
||||||
import { DEFAULT_ALLOWED_HOSTS, hostCheckPlugin } from './allowedHosts.js';
|
import { DEFAULT_ALLOWED_HOSTS, hostCheckPlugin } from './allowedHosts.js';
|
||||||
import { embedFramingPlugin } from './embed-framing.js';
|
import { embedFramingPlugin } from './embed-framing.js';
|
||||||
import { panelBuildPlugin } from './panel.js';
|
import { panelBuildPlugin } from './panel.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The repository root (this file lives at `<root>/build/vite.js`), as a
|
||||||
|
* forward-slash path for glob matching. Vite's `fs.deny` patterns are
|
||||||
|
* matched against the full absolute file path, and a bare `**\/dist/**`
|
||||||
|
* matches a `dist` directory at *any* depth — including dependencies that
|
||||||
|
* ship their own, such as `node_modules/vite/dist/client/client.mjs` (the
|
||||||
|
* dev client itself). Anchoring to this repository's root denies only the
|
||||||
|
* project's own build output.
|
||||||
|
*/
|
||||||
|
const REPO_ROOT = fileURLToPath(new URL('..', import.meta.url))
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
.replace(/\/$/, '');
|
||||||
|
|
||||||
|
export function escapeFsDenyPath(path) {
|
||||||
|
return path.replace(/[()[\]{}*?!+@|]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
const REPO_ROOT_GLOB = escapeFsDenyPath(REPO_ROOT);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content-Security-Policy for every document the dev/preview server serves.
|
* Content-Security-Policy for every document the dev/preview server serves.
|
||||||
* No inline script is permitted, and the only foreign script origins are the
|
* No inline script is permitted, and the only foreign script origins are the
|
||||||
@@ -165,7 +185,19 @@ export function createBrowserViteConfig({
|
|||||||
// build/allowedHosts.js); IP addresses are always accepted.
|
// build/allowedHosts.js); IP addresses are always accepted.
|
||||||
allowedHosts: [...allowedHosts],
|
allowedHosts: [...allowedHosts],
|
||||||
fs: {
|
fs: {
|
||||||
deny: ['.env', '.env.*', '*.{crt,pem}', '**/.git/**', '**/ENVIRONMENT'],
|
deny: [
|
||||||
|
'.env',
|
||||||
|
'.env.*',
|
||||||
|
'*.{crt,pem}',
|
||||||
|
'**/.git/**',
|
||||||
|
'**/ENVIRONMENT',
|
||||||
|
'**/.gev-logs/**',
|
||||||
|
'**/.gev-cache/**',
|
||||||
|
// Root-anchored: a bare '**/dist/**' would also deny every
|
||||||
|
// dependency's own dist/ (e.g. node_modules/vite/dist/client),
|
||||||
|
// breaking /@vite/client and other dev-client assets.
|
||||||
|
`${REPO_ROOT_GLOB}/dist/**`,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
// These headers protect the document containing Provider Settings and
|
// These headers protect the document containing Provider Settings and
|
||||||
// give the whole page a real Content-Security-Policy (BROWSER_CSP).
|
// give the whole page a real Content-Security-Policy (BROWSER_CSP).
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
- Harden Vite's filesystem deny list for local-only artifacts: repo `dist/`,
|
||||||
|
`.gev-logs/`, and `.gev-cache/` are denied without blocking dependency
|
||||||
|
`dist/` directories, and checkout paths containing glob metacharacters are
|
||||||
|
escaped before building the root-anchored pattern (blowing-a-raspberry, #948).
|
||||||
@@ -5385,6 +5385,12 @@ pins and share links already supply complete boxes.
|
|||||||
|
|
||||||
## Auth + Launch
|
## Auth + Launch
|
||||||
|
|
||||||
|
Vite's development filesystem deny list excludes local `.gev-logs/` and
|
||||||
|
`.gev-cache/` contents and the package checkout's root `dist/`, while allowing
|
||||||
|
dependency `dist/` assets. Checkout glob characters are escaped. This does not
|
||||||
|
apply to `public/` assets or built-preview serving.
|
||||||
|
|
||||||
|
|
||||||
- Recommended launcher: `./scripts/dev-fresh.sh` (also: `dev-secure.sh` for stricter bindings, `dev-cctv.sh` for CCTV source-pack tuning)
|
- Recommended launcher: `./scripts/dev-fresh.sh` (also: `dev-secure.sh` for stricter bindings, `dev-cctv.sh` for CCTV source-pack tuning)
|
||||||
- A successful Pinokio install writes the owner-only `pinokio/.installed`
|
- A successful Pinokio install writes the owner-only `pinokio/.installed`
|
||||||
marker. The nested launcher menu resolves that marker from its own directory:
|
marker. The nested launcher menu resolves that marker from its own directory:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
@@ -5,7 +6,11 @@ import { rm, writeFile } from 'node:fs/promises';
|
|||||||
import { request } from 'node:http';
|
import { request } from 'node:http';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { build, createServer, preview } from 'vite';
|
import { build, createServer, preview } from 'vite';
|
||||||
import { BROWSER_CSP, createBrowserViteConfig } from '../../build/vite.js';
|
import {
|
||||||
|
BROWSER_CSP,
|
||||||
|
createBrowserViteConfig,
|
||||||
|
escapeFsDenyPath,
|
||||||
|
} from '../../build/vite.js';
|
||||||
import standaloneConfig, * as compatibility from '../../vite.config.js';
|
import standaloneConfig, * as compatibility from '../../vite.config.js';
|
||||||
import * as providers from '../../server/providers/local.js';
|
import * as providers from '../../server/providers/local.js';
|
||||||
import { apiNotFoundPlugin } from '../../server/standalone/api-not-found.js';
|
import { apiNotFoundPlugin } from '../../server/standalone/api-not-found.js';
|
||||||
@@ -30,6 +35,20 @@ test('explicit build inputs preserve browser-only defines, plugin order and loop
|
|||||||
assert.deepEqual(config.server.allowedHosts, ['localhost', '127.0.0.1']);
|
assert.deepEqual(config.server.allowedHosts, ['localhost', '127.0.0.1']);
|
||||||
assert.ok(config.server.fs.deny.includes('**/ENVIRONMENT'));
|
assert.ok(config.server.fs.deny.includes('**/ENVIRONMENT'));
|
||||||
assert.ok(config.server.fs.deny.includes('.env.*'));
|
assert.ok(config.server.fs.deny.includes('.env.*'));
|
||||||
|
assert.ok(config.server.fs.deny.includes('**/.gev-logs/**'));
|
||||||
|
assert.ok(config.server.fs.deny.includes('**/.gev-cache/**'));
|
||||||
|
const root = fileURLToPath(new URL('../../', import.meta.url))
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
.replace(/\/$/, '');
|
||||||
|
assert.deepEqual(
|
||||||
|
config.server.fs.deny.filter((pattern) => pattern.endsWith('/dist/**')),
|
||||||
|
[`${escapeFsDenyPath(root)}/dist/**`],
|
||||||
|
'only project build output is denied, not dependency dist directories',
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
escapeFsDenyPath('C:/Users/a/project (1) [test]{copy}'),
|
||||||
|
'C:/Users/a/project \\(1\\) \\[test\\]\\{copy\\}',
|
||||||
|
);
|
||||||
assert.equal(config.server.headers['X-Frame-Options'], 'DENY');
|
assert.equal(config.server.headers['X-Frame-Options'], 'DENY');
|
||||||
assert.equal(config.server.headers['X-Content-Type-Options'], 'nosniff');
|
assert.equal(config.server.headers['X-Content-Type-Options'], 'nosniff');
|
||||||
const csp = config.server.headers['Content-Security-Policy'];
|
const csp = config.server.headers['Content-Security-Policy'];
|
||||||
|
|||||||
Reference in new issue
Block a user