Basekick-Labs / Basekick-Labs/arc

Edge-sync spoke IDs can still collide through case folding and Unicode normalization

Open
#740 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
677
Forks
53
Avg merge
9h 14m
Merged PRs (30d)
164

Description

Follow-up from #737, found while writing its regression test.

#737 closed the `..` fold. The same class remains open for two other many-to-one mappings the filesystem applies rather than Arc:

- **Case.** `rocket_01` and `ROCKET_01` both pass `validateSpokeID`. On a case-insensitive volume (macOS APFS by default, Windows) they are one directory, so either spoke can write into the other's namespace. Exactly #737 with a different fold.
- **Unicode normalization.** `café` in NFC and NFD are distinct byte strings, both accepted, and the same volumes treat them as one name.

Arc ships a `darwin-arm64` binary through the Homebrew tap (`release-build.yml:353`), so macOS is a supported platform to run and develop on. CI is `ubuntu-latest` only (`ci.yml:29`), so nothing catches it.

The precondition is the same as #737: an administrator has to register the colliding ID, since registration is admin-only and there is no self-registration. No advisory.

`TestSpokeNamespacesAreInjective` in `internal/edgesync/receive_test.go` deliberately excludes `ROCKET_01` from its candidate list with a comment pointing here, because including it would pass on Linux CI and fail on a macOS dev machine.

## Why it was not folded into #737

Rejecting non-lowercase or non-NFC IDs would refuse identifiers that are legitimate today, so it breaks existing deployments in a way the `..` fix does not: `..` in a spoke ID is always pathological, mixed case is not. That needs a deprecation path rather than a validator line.

## Options

1. Reject non-lowercase and non-NFC IDs at registration only, leaving existing rows working, and warn about the ones already stored (`Registry.InvalidStoredIDs` added in #737 is the hook).
2. Normalize on the way in and store the canonical form, which changes what `registry.List()` returns and therefore what `receivedNamespaces` in `cmd/arc/main.go` matches against on disk. Needs care.
3. Make the invariant structural at the storage boundary so the namespace encoding is injective regardless of identifier spelling. See the related `sanitizePath` issue.

Contributor guide

Open the contributing guide

Research direction

Start with TestSpokeNamespacesAreInjective in internal/edgesync/receive_test.go and read the related changes from #737, including Registry.InvalidStoredIDs. Trace how registry.List() is matched by receivedNamespaces in cmd/arc/main.go, then compare the three listed options against existing registration and storage behavior. Done means the chosen approach prevents case-folding and Unicode-normalization collisions without silently breaking existing deployments, with regression coverage.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, macos
Domain
backend, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.