Skip to content

docs(security): propose helper credential pairing - #102

Merged
psimaker merged 4 commits into
mainfrom
agent/decision-022-helper-pairing
Jul 12, 2026
Merged

docs(security): propose helper credential pairing#102
psimaker merged 4 commits into
mainfrom
agent/decision-022-helper-pairing

Conversation

@psimaker

@psimaker psimaker commented Jul 12, 2026

Copy link
Copy Markdown
Owner

What changed

Adds proposed Decision 022 for the credential and mutual-pairing gate required by Decision 021. It specifies the cryptographic suite, deterministic pairing encoding and signature domains, iOS/helper key storage, explicit QR plus pinned-TLS bootstrap, homeserver/folder bindings, multi-installation authorization, rotation, revocation, recovery, replay defense, downgrade behavior, compatibility, privacy constraints, and the required test matrix.

Safety boundary

This is design-only. It creates no endpoint, key, pairing record, namespace, probe, installer permission, Relay change, or rollout. The document is explicitly not independently reviewed or approved and lists the human security/product decisions still required.

Validation

  • git diff --check
  • local relative-link target checks
  • HTTP checks for the RFC and Apple references
  • secret-pattern scan
  • single public Markdown file; no generated or xcresult artifacts

Summary

  • Adds a new design-only document (Decision 022) for a future diagnostics-helper credential and fail-closed mutual-pairing gate required by Decision 021.
  • Defines deterministic pairing/QR/bootstrap transcripts, domain-separated signatures/HMAC inputs, credential/key identifiers, and strict state-machine semantics for unpaired/old/downgraded/revoked or replayed helpers.
  • Specifies iOS/keychain and helper runtime storage isolation, TLS 1.3 bootstrap with pinned SPKI, fixed bootstrap endpoint behavior, and explicit privacy/logging/storage prohibitions (including Cloud Relay constraints).
  • Covers multi-installation isolation plus rotation, revocation, recovery (including pending/finalize/abort flows), and downgrade/compatibility handling; the design is explicitly scoped to the diagnostics capability.
  • No user-visible sync behavior changes are introduced (no implemented endpoints, pairing records, namespaces/probes, Relay changes, installer permissions, or rollout). No background-execution/runtime changes are introduced.
  • Security verification is captured as a comprehensive pre-implementation test checklist; no implementation/test code is added in this draft, and human security/product approval remains pending.

Scope clarification: the decision's invariants and bootstrap prohibitions apply only to the diagnostics capability.

Define a fail-closed mutual pairing and credential lifecycle for a future diagnostics capability, including canonical signature domains, homeserver and folder bindings, multi-installation authorization, rotation, revocation, recovery, replay defense, and downgrade behavior. Keep the design explicitly unimplemented and subject to human security and product approval.
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d97e0f84-b863-4ca8-a318-89746d59462d

📥 Commits

Reviewing files that changed from the base of the PR and between e750e61 and 726092f.

📒 Files selected for processing (1)
  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md

📝 Walkthrough

Walkthrough

Adds a proposed architecture decision for fail-closed diagnostics-helper credentials and mutual pairing, covering cryptographic messages, QR bootstrap, isolated storage, authorization lifecycle, compatibility, privacy restrictions, testing, and approval gates.

Changes

Diagnostics helper pairing

Layer / File(s) Summary
Protocol contracts and cryptographic registry
docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
Defines scope boundaries, cryptographic primitives, canonical CBOR encoding, digest derivations, QR payloads, lifecycle fields, signer roles, and fail-closed validation rules.
Credential storage and explicit bootstrap
docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
Specifies isolated credential storage, local binding checks, one-time bootstrap secrets, pinned TLS, pending-record handling, and atomic pairing commits.
Authentication, authorization, and credential lifecycle
docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
Defines mutual authentication, installation and folder isolation, rotation, revocation, recovery, replay defenses, downgrade behavior, and compatibility rules.
Privacy controls and implementation gates
docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
Adds sensitive-data logging bans, pre-implementation tests, human approval requirements, blocked rollout status, and related decision references.

Estimated code review effort: 2 (Simple) | ~10 minutes

Poem

Keys wait quietly, paired by light,
QR paths stay clean and tight.
Digests guard each careful start,
Fail-closed rules protect the heart.
No secret drifts into the night.

