Skip to content

feat: add helper runtime and packaging readiness#114

Merged
psimaker merged 6 commits into
mainfrom
agent/helper-runtime-packaging-readiness
Jul 14, 2026
Merged

feat: add helper runtime and packaging readiness#114
psimaker merged 6 commits into
mainfrom
agent/helper-runtime-packaging-readiness

Conversation

@psimaker

@psimaker psimaker commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

  • Connect the reviewed D022-D024 helper foundations to an explicit opt-in TLS 1.3 runtime with fixed POST endpoints and mutually authenticated Ed25519 application messages.
  • Require exact pairing, namespace authorization, Syncthing and host-bind preflight, lifecycle rotation/revocation, bounded crash recovery, and fail-closed operation reconstruction.
  • Add the single supported rootful-Docker host-bind installer plus install, upgrade, downgrade, rollback, and forward-recovery proofs.

Safety and compatibility

  • Existing installations remain dormant unless both explicit diagnostics configuration values are present. Without them, no diagnostics listener, credential, mapping, namespace, artifact, trust, or new Relay request is created.
  • The listener uses an explicit private/loopback/link-local/CGNAT address and non-root port, TLS 1.3, and a pinned P-256 SPKI. There is no public default port, Relay tunnel, mDNS, UPnP, automatic discovery, trust transfer, namespace creation, share mutation, or Syncthing configuration change.
  • Only rootful Docker on an explicitly confirmed standard Linux host with an exact host bind is supported. Named volumes, rootless Docker, NAS/FUSE/remote storage, Docker Desktop, WSL, macOS, Windows, and systemd packaging remain unsupported.
  • D024 is unchanged at blob f41f597d3ceca73da102e5e447382dfae07d2e08.
  • Trigger v1 and Relay v1 remain wire-compatible. Old apps do not call the new endpoints; a capable future app sees honest unavailable/unsupported behavior from an old or unconfigured helper.
  • Upload, download, and roundtrip evidence remain unset. Cleanup remains evidence-orthogonal.

Verification

  • go test ./... -count=1
  • go test -race ./... -count=1
  • go vet ./...
  • go mod verify
  • govulncheck ./... — no vulnerabilities found
  • all eight diagnostics fuzz targets, five seconds each
  • full Linux notify module test, three repetitions, in a pinned network-disabled Go container
  • M4 descriptor/host-bind confinement proof
  • two-instance isolated Syncthing M5 E2E
  • committed head 7faae6481492a99b434c47eb7bcd480dc22c2255: old → new → old → new packaging proof with byte-identical credential state; new local image index sha256:6ae51aae1080c537fcd753f6597bf90cc394186917863115ab8fc79312194e91
  • Xcode simulator build and 366 tests in 58 suites
  • privacy lint, formatting, diff check, design-token lint, string-key parity, install dry run, publish-safety policy, ShellCheck, and attribution/secret heuristics
  • the PR CI adds the clean standard-Linux/rootful-Docker installer proof for this exact committed head

Existing-user, migration, and rollback

No migration runs for existing users. Opt-in state is separate from Syncthing configuration and the synchronized namespace. Downgrading to the old helper makes diagnostics unavailable without rewriting the preserved state; re-upgrading the same reviewed image revalidates the same identity and SPKI pin. Backups, versions, conflicts, remote history, and tombstones can retain synchronized artifacts and are never represented as cleanup success.

Not in this PR

This PR does not publish or deploy a helper, add app runtime calls, establish real-device upload/download/roundtrip evidence, change Relay v1, or make VaultSync 2.0 release-ready.

psimaker added 2 commits July 14, 2026 07:24
Connect the dormant D022-D024 foundations to an opt-in TLS 1.3 helper runtime with fixed endpoints and mutually signed application messages.

Require explicit pairing and namespace authorization, exact Syncthing and host-bind preflight, protected credential lifecycle handling, bounded recovery, and fail-closed operation reconstruction.

Keep existing installations dormant unless both operator configuration values are present, and keep upload, download, and roundtrip evidence unset.
Add the explicit rootful Docker host-bind installer and CI proofs for install, upgrade, downgrade, and forward recovery using immutable local image IDs.

Document supported and unsupported deployment rows, compatibility, privacy, existing-user, retention, and rollback boundaries.

Keep publication and production rollout deferred, and keep upload, download, and roundtrip evidence explicitly unset.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds an opt-in, operator-configured diagnostics runtime with signed CBOR capability flows, namespace authorization and recovery, TLS 1.3 serving, hardened Docker packaging, expanded Syncthing validation, documentation, tests, and CI coverage.

Changes

Diagnostics runtime and packaging

