Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
4de5ad9
Add a package for security regression tests
afit21 Oct 14, 2026
203402a
Add security tests for zero-size file children, user pins, folder swa…
afit21 Oct 14, 2026
a874474
Add page security regression tests for H2, N1, M6, L10 and L14
afit21 Oct 14, 2026
eef2c11
Add a real-RNS regression test for the H1 /blocks slot-hold denial of…
afit21 Oct 14, 2026
a047532
Merge branches 'sec/content', 'sec/pages' and 'sec/h1rns' into securi…
afit21 Oct 14, 2026
4acf0e6
Add regression tests for keeper discovery, provider admission and nam…
afit21 Oct 14, 2026
077f33a
Fuzz the strict parsers with hypothesis when it is installed
afit21 Oct 14, 2026
da079d2
Test that parse_name rejects whitespace between the hex digits
afit21 Oct 14, 2026
23e11df
Merge branches 'sec/dht' and 'sec/tooling' into security/audit-2026-10
afit21 Oct 14, 2026
68dcb8f
Send a fixed message for gateway errors from the node and drop the pa…
afit21 Oct 14, 2026
d77be66
Remove the gateway's CORS rule, which never fired in browsers
afit21 Oct 14, 2026
a678443
Answer web pages' gateway requests the same whether content is stored…
afit21 Oct 14, 2026
211c470
Bound the bytes of a fetched DAG by what its declared size and direct…
afit21 Oct 14, 2026
7640d0b
Keep the disk reserve free when get and export write their output
afit21 Oct 14, 2026
63ebae9
Accept only hex digits in names given as text
afit21 Oct 14, 2026
641b9b2
Keep a newcomer pool in the provider store and grow each publisher's …
afit21 Oct 14, 2026
6b23f35
Show only the operator's pins and allowlisted CIDs on the display-mu-…
afit21 Oct 14, 2026
9098381
List only the operator's unsealed pins on the file-index-node page an…
afit21 Oct 14, 2026
7f8cbda
Update the gateway tests for the removed CORS rule, fixed error messa…
afit21 Oct 14, 2026
5bff67a
Merge branch 'fix/pagesfix' into security/audit-2026-10
afit21 Oct 14, 2026
4a765d4
Document the gateway_fetches limit as an accepted storage leak and te…
afit21 Oct 14, 2026
424b20b
Search past alpha for a name's keepers, rank them by routing-table st…
afit21 Oct 14, 2026
bfe9bc7
Test that ?filename= can't inject headers and that every API response…
afit21 Oct 14, 2026
d3a8493
Count directory nodes in full against a quota when holding requests t…
afit21 Oct 14, 2026
97b2537
Keep resolved names in the name table under announce floods and save …
afit21 Oct 14, 2026
ed92082
State in the specification that a node's names are linked to it throu…
afit21 Oct 14, 2026
d017197
Merge branch 'fix/n1fix' into security/audit-2026-10
afit21 Oct 14, 2026
dbd1144
Make /blocks displacement protection earned by progress
afit21 Oct 14, 2026
80b950d
Refuse /blocks requests that repeat a CID
afit21 Oct 14, 2026
7b9f6b9
Drop expectedFailure from the real-RNS H1 slot-hold test
afit21 Oct 14, 2026
a22bc1b
Document the /blocks slot fairness fix in spec, design and changelog
afit21 Oct 14, 2026
3e6ed04
Merge branch 'fix/h1fix' into security/audit-2026-10
afit21 Oct 14, 2026
1d929ec
Merge branch 'fix/dhtfix' into security/audit-2026-10
afit21 Oct 14, 2026
f00c8b9
Update the provider store tests for the newcomer pool
afit21 Oct 14, 2026
f17fd10
Describe provider_seniority's new uses in the README
afit21 Oct 14, 2026
2597934
Update the server tests for the CHUNK_SIZE budget on links with no ra…
afit21 Oct 14, 2026
a98b985
Cap the /blocks budget at CHUNK_SIZE on links with no rate estimate
afit21 Oct 14, 2026
2cd2469
Update the server tests for charging upload_budget as responses are sent
afit21 Oct 14, 2026
0726417
Charge upload_budget for bytes actually sent, not a response's full s…
afit21 Oct 14, 2026
09c6324
Keep half-open incoming links in a capped bucket of their own, closin…
afit21 Oct 14, 2026
c8d09ad
Update the real-RNS H1 attacker to request one CID, since a repeated …
afit21 Oct 14, 2026
4c8d651
Document the measured link-churn residual on the /blocks fast pool
afit21 Oct 14, 2026
c2c36b6
Note the /blocks budget cap, budget charging, half-open links and the…
afit21 Oct 14, 2026
3265621
Answer every page request and HEAD for ?format=raw with 409, as for a…
afit21 Oct 14, 2026
67b2843
Update the gateway format tests for raw blocks answered only to the u…
afit21 Oct 14, 2026
c3b4d45
Cancel a re-provided key's queued withdrawals and save where a publis…
afit21 Oct 14, 2026
b77bf1a
Check directories again when walking by path, and each file's final p…
afit21 Oct 14, 2026
b18fb02
Expect the path walk to stay in the shared folder in the L8 swap test
afit21 Oct 14, 2026
a443b49
Merge branch 'fix/srvfix' into security/audit-2026-10
afit21 Oct 14, 2026
1d0b0e6
Give the rnfs dir, API token and key files a private access list on W…
afit21 Oct 14, 2026
ea88d13
Don't follow a symlink when tightening the identity file's permissions
afit21 Oct 14, 2026
8ecad5b
Make name publish crash-consistent and reconcile name, follow and LXM…
afit21 Oct 14, 2026
0cc8193
Keep trusted LXMF senders' inbox messages under floods of new identit…
afit21 Oct 14, 2026
70f91a5
Update the LXMF inbox flood tests for the rate limit on untrusted sen…
afit21 Oct 14, 2026
24898d2
Limit held LXMF messages per claimed sender, drop unanswered ones ear…
afit21 Oct 14, 2026
84213fa
Take LXMF auto-fetches round-robin by sender and share a full queue b…
afit21 Oct 14, 2026
f41e8fb
Document the LXMF inbox, hold queue and auto-fetch fairness fixes and…
afit21 Oct 14, 2026
e2b5686
Install the release sdist in install.sh, checked against SHA256SUMS a…
afit21 Oct 14, 2026
cb7f8ec
Expect install.sh to install the release sdist in the packaging tests
afit21 Oct 14, 2026
1f3cb70
Mount the checkout read-only in the release's Debian smoke test, pin …
afit21 Oct 14, 2026
cbf257e
Run the release job in a release environment and filter the systemd u…
afit21 Oct 14, 2026
719c1bc
Fetch every accepted remote pin: deregister the worker when its queue…
afit21 Oct 14, 2026
1ffc8eb
Keep and hand out only members' provider records in private mode, and…
afit21 Oct 14, 2026
d74dcc5
Refuse a /provide from a publisher at its limit without rescanning it…
afit21 Oct 14, 2026
863f0b7
Answer GET /api/status without the API token with the version only, a…
afit21 Oct 14, 2026
9544fd6
Mark sealed files in pin ls
afit21 Oct 14, 2026
c14acc7
Document the remote pin, private mode, /provide, status and pin ls fi…
afit21 Oct 14, 2026
369fe5e
Update existing tests for token-gated status, sealed pin ls entries, …
afit21 Oct 14, 2026
6f6b36e
Read the DAG nodes eviction and gc walk before taking the storage lock
afit21 Oct 14, 2026
2be6acf
Retry setting a new file's private access list while Windows reports …
afit21 Oct 14, 2026
ef3cc9d
Start the node even when releasing a stray follow or LXMF pin fails
afit21 Oct 14, 2026
d73ba39
Merge branch 'fix/corefix1' into security/audit-2026-10
afit21 Oct 14, 2026
afe3d41
Merge branch 'fix/corefix2' into security/audit-2026-10
afit21 Oct 14, 2026
b67b35f
Merge branch 'fix/lxmffix' into security/audit-2026-10
afit21 Oct 14, 2026
732564c
Merge branch 'fix/platfix' into security/audit-2026-10
afit21 Oct 14, 2026
73041a7
Check rnfs.dht responses' msgpack headers before RNS decodes them, an…
afit21 Oct 14, 2026
6c79d4c
Limit each incoming link's request rate before parsing, refusing a fe…
afit21 Oct 15, 2026
42aadca
Cool down a requester that rejects a /blocks response before any of i…
afit21 Oct 15, 2026
4f203e4
Document the response header check, the per-link request rate limit a…
afit21 Oct 15, 2026
cc0736d
Fuzz the response header check with mutated honest responses
afit21 Oct 15, 2026
5092f80
Render the file-index-node page's Files section again: the page body …
afit21 Oct 15, 2026
49df3c9
Skip remote_pins.json entries that aren't a pair of strings, so one c…
afit21 Oct 15, 2026
0e350ae
Release stray follow and LXMF pins at start only when follow.json or …
afit21 Oct 15, 2026
56ed5ac
Keep the pins and names.json entry of a name whose key file can't be …
afit21 Oct 15, 2026
1d7874e
Document the start-up reconcile with damaged state, unreadable name k…
afit21 Oct 15, 2026
126063d
Treat a shared instance's local interface as of unknown bitrate in th…
afit21 Oct 15, 2026
c6f4a74
Let the per-interface fairness rule evict only responses past half th…
afit21 Oct 15, 2026
86c4475
Give links that aren't slow 16 /blocks slots by default (fast_blocks_…
afit21 Oct 15, 2026
1af981f
Count a /blocks response's upload_budget commitment after its grace o…
afit21 Oct 15, 2026
9fc2a35
Update the H1 slot tests for the 16-slot fast pool, the fairness rule…
afit21 Oct 15, 2026
90a2952
Test the shared-instance, multi-hop and queued-radio graces, the fair…
afit21 Oct 15, 2026
e6fada8
Document the /blocks grace for shared instances and multi-hop paths, …
afit21 Oct 15, 2026
2290b66
Bound the file nodes a byte-range read meets and the blocks a listing…
afit21 Oct 15, 2026
4bdd14a
Measure test_dag_caps' honest trees against the credit directory and …
afit21 Oct 15, 2026
383f770
Refuse a get or export whose destination's free space can't be read
afit21 Oct 15, 2026
bb23b64
Accept only 32 hex digits as a node ID or name in the local API and g…
afit21 Oct 15, 2026
a9cf534
Document the byte-range and listing bounds, the per-entry credit, and…
afit21 Oct 15, 2026
0fbec2a
Merge branch 'fix/h1r2' into security/audit-2026-10
afit21 Oct 15, 2026
bfcf2fb
Merge branch 'fix/fetchr2' into security/audit-2026-10
afit21 Oct 15, 2026
e2e78c2
Let test_m3_keeper_discovery's ranking stand-ins take the standing sn…
afit21 Oct 15, 2026
c83ceab
Rank keepers on the routing table as it stood before the search, end …
afit21 Oct 15, 2026
2f72d80
Evict from the newcomer pool's biggest holder, and remember first-see…
afit21 Oct 15, 2026
a9c5640
Save the name floor when a resolve raises it, at most every 5 s, and …
afit21 Oct 15, 2026
dc1f71d
Give each untrusted LXMF sender an allowance before the node-wide buc…
afit21 Oct 15, 2026
7a67709
Age the floor entries in test_floor_bounded, and space one sender's m…
afit21 Oct 15, 2026
0a50020
Document keeper ranking by standing before the search, fair-share poo…
afit21 Oct 15, 2026
7f489ed
Merge branch 'fix/dhtr2b' into security/audit-2026-10
afit21 Oct 15, 2026
2f550e5
Say that the parser fuzz tests run only by hand, since CI doesn't ins…
afit21 Oct 15, 2026
35ef957
Give the fake ACL backend create_private_dir and the page's fake CLI …
afit21 Oct 15, 2026
4383bfa
Create the Windows rnfs dir and its subdirectories with the private a…
afit21 Oct 15, 2026
2db7830
Stop the node when its identity file is a symlink, another user's or …
afit21 Oct 15, 2026
1d8e099
Skip files with other hard links when scanning the shared folder
afit21 Oct 15, 2026
3707b53
Show only allowlisted CIDs on display-mu-file in private mode, and ra…
afit21 Oct 15, 2026
0c8d781
Document the Windows rnfs dir created private, the identity and setti…
afit21 Oct 15, 2026
35a3f53
Merge branch 'fix/platr2' into security/audit-2026-10
afit21 Oct 15, 2026
12c4345
Remove a leftover fragment from the README and point design.md at the…
afit21 Oct 15, 2026
f8ec0c6
Remove Planned-companion-applications.md as it is no longer needed
afit21 Oct 15, 2026
f02927c
Update roadmap.md for versioning and patch notes
afit21 Oct 15, 2026
47d1b15
Make the new security tests pass on Windows: compare logged paths acr…
afit21 Oct 15, 2026
a604399
Hold the remote pin fetches in test_remote_pins_waiting_are_capped so…
afit21 Oct 15, 2026
161fb8a
Set the version to 0.1.4
afit21 Oct 15, 2026
72164bb
Merge main's README edits into dev
afit21 Oct 15, 2026
a452ada
Find the README's Checking a download section at any heading level
afit21 Oct 15, 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
22 changes: 17 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ on:
permissions:
contents: read

