AkurAI Build
Menu

AkurAI-Build

public

Latest change ced52019f33abc850db992648542f41b056148b5 - artifact: distinct trailing-slash modes, encoded redirects, bounded scans by Olafur Bui

# bunfork

`bunfork` is a Linux-first Rust web runtime with one CLI for verified static artifacts, server-rendered MiniJinja pages, SQLCipher migrations/backups, and bounded exhaustive vector search.

It hosts prebuilt static output from documented Svelte, SvelteKit, Next.js, Vue, Nuxt, and Vite configurations. It is not source-, build-, adapter-, or server-runtime-compatible with those frameworks: Bunfork does not embed JavaScript, Node, V8, JavaScriptCore, RSC, Nitro, or a framework compiler. Browser JavaScript in an admitted artifact is served byte-for-byte and runs only in the browser.

The default listener is `0.0.0.0:3100`; port 3000 is never used by default.

## Quick starts

Build Bunfork:

```sh
cargo build --release --locked
```

Run the native MiniJinja/vector application:

```sh
./target/release/bunfork keygen
./target/release/bunfork keygen --out .bunfork.token
./target/release/bunfork migrate
./target/release/bunfork dev
```

The database key and API token must decode to different 32-byte secrets; case-only variations of the same hexadecimal key are rejected.

Run a prebuilt static export with no database, key, token, migration, tenant, or model state:

```sh
./target/release/bunfork admit \
  --artifact frontend/dist \
  --manifest bunfork-static.json \
  --mode spa \
  --fallback index.html \
  --trailing-slash ignore

./target/release/bunfork doctor \
  --static frontend/dist \
  --manifest bunfork-static.json

./target/release/bunfork serve \
  --static frontend/dist \
  --manifest bunfork-static.json
```

## One CLI

Global data options precede the command:

| Option | Environment | Default |
| --- | --- | --- |
| `--database <PATH>` | `BUNFORK_DB` | `data/bunfork.db` |
| `--key-file <PATH>` | `BUNFORK_KEY_FILE` | `.bunfork.key` |
| `--tenant <ID>` | `BUNFORK_TENANT` | `default` |
| `--model <ID>` | `BUNFORK_MODEL` | `default` |

| Command | Purpose |
| --- | --- |
| `keygen [--out <PATH>]` | Create a private 256-bit secret. |
| `build --run <PROGRAM> --arg <ARG>... --output <DIR> --manifest <FILE> [artifact options]` | Run one explicit argv build command with a reduced environment, then admit its output. No shell or framework detection is inserted. |
| `admit --artifact <DIR> --manifest <FILE> [--mode mpa\|spa] [--base /] [--fallback FILE] [--trailing-slash ...] [--json]` | Scan a completed static directory and atomically write its deterministic manifest. |
| `doctor --static <DIR> --manifest <FILE> [--json]` | Verify every file, byte length, SHA-256 digest, and manifest invariant offline. |
| `dev [native server options]` | Rescan native pages and re-read `public` assets per request. This is reload, not HMR. |
| `dev --static <DIR> --manifest <FILE>` | Serve a frozen artifact without data state; rebuild/restart to see changes. |
| `serve [native server options]` | Serve native pages and vector APIs. `start` is an alias. |
| `serve --static <DIR> --manifest <FILE>` | Verify, preload, and serve a static artifact without opening SQLCipher. |
| `migrate` | Apply embedded migrations transactionally and verify the exact schema. |
| `backup [--out <PATH>]` | Create, publish, and verify an encrypted no-overwrite backup. |
| `restore <BACKUP> --yes` | Verify and restore a backup while preserving the previous live database. |
| `vector put\|search\|delete ...` | Manage tenant/model-scoped encrypted vectors. |
| `test [--pages <DIR>] [--public <DIR>]` | Run locked Rust, route, static-listener, migration, vector, backup, and restore checks. |
| `deploy [--out <DIR>] [--force]` | Build a digest-owned native bundle; default output is `bunfork-dist`. |
| `deploy --artifact <DIR> --static-manifest <FILE> [--out <DIR>] [--force]` | Build a digest-owned static bundle with the Bunfork binary and systemd units. |

Secret files take precedence over `BUNFORK_DB_KEY` and `BUNFORK_API_TOKEN` environment values.