Layer / File(s) Summary
Protocol, identity, and lifecycle foundations
notify/diagnostics_capability_protocol.go, notify/diagnostics_pairing_*.go, notify/diagnostics_namespace_*.go
Adds signed capability encoding and validation, canonical device-ID parsing, lifecycle confirmation, helper-rotation authorization, historical-chain validation, and crash-safe namespace installation and recovery.
Configuration, preflight, and namespace runtime
notify/diagnostics_runtime_config.go, notify/diagnostics_runtime_namespace.go, notify/diagnostics_runtime_mount.go, notify/diagnostics_runtime_ignore.go, notify/syncthing.go
Loads strict opt-in configuration, validates paths and ephemeral mount bindings, bounds Syncthing responses, rejects redirects, evaluates expanded ignore patterns, and performs namespace authorization and preflight checks.
Operator, server, sessions, TLS, and CLI wiring
notify/diagnostics_operator.go, notify/diagnostics_runtime_*.go, notify/main.go
Adds Unix-socket operator commands, TLS 1.3 diagnostics endpoints, session binding, capability handling, admin operations, CLI modes, and service lifecycle wiring.
Packaging, documentation, and verification
notify/scripts/*, notify/tests/runtime-packaging/*, .github/workflows/ci.yml, notify/Dockerfile, docs/*, PRIVACY.md, .github/dependabot.yml
Adds rootful Docker installation and rollback proofs, digest-pinned images, runtime documentation, dependency tracking, shell checks, and CI gates.
Client redirect protection
notify/relay.go, notify/relay_test.go
Rejects HTTP redirects for relay and Syncthing clients and verifies redirect targets receive no requests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Poem

Signed bytes hum softly,
Through a socket locked and bright;
Namespaces recover forward,
TLS guards the night.
Docker rolls, then rolls ahead—
State stays in sight.

🚥 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 code keeps secrets out of logs and uses local-only/operator requests; logs only field names/counts, and secret-bearing payloads stay on disk or in intended auth headers.
Bounded Ios Background Work ✅ Passed PR touches only notify/docs; no ios/.swift files changed, so no Swift background-execution path was modified.
Bridge Contract Compatibility ✅ Passed HEAD only changes notify/*; no go/bridge or ios bridge-service files were modified, so the Go/Swift bridge contract is untouched.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise, conventional-commit style, and accurately reflects the PR’s main addition of an opt-in helper runtime and packaging readiness work.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/helper-runtime-packaging-readiness

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.

psimaker added 2 commits July 14, 2026 07:28
Mark the explicit Docker installer and both runtime packaging proof entrypoints executable so GitHub Actions can invoke the standard-Linux gate directly.
Filter the runtime constraint comparison to persistent bind mounts and remove Docker's empty template line while retaining the separate exact tmpfs assertion.
@psimaker
psimaker marked this pull request as ready for review July 14, 2026 05:52

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
notify/diagnostics_contract_model_test.go (1)

369-378: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

New diagnostics_capability_protocol.go isn't checked to stay operationally dormant.

This PR adds capabilityProtocolCarrier as an allowed home for capability strings/roundtrip domains (lines 292-324), putting it in the same architectural role as diagnostics_namespace_protocol.go, diagnostics_upload_protocol.go, and diagnostics_response_protocol.go — all of which are enforced here to never contain ListenAndServe/http.Server/net.Listen/RelayClient/SyncthingClient. The new file is missing from this list, so the exact invariant this test suite exists to enforce doesn't cover it.

♻️ Proposed fix
 	for _, path := range []string{
 		filepath.Join(repoRoot, "notify", "diagnostics_pairing_manager.go"),
 		filepath.Join(repoRoot, "notify", "diagnostics_pairing_crypto.go"),
 		filepath.Join(repoRoot, "notify", "diagnostics_pairing_store.go"),
 		filepath.Join(repoRoot, "notify", "diagnostics_namespace_protocol.go"),
 		filepath.Join(repoRoot, "notify", "diagnostics_upload_protocol.go"),
 		filepath.Join(repoRoot, "notify", "diagnostics_upload_attestor.go"),
 		filepath.Join(repoRoot, "notify", "diagnostics_response_protocol.go"),
 		filepath.Join(repoRoot, "notify", "diagnostics_response_foundation.go"),
+		filepath.Join(repoRoot, "notify", "diagnostics_capability_protocol.go"),
 	} {
🤖 Prompt for 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.

In `@notify/diagnostics_contract_model_test.go` around lines 369 - 378, Extend the
file list in the operational-dormancy test to include
diagnostics_capability_protocol.go alongside the other protocol files. Keep the
existing forbidden-symbol checks unchanged so capabilityProtocolCarrier receives
the same enforcement against ListenAndServe, http.Server, net.Listen,
RelayClient, and SyncthingClient.
🧹 Nitpick comments (2)
notify/diagnostics_capability_protocol_test.go (1)

154-180: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Test name promises "WrongTuple" coverage it doesn't exercise.

TestDiagnosticsCapabilityRejectsStaleAndWrongTuple only asserts on validateDiagnosticsCapabilityClock (staleness). There's no case that mutates the homeserver/folder/key-ID tuple and asserts diagnosticsCapabilityMatchesBinding rejects it — the "WrongTuple" half of the name is untested. Given this decoder gate is the core of the capability binding contract, worth closing the gap.

♻️ Suggested addition
wrongFolder := diagnosticsUploadBinding{
	homeserverBinding: bytes.Repeat([]byte{0x83}, 32),
	folderBinding:      bytes.Repeat([]byte{0xAA}, 32), // mismatched vs. query's field 6
	appPublicKey:       context.appPublicKey,
	// ...
}
if diagnosticsCapabilityMatchesBinding(message, wrongFolder) {
	t.Fatal("capability query matched an unrelated binding tuple")
}
🤖 Prompt for 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.

In `@notify/diagnostics_capability_protocol_test.go` around lines 154 - 180,
Extend TestDiagnosticsCapabilityRejectsStaleAndWrongTuple to construct a
diagnosticsUploadBinding using the decoded message’s app key but a different
folder binding, then assert diagnosticsCapabilityMatchesBinding returns false.
Keep the existing stale-clock assertion and use the query’s field-6 value as the
baseline so the test specifically covers rejection of a mismatched
homeserver/folder/key-ID tuple.
notify/diagnostics_runtime_namespace.go (1)

205-298: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Narrow the mutex scope around authorize()

runtime.mutex stays locked through openDiagnosticsNamespaceRoot, ScanFixedLayout*, preflightNamespaceHandle (with a 3s Syncthing timeout), and the immutable read/write path. That still serializes every namespace authorization behind one slow mount or Syncthing response, so unrelated folders on the same runtime can stall.

Consider moving the shared-state lock to just the in-memory checks and revalidating before commit.

🤖 Prompt for 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.

In `@notify/diagnostics_runtime_namespace.go` around lines 205 - 298, Reduce the
scope of runtime.mutex in diagnosticsNamespaceRuntime.authorize so it covers
only the in-memory rate-limit, session lookup, and state-validation operations,
not filesystem access, scans, preflight, or immutable record work. Release the
lock before openDiagnosticsNamespaceRoot and reacquire it immediately before the
authorization commit, revalidating the relevant session/state and preserving
idempotent authorizationAlreadyApplied behavior to prevent stale results or
concurrent commits.

Source: Path instructions

🤖 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 `@notify/diagnostics_pairing_lifecycle.go`:
- Around line 572-694: Move the prepare(plan) invocation in
observeLifecycleCapability outside manager.mutex, while retaining the lock for
state validation and the final atomic commit. Restructure the method so the lock
is released before prepareLifecycleCommit-related work, then reacquired or
otherwise revalidate the transition under the store update before applying
changes; preserve all existing digest, stage, confirmation, and readiness
checks.

---

Outside diff comments:
In `@notify/diagnostics_contract_model_test.go`:
- Around line 369-378: Extend the file list in the operational-dormancy test to
include diagnostics_capability_protocol.go alongside the other protocol files.
Keep the existing forbidden-symbol checks unchanged so capabilityProtocolCarrier
receives the same enforcement against ListenAndServe, http.Server, net.Listen,
RelayClient, and SyncthingClient.

---

Nitpick comments:
In `@notify/diagnostics_capability_protocol_test.go`:
- Around line 154-180: Extend TestDiagnosticsCapabilityRejectsStaleAndWrongTuple
to construct a diagnosticsUploadBinding using the decoded message’s app key but
a different folder binding, then assert diagnosticsCapabilityMatchesBinding
returns false. Keep the existing stale-clock assertion and use the query’s
field-6 value as the baseline so the test specifically covers rejection of a
mismatched homeserver/folder/key-ID tuple.

In `@notify/diagnostics_runtime_namespace.go`:
- Around line 205-298: Reduce the scope of runtime.mutex in
diagnosticsNamespaceRuntime.authorize so it covers only the in-memory
rate-limit, session lookup, and state-validation operations, not filesystem
access, scans, preflight, or immutable record work. Release the lock before
openDiagnosticsNamespaceRoot and reacquire it immediately before the
authorization commit, revalidating the relevant session/state and preserving
idempotent authorizationAlreadyApplied behavior to prevent stale results or
concurrent commits.
🪄 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: 4ba8be8d-3781-46c0-84d4-365a502b911e

📥 Commits

Reviewing files that changed from the base of the PR and between 85e527d and 0c756b4.

⛔ Files ignored due to path filters (1)
  • notify/go.sum is excluded by !**/*.sum and included by **/*
📒 Files selected for processing (46)
  • .github/dependabot.yml
  • .github/workflows/ci.yml
  • PRIVACY.md
  • docs/architecture.md
  • docs/helper-runtime-packaging-readiness.md
  • notify/Dockerfile
  • notify/README.md
  • notify/diagnostics_capability_protocol.go
  • notify/diagnostics_capability_protocol_test.go
  • notify/diagnostics_contract_model_test.go
  • notify/diagnostics_device_id.go
  • notify/diagnostics_device_id_test.go
  • notify/diagnostics_namespace_filesystem_linux.go
  • notify/diagnostics_namespace_filesystem_unsupported.go
  • notify/diagnostics_namespace_installer.go
  • notify/diagnostics_namespace_installer_linux.go
  • notify/diagnostics_namespace_linux_test.go
  • notify/diagnostics_namespace_protocol.go
  • notify/diagnostics_operator.go
  • notify/diagnostics_pairing_lifecycle.go
  • notify/diagnostics_pairing_permissions_unix.go
  • notify/diagnostics_pairing_permissions_windows.go
  • notify/diagnostics_pairing_store.go
  • notify/diagnostics_pairing_test.go
  • notify/diagnostics_response_foundation.go
  • notify/diagnostics_runtime_config.go
  • notify/diagnostics_runtime_config_test.go
  • notify/diagnostics_runtime_ignore.go
  • notify/diagnostics_runtime_integration_test.go
  • notify/diagnostics_runtime_mount.go
  • notify/diagnostics_runtime_namespace.go
  • notify/diagnostics_runtime_server.go
  • notify/diagnostics_runtime_state.go
  • notify/diagnostics_runtime_tls.go
  • notify/diagnostics_runtime_tls_test.go
  • notify/go.mod
  • notify/main.go
  • notify/relay.go
  • notify/relay_test.go
  • notify/scripts/diagnostics-docker.sh
  • notify/syncthing.go
  • notify/tests/m4-docker/Dockerfile
  • notify/tests/runtime-packaging/mock/Dockerfile
  • notify/tests/runtime-packaging/mock/main.go
  • notify/tests/runtime-packaging/run-linux-host.sh
  • notify/tests/runtime-packaging/run.sh
👮 Files not reviewed due to content moderation or server errors (8)
  • notify/diagnostics_operator.go
  • notify/diagnostics_runtime_server.go
  • notify/diagnostics_runtime_state.go
  • notify/diagnostics_runtime_tls.go
  • notify/main.go
  • notify/relay.go
  • notify/relay_test.go
  • notify/tests/runtime-packaging/mock/main.go
📜 Review details
🧰 Additional context used
📓 Path-based instructions (8)
**/*