# Actions are pinned to commit SHAs and the Flatpak container to a digest (the comments say which tag);
# update both together. Values from expressions reach run: scripts only through env:, never spliced in.
# Actions are pinned to commit SHAs and the Flatpak and Debian containers to digests (the comments say which
# tag); update them together. Values from expressions reach run: scripts only through env:, never spliced in.
# Build tools come from packaging/build-tools.lock and the build backend from it (or build-backend.lock) with
# --require-hashes, and builds run without build isolation, so nothing unpinned is fetched for a build.
jobs:
Expand Down Expand Up @@ -95,14 +95,23 @@ jobs:
rnfs --version
python3 packaging/smoke_test.py /usr/bin/rnfs
sudo apt-get remove -y rnfs
# The .deb is uploaded, checksummed and attested next, so the container must not be able to change it:
# the checkout is mounted read-only (the .deb is copied out to be installed), the image is pinned by
# digest (debian:12, the multi-arch index as of 2026-10-14), and only apt-get runs as root; the smoke
# test runs as nobody
- name: Install and smoke test (Debian 12, Python 3.11)
if: matrix.arch == 'amd64'
run: |
docker run --rm -v "$PWD:/w" -w /w debian:12 sh -ec '
docker run --rm --security-opt no-new-privileges -v "$PWD:/w:ro" -w /w \
debian:12@sha256:2c037a04925515fdd6ea85ea14a682d0e79931f5e9f5d07b6dbfc6ba12f9e858 sh -ec '
cp dist/rnfs_*_amd64.deb /tmp/
apt-get update -qq
apt-get install -y -qq ./dist/rnfs_*_amd64.deb >/dev/null
apt-get install -y -qq /tmp/rnfs_*_amd64.deb >/dev/null
python3 --version
python3 packaging/smoke_test.py /usr/bin/rnfs'
mkdir /tmp/smoke
chown nobody:nogroup /tmp/smoke
HOME=/tmp/smoke TMPDIR=/tmp/smoke setpriv --reuid=nobody --regid=nogroup --clear-groups \
python3 packaging/smoke_test.py /usr/bin/rnfs'
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: release-deb-${{ matrix.arch }}
Expand Down Expand Up @@ -215,6 +224,9 @@ jobs:
if: github.event_name == 'push' && github.ref_type == 'tag'
needs: [tests, version, python, deb, flatpak, windows]
runs-on: ubuntu-latest
# Protection rules (required reviewers, which tags may deploy) are set on this environment in the
# repository's settings, so publishing can be gated beyond whoever can push a v* tag
environment: release
permissions:
contents: write # create the release
id-token: write # sign the provenance attestation (Sigstore certificate for this workflow run)
Expand Down
284 changes: 284 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

