Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
49 changes: 45 additions & 4 deletions PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ file or vault path, file content, or diagnostic check identifier.

### Optional Helper Diagnostics Runtime

The source tree contains an optional local helper runtime for the authenticated
diagnostics protocol. It is not yet published or deployed, and the released app
does not call it. Installing or upgrading through the ordinary helper installers
does not activate it. The runtime starts only when an operator separately
The published helper 2.0.2 contains an optional local runtime for the
authenticated diagnostics protocol. Publication or installation alone does not
activate it, and the currently released app does not call it. The runtime starts
only when an operator separately
supplies both a read-only diagnostics configuration and a writable private state
directory; otherwise it creates no listener, credential, mapping, namespace, or
artifact.
Expand Down Expand Up @@ -188,6 +188,47 @@ packages, Docker Desktop, WSL, remote/NAS/FUSE filesystems, Linux binary/systemd
installs, macOS packaging, and Windows packaging remain unsupported unless their
isolation and rollback are separately proven.

### Optional App Pairing and Namespace Control

The app source contains a separate Controlled Diagnostics Settings surface for
the authenticated helper control plane. This source is not yet the publicly
released app. Opening the surface performs only a read-only inspection. An app
upgrade, launch, settings visit, Relay wake-up, or ordinary sync creates no
diagnostics key, marker, pairing, network request, namespace, artifact, share,
peer, or trust decision.

The first mutation requires the user to select one already configured
homeserver and shared folder, accept localized consent, and scan or paste a
five-minute invitation generated by the local helper operator. VaultSync then
stores an installation signing seed and scoped pairing records only in the
dedicated non-synchronizable, device-only diagnostics Keychain service, bound
to a complete-protection app-container marker. The records contain opaque
bindings, public keys and identifiers, epochs, the TLS pin, fixed endpoint, and
exact latest control messages needed for idempotent retry. They contain no QR
secret, note content, user filename, folder path/name, operation payload, or
upload/download history, and they are not sent to Cloud Relay.

Pairing uses TLS 1.3, an exact QR-pinned leaf SPKI, mutually signed canonical
messages, and an app/operator comparison of a 12-hex transcript fingerprint.
There is no automatic discovery, trust transfer, public default port, Relay
tunnel, redirect, or weaker fallback. Capability negotiation is authenticated
but creates no synchronized artifact and provides no upload, download, or
roundtrip evidence.

Namespace enablement is a later, separate user action and remains only a signed
request until the helper operator separately confirms the exact folder/path and
retention warning. The app never creates or adopts the visible `VaultSync
Diagnostics` folder. It accepts ownership only after fixed, symlink-resistant
local reads validate the helper-signed root and the exact helper-countersigned
app authorization received through Syncthing. Credential rotation requires a
fresh immutable authorization epoch before operations can resume.

Revocation, app downgrade, or lost-key recovery stops new app activity but does
not delete the helper authorization, namespace, peer copies, backups, versions,
conflicts, history, or tombstones. Lost-key recovery deliberately requires new
pairing and a separate operator revocation of the surviving old authorization.
See [app capability, pairing, and namespace readiness](docs/app-capability-pairing-namespace-readiness.md).

### Data Security

- APNs device tokens are encrypted at rest (AES-256-GCM)
Expand Down
170 changes: 170 additions & 0 deletions docs/app-capability-pairing-namespace-readiness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# App capability, pairing, and namespace readiness

**Status:** App source readiness for the explicit Decision 022/023 control
plane is implemented and locally verified. It is not an App Store release or a
transfer milestone. The published helper baseline is `notify-v2.0.2`; the app
change remains unreleased until its own PR and later release gates complete.
Upload, download, and roundtrip evidence are all unset.

## User-controlled scope

Controlled Diagnostics is a separate Settings surface. Opening it performs one
read-only inspection of protected app storage and the dedicated diagnostics
Keychain service. An app upgrade, launch, settings visit, ordinary sync, Relay
wake-up, or background run does not create a key, marker, pairing, endpoint
request, namespace, artifact, Syncthing share, peer, trust decision, or folder
configuration.

The first mutation requires all of the following explicit actions:

1. The user selects one already configured homeserver Device ID and one folder
already shared with that device.
2. The user accepts localized pairing consent.
3. The user scans or pastes an operator-generated, five-minute D022 invitation.
4. The app validates the exact target digests, fixed endpoint, helper key, TLS
SPKI pin, canonical CBOR, HMAC, signature suite, nonce, epoch, and clock.
5. The app and local operator compare the same 12-hex transcript fingerprint.
6. Only the user's confirmation advances the persisted types 3, 5, and 7.

No helper is discovered automatically. The app never uses mDNS, UPnP, public
port defaults, Cloud Relay, APNs, Syncthing discovery, or a Relay tunnel for
this control plane. It never creates or adopts Syncthing trust, a peer, a share,
or a namespace.

## Credential and transport boundary