⚙️ 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:

  • notify/tests/runtime-packaging/mock/Dockerfile
  • notify/diagnostics_pairing_permissions_unix.go
  • notify/diagnostics_device_id_test.go
  • notify/diagnostics_pairing_permissions_windows.go
  • notify/Dockerfile
  • notify/diagnostics_response_foundation.go
  • notify/diagnostics_device_id.go
  • notify/diagnostics_namespace_protocol.go
  • notify/diagnostics_namespace_filesystem_unsupported.go
  • notify/relay_test.go
  • notify/diagnostics_runtime_mount.go
  • notify/diagnostics_runtime_ignore.go
  • notify/diagnostics_pairing_test.go
  • notify/tests/runtime-packaging/mock/main.go
  • docs/architecture.md
  • notify/diagnostics_runtime_tls.go
  • notify/diagnostics_capability_protocol.go
  • notify/diagnostics_runtime_config.go
  • notify/relay.go
  • notify/tests/m4-docker/Dockerfile
  • notify/diagnostics_capability_protocol_test.go
  • docs/helper-runtime-packaging-readiness.md
  • notify/diagnostics_runtime_tls_test.go
  • notify/diagnostics_namespace_linux_test.go
  • notify/README.md
  • notify/go.mod
  • notify/diagnostics_runtime_config_test.go
  • notify/diagnostics_contract_model_test.go
  • notify/diagnostics_pairing_store.go
  • notify/diagnostics_namespace_filesystem_linux.go
  • notify/diagnostics_runtime_state.go
  • PRIVACY.md
  • notify/diagnostics_namespace_installer.go
  • notify/scripts/diagnostics-docker.sh
  • notify/tests/runtime-packaging/run.sh
  • notify/diagnostics_pairing_lifecycle.go
  • notify/syncthing.go
  • notify/diagnostics_runtime_server.go
  • notify/diagnostics_runtime_namespace.go
  • notify/diagnostics_operator.go
  • notify/diagnostics_namespace_installer_linux.go
  • notify/tests/runtime-packaging/run-linux-host.sh
  • notify/main.go
  • notify/diagnostics_runtime_integration_test.go