🚥 Pre-merge checks | ✅ 7
✅ Passed checks (7 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
No Private Note Leakage ✅ Passed Changed file is a design-only ADR; it explicitly forbids logging or transmitting note contents, paths, keys, tokens, or receipts and adds no implementation.
Bounded Ios Background Work ✅ Passed PASS: PR only adds a design doc; no Swift background-execution code changed, so this check is not applicable.
Bridge Contract Compatibility ✅ Passed Only a docs/decision markdown file changed; no Go bridge, Swift bridge-service, JSON shapes, empty-string conventions, or bridge tests were modified.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise, conventional-commit styled, and accurately describes the design proposal for helper credential pairing.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/decision-022-helper-pairing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Specify byte-exact bootstrap derivations, bounded pairing envelopes, lifecycle signer domains, and rotation/revocation digest chains for independent review.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md`:
- Line 51: Update the bootstrap HMAC specification in the diagnostics helper
credentials decision to explicitly use bootstrap_secret as the HMAC-SHA-256 key,
with the stated domain and deterministic map excluding HMAC and signature fields
as the message. Add a golden vector validating this exact derivation, including
the resulting HMAC.
- Around line 167-169: Replace the cross-device atomic-commit claims in the
authorization and key-rotation flows with an explicit authenticated
pending/finalize/abort protocol. Define expiring pending records, require both
peers to reach the same terminal state before enabling capabilities, and specify
crash/network recovery for one-sided commits; do not treat local revocation
alone as recovery.
- Around line 146-148: Clarify the binding lifecycle in the diagnostics pairing
design: make homeserver and folder bindings stable for their helper/folder
scope, or explicitly per authorization with a deterministic reuse rule, and
ensure active records never regenerate an already-used binding. Update the
pairing-invitation flow around the binding generation described at line 163 to
reuse the existing binding safely, and add a multi-installation test covering
two app installations paired to the same folder.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 212deb3d-8500-409d-9c91-d059fd5ec9bd

📥 Commits

Reviewing files that changed from the base of the PR and between 2b9c1b2 and e750e61.

📒 Files selected for processing (1)
  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
📜 Review details
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{md,sh,go,swift}

📄 CodeRabbit inference engine (README.md)

Keep Relay-side request observation and wake-ups received on the iPhone as separate diagnostics evidence; one must not be treated as proof of the other.

Files:

  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
docs/decisions/**/*

📄 CodeRabbit inference engine (docs/decisions/014-vault-subfolders-override-stray-root-config.md)

docs/decisions/**/*: Classify a connected root as a container, never as a vault-as-root, when it contains at least one direct subdirectory containing .obsidian/, regardless of whether the root itself contains .obsidian/. Use VaultManager.rootIsItselfVault as the single classification source for all sites, including grantAccess, share acceptance, and resolveSharePath.
Do not inspect the contents of the root-level .obsidian/ directory to distinguish a real vault from a stray configuration; use the presence of vault subfolders as the stronger signal.
Maintain regression coverage for root classification, including a root with both a stray root-level .obsidian/ and one or more vault subfolders, using the VaultRootClassificationTests suite.

docs/decisions/**/*: Every Relay provision request must require a locally verified, active Relay entitlement and its signed StoreKit transaction; never send a placeholder when this evidence is unavailable.
Preserve existing local and remote registration evidence across verification failures, network failures, and partial multi-homeserver failures.
Persist verified migration success independently for each homeserver.
Treat pre-migration success flags as indicating that migration is required, not that migration has been verified.
Do not clear registrations before migration; transient migration failures must not disable an otherwise working paid setup.
Do not use one global migration flag; one homeserver's failure must not hide or roll back another homeserver's success.

docs/decisions/**/*: Keep background start, local data progress, upload, download, and full-roundtrip proof as independent fields; never derive a global success flag.
Set only fresh local data progress after a successful file ItemFinished newer than both the check cursor and nanosecond start time within one stable engine generation.
Keep manual results in memory and isolate them per folder and its sole connected peer; preserve partial, unsupported...

Files:

  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
docs/**/*.md

📄 CodeRabbit inference engine (docs/troubleshooting.md)

docs/**/*.md: After applying each troubleshooting fix, retry from the app to confirm whether the issue is resolved.
For a missing Device ID or “Sync Engine Not Running,” force-close and reopen VaultSync, keep it foregrounded for 20–30 seconds, confirm the Device ID, rescan the vault, and reboot if necessary.
vaultsync-notify must read the Syncthing API key from config.xml; permission or authentication failures should be diagnosed as wrong-file or read-permission problems before changing credentials.
Run vaultsync-notify as the user owning Syncthing’s mode-0600 config.xml, or configure the appropriate container PUID/PGID.
Set SYNCTHING_CONFIG when the Syncthing configuration is not auto-detected, and remove or correct SYNCTHING_API_KEY overrides that replace automatic detection.
For relay failures, verify internet access, RELAY_URL, server egress rules, relay health with curl, and the app’s relay diagnostics; confirm delivery through an updated “Last Trigger Received” timestamp.
A relay HTTP 429 from --doctor counts as success because it proves the trigger endpoint is reachable; an inactive subscription is a warning fixed through app subscription or provisioning.
Peer-state warnings from --doctor must not fail the command: offline peers are normal, and --healthcheck must skip peer-state diagnostics.
For missing pending shares, verify the desktop folder shares the exact iOS Device ID, ensure Syncthing is online and unpaused, refresh pending shares, and re-share if needed.
For APNs issues, retry APNs registration, verify a token appears, retry provisioning, and confirm delivery with “Last Trigger Received”; notification permission is not required for silent wake-ups.
When connecting Obsidian storage, choose On My iPhone → Obsidian or the vault root containing .obsidian, then confirm detected vaults or pending shares.
VaultSync must not move, recreate, or delete vault folders automatically; recovery from moved or deleted folders requires ma...

Files:

  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
docs/decisions/**/*.{md,markdown}

📄 CodeRabbit inference engine (docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md)

Update PRIVACY.md before introducing any runtime credential or pairing transport.

Files:

  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
**

⚙️ CodeRabbit configuration file

**:

VaultSync

VaultSync

Self-hosted Obsidian vault sync for iPhone and iPad.

Your notes sync peer-to-peer over Syncthing, straight into Obsidian's iOS sandbox — no note cloud, no account, no tracking.

Download on the App Store



Stars
License: MPL-2.0
iOS 18+
CI

VaultSync welcome screen VaultSync home screen

🔭 Why VaultSync

  • Peer-to-peer & private — syncs directly between your own devices over Syncthing. No note cloud, no account, no tracking.
  • Lands in Obsidian — files sync into Obsidian's iOS sandbox, where the app already looks for them.
  • Pair by QR, resolve conflicts — connect your server in seconds; settle Markdown conflicts with side-by-side diffs.
  • Server changes can wake your iPhone — optional Cloud Relay asks iOS to wake the app after the helper observes a server change. T...

Files:

  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
docs/**

⚙️ CodeRabbit configuration file

docs/**: # Architecture

VaultSync embeds Syncthing's Go reference implementation as an iOS library via gomobile — no reimplementation of the protocol in Swift, and guaranteed wire compatibility.

┌─────────────────────────────────┐
│         SwiftUI Frontend        │   iOS-native UI, Swift 6
├─────────────────────────────────┤
│       Swift ↔ Go Bridge         │   thin API via gomobile
│                                 │   → exported as .xcframework
├─────────────────────────────────┤
│        syncthing/lib (Go)       │   protocol, discovery, sync
└─────────────────────────────────┘
              ↕ filesystem
┌─────────────────────────────────┐
│    Obsidian Vault (direct)      │   Obsidian's iOS sandbox
└─────────────────────────────────┘

🔄 Sync strategy

  • Foreground — Syncthing runs unrestricted: immediate, continuous sync.
  • BackgroundBGAppRefreshTask (requested ~15 min out; iOS decides the actual timing) + BGProcessingTask (overnight catch-up: multi-minute budget while charging with network) + BGContinuedProcessingTask (iOS 26+, longer runtime for user-initiated tasks). A ~30s grace window after backgrounding lets in-flight work finish.
  • Push (Cloud Relay) — optional. Near-realtime server → iPhone wake-ups via APNs silent push. See relay-spec.md.

VaultSync is intentionally asymmetric:

Direction Path
Server → iPhone vaultsync-notify spots outgoing changes → Cloud Relay silent push → VaultSync wakes and pulls.
iPhone → Server iOS doesn't guarantee timely background execution for local edits. The reliable path is to open VaultSync and let embedded Syncthing run in the foreground — a Shortcuts automation can do that automatically whenever you leave Obsidian.

Cloud Relay is a server → iPhone acceleration path, not a guarantee of symmetric real-time background sync.

Relay and sync proof hierarchy

VaultSync models proof as independent fi...

Files:

  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
docs/decisions/**

⚙️ CodeRabbit configuration file

docs/decisions/**: # 001 — Two Syncthing folders never overlap on disk (three enforcement layers)

Context: A server sharing more than one vault could hand the second share the same local folder as the first (1.6.0–1.7.0) or a subfolder inside an existing vault (1.7.1). Overlapping folders sync each other's content as their own — deleting the stray copy on any peer would have deleted the inner vault everywhere (#45).

Decision: The no-overlap invariant (equal, nested, or containing paths) is enforced in three independent layers, each with its own tests: the Go hard floor (AcceptPendingFolder/AddFolder reject with folderPathOverlapError), the Swift mapping (VaultManager.resolveSharePath returns nil rather than an overlapping path), and the launch shield (PathCollisionGuard pauses already-overlapping folders exactly once).

Why: The single-layer version failed twice — both #45 bugs lived in the Swift mapping. The Go floor backstops future mapping bugs, the Swift layer turns a hard engine error into user guidance, and only the shield catches damage that predates the fix.

Rejected alternative: Enforcing only in the Swift mapping — the proven failure mode; a bug there would silently re-open the hole with no backstop.

Links: #45, PR #47 (same-folder merge, 1.7.1), PR #51 (nesting, 1.7.2).

docs/decisions/**: # 002 — Recovery from data damage is never automatic

Context: When VaultSync detects existing damage (two vaults merged into one folder, or one nested inside another, #45), an automatic repair would have to move, rename, delete, or re-accept user data whose true state only the user knows — after a merge, the app cannot tell which files belong to which vault.

Decision: Recovery is always: pause the affected folders (exactly once — a folder the user deliberately resumes is never re-paused), explain the problem in a critical issue with concrete recovery steps, and let the user act.

Why: Sync propagates every local act...

Files:

  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
**/*

⚙️ CodeRabbit configuration file

**/*: VaultSync syncs private Obsidian notes through Syncthing. Treat data loss,
privacy leaks, security regressions, and broken sync behavior as high priority.
Do not nitpick formatting unless it affects maintainability, correctness, or public API clarity.
Flag any accidental logging, telemetry, crash reporting, or network transfer of note contents,
vault paths, filenames with private context, API keys, APNs tokens, relay keys, or security-scoped bookmark data.

Files:

  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
**/*.md

⚙️ CodeRabbit configuration file

**/*.md: Review public documentation for technical accuracy, privacy/security claims, App Store-facing wording,
setup correctness, and consistency with the free app plus optional Cloud Relay subscription model.

Files:

  • docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-12T15:13:03.258Z
Learning: Treat this design as proposed only: it is not implemented, independently reviewed, approved, or authorized for runtime use; require human security and product approval before implementation.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-12T15:13:03.258Z
Learning: Keep the diagnostics capability separate from existing Syncthing pairing, Trigger v1, Cloud Relay, APNs, StoreKit, folder mappings, and the published helper.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-12T15:13:03.258Z
Learning: Pairing must be explicit, mutually authenticated, scoped to one app installation, one homeserver, and selected folders; it must never be inferred or repeated automatically.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-12T15:13:03.258Z
Learning: Pairing alone must create no synchronized namespace and provide no upload, download, or roundtrip evidence.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-12T15:13:03.258Z
Learning: Do not use mDNS, UPnP, Cloud Relay, unauthenticated synchronized files, StoreKit transactions, Syncthing TLS keys, public listeners, or automatic discovery in bootstrap.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-12T15:13:03.258Z
Learning: Treat old, unpaired, unreachable, downgraded, or revoked helpers as capability unavailable or re-pair required; never convert them into general sync errors, weaker proof, or automatic trust.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-12T15:13:03.258Z
Learning: Give every app installation a distinct authorization, key, epoch, folder scope, quota, and revocation state; adding an installation requires a new one-time pairing.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-12T15:13:03.258Z
Learning: Do not automatically recover lost app keys, adopt old helper credentials, escrow credentials, or delete credentials and namespace roots during upgrades, downgrades, or rollbacks.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-12T15:13:03.258Z
Learning: Keep Cloud Relay, Trigger v1, and existing app/helper compatibility behavior unchanged; the new capability must be additive and dormant until explicitly paired.
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-12T15:13:03.258Z
Learning: Obtain explicit human approval for the cryptographic suite, storage model, helper state backup policy, QR/pinned-TLS bootstrap, per-installation authorization, downgrade behavior, and lifecycle registry before implementation.
🪛 LanguageTool
docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md

[style] ~122-~122: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...y, and type 8 by the current app key. Type 9 is signed by the current helper key...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~122-~122: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...y and type 10 by the current app key. Type 11 is signed by the current app key; ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

Comment thread docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md Outdated
Comment thread docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md Outdated
Comment thread docs/decisions/022-diagnostics-helper-credentials-and-mutual-pairing.md Outdated
psimaker added 2 commits July 12, 2026 17:32
Specify the bootstrap HMAC key and golden vector, stable binding reuse, and authenticated pending/finalize/abort recovery without cross-device atomicity claims.
@psimaker
psimaker marked this pull request as ready for review July 12, 2026 19:16
@psimaker
psimaker merged commit 1b8ac1b into main Jul 12, 2026
16 checks passed
@psimaker
psimaker deleted the agent/decision-022-helper-pairing branch July 12, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant