Basekick-Labs / Basekick-Labs/arc
Edge-sync spoke IDs can still collide through case folding and Unicode normalization
- 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
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