notify/**/*.{sh,go}

📄 CodeRabbit inference engine (README.md)

The optional notify sidecar must support server-side wake-up requests and must not receive notes, file or folder names, or vault structure.

Files:

  • notify/diagnostics_pairing_permissions_unix.go
  • notify/diagnostics_device_id_test.go
  • notify/diagnostics_pairing_permissions_windows.go
  • notify/diagnostics_response_foundation.go
  • notify/diagnostics_device_id.go
  • notify/diagnostics_namespace_protocol.go
  • notify/diagnostics_namespace_filesystem_unsupported.go
  • notify/relay_test.go
  • notify/diagnostics_runtime_mount.go
  • notify/diagnostics_runtime_ignore.go
  • notify/diagnostics_pairing_test.go
  • notify/tests/runtime-packaging/mock/main.go
  • notify/diagnostics_runtime_tls.go
  • notify/diagnostics_capability_protocol.go
  • notify/diagnostics_runtime_config.go
  • notify/relay.go
  • notify/diagnostics_capability_protocol_test.go
  • notify/diagnostics_runtime_tls_test.go
  • notify/diagnostics_namespace_linux_test.go
  • notify/diagnostics_runtime_config_test.go
  • notify/diagnostics_contract_model_test.go
  • notify/diagnostics_pairing_store.go
  • notify/diagnostics_namespace_filesystem_linux.go
  • notify/diagnostics_runtime_state.go
  • notify/diagnostics_namespace_installer.go
  • notify/scripts/diagnostics-docker.sh
  • notify/tests/runtime-packaging/run.sh
  • notify/diagnostics_pairing_lifecycle.go
  • notify/syncthing.go
  • notify/diagnostics_runtime_server.go
  • notify/diagnostics_runtime_namespace.go
  • notify/diagnostics_operator.go
  • notify/diagnostics_namespace_installer_linux.go
  • notify/tests/runtime-packaging/run-linux-host.sh
  • notify/main.go
  • notify/diagnostics_runtime_integration_test.go