6 changes: 0 additions & 6 deletions Planned-companion-applications.md

This file was deleted.

123 changes: 88 additions & 35 deletions README.md

Large diffs are not rendered by default.

113 changes: 113 additions & 0 deletions companion-nomadnet-sites/display-mu-file/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# display-mu-file

A NomadNet page that shows an RNFS file by its CID, for example a Micron page you published with RNFS.
Visitors open it with the CID as a link variable:

```
`[My page`:/page/display-mu-file.mu`cid=bafkreiailvfecuuvt6tctx3owax2jic7y5af2ndriwo6xymcq2uwyut34a]
```

## Install

Copy `index.mu` into NomadNet's pages directory (usually `~/.nomadnetwork/storage/pages/`), for example as
`display-mu-file.mu`, and make it executable (`chmod +x`). The page runs the `rnfs` CLI, so the rnfs daemon
must be running as the same user as NomadNet.

## What visitors can see

Anyone who can reach your NomadNet node can open the page. The page never fetches, pins or provides anything
just because a visitor asked for it. It shows a file only if one of these is true:

- **You pinned it and this node has all of it.** The pin must come from you (source `user` or `folder` in
`rnfs pin ls`), not from a contact, a followed name or a remote pin request. The page checks this with
`rnfs pin ls`, which doesn't touch the network. For any other CID it runs nothing else, so it doesn't even
fetch the root block.
- **Its CID is in your allowlist** (see below). These are the only files the page fetches from the network
if this node doesn't have them yet.

If your node runs in `private` mode (members only), the page shows **only allowlisted CIDs**: RNFS shares your
pins with members only, and the page can't tell who is one. It asks `rnfs status` whether the node is private,
and treats an answer that doesn't say as private. Otherwise your pins are shown to anyone who knows the CID, so
don't pin files you wouldn't put on the page.

The page also refuses:

- files larger than `MAX_SIZE` (200 KB), according to `rnfs stat`;
- folders;
- sealed (encrypted) files: it reads files with `rnfs cat --raw`, so it never decrypts a file sealed to
this node, and it refuses one it recognises;
- files that aren't UTF-8 text.

### How files are rendered

- **Allowlisted CIDs** are printed as Micron, exactly as written: links, fields and formatting work.
- **Everything else**, including your own pins that aren't on the allowlist, is shown as plain text under a
"Third-party content" banner. The text is put in a Micron literal block, so its links, fields, headings
and page headers (such as `#!bg=`) don't work, and control characters are replaced. This stops someone
else's page (for example a phishing form, or one that imitates your own pages) from appearing as your
node's content.

