Files

259 lines
14 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<!-- Generated by scripts/build-docs.js from doc/*.md. Edits here are overwritten;
change the Markdown instead. -->
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src 'self' data:; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; upgrade-insecure-requests;">
<meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta http-equiv="Referrer-Policy" content="strict-origin-when-cross-origin">
<title>Contributing - SecureBit.chat</title>
<meta name="description" content="Tests are individual .mjs files run in sequence by npm test. There is no test runner and no mocking library. A new suite is a new file, added to the test…">
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1">
<link rel="canonical" href="https://securebit.chat/docs/contributing/">
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico">
<meta property="og:site_name" content="SecureBit.chat">
<meta property="og:title" content="Contributing - SecureBit.chat">
<meta property="og:description" content="Tests are individual .mjs files run in sequence by npm test. There is no test runner and no mocking library. A new suite is a new file, added to the test…">
<meta property="og:url" content="https://securebit.chat/docs/contributing/">
<meta property="og:type" content="article">
<meta property="og:locale" content="en_US">
<meta property="og:image" content="https://securebit.chat/assets/social-card.png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Contributing - SecureBit.chat">
<meta name="twitter:description" content="Tests are individual .mjs files run in sequence by npm test. There is no test runner and no mocking library. A new suite is a new file, added to the test…">
<meta name="twitter:image" content="https://securebit.chat/assets/social-card.png">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "Contributing",
"description": "Tests are individual .mjs files run in sequence by npm test. There is no test runner and no mocking library. A new suite is a new file, added to the test…",
"url": "https://securebit.chat/docs/contributing/",
"inLanguage": "en",
"isPartOf": {
"@type": "WebSite",
"@id": "https://securebit.chat/#website"
},
"publisher": {
"@id": "https://securebit.chat/#organization"
}
}
</script>
<style>
2026-09-04 17:38:41 -04:00
/* These pages carry no script — that is the point of them — so the theme can only
come from the media query. There is no toggle here and no stored preference: a
reference page follows the reader's system and nothing else. The values are the
same two palettes as src/styles/theme.css, restated because these pages do not
load the app's stylesheet and are not going to start for eleven declarations. */
:root {
color-scheme: dark;
--d-ink: 255, 255, 255;
--d-bg: #0f0f11;
--d-bg-deep: #0b0b0e;
--d-code-bg: #17171c;
--d-text: #d6d6dc;
--d-heading: #f4f4f6;
--d-strong: #e8e8eb;
--d-body: #a9a9b3;
--d-pre: #c9c9d1;
--d-muted: #8a8a92;
--d-faint: #6b6b73;
--d-accent: #f0892a;
--d-accent-rgb: 240, 137, 42;
}
@media (prefers-color-scheme: light) {
:root {
color-scheme: light;
--d-ink: 0, 0, 0;
--d-bg: #fbfbfc;
--d-bg-deep: #f2f3f5;
--d-code-bg: #f2f3f5;
--d-text: #26262c;
--d-heading: #0e0e12;
--d-strong: #1b1b20;
--d-body: #43434c;
--d-pre: #2f3340;
--d-muted: #63636c;
--d-faint: #7c7c85;
--d-accent: #b05c08;
--d-accent-rgb: 176, 92, 8;
}
}
* { box-sizing: border-box; }
body {
margin: 0;
2026-09-04 17:38:41 -04:00
background: var(--d-bg);
color: var(--d-text);
font-family: Inter, system-ui, -apple-system, "Segoe UI", sans-serif;
font-size: 16px;
line-height: 1.68;
}
.wrap { max-width: 46rem; margin: 0 auto; padding: 28px 24px 90px; }
.top {
display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: baseline;
padding-bottom: 16px; margin-bottom: 40px;
2026-09-04 17:38:41 -04:00
border-bottom: 1px solid rgba(var(--d-ink),.08);
font-size: 13.5px;
}
2026-09-04 17:38:41 -04:00
.top a { color: var(--d-muted); text-decoration: none; }
.top a:hover, .top a:focus-visible { color: var(--d-accent); }
.top .brand { color: var(--d-accent); font-weight: 700; letter-spacing: .04em; }
.top .here { color: var(--d-text); margin-inline-start: auto; }
h1 { font-size: clamp(28px, 5vw, 36px); font-weight: 800; letter-spacing: -1px; line-height: 1.14; color: var(--d-heading); margin: 0 0 24px; }
h2 { font-size: 22px; font-weight: 700; letter-spacing: -.4px; color: var(--d-heading); margin: 46px 0 12px; padding-top: 14px; border-top: 1px solid rgba(var(--d-ink),.07); }
h3 { font-size: 17.5px; font-weight: 700; color: var(--d-strong); margin: 30px 0 8px; }
h4 { font-size: 15.5px; font-weight: 700; color: var(--d-strong); margin: 22px 0 6px; }
p, li { color: var(--d-body); }
p { margin: 0 0 16px; }
ul, ol { padding-inline-start: 22px; margin: 0 0 16px; }
li { margin: 5px 0; }
2026-09-04 17:38:41 -04:00
a { color: var(--d-accent); text-underline-offset: 2px; }
strong { color: var(--d-strong); }
code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .88em; background: var(--d-code-bg); border: 1px solid rgba(var(--d-ink),.07); border-radius: 4px; padding: 1px 5px; color: var(--d-strong); }
pre { background: var(--d-bg-deep); border: 1px solid rgba(var(--d-ink),.08); border-radius: 8px; padding: 14px 16px; overflow-x: auto; margin: 0 0 18px; }
pre code { background: none; border: 0; padding: 0; font-size: 13px; line-height: 1.62; color: var(--d-pre); }
.tablewrap { overflow-x: auto; margin: 0 0 20px; }
table { border-collapse: collapse; width: 100%; font-size: 14.5px; min-width: 30rem; }
2026-09-04 17:38:41 -04:00
th { text-align: start; color: var(--d-muted); font-weight: 600; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; padding: 0 14px 8px 0; border-bottom: 1px solid rgba(var(--d-ink),.12); }
td { padding: 9px 14px 9px 0; border-bottom: 1px solid rgba(var(--d-ink),.06); vertical-align: top; color: var(--d-body); }
td:first-child, th:first-child { padding-inline-start: 0; }
2026-09-04 17:38:41 -04:00
blockquote { margin: 0 0 18px; padding: 2px 0 2px 16px; border-inline-start: 3px solid rgba(var(--d-accent-rgb),.4); color: var(--d-muted); }
hr { border: 0; border-top: 1px solid rgba(var(--d-ink),.08); margin: 34px 0; }
img { max-width: 100%; height: auto; }
2026-09-04 17:38:41 -04:00
a:focus-visible { outline: 2px solid var(--d-accent); outline-offset: 2px; border-radius: 2px; }
.more { margin-top: 64px; padding-top: 22px; border-top: 1px solid rgba(var(--d-ink),.08); }
.more h2 { font-size: 13px; letter-spacing: .12em; text-transform: uppercase; color: var(--d-faint); border: 0; margin: 0 0 12px; padding: 0; font-weight: 700; }
.more ul { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: 4px 24px; }
2026-09-04 17:38:41 -04:00
.more li { margin: 0; padding: 7px 0; border-bottom: 1px solid rgba(var(--d-ink),.05); font-size: 14.5px; }
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
</style>
</head>
<body>
<div class="wrap">
<nav class="top">
<a class="brand" href="/">SecureBit.chat</a>
<a href="/docs/">Documentation</a>
<a href="https://github.com/SecureBitChat/securebit-chat" rel="noopener">GitHub</a>
<span class="here">Contributing</span>
</nav>
<h1>Contributing</h1>
<h2 id="workflow">Workflow</h2>
<pre><code class="language-bash">npm install
npm test # 41 suites, plain node:assert, no framework
npm audit
npm run build
</code></pre>
<p>Tests are individual <code>.mjs</code> files run in sequence by <code>npm test</code>. There is no test
runner and no mocking library. A new suite is a new file, added to the <code>test</code>
script in <code>package.json</code>.</p>
<h2 id="areas-that-need-extra-care">Areas that need extra care</h2>
<p>Changes touching any of these should come with tests that would fail without the
change:</p>
<ul>
<li>Verification: the safety code, the gate on control frames, protocol
compatibility</li>
<li>The Double Ratchet: key derivation, chain advance, skipped-key bounds, the
order in which state is committed</li>
<li>The inbound message path: anything that decides what reaches the interface</li>
<li>ICE and TURN behaviour, and the connection recovery cycle</li>
<li>File transfer consent and type policy</li>
<li>IndexedDB migration</li>
<li>Disconnect and resource cleanup</li>
</ul>
<h2 id="writing-tests-that-are-worth-having">Writing tests that are worth having</h2>
<p>Two bugs reached production during recent work, and both had the same cause: the
test built its own input instead of using what the application actually produces.</p>
<p>One test generated its own ECDH key pairs with usages the real generator did not
grant, and passed against a build that could not establish a session at all.
Another passed a locally generated public key where the application always
supplies an imported, non-extractable one, and missed a failure that disabled
forward secrecy for one side of every conversation. Locally generated public keys
are always extractable in WebCrypto regardless of the flag you pass, so that
difference is invisible unless you look for it.</p>
<p>The lesson is worth stating plainly: use the real factory functions, and where a
value crosses a boundary in the application, make the test cross the same
boundary. A test that constructs its inputs verifies the algorithm. Only a test
that uses the shipped path verifies the code.</p>
<p>Before relying on a new test, confirm it fails when the fix is removed. A test
that cannot fail is worse than no test, because it is read as coverage.</p>
<h2 id="documentation">Documentation</h2>
<p>When behaviour changes, update the documentation in the same commit:</p>
<div class="tablewrap"><table>
<thead>
<tr>
<th>Change</th>
<th>Documents</th>
</tr>
</thead>
<tbody><tr>
<td>Anything user-visible</td>
<td><code>README.md</code>, <code>CHANGELOG.md</code></td>
</tr>
<tr>
<td>Verification, keys, the ratchet</td>
<td><code>doc/CRYPTOGRAPHY.md</code>, <code>doc/ARCHITECTURE.md</code></td>
</tr>
<tr>
<td>The invitation format or the in-band key exchange</td>
<td><code>doc/DESCRIPTOR-SBQ2.md</code>, <code>doc/CRYPTOGRAPHY.md</code>, <code>doc/ARCHITECTURE.md</code></td>
</tr>
<tr>
<td>Deployment, ICE, file policy</td>
<td><code>doc/CONFIGURATION.md</code></td>
</tr>
<tr>
<td>Calls, codecs, adaptation</td>
<td><code>doc/CALLS.md</code></td>
</tr>
<tr>
<td>Internal interfaces</td>
<td><code>doc/API.md</code></td>
</tr>
<tr>
<td>Anything security relevant</td>
<td><code>SECURITY.md</code></td>
</tr>
</tbody></table></div>
<p>Values in the documentation (limits, timeouts, algorithm parameters) are taken
from the source. If you change one in code, change it in the documentation too,
otherwise the next person will trust the wrong number.</p>
<h2 id="release-notes-and-security-fixes">Release notes and security fixes</h2>
<p>Release notes describe what improved. They do not spell out how a weakness could
have been exploited, and neither do source comments. Users who have not updated
are the ones exposed by that detail, and with no server there is no way to update
everyone at once.</p>
<p>Comments explaining why a guard exists are valuable and should stay, because they
are what stops the guard being removed later. The distinction is between &quot;this
check exists because completing the handshake does not prove identity&quot; and a
reproduction recipe.</p>
<h2 id="pull-requests">Pull requests</h2>
<p>Include:</p>
<ul>
<li>what the problem is</li>
<li>what the change does</li>
<li>which tests you ran, and which new ones you added</li>
<li>what could regress</li>
<li>for user-visible changes, a screenshot or a log</li>
</ul>
<nav class="more">
<h2>More documentation</h2>
<ul>
<li><a href="/docs/">Documentation</a></li>
<li><a href="/docs/faq/">Frequently asked questions</a></li>
<li><a href="/docs/architecture/">Architecture</a></li>
<li><a href="/docs/cryptography/">Cryptography</a></li>
<li><a href="/docs/descriptor-sbq2/">SBQ2 — connection descriptor v2</a></li>
<li><a href="/docs/configuration/">Configuration</a></li>
<li><a href="/docs/calls/">Voice and video calls</a></li>
<li><a href="/docs/api/">API Notes</a></li>
<li><a href="/docs/use-policy/">Use policy and limitations</a></li>
</ul>
</nav>
</div>
</body>
</html>