**/*.{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:

  • notify/diagnostics_pairing_permissions_unix.go
  • notify/diagnostics_device_id_test.go
  • notify/diagnostics_pairing_permissions_windows.go
  • notify/diagnostics_response_foundation.go
  • notify/diagnostics_device_id.go
  • notify/diagnostics_namespace_protocol.go
  • notify/diagnostics_namespace_filesystem_unsupported.go
  • notify/relay_test.go
  • notify/diagnostics_runtime_mount.go
  • notify/diagnostics_runtime_ignore.go
  • notify/diagnostics_pairing_test.go
  • notify/tests/runtime-packaging/mock/main.go
  • docs/architecture.md
  • notify/diagnostics_runtime_tls.go
  • notify/diagnostics_capability_protocol.go
  • notify/diagnostics_runtime_config.go
  • notify/relay.go
  • notify/diagnostics_capability_protocol_test.go
  • docs/helper-runtime-packaging-readiness.md
  • notify/diagnostics_runtime_tls_test.go
  • notify/diagnostics_namespace_linux_test.go
  • notify/README.md
  • notify/diagnostics_runtime_config_test.go
  • notify/diagnostics_contract_model_test.go
  • notify/diagnostics_pairing_store.go
  • notify/diagnostics_namespace_filesystem_linux.go
  • notify/diagnostics_runtime_state.go
  • PRIVACY.md
  • notify/diagnostics_namespace_installer.go
  • notify/scripts/diagnostics-docker.sh
  • notify/tests/runtime-packaging/run.sh
  • notify/diagnostics_pairing_lifecycle.go
  • notify/syncthing.go
  • notify/diagnostics_runtime_server.go
  • notify/diagnostics_runtime_namespace.go
  • notify/diagnostics_operator.go
  • notify/diagnostics_namespace_installer_linux.go
  • notify/tests/runtime-packaging/run-linux-host.sh
  • notify/main.go
  • notify/diagnostics_runtime_integration_test.go
notify/**/*.go

⚙️ CodeRabbit configuration file

notify/**/*.go: Review goroutine lifecycle, context cancellation, HTTP timeouts, signal handling, debounce behavior,
Syncthing REST API polling, relay API calls, error classification, and API-key handling.
Flag leaked request bodies, note metadata, Syncthing API keys, relay keys, or APNs-related secrets.

Files:

  • notify/diagnostics_pairing_permissions_unix.go
  • notify/diagnostics_device_id_test.go
  • notify/diagnostics_pairing_permissions_windows.go
  • notify/diagnostics_response_foundation.go
  • notify/diagnostics_device_id.go
  • notify/diagnostics_namespace_protocol.go
  • notify/diagnostics_namespace_filesystem_unsupported.go
  • notify/relay_test.go
  • notify/diagnostics_runtime_mount.go
  • notify/diagnostics_runtime_ignore.go
  • notify/diagnostics_pairing_test.go
  • notify/tests/runtime-packaging/mock/main.go
  • notify/diagnostics_runtime_tls.go
  • notify/diagnostics_capability_protocol.go
  • notify/diagnostics_runtime_config.go
  • notify/relay.go
  • notify/diagnostics_capability_protocol_test.go
  • notify/diagnostics_runtime_tls_test.go
  • notify/diagnostics_namespace_linux_test.go
  • notify/diagnostics_runtime_config_test.go
  • notify/diagnostics_contract_model_test.go
  • notify/diagnostics_pairing_store.go
  • notify/diagnostics_namespace_filesystem_linux.go
  • notify/diagnostics_runtime_state.go
  • notify/diagnostics_namespace_installer.go
  • notify/diagnostics_pairing_lifecycle.go
  • notify/syncthing.go
  • notify/diagnostics_runtime_server.go
  • notify/diagnostics_runtime_namespace.go
  • notify/diagnostics_operator.go
  • notify/diagnostics_namespace_installer_linux.go
  • notify/main.go
  • notify/diagnostics_runtime_integration_test.go
notify/Dockerfile

⚙️ CodeRabbit configuration file