To show one of your own Micron pages as a page, add its CID to the allowlist.

## The data directory

The page keeps its files in `~/.local/state/rnfs-pages/display-mu-file/`, which is outside NomadNet's pages
directory, so none of it is served. The page creates the directory with mode 0700 on first use.

| File | What it is |
| --- | --- |
| `allowlist.txt` | Your allowlist. You create and edit it. |
| `page.log` | Errors, with details. Moved to `page.log.1` past 1 MB. |
| `lock` | Lets only one request run at a time. |
| `rate.json` | Times of recent requests (with the visitor's identity hash, if they identified), for the rate limits. |

NomadNet runs pages with a minimal environment. `RNFS_PAGE_DIR` overrides the directory, but it only works
when you run the page by hand, for example to test it:

```
var_cid=<CID> RNFS_PAGE_DIR=/tmp/page-test ./index.mu
```

### The allowlist

`allowlist.txt` lists one CID per line. Text after `#` is a comment, and blank lines and anything that isn't
a CID are ignored:

```
# My pages
bafkreiailvfecuuvt6tctx3owax2jic7y5af2ndriwo6xymcq2uwyut34a # front page
```

An allowlisted CID that this node doesn't have yet is fetched with `rnfs cat` when a visitor opens it. That
read covers at most `MAX_SIZE` bytes, even if the file understates its size, and it pins nothing and starts
no daemon job. If it takes longer than the page's deadline (`TIMEOUT`, 120 s for the whole request), the
command is killed and nothing is left running. The fetched blocks are cached like any other read. With the
default `provide_strategy = "roots"`, this node then provides the file while it stays cached. To keep an
allowlisted file available, pin it yourself with `rnfs pin add <CID>`.

### Busy and rate limits

Only one request runs at a time. A visitor who arrives while another request is running is asked to try
again in a moment. There are also rate limits, each over any `RATE_WINDOW` (60 s):

- `VISITOR_LIMIT` (10) requests from each visitor who identified to your node. NomadNet tells the page who
that is (`remote_identity`) only when the visitor chose to identify on the link.
- `RATE_LIMIT` (30) requests from all visitors who didn't identify, together. Nothing tells them apart:
NomadNet's other value, `link_id`, is new with every link the visitor opens.
- `TOTAL_LIMIT` (60) requests from everyone.

So one visitor can't use up everyone else's requests. Identities cost nothing to make, though: someone who
makes several can still reach `TOTAL_LIMIT`, and then every visitor waits for the window to pass. Requests
with a malformed CID aren't counted, because they never reach rnfs. All checks fail closed: if the page can't
use its data directory, it shows nothing. You can change these numbers at the top of `index.mu`.

### The log

Visitors only see short messages such as "can't be shown right now". The details go to `page.log`: CLI
error messages (which can contain local paths), exceptions and timeouts. Check the log if the page
misbehaves.

## Upgrading from an earlier version

Earlier versions of this page pinned (source `user`) and provided every CID a visitor asked for, without
limit. Check `rnfs pin ls` for pins you didn't make, and remove them with `rnfs pin rm <CID>`.
Loading
Loading