Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
582b452
add prom-client to provide prometheus metrics
labrenbe Feb 19, 2026
02624ed
readd deleted comment
labrenbe Feb 19, 2026
8d16657
add monaco editor and simple trino client
labrenbe Feb 23, 2026
132bcb6
fix: run query shortcut not working when editor is selected
labrenbe Feb 23, 2026
6152577
reduce http request metric buckets
labrenbe Feb 23, 2026
0b16e10
Merge remote-tracking branch 'origin/main' into feat/prometheus-metrics
labrenbe Feb 24, 2026
2858ce1
Merge branch 'main' into feat/prometheus-metrics
labrenbe Feb 25, 2026
0d0bcaf
fix invisible Enter symbol on button
labrenbe Feb 25, 2026
b018079
replace per-component theme detection with shared reactive state
labrenbe Feb 25, 2026
3983942
Add missing i18n message
labrenbe Feb 25, 2026
3aa197a
Remove undici & skip tls validation in dev through env
labrenbe Feb 25, 2026
8e6f333
Document tech debt in new file
labrenbe Feb 25, 2026
94f2fdc
disable all TLS validation when running as dev server
labrenbe Feb 25, 2026
c74e1b2
Merge remote-tracking branch 'origin/main' into feat/simple-trino-client
labrenbe Feb 25, 2026
2640e9e
Merge remote-tracking branch 'origin/main' into feat/simple-trino-client
labrenbe Feb 25, 2026
3c9bec4
Use pino for logs and add missing i18n
labrenbe Feb 25, 2026
033bf56
Move trino query execution to server-side form action
labrenbe Feb 26, 2026
5016014
Replace localStorage-polling hydration check with body.hydrated class
labrenbe Feb 26, 2026
5196eb2
Fix UI bug
labrenbe Feb 27, 2026
f69d9b6
fix e2e test and pre-commit
labrenbe Feb 27, 2026
93c2632
fix e2e testflakiness in CI
labrenbe Feb 27, 2026
a456dca
Update src/routes/(app)/trino/+page.svelte
labrenbe Mar 3, 2026
4bdb784
Merge remote-tracking branch 'origin/main' into feat/prometheus-metrics
labrenbe Mar 3, 2026
c95acab
Merge branch 'feat/prometheus-metrics' into feat/simple-trino-client
labrenbe Mar 3, 2026
410770c
Update AGENTS.md
labrenbe Mar 3, 2026
7efb848
Merge remote-tracking branch 'origin/main' into feat/simple-trino-client
labrenbe Mar 3, 2026
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
Empty file added .env.development
Empty file.
7 changes: 6 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,14 @@ This is a **single SvelteKit application** (not a monorepo).
├── e2e/ # Playwright E2E tests
├── static/ # Static assets
├── docker/Dockerfile # Production container image
└── CLAUDE.md # AI assistant instructions
├── CLAUDE.md # AI assistant instructions
└── TECH_DEBT.md # Known tech debt and deferred security concerns
```

## Tech Debt

When introducing shortcuts, known issues, or deferred security work, add an entry to `TECH_DEBT.md`. Keep entries concise: what the issue is, why it is acceptable now, and what the correct long-term fix is.

## Development Guidelines

### Browser Compatibility
Expand Down
79 changes: 79 additions & 0 deletions TECH_DEBT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Tech Debt

Tracked issues that are acceptable at the current early stage but must be addressed before production.

---

## Security

### No authentication or authorisation on the Trino API route

**File:** `src/routes/api/trino/query/+server.ts`, `src/hooks.server.ts`

The `/api/trino/query` endpoint is completely unauthenticated. Any request — from any origin — can execute arbitrary SQL against any Trino instance. OIDC authentication is planned (env vars are wired up, `hooks.server.ts` has the right structure) but not yet implemented. Until auth middleware is in place there is also no per-user rate limiting or query quota.

---

### Trino credentials stored in localStorage

**File:** `src/routes/(app)/trino/+page.svelte:41–44`

Username and password are persisted in plaintext localStorage. This is convenient for development (survives page reloads) but violates credential storage best practices — localStorage is accessible to any script on the page and visible in DevTools. Long-term the connection config should be stored server-side (tied to the authenticated session), with credentials never leaving the server after initial setup.

---

### Credentials sent in every request body

**File:** `src/routes/(app)/trino/+page.svelte:82–84`

Because there is no server-side session yet, connection credentials (including password) are included in the JSON body of every `/api/trino/query` POST. Once server-side sessions exist the client should send only a session token, not raw credentials.

---

### Raw upstream error messages returned to the client

**File:** `src/routes/api/trino/query/+server.ts:165, 181, 209`

Trino error messages and Node.js exception messages are returned to the browser without any sanitisation. Trino errors may expose schema details, table names, or internal query plans. These should be classified (query error vs. infrastructure error) and sanitised before being surfaced to users.

---

## API & Validation

### API route request body not validated with Zod

**File:** `src/routes/api/trino/query/+server.ts:83–103`

`parseConnection` uses manual `typeof` checks instead of a Zod schema. The AGENTS.md guidelines require Zod for all validation. Additionally, `request.json()` is called without a try/catch — a malformed JSON body will throw an unhandled error rather than returning a 400.

---

### In-memory query cache has no total size bound

**File:** `src/routes/api/trino/query/+server.ts:40–47`

Each cached query can hold up to `MAX_CACHED_ROWS` (100 000) rows. `evictStale()` is only called when a new query arrives, not on a timer, so a long idle period followed by many concurrent queries could accumulate significant memory before eviction runs. Needs a bounded cache (e.g. LRU with a memory cap) and a periodic eviction timer.

---

### Displayed results not cleared on connection change

**File:** `src/routes/(app)/trino/+page.svelte:40–46`

The `$effect` that persists connection settings only resets `queryId`, not `rows`, `columns`, or `error`. After switching to a different Trino instance the previous result set remains visible until a new query is run, which is confusing.

---

## Infrastructure

### No Content Security Policy headers

No CSP headers are set anywhere. This leaves the app exposed to XSS in ways that a strict CSP would mitigate. Should be added in a SvelteKit hook once the app stabilises.

---

### `allowedHosts: true` in Vite config

**File:** `vite.config.ts`

The dev server accepts requests from any host. This enables DNS rebinding attacks against local development environments. Should be restricted to `localhost` / `127.0.0.1` unless remote dev access is explicitly needed.
11 changes: 11 additions & 0 deletions e2e/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Page } from '@playwright/test';

/**
* Wait for SvelteKit client-side hydration to complete.
*
* The root layout adds a `hydrated` class to `<body>` inside `onMount`,
* which fires after hydration finishes and the app is fully interactive.
*/
export async function waitForHydration(page: Page) {
await page.locator('body.hydrated').waitFor();
}
5 changes: 2 additions & 3 deletions e2e/i18n.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { test, expect } from '@playwright/test';
import { waitForHydration } from './helpers';

test.describe('Internationalisation', () => {
test.use({ locale: 'en-US' });
Expand Down Expand Up @@ -31,9 +32,7 @@ test.describe('Internationalisation', () => {
await expect(page.locator('html')).toHaveAttribute('lang', 'en');

// Wait for client hydration; locale switch relies on an attached click handler.
await expect
.poll(() => page.evaluate(() => localStorage.getItem('theme')))
.toMatch(/^(light|dark)$/);
await waitForHydration(page);

await page.getByRole('button', { name: 'Language' }).click();
const englishOption = page.locator('#lang-switcher button[lang="en"]');
Expand Down
10 changes: 5 additions & 5 deletions e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { test, expect } from '@playwright/test';
import { waitForHydration } from './helpers';

test.describe('Smoke tests', () => {
test.use({ locale: 'en-US' });
Expand All @@ -20,9 +21,10 @@ test.describe('Smoke tests', () => {
// Dashboard content is rendered
await expect(page.getByText('Welcome back')).toBeVisible();

// Trino nav item is present but disabled
// Trino nav item is present and navigable
const trinoLink = page.getByRole('link', { name: 'Trino' });
await expect(trinoLink).toHaveAttribute('aria-disabled', 'true');
await expect(trinoLink).toBeVisible();
await expect(trinoLink).not.toHaveAttribute('aria-disabled', 'true');
});

test('theme toggle switches between light and dark', async ({ page }) => {
Expand All @@ -34,9 +36,7 @@ test.describe('Smoke tests', () => {
});

// Wait for client hydration/theme initialisation before interacting.
await expect
.poll(() => page.evaluate(() => localStorage.getItem('theme')))
.toMatch(/^(light|dark)$/);
await waitForHydration(page);
await expect(html).toHaveAttribute('data-theme', /^(light|dark)$/);
await expect(toggle).toBeVisible();

Expand Down
Loading