notify/Dockerfile: Check container security, minimal runtime surface, non-root execution where practical,
HEALTHCHECK behavior, reproducible builds, and safe handling of runtime secrets.

Files:

  • notify/Dockerfile
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/architecture.md
  • docs/helper-runtime-packaging-readiness.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/architecture.md
  • docs/helper-runtime-packaging-readiness.md
  • notify/README.md
  • PRIVACY.md
.github/workflows/**/*.yml

⚙️ CodeRabbit configuration file

.github/workflows/**/*.yml: Review CI for correct Go test tags, Xcode/iOS simulator assumptions, secret scoping,
dependency integrity, permissions, Docker publishing safety, and unnecessary privilege escalation.

Files:

  • .github/workflows/ci.yml
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: psimaker/vaultsync

Timestamp: 2026-07-14T05:52:52.914Z
Learning: Do not infer publication, registry digests, SBOM or vulnerability results, production rollout, production rollback evidence, upload, download, or roundtrip evidence from this source-readiness milestone.
🪛 ast-grep (0.44.1)
notify/tests/runtime-packaging/mock/main.go

[warning] 98-104: TLS certificate verification is disabled by setting InsecureSkipVerify: true on the tls.Config. This makes the connection vulnerable to man-in-the-middle attacks because the server's certificate chain and host name are not validated. Remove InsecureSkipVerify (or set it to false) and provide a proper RootCAs pool to trust custom certificates instead.
Context: tls.Config{
// #nosec G402 -- this isolated proof reads the self-signed endpoint's
// public-key pin; production clients verify the configured SPKI pin.
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS13,
MaxVersion: tls.VersionTLS13,
}
Note: [CWE-295] Improper Certificate Validation.

(tls-insecure-skip-verify-go)


[warning] 79-84: This http.Server is constructed without a ReadTimeout. Without a read timeout, a slow or malicious client can hold connections open indefinitely (e.g. a Slowloris attack), exhausting server resources and causing a denial of service. Set ReadTimeout (and ideally ReadHeaderTimeout, WriteTimeout, and IdleTimeout) on the http.Server to bound how long the server waits while reading a request.
Context: http.Server{
Addr: listenAddress,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 10 * time.Second,
}
Note: [CWE-400] Uncontrolled Resource Consumption.

(http-server-missing-read-timeout-go)

notify/scripts/diagnostics-docker.sh

[error] 40-40: eval is invoked on a variable, parameter expansion, or command-substitution result, which re-parses the value as shell code. If any part of that value is attacker-controlled (arguments, environment, file contents, network output), it allows arbitrary command execution. Do not eval dynamic data: invoke the command directly with proper quoting (e.g. "$cmd" "$arg"), use arrays for argument lists (cmd=(prog --flag "$value"); "${cmd[@]}"), or restrict input to a validated allowlist before running it.
Context: eval "variable_value=${$variable_name:-}"
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(eval-on-variable-bash)

notify/tests/runtime-packaging/run.sh

[warning] 17-17: A credential-bearing variable (e.g. PASSWORD, PASSWD, SECRET, TOKEN, API_KEY) is assigned a hardcoded string literal. Secrets committed to a script are exposed in source control, process listings, and shell history, and cannot be rotated without a code change. Read the value from a secrets manager or an injected environment variable at runtime instead (e.g. PASSWORD="${DB_PASSWORD:?must be set}"), and never commit the literal.
Context: api_key=runtime-packaging-api-key-sentinel
Note: [CWE-798] Use of Hard-coded Credentials.

(hardcoded-password-assignment-bash)

notify/tests/runtime-packaging/run-linux-host.sh

[warning] 18-18: A credential-bearing variable (e.g. PASSWORD, PASSWD, SECRET, TOKEN, API_KEY) is assigned a hardcoded string literal. Secrets committed to a script are exposed in source control, process listings, and shell history, and cannot be rotated without a code change. Read the value from a secrets manager or an injected environment variable at runtime instead (e.g. PASSWORD="${DB_PASSWORD:?must be set}"), and never commit the literal.
Context: api_key=runtime-linux-api-key-sentinel
Note: [CWE-798] Use of Hard-coded Credentials.

(hardcoded-password-assignment-bash)

notify/diagnostics_runtime_integration_test.go

[warning] 1007-1007: TLS certificate verification is disabled by setting InsecureSkipVerify: true on the tls.Config. This makes the connection vulnerable to man-in-the-middle attacks because the server's certificate chain and host name are not validated. Remove InsecureSkipVerify (or set it to false) and provide a proper RootCAs pool to trust custom certificates instead.
Context: tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12}
Note: [CWE-295] Improper Certificate Validation.

(tls-insecure-skip-verify-go)

🪛 OpenGrep (1.25.0)
notify/tests/runtime-packaging/mock/main.go

[ERROR] 99-105: TLS certificate verification is disabled via InsecureSkipVerify. This allows man-in-the-middle attacks. Remove InsecureSkipVerify or set it to false.

(coderabbit.tls.go-insecure-skip-verify)