## `bunfork-static-v1`

Admission accepts an immutable directory of ordinary browser files and writes a strict manifest:

```json
{
  "schema": "bunfork-static-v1",
  "root": ".",
  "mode": "mpa",
  "base": "/",
  "fallback": null,
  "trailing_slash": "directory",
  "precompressed": true,
  "immutable": true,
  "files": {
    "index.html": {
      "sha256": "…64 lowercase hexadecimal characters…",
      "bytes": 1234
    }
  }
}
```

Unknown/duplicate fields, extra/missing/mutated files, symlinks, hard links, special files, hidden/sensitive paths, malformed URLs, and traversal fail closed. Scans are bounded to 50,000 files, 256 MiB per file, and 512 MiB total; serving preloads the entire verified artifact after re-hashing, so admission and serving share one bound and post-bind filesystem swaps are eliminated.

Static HTTP behavior includes base paths, directory routes, `.html` exports, MPA 404 pages, explicit SPA fallbacks, asset/API fallback exclusions, `GET`/`HEAD`/`405`, ETags/304, conservative document caching, immutable caching only for semantic SvelteKit/Next namespaces, and quality-aware `.br`/`.gz` negotiation. Uncertain Vite/Nuxt/custom assets revalidate instead of relying on filename guesses. `trailing_slash` selects the canonical URL shape: `directory` redirects `/guide` to `/guide/`, `redirect` canonicalizes `/guide/` to `/guide`, and `ignore` serves both without redirects. `/_bunfork/health` and `/_bunfork/ready` are reserved.

Producer configurations:

| Producer | Admitted output | Bunfork mode |
| --- | --- | --- |
| Svelte or Vue with Vite | `vite build` output, normally `dist` | `spa --fallback index.html --trailing-slash ignore` for a client router; otherwise `mpa` |
| SvelteKit | `adapter-static` output, normally `build` | `mpa`; use `spa` only with an explicit adapter fallback |
| Next.js | `output: 'export'` output, normally `out`; `trailingSlash: true` is recommended | `mpa --trailing-slash directory` |
| Nuxt | `nuxt generate` / prerendered `.output/public` | `mpa`, or explicit `spa` for a client-only fallback export |

`.next/server`, Next standalone, SvelteKit adapter-node, Vite SSR, and Nuxt/Nitro server output are rejected scope, not partially emulated features.

## Native pages and assets

Only `.html` files below `app/pages` become routes:

| File | Route |
| --- | --- |
| `index.html` | `/` |
| `about.html` | `/about` |
| `notes/[slug].html` | `/notes/:slug` |
| `docs/[...path].html` | required terminal catch-all |
| `docs/[[...path]].html` | optional terminal catch-all |
| `(site)/about.html` | `/about`; route groups do not enter the URL |

Repeated parameters, ambiguous routes, invalid templates, hidden entries, links, and special files fail startup/deploy. Production pages are compiled and loaded before binding, so later path swaps cannot expose files.

Templates in `app/templates` support MiniJinja inheritance/includes/macros; the sample uses `base.html`. `_404.html` and `_500.html` are optional error pages. Pages receive auto-escaped `params`, bounded multi-value `query`, `pathname`, `tenant`, and `model`. Files in `public` are validated, hashed, and preloaded before bind, then served below `/assets` without request-time filesystem reads.

## Vector API

`GET /api/health`, `/api/ready`, `/_bunfork/health`, and `/_bunfork/ready` are public operational probes. Vector endpoints require `Authorization: Bearer <API_TOKEN>`:

```text
PUT    /api/vectors/note-1
       {"id":"note-1","content":"hello","embedding":[1,0,0]}

POST   /api/vectors/search
       {"embedding":[1,0,0],"limit":5}

DELETE /api/vectors/note-1
```

Invalid identifiers, dimensions, norms, limits, and budgets are client errors rather than HTTP 500s. The exhaustive cosine-similarity scan is bounded to 4,096 dimensions, 10,000 candidates, 10,000,000 multiply-adds, 25 results, and 64 KiB content. JSON bodies are capped at 1 MiB. Bunfork does not load SQLite extensions or claim ANN/full-text search.

## Encrypted data and recovery

SQLite is encrypted with bundled SQLCipher. Migrations are embedded and transactional; validation checks application/user versions, the exact migration ledger, exact table/index SQL, and rejects unexpected schema objects.

