220 lines
12 KiB
HTML
220 lines
12 KiB
HTML
<!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>
|
||
|
|
:root { color-scheme: dark; }
|
||
|
|
* { box-sizing: border-box; }
|
||
|
|
body {
|
||
|
|
margin: 0;
|
||
|
|
background: #0f0f11;
|
||
|
|
color: #d6d6dc;
|
||
|
|
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;
|
||
|
|
border-bottom: 1px solid rgba(255,255,255,.08);
|
||
|
|
font-size: 13.5px;
|
||
|
|
}
|
||
|
|
.top a { color: #8a8a92; text-decoration: none; }
|
||
|
|
.top a:hover, .top a:focus-visible { color: #f0892a; }
|
||
|
|
.top .brand { color: #f0892a; font-weight: 700; letter-spacing: .04em; }
|
||
|
|
.top .here { color: #d6d6dc; margin-inline-start: auto; }
|
||
|
|
h1 { font-size: clamp(28px, 5vw, 36px); font-weight: 800; letter-spacing: -1px; line-height: 1.14; color: #f4f4f6; margin: 0 0 24px; }
|
||
|
|
h2 { font-size: 22px; font-weight: 700; letter-spacing: -.4px; color: #f4f4f6; margin: 46px 0 12px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,.07); }
|
||
|
|
h3 { font-size: 17.5px; font-weight: 700; color: #e8e8eb; margin: 30px 0 8px; }
|
||
|
|
h4 { font-size: 15.5px; font-weight: 700; color: #e8e8eb; margin: 22px 0 6px; }
|
||
|
|
p, li { color: #a9a9b3; }
|
||
|
|
p { margin: 0 0 16px; }
|
||
|
|
ul, ol { padding-inline-start: 22px; margin: 0 0 16px; }
|
||
|
|
li { margin: 5px 0; }
|
||
|
|
a { color: #f0892a; text-underline-offset: 2px; }
|
||
|
|
strong { color: #e8e8eb; }
|
||
|
|
code { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: .88em; background: #17171c; border: 1px solid rgba(255,255,255,.07); border-radius: 4px; padding: 1px 5px; color: #e8e8eb; }
|
||
|
|
pre { background: #0b0b0e; border: 1px solid rgba(255,255,255,.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: #c9c9d1; }
|
||
|
|
.tablewrap { overflow-x: auto; margin: 0 0 20px; }
|
||
|
|
table { border-collapse: collapse; width: 100%; font-size: 14.5px; min-width: 30rem; }
|
||
|
|
th { text-align: start; color: #8a8a92; font-weight: 600; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; padding: 0 14px 8px 0; border-bottom: 1px solid rgba(255,255,255,.12); }
|
||
|
|
td { padding: 9px 14px 9px 0; border-bottom: 1px solid rgba(255,255,255,.06); vertical-align: top; color: #a9a9b3; }
|
||
|
|
td:first-child, th:first-child { padding-inline-start: 0; }
|
||
|
|
blockquote { margin: 0 0 18px; padding: 2px 0 2px 16px; border-inline-start: 3px solid rgba(240,137,42,.4); color: #8a8a92; }
|
||
|
|
hr { border: 0; border-top: 1px solid rgba(255,255,255,.08); margin: 34px 0; }
|
||
|
|
img { max-width: 100%; height: auto; }
|
||
|
|
a:focus-visible { outline: 2px solid #f0892a; outline-offset: 2px; border-radius: 2px; }
|
||
|
|
.more { margin-top: 64px; padding-top: 22px; border-top: 1px solid rgba(255,255,255,.08); }
|
||
|
|
.more h2 { font-size: 13px; letter-spacing: .12em; text-transform: uppercase; color: #6b6b73; 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; }
|
||
|
|
.more li { margin: 0; padding: 7px 0; border-bottom: 1px solid rgba(255,255,255,.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 "this
|
||
|
|
check exists because completing the handshake does not prove identity" 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>
|