[ERROR] 99-105: TLS certificate verification is disabled via InsecureSkipVerify. This allows man-in-the-middle attacks. Remove InsecureSkipVerify or set it to false.

(coderabbit.tls.go-insecure-skip-verify)

notify/diagnostics_runtime_integration_test.go

[ERROR] 1008-1008: TLS certificate verification is disabled via InsecureSkipVerify. This allows man-in-the-middle attacks. Remove InsecureSkipVerify or set it to false.

(coderabbit.tls.go-insecure-skip-verify)


[ERROR] 1008-1008: TLS certificate verification is disabled via InsecureSkipVerify. This allows man-in-the-middle attacks. Remove InsecureSkipVerify or set it to false.

(coderabbit.tls.go-insecure-skip-verify)

🔇 Additional comments (45)
notify/diagnostics_runtime_tls_test.go (1)

1-95: LGTM!

notify/diagnostics_pairing_permissions_unix.go (1)

29-36: LGTM!

notify/diagnostics_pairing_permissions_windows.go (1)

22-25: LGTM!

.github/dependabot.yml (1)

59-72: LGTM!

.github/workflows/ci.yml (1)

106-118: LGTM!

Also applies to: 145-161, 194-196, 252-252

PRIVACY.md (1)

3-3: LGTM!

Also applies to: 76-143, 159-169, 181-189

notify/tests/runtime-packaging/run-linux-host.sh (1)

1-214: LGTM!

docs/architecture.md (1)

94-199: LGTM!

docs/helper-runtime-packaging-readiness.md (1)

1-243: LGTM!

notify/Dockerfile (1)

1-21: LGTM! Confirmed via web search that Go 1.26 and Alpine 3.24 are both real, current releases, so the pinned digests target valid, up-to-date base images.

notify/README.md (1)

119-120: LGTM!

Also applies to: 132-215, 249-249, 267-267

notify/diagnostics_response_foundation.go (1)

56-59: LGTM!

notify/scripts/diagnostics-docker.sh (2)

1-378: Rest of the installer — supported-host/storage checks, folder/config canonicalization, device/inode pinning, mount-alias handling, and the command dispatch table — looks solid and matches the documented support matrix.


105-152: 🔒 Security & Privacy

listen_address is already range-checked downstream. notify/diagnostics_runtime_config.go rejects non-loopback/link-local/private/CGNAT bind addresses before net.Listen, so the shell script’s character-only preflight is not the enforcement point.

			> Likely an incorrect or invalid review comment.
notify/tests/runtime-packaging/mock/Dockerfile (1)

1-10: LGTM!

notify/tests/runtime-packaging/run.sh (1)

1-207: LGTM!

notify/diagnostics_capability_protocol.go (1)

1-220: LGTM!

notify/diagnostics_pairing_lifecycle.go (2)

232-232: LGTM!

Also applies to: 268-268, 285-285, 324-324, 817-817, 832-838


840-870: LGTM!

notify/diagnostics_pairing_test.go (1)

721-787: LGTM!

Also applies to: 974-977

notify/diagnostics_namespace_protocol.go (1)

635-700: LGTM!

notify/diagnostics_namespace_filesystem_linux.go (1)

374-385: LGTM!

Also applies to: 540-546

notify/diagnostics_namespace_installer.go (1)

4-4: LGTM!

Also applies to: 36-51, 65-73

notify/diagnostics_namespace_installer_linux.go (2)

156-194: 🔒 Security & Privacy

Path-based reopen after the trusted-parent recheck.

recoverDiagnosticsNamespaceExplicit re-verifies diagnosticsNamespaceTrustedParentStillSelected at Line 163, then reopens the root by joining request.parentPath into a fresh string path (Line 166-167) instead of anchoring off the already-verified parent *os.Root handle. That leaves a narrow window between the check and the open where the parent directory could be swapped out from under it. This mirrors the pre-existing root-creation path's mitigation idiom (check → path-open → re-check), so it may be an accepted tradeoff — worth confirming whether openDiagnosticsNamespaceRoot could instead be anchored to parent (e.g. parent.OpenRoot(diagnosticsNamespaceRootName)) to remove the gap rather than only detecting it after the fact.


17-155: LGTM!

Also applies to: 196-227

notify/diagnostics_namespace_linux_test.go (1)

13-15: LGTM!

Also applies to: 51-63, 271-323, 680-698

notify/diagnostics_capability_protocol_test.go (1)

1-152: LGTM!

notify/diagnostics_contract_model_test.go (2)

284-334: LGTM!

Also applies to: 360-368


336-359: 🎯 Functional Correctness

No missing default-entrypoint files The referenced notify/docker-compose.yml, notify/scripts/install.sh, notify/scripts/install.ps1, notify/scripts/bootstrap.sh, and notify/Dockerfile files are present, so this test won’t fail on missing paths.

			> Likely an incorrect or invalid review comment.
notify/diagnostics_device_id.go (1)

1-66: LGTM!

notify/diagnostics_pairing_store.go (1)

