All released versions of Radicle are affected by vulnerabilities [1] in the
network protocol: traffic is unencrypted and peers are not authenticated.
A network observer can read the contents of private repositories, and an
attacker can impersonate a node that to fetch them. No fix has been released
yet.
Since users may rely on private repositories being confidential, `rad
init` now warns about this before the interactive "Visibility" prompt,
so the warning is printed before the choice is made. The warning is printed
again in the epilogue whenever the created repository is private. This
also covers `--private`, which skips the prompt.
Update the `rad-init-private` and `rad-init-private-no-seed` examples
to match the new output.
[1]: https://radicle.dev/2026/09/23/disclosure-of-vulnerability-in-network-protocol
Testing revealed that the implementation violated RIP-0006, which
requires the most specific rule matching the target of `HEAD`
to use an `allow` of `delegates` and the document threshold.
`validate_head_rule` took the first match from `RawRules`, which orders
patterns lexicographically, so a glob could be validated in place of the
rule that actually governs the branch.
The issue here is that the correct order for matching patterns is only
guaranteed on `Rules`, but not on `RawRules`.
To prevent such errors in the future, remove `RawRules::matches`. This
requires some refactoring, and makes synthesis of the default branch
rule slightly less efficient in the worst case, but we are willing to
take that hit for a cleaner implementation.
Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.dev>
Many codepaths error if the `xyz.radicle.project` payload is not present
in the identity document. Change these to fall back gracefully.
Loading of payloads is unified in `GetPayload`.
An important detail is that `PayloadError::NotFound` is removed, and
`Option` is used instead.
The tests `rad-warn-ipv6.md` and `rad-warn-old-nodes.md` are meant to
primarily test the output of warnings. They test the output twice, via
`rad debug` and via `rad node status`.
Testing the output via one command should be sufficient.
Reasons for the rewrite:
- Migrate away from `ec25519` as our implementation of Ed25519,
because that crate is unmaintained.
Instead of `ec25519` we use `ed25519-dalek` which is the most popular
implementation of Ed25519 in Rust.
- Migrate away from `amplify`, because we make very little use of its
features. Here, we only use `amplify::Bytes32`, which is just a thin
wrapper of `[u8; 32]`.
Instead of `amplify::Bytes32` we use `type PublicKeyBytes = [u8; 32]`.
The realization that `ExtendedSignature` is not really tied to SSH, but
is merely a representation of a key together with a signature, allows to
relax the bounds on traits in `radicle-cob`.
Separating out uses of "public key" (a rather simple model of a key,
just 32 bytes) and "verifying key" (an actual point on the Edwards
curve, ready to verify signatures, using more memory than just 32 bytes)
allows a more sensible definition of `crypto::Signer`, which propagates
to `radicle-cob`, `radicle`, `radicle-cli`, `radicle-node` and
simplifies trait bounds.
In `radicle-node`, make the fact clearer that actually the secret key
itself is required (for our implementation of Diffie-Hellman) not
"just" signing. This results in removal of the generic argument
(usually called `G`) of multiple types.
Concretizing the types in `radicle-node` this way, i.e. by (morally)
setting `G = radicle_crypto::SigningKey`, leads to removal of
`MemorySigner` and `MockSigner`, as they really just wrap a signing
key.
Re-exporting `ed25519::Signature` (via re-export in `ed25519-dalek`)
is hopefully more future-proof than using our own type for signatures.
However, the fact that `ed25519::Signature` is not serializable and
does not implement `std::hash::Hash` means that we have to resort to
ad-hoc solutions in `radicle`:
1. The Identity COB requires serialization of signatures. These are
generally questionable, since the security of the Identity COB
should rely on the security of Signed References. In the medium
term we might even want to consider ignoring signatures in actions.
2. When reading Signed References, detection of Replay Attacks uses
a `HashMap` keyed by signatures. This detection only exists for
backwards compatibility.
These two use cases are exotic enough to warrant ad-hoc implementations.
By not wrapping `ed25519::Signature`, we encourage writing more widely
compatible code in the future.
Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
This introduces a `fetch_timeout` setting to the node configuration,
replacing the hardcoded constant. It defines the duration a fetch stream
is allowed to stall before being aborted. The default remains 30s, but
users on anonymizing transports like Tor can now increase it to avoid
spurious timeouts during network congestion.
On `Action::RevisionAccept` use `has_active_sibling_accept` to check for a sibling accept.
If one is found, the `RevisionAccept` is logged and skipped.
This preserves the invariant of a single accept per delegate on active, sibling
revisions without throwing an error on existing histories.
The evaluation of the reposioty identity is rewritten to better handle cases
where there are child and sibling revisions that are active at the same time.
In particular, `fn Identity::action` is now free of any references to
`self.current`.
This is achieved through two main improvements. The first is that the `State`
of revisions changes to improve clarity:
1. `Active` and `Accepted` remain, and their meanings also remain the same.
2. `Stale` is removed entirely.
3. `Rejected` is improved to also contain `RejectedBy`, to keep track of the
reason for rejection.
4. `Redacted` is promoted to a state. Similarly, the reason for
redaction is tracked by `RedactedBy`.
The transition of a revision from `Active` to one of the other states now
influences siblings and children.
If a revision transitions to `Accepted`, then the sibling revisions can no
longer transition to `Accepted`. They are considered `Rejected` where the reason
is `Sibling`. This cascades: Children of siblings are `Rejected`
recursively, tracking `RejectedBy::Parent`.
Any children of the `Accepted` revision are also evaluated to see if they can be
similarly transitioned to the `Accepted` state; since they may have already been
voted on.
If a revision was rejected by a majority, then the it transitions to `Rejected`
with `RejectedBy::Vote`. Dually to acceptance, the children of this revision
are also rejected with the reason of `Ancestor`.
Finally, when the author of a revision redacts a revision, it transitions
to `Redacted`, tracking `RedactedBy::Author`, and its children are redacted
tracking `RedactedBy::Parent`.
The test is adjusted to `remove_delegate_concurrent` reflect that concurrently
proposed revisions are retained in the timeline and explicitly marked as rejected,
rather than being dropped entirely (as before).
Introduces support for Gerrit-style magic push references via
`refs/for/<branch>`. Pushing to this ref automatically extracts the
target branch and opens a patch against it, bypassing the need for the
push option `patch.target`. Example:
```
$ git push rad HEAD:refs/for/accepted
```
Will open a patch with its `patch.target` set to `refs/heads/accepted`.
The `rad_cob_update` test accidentally use the `rad-cob-log` file
instead of `rad-cob-log`.
This change fixes that, and updates the `rad-cob-update` example file
to the latest patch show format.
The normalization of paths was converting valid `\n` characters to `/n`.
This had not been noticed until codespell suggest the change of
`/ndefined` to be `/undefined`.
Use `Assert::normalize_paths(false)` to disable this behaviour.
The documentation for `RateLimit::fill_rate` and `RateLimit::capacity`
were missing.
Add the documentation to help users decided on values based on how the
rate limiting works.
Add custom check for ellipses "...", asking for replacement of "…".
Git ranges and the CLI wildcard matches are ignored.
This change includes fixes to all sites that did not pass the check.
When invoked without any of `--patch`, `--delete`, `--accept`,
or `--reject`, the command panicked.
It will now correctly return an error to use `--accept`, `--reject`,
or to supply a message.
Note that `--patch` and `--delete` are removed as these commands are
now obsolete.
The new doc test also documents two previously-untested behaviors
of `rad patch review`: neutral reviews (verdict=None with summary)
and the COB layer's silent no-op when a duplicate review is submitted
(see `test_patch_review_duplicate` in radicle-cli/tests/commands/patch.rs).
Fixes 8ceff5d4b8
Signed-off-by: Arthur Mariano <arthvm@proton.me>
Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
Exercise the edge case where Bob, as a delegate, has never pushed to the default branch.
He then merges Alice's patch, and it is correctly marked as merged.
When delegates collaborate on the repository identity, their voting
narrows the tree of active proposals to the chain of accepted
proposals. Thus, they crucially must understand which proposals are
siblings, i.e., which proposals share the same parent, because no
two sibling proposals should ever be accepted at the same time, as
this would mean a fork.
To ease collaboration of delegates and improve their overview, also
print the parent revision, if present. Note that the initial revision
is the only revision that has no parent.
Parsing of IPv6 addresses not enclosed in square brackets causes a
warning to be logged.
This is too strict, since the user can not influence IPv6 addresses
received over the network, but only those in their configuration.
Remove the warning from parsing, and instead carry a boolean to warn the
user later.
Canonical references can not be used to model symbolic references.
Relax this restriction by adding another member to the payload, named
"symbolic". Its key-value/name-target pairs then translate directly to
canonical symbolic references.
Care is taken to not allow circular references, and that there always
is a rule that could generate the target of a symbolic reference
(or a chain of symbolic references). Still, a symbolic reference may
dangle (for example when its target reference cannot be computed
because of divergence), but at least it can be prevented that a
symref *always* dangles, because there is no rule that would produce
its target.
Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
Update the output of `rad_jj_colocated_patch` so that it passes with the current version of `jj` being used:
```
jj --version
jj 0.35.0
```
Mark `rad_jj_bare` as `ignore` since it currently cannot determine the
RID from the `rad` remote.
Two tests assumed that `refs/remotes/rad/HEAD` would automatically be
crated. It turns out, however, that this only the case if executed
with Git 2.48.0 or newer. Git versions older than 2.48.0 do not touch
'refs/remotes/<remote>/HEAD'.
Git 2.48.0 and newer will update `refs/remotes/<remote>/HEAD` by
default. This can be disabled by setting
`remote.<remote>.followRemoteHEAD` to "never". Thus, with these
versions, setting that configuration to "never" emulates the behaviour
of older versions.
Therefore, to ensure that test results are consistent across Git
versions before and after 2.48.0, the fixture of test repositories
now include setting 'remote.rad.followRemoteHEAD = never'.
The affected tests are adjusted accordingly.
This change can possibly be reverted once Radicle requires usage of Git
2.48.0 or newer.
Co-authored-by: Lorenz Leutgeb <lorenz.leutgeb@radicle.xyz>
The main motivation behind this change is to get just a little
telemetry information from nodes on the network, namely the version
of Radicle they are running.
This is achieved by rewriting `impl Default for UserAgent` which now
uses the version information provided by the build script also used
in other crates.
Also, a new configuration option `node.userAgent` is added, which
allows users to override the user agent if they so please, or set
the value `null`, which will in turn send the user agent
`/radicle/`, which is not really helpful, and the default prior to
this commit.
Creations of `UserAgent` in the whole workspace is cleaned up. In
order to do that `UserAgent::test` is introduced.
Each revision now shows a `{base}..{head}` range instead of just the
head. This is meant to clarify when a patch is rebased, and to be
helpful when one might want to observe changes using 'git range-diff'.
The intent to drop outgoing connections is modeled as `Option::None`
which is brittle and easy to miss.
Extend `enum AddressConfig` with a variant that is more explicit.
For stable commits, use the same timestamp as the CLI tests do.
This preserves that commits that are created in the Signed References
module appears as "now" in the output.
Add a configuration knob `node.fetch.signedReferences.featureLevel.minimum` to
allow node operators to enforce more strict verification when fetching
Signed References.
In order to allow automatic migration to the feature level 'parent',
introduce a way to force writing signed references, even if the
user-controlled refs are unchanged.
To detect the special case of a Signed References history with a single
and root commit (which would otherwise be detected as feature level
'parent', even though the `refs/rad/sigrefs-parent` ref cannot be
written), keep information about the parent commit in `SignedRefs`.
The output of `rad inspect --sigrefs` is changed to match the more
strict interpretation of the feature level for histories with a single
and root commit.
In order for users to be able to inspect the feature level of Signed
References in a repository, have `rad inspect --sigrefs` attempt to
load Signed References, and, if successful, print the feature level.
In issue
29c6c6fc81
was noted that it would be nice to use value parsers for the timeouts in
the CLI.
This patch implements this.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
Co-Authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
This patch adds a blank line to seperate the issue reply header from the
issue reply payload.
This makes visually parsing the output easier.
Co-authored-by: Fintan Halpenny <fintan.halpenny@gmail.com>
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>