Menu
AkurAI-Build
publicLatest change 0cf28150faedd8c150474a4f16be4c9922c04f56 - feat: add Ponytail UI design system by Olafur Bui
{% extends "layouts/docs.html" %} {% block title %}Security model ยท Bunfork
docs{% endblock %} {% block description %}Understand Bunfork's secret handling,
artifact verification, origin controls, security headers, network boundary, and
deliberate limitations.{% endblock %} {% block breadcrumb %}Security{% endblock
%} {% block docs_title %}Security model{% endblock %} {% block docs_summary
%}Bunfork reduces the runtime and fails closed at file, schema, route, and input
boundaries. It cannot replace host isolation, TLS, identity, or operational
discipline.{% endblock %} {% block docs_content %}
<section class="doc-section" aria-labelledby="production-checklist">
<h2 id="production-checklist">Production checklist</h2>
<ul class="security-checklist">
<li>
<strong>Firewall port 3100.</strong
><span
>The default <code>0.0.0.0:3100</code> listener is intentionally
LAN-visible.</span
>
</li>
<li>
<strong>Terminate TLS at a trusted reverse proxy.</strong
><span
>Bunfork has no built-in TLS and should not face the public internet
directly.</span
>
</li>
<li>
<strong>Set the public HTTPS origin.</strong
><span
>Use <code>BUNFORK_PUBLIC_ORIGIN=https://app.example.com</code> so
browser mutations must match it.</span
>
</li>
<li>
<strong>Separate the database key and API token.</strong
><span
>Generate each independently and keep both readable only by the service
account.</span
>
</li>
<li>
<strong>Keep state outside releases.</strong
><span
>Never place keys, tokens, databases, or backups under
<code>app/pages</code>, <code>public</code>, or the deployment
directory.</span
>
</li>
<li>
<strong>Back up the key separately.</strong
><span
>Database backups remain encrypted with the live database key.</span
>
</li>
<li>
<strong>Stop writers before restore.</strong
><span
>Restore is a verified offline operation, not a distributed
transaction.</span
>
</li>
</ul>
</section>
<section class="doc-section" aria-labelledby="secret-handling">
<h2 id="secret-handling">Secret handling</h2>
<ul class="check-list">
<li>
<code>keygen</code> creates a 256-bit random value in a private file.
</li>
<li>Secret files must not be accessible by group or others.</li>
<li>File values take precedence over environment values.</li>
<li>
Decoded secret comparisons are constant-time, including the key/token
inequality check.
</li>
<li>
The explicit frontend build environment excludes
<code>BUNFORK_DB_KEY</code> and <code>BUNFORK_API_TOKEN</code>.
</li>
<li>
Page trees reject hidden entries, links, and special files; public assets
reject sensitive paths; deploy inventory rejects recognized secret
patterns.
</li>
</ul>
</section>
<section class="doc-section" aria-labelledby="request-boundary">
<h2 id="request-boundary">Request boundary</h2>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Surface</th>
<th>Control</th>
</tr>
</thead>
<tbody>
<tr>
<td>Vector API</td>
<td>Bearer authentication; constant-time token comparison</td>
</tr>
<tr>
<td>Browser mutations</td>
<td>
Origin must equal the configured public origin or current host
</td>
</tr>
<tr>
<td>Request bodies</td>
<td>1 MiB cap plus vector-specific validation and work budgets</td>
</tr>
<tr>
<td>Native pages</td>
<td>
Auto-escaped template values and bounded multi-value query parsing
</td>
</tr>
<tr>
<td>Hidden request paths</td>
<td>Rejected before routing</td>
</tr>
<tr>
<td>Health/readiness</td>
<td>Public and GET-only by design</td>
</tr>
</tbody>
</table>
</div>
</section>
<section class="doc-section" aria-labelledby="response-headers">
<h2 id="response-headers">Response headers</h2>
<p>
Native responses set a self-only content security policy, deny framing,
disable MIME sniffing, use a no-referrer policy, and disable camera,
geolocation, and microphone access.
</p>
<p>
Static exports need same-origin browser code and may contain framework
bootstrap inline scripts or styles. Bunfork's static policy permits those
official export patterns. For a narrower policy, configure build-specific
hashes or nonces at the trusted reverse proxy.
</p>
</section>
<section class="doc-section" aria-labelledby="file-boundary">
<h2 id="file-boundary">File and artifact boundary</h2>
<p>
Native routes, templates, and public assets reject hidden entries, links,
hard links, special files, ambiguous routes, and unsafe paths. Production
compiles routes and preloads assets before binding.
</p>
<p>
Static admission additionally records every byte length and digest in a
strict manifest. Verification happens offline with <code>doctor</code> and
again before the static server binds.
</p>
</section>
<aside class="doc-callout warning" aria-labelledby="not-protected">
<h2 id="not-protected">What encryption cannot protect</h2>
<p>
A compromised live process, service account, root user, TLS proxy, binary,
or unlocked key can access plaintext. Bunfork also has no user accounts,
roles, remote backup transport, distributed locks, Windows hardening
guarantee, or defense against hostile same-UID filesystem races. Isolate
untrusted code under another OS account or container.
</p>
</aside>
<section class="doc-section" aria-labelledby="static-scope">
<h2 id="static-scope">The smallest security surface</h2>
<p>
If an application only needs compiled browser files, use static mode. It
does not open the database or require a key, token, tenant, model,
migration, native template engine, or foreign server runtime.
</p>
<pre><code>./target/release/bunfork doctor \
--static frontend/out \
--manifest bunfork-static.json
./target/release/bunfork serve \
--static frontend/out \
--manifest bunfork-static.json</code></pre>
</section>
<nav class="docs-pagination" aria-label="Documentation pagination">
<a href="/docs/deployment"
><small>Previous</small><span aria-hidden="true">โ</span> Deploy &
recover</a
>
<a href="/docs"
><small>Back to start</small>Documentation overview
<span aria-hidden="true">โ</span></a
>
</nav>
{% endblock %}