69-95: LGTM!

Also applies to: 116-122, 473-482, 543-545

notify/diagnostics_namespace_filesystem_unsupported.go (1)

37-40: LGTM!

notify/diagnostics_runtime_config.go (1)

1-249: LGTM!

notify/diagnostics_runtime_namespace.go (3)

1-204: LGTM!

Also applies to: 300-393, 404-495, 553-821


521-552: 🗄️ Data Integrity & Integration

Recovery path already requires an existing namespace root. It returns unsupported unless a root record exists for the folder, and the helper path revalidates the on-disk root manifest and current helper epoch/key; a separate NamespaceInitialAppKeyID check isn’t needed here.

			> Likely an incorrect or invalid review comment.

395-402: 🩺 Stability & Availability

Discarded errors here are harmless. diagnosticsNamespaceAuthorizationPaths returns a fixed-size array, so the epoch <= 1 branch can't panic, and a zero-value diagnosticsNamespacePath is rejected by ReadImmutable rather than treated as a real namespace path.

			> Likely an incorrect or invalid review comment.
notify/diagnostics_device_id_test.go (1)

1-56: LGTM!

notify/diagnostics_runtime_config_test.go (1)

1-306: LGTM!

notify/diagnostics_runtime_mount.go (2)

53-64: 🔒 Security & Privacy | ⚡ Quick win

Test-only bypass lives inside the production binding check.

mountBindingMatches returns true unconditionally whenever mountPathOverrides[alias] is non-empty, skipping the digest comparison entirely. The comment says this field is "unexported and cannot be loaded from config," but that's an invariant of loadDiagnosticsRuntimeConfig elsewhere, not something this function enforces itself — any future change to the loader (or another same-package caller) that populates this map would silently disable the mount-identity check that gates access to the real vault namespace.

Consider isolating the override behind a build tag (e.g. a _test.go-only setter) or at least asserting in this function that overrides can only apply when a dedicated "test mode" flag is also set, so the bypass can't be reached by a production code path without an explicit, auditable switch.


19-38: 🔒 Security & Privacy | 🏗️ Heavy lift

Confirm this namespace/mount-binding feature is an intentional exception to the "no vault structure" sidecar boundary.

Per coding guidelines, notify/**/*.{sh,go} must keep the sidecar limited to server-side wake-up requests and "must not receive notes, file or folder names, or vault structure." diagnosticsRuntimeMountBinding takes folderPath directly, and mountBindingMatches is invoked (per the cross-file snippet from diagnostics_runtime_server.go) with the live folder.Path fetched from Syncthing's REST API during preflight — i.e., the running notify binary does hold and hash the real vault folder path in-process, even though only the digest ever leaves the host.

This is likely an intentional, disclosed scope expansion for the new opt-in diagnostics runtime rather than an oversight, but given guidelines are meant to be treated as authoritative, it's worth an explicit confirmation that this new capability is understood as exempt from that specific sidecar constraint.

Based on learnings, the coding guideline for notify/**/*.{sh,go} states: "The optional notify sidecar must support server-side wake-up requests and must not receive notes, file or folder names, or vault structure."

Also applies to: 53-71

Source: Coding guidelines

notify/diagnostics_runtime_ignore.go (1)

1-93: LGTM! Verified against Syncthing's own documented expanded ignore-pattern semantics (gobwas/glob syntax, any-order prefix modifiers, first-match-wins) and confirmed the calmh/glob pin matches the exact fork/version Syncthing itself vendors.

notify/syncthing.go (1)

8-8: LGTM! Verified ErrUseLastResponse semantics against Go's net/http documentation — the redirect response is returned unfollowed with a non-200 status, so it correctly surfaces as an HTTPStatusError rather than leaking data to a redirect target. The bounded-JSON boundary handling in getJSONBounded (exact-limit case) is also correct.

Also applies to: 39-48, 197-217, 234-280, 339-377

notify/diagnostics_runtime_integration_test.go (1)

1-1253: LGTM!

notify/tests/m4-docker/Dockerfile (1)

1-11: LGTM!

notify/go.mod (1)

4-8: LGTM! Confirmed via web search that this is the exact calmh/glob fork/version Syncthing itself vendors, so pinning it here gives byte-for-byte parity with Syncthing's own ignore-pattern grammar.

Comment thread notify/diagnostics_pairing_lifecycle.go

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 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.

psimaker added 2 commits July 14, 2026 09:12
Move lifecycle and namespace preparation outside global mutexes while binding each commit to the exact re-read credential and root state.

Anchor namespace opens to the verified parent, require mount digests even in test overrides, and extend dormancy and tuple regression coverage.
Require an exact active pairing authorization before allocating the per-folder namespace lock, then re-read it after lock acquisition before any slow work.

Add a regression proving a validly signed but unpaired folder tuple leaves the lock map empty.
@psimaker
psimaker merged commit f24952a into main Jul 14, 2026
25 checks passed
@psimaker
psimaker deleted the agent/helper-runtime-packaging-readiness branch July 14, 2026 07:44
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