The app stores only its diagnostics installation seed and scoped pairing
records in the dedicated generic-password service
`eu.vaultsync.app.diagnostics.v1`. Items are non-synchronizable and use
`WhenUnlockedThisDeviceOnly`; no shared access group or cloud escrow is used.
A separate complete-protection marker binds the Keychain item to this app
container. A missing or mismatched half is `re-pair required`, never silent key
adoption.

Each record is scoped to the app installation, homeserver binding, folder
binding, helper, TLS pin, and current app/helper epochs. It retains the exact
latest outgoing/incoming control bytes needed for byte-identical retry, but no
QR secret, folder path, folder/vault name, note content, user filename,
operation payload, upload/download result, or proof history.

The fixed local/LAN/VPN endpoint uses an ephemeral URL session with TLS 1.3 as
both minimum and maximum, an exact P-256 leaf-SPKI SHA-256 pin, no redirects,
cookies, cache, compression, query, or fragment, fixed CBOR media types and
body limits, and mutually authenticated application signatures. Network
errors become `capability unavailable`; authenticated protocol, tuple, or
mandatory-flag mismatches become `unsupported`. Neither state falls back to a
weaker success. Only the four fixed M3 pairing, capability, namespace-
enablement, and namespace-authorization paths are accepted. A successful
capability response can authorize the next explicit control step only through
its exact signed expiry; it is invalidated on restart, error, or credential
transition.

Every persisted pending D022/D023 operation also carries an app-local
`mach_continuous_time` deadline bound to its signed wall-clock window. Restart
reconstruction requires both clocks to remain within the original interval;
rolling the wall clock back cannot extend a pairing, namespace, rotation, or
revocation attempt beyond five elapsed minutes. Completed immutable namespace
records remain separately verifiable after that local network-attempt deadline.

App-key, helper-key, and TLS-pin changes are explicit signed D022 transitions.
The old credential remains authoritative until the terminal acknowledgement
and an exact capability response under the proposed state both validate. A
new app-key generation is selected once in the installation Keychain and reused
for every separately staged folder authorization. Another generation is blocked
until every non-revoked authorization is stable on that selected key; there is
no cross-folder atomicity claim. A
pre-commit transition can be explicitly aborted with signed types 23/24; an
expired pre-commit transition can be discarded only after its signed expiry
and clock-skew window. A type-21 finalization that may have reached the helper
is never silently rolled back. A
completed credential change makes an existing namespace unavailable until the
app and helper append its next immutable D023 authorization epoch. Revocation
is scoped to this app authorization. Lost-key recovery removes only this app's
local diagnostics records and instructs the operator to revoke the surviving
helper authorization separately.

## Separate namespace enablement

Pairing and capability checks create no synchronized content. Namespace
enablement is a second explicit app action and remains only a signed request
until the helper operator separately runs the supported installer, confirms
the exact existing folder/path, and accepts visibility and retention.

The app never creates or adopts `VaultSync Diagnostics`. After the operator
step, it reads only fixed D023 paths beneath the app's existing settled folder
through descriptor-relative, `O_NOFOLLOW` opens. It requires regular,
single-link, size-bounded immutable files, validates the root/helper epoch
chain, and sends the exact app-signed authorization candidate to the pinned
helper. The namespace becomes active only after the helper-countersigned file
arrives through Syncthing and validates against that exact candidate. Rotation
uses append-only authorization epochs 2 through 9.

The visible namespace and its opaque records can remain on peers, in backups,
Syncthing versions, conflict copies, remote history, and tombstones. Disabling,
revoking, downgrading, or resetting app credentials does not delete those
copies, the namespace root, helper state, a share, or user data.

## Compatibility matrix

The diagnostics contract is additive. Trigger v1 and Relay v1 are unchanged.

| App | Helper | Relay | Honest result |
|---|---|---|---|
| Released old app | Old helper | Existing Relay | Existing behavior only; no diagnostics state. |
| Released old app | Published helper 2.0.2 | Existing or new Relay | Helper remains dormant unless separately configured; the old app makes no diagnostics calls. |
| M3-capable app | Old helper | Any Relay v1 | `Capability unavailable`; no pairing fallback, namespace, or artifact. |
| M3-capable app | Helper 2.0.2, diagnostics unset | Any Relay v1 | `Capability unavailable`; Trigger v1 remains unchanged. |
| M3-capable app | Helper 2.0.2, enabled but unpaired | Any Relay v1 | Explicit QR pairing is offered; no trust or namespace is inherited. |
| M3-capable app | Helper 2.0.2, paired but namespace absent | Any Relay v1 | Authenticated capability can succeed; upload, download, and roundtrip remain unset. |
| M3-capable app | Helper 2.0.2, explicitly namespace-authorized | Existing or new Relay v1 | D022/D023 control plane active; no transfer artifact exists in this milestone. |
| App downgrade | Helper 2.0.2 | Any Relay v1 | Old app ignores the additive records; helper stays dormant for it; credentials and namespace copies are retained. |
| App re-upgrade | Helper 2.0.2 | Any Relay v1 | Read-only reconstruction, fresh capability, and current namespace authorization are required; no operation resumes. |