Backups use the same key as the live database; they are not key rotation. Backup/restore pin file identity with no-follow descriptors, reject links, sync publication, verify the exact encrypted schema, and preserve a timestamped pre-restore database. Stop the server before restore. Same-UID hostile races remain outside the filesystem guarantee; isolate untrusted code under another OS account/container.

## Deployment

`deploy` tests, release-builds, stages, hashes, fsyncs, and atomically installs `bunfork-dist`. `bunfork.json` uses the `bunfork-deployment-v2` schema and covers every bundled file except itself. `--force` validates that complete inventory before replacement. Hidden/protected outputs—including `.git`, `.refrepos`, `app`, `src`, `data`, `backups`, `deploy`, `migrations`, `public`, and `target`—are always refused. The prior verified release is preserved as a sibling and never automatically deleted.

Native runtime state belongs outside the release:

```sh
./bunfork-dist/bunfork \
  --database "$HOME/.local/share/bunfork/bunfork.db" \
  --key-file "$HOME/.config/bunfork/db.key" \
  serve --token-file "$HOME/.config/bunfork/api.token"
```

Static bundles run from any working directory:

```sh
./bunfork-dist/bunfork serve \
  --static ./bunfork-dist/site \
  --manifest ./bunfork-dist/bunfork-static.json
```

The included native/static user units use `%h/.local/lib/bunfork`, `0.0.0.0:3100`, and systemd filesystem/process hardening. Bunfork does not provision hosts, upload releases, configure DNS/TLS, or delete prior releases.

## Security boundaries

- `0.0.0.0:3100` is intentionally LAN-visible. Firewall it and use a trusted TLS reverse proxy before internet exposure; Bunfork has no built-in TLS.
- Bearer and decoded-secret comparisons are constant-time. Browser mutations with `Origin` must match the configured public origin; without one, the Host-header fallback applies only to loopback listeners and Origin-bearing mutations are otherwise rejected. The bearer token remains primary API authorization.
- Native CSP is strict. Static CSP permits same-origin code plus inline scripts/styles because official SvelteKit/Next exports require bootstrap inline code; use a reverse proxy with build-specific CSP hashes/nonces for a stricter policy.
- Static mode has no data/secret dependency. A server-side foreign runtime is not supervised because a same-UID process could read Bunfork keys/database regardless of environment filtering.
- Encryption does not protect a compromised live process, service account, root user, TLS proxy, binary, or unlocked key. There are no user accounts, roles, distributed locks, remote backup transport, or Windows hardening guarantees.

## Verification and references

Ten read-only Codex CLI red-team passes and the final no-ship checklist are preserved in [docs/adversarial](docs/adversarial). The P0 findings drove digest-owned deploys, secret byte comparison, descriptor-pinned recovery, strict artifact admission, and listener-level regression tests.

Exact package versions and live results for official Svelte/Vite, SvelteKit, Next, Vue/Vite, and Nuxt builds are recorded in [docs/framework-e2e.md](docs/framework-e2e.md).

Pinned research checkouts under `.refrepos/` are excluded from compilation/deployment:

| Repository | Commit | Behavior studied |
| --- | --- | --- |
| Bun | `aaaa53f1` | compact CLI/runtime boundaries |
| Deno | `c537e012` | artifact/runtime distinctions and shutdown |
| Svelte | `b4d1583ae20f` | compiled browser artifact boundary |
| SvelteKit | `5c38e515db7f` | adapter-static, fallbacks, routes |
| Next.js | `1bd2fd585aac` | export/standalone split and static routing |
| Vue | `c0606e91798c` | SFC compiler versus browser output |
| Nuxt | `33f0fdb9d208` | generated public versus Nitro output |
| Vite | `c961cae2868c` | static output, base paths, hashed assets |
| Rari | `373fa706` | Rust routing versus V8/RSC boundary |
| Leptos | `f81ffcd5` | request-scoped Rust SSR patterns |
| Dioxus | `e1c63426` | deterministic routes and full-stack boundaries |
| Andromeda | `0ef48722` | Rust runtime and SQLite boundaries |
| Ponytail | `14a0d795` | minimal implementation discipline |

Bunfork is MIT licensed; see [LICENSE](LICENSE) and [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).