## Evidence boundary

The strongest app-side proof in this milestone is exact production-code
decoding of all D022 types 0–24, byte-exact D024 capability-query generation,
mutually signed capability-response validation, exact D023 golden-chain
validation/generation, device-only credential persistence, and a restart-safe
explicit pairing state machine. This is control-plane evidence only.

| Claim | State |
|---|---|
| Authenticated capability | Implemented in production app source; cross-language vectors and a deterministic pinned-transport harness pass. Real-device/helper deployment evidence remains unset. |
| Pairing | Explicit, fingerprint-confirmed, scoped, restart-safe D022 state machine. |
| Namespace | Explicit app request plus separate operator creation and helper-countersigned D023 authorization. |
| Upload | Unset; no request artifact is created by this milestone. |
| Download | Unset; no response artifact or fresh `ItemFinished` baseline exists. |
| Roundtrip | Unset; no same-chain directional evidence exists. |
| Cleanup | No app cleanup runtime in this milestone; helper foundation remains evidence-orthogonal. |

Signatures prove authorship and exact causal bindings. They do not prove a
transport route, direct peer, byte provenance, future delivery, or global sync
health.

## Local verification

All Xcode result bundles must be written below `/tmp`. The milestone gate runs:

```sh
cd ios
xcodegen generate
./scripts/strings-key-parity.sh
xcodebuild -project VaultSync.xcodeproj -scheme VaultSync \
-destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=26.5' \
-derivedDataPath /tmp/vaultsync-m3-derived CODE_SIGNING_ALLOWED=NO test \
-resultBundlePath /tmp/vaultsync-m3-tests.xcresult
```

The focused runtime suite covers production D022/D023/D024 golden vectors,
canonical parser rejection, signature/mandatory-flag tampering, exact target
bindings, endpoint literal canonicalization, device-only Keychain query
attributes, existing-user no-mutation, explicit fingerprint gating, persistence,
restart reconstruction, capability expiry, namespace/operator state isolation,
app/helper/TLS rotation, signed pre-commit abort, revocation, persisted monotonic
deadlines with wall-clock rollback, fixed transport paths, arithmetic boundaries,
and honest unavailable/unsupported states. The
pre-PR local run passed all 424 iOS tests; Go bridge and Notify suites, Go Vet,
design-token lint, strings parity, and localized plist lint also passed. These
results are local engineering evidence, not real-device, rollout, or Store
evidence.
13 changes: 9 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,12 @@ milestone.
#### Opt-in correlated-roundtrip helper runtime — no app evidence yet

[Decisions 021–024](decisions/021-capability-negotiated-helper-contract-for-correlated-roundtrip-proof.md)
define the proof and rollout boundaries. The source tree now contains the first
helper runtime carrier for those foundations, but it is not yet published or
deployed and no app runtime calls it. VaultSync 2.0 remains NO-GO; product
upload, controlled download, and causal roundtrip evidence are all unset.
define the proof and rollout boundaries. Helper 2.0.2 is published and its
immutable digest plus upgrade, downgrade, and forward-recovery path are
verified. The source tree now also contains the unreleased app-side explicit
capability, pairing, credential-lifecycle, and namespace-authorization control
plane. VaultSync 2.0 remains NO-GO; product upload, controlled download, and
causal roundtrip evidence are all unset.

The runtime is gated by an operator-authored read-only configuration plus a
separate writable state directory. If either is absent, existing helpers retain
Expand Down Expand Up @@ -197,6 +199,9 @@ fresh post-authorization iPhone cursor/nanosecond/generation/`ItemFinished`
baseline. Cleanup remains evidence-orthogonal. Helper-first publication,
production rollout, rollback, and then separate app milestones remain mandatory.
See [helper runtime and packaging readiness](helper-runtime-packaging-readiness.md).
The app-side scope, compatibility, persistence, consent, and rollback boundaries
are documented in
[app capability, pairing, and namespace readiness](app-capability-pairing-namespace-readiness.md).

### Connection paths & iOS network privacy

Expand Down
7 changes: 4 additions & 3 deletions docs/helper-runtime-packaging-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,10 @@ their existing wire formats.
| Future capable | New, enabled and exactly authorized | Existing v1 | Helper-side D022–D024 contract is available; Relay v1 is unchanged and is not evidence for upload, download, or roundtrip. |
| Any | New → old rollback → same new image | Existing v1 | Diagnostics becomes unavailable on rollback; preserved state is revalidated on forward recovery and no operation resumes automatically. |

This milestone does not claim compatibility with an unreleased app
implementation. App-side old/new matrices and real-device evidence remain
gates for the later app milestones.
This helper milestone does not claim compatibility with a released capable app.
The now-implemented, still-unreleased app control-plane matrix is tracked in
[app capability, pairing, and namespace readiness](app-capability-pairing-namespace-readiness.md);
transfer and real-device evidence remain later gates.

## Evidence boundary

Expand Down
Loading
Loading