File-Only GitHub Sync Implementation Plan
Status: implemented and verified
Objective
Limit GitHub sync to portable files and remove encrypted API-key synchronization from the sync system.
GitHub sync will synchronize:
- portable configuration;
- user skills;
- Markdown memory files.
API keys, credentials, provider tokens, recovery phrases, and secret-manager sessions will remain device-local. A Bitwarden-compatible secret provider can be implemented separately without coupling it to file reconciliation.
Non-goals
- Implementing Vaultwarden or Bitwarden integration in this pull request.
- Synchronizing encrypted credentials through Git.
- Retaining Shibboleth, mnemonic, or shared-key-vault workflows.
- Rewriting Git history to remove historical encrypted bundles.
- Redesigning the complete Git transport layer beyond what safe file reconciliation requires.
Portable-file boundary
The payload may contain only:
config/config.yaml
config/default.yaml
config/skills.json
skills/
memory/**/*.md
The payload must exclude:
.env
.secret_key
local.yaml
secrets.db
sync_profiles.json
node_devices.json
node_host_devices.json
node_peers.json
permission-audit.jsonl
permissions.yaml
node_identity.json
mcp_servers.json
chatgpt/**
_sync/secrets/**
Database files, runtime data, caches, sessions, exports, backups, device tokens, and other machine-local state must continue to be excluded.
Configuration uses an allowlist, not an exclusion-only traversal. Pull replaces only the three portable configuration files and must preserve every other local configuration file, including files introduced by future features.
Tests must prove that representative API-key values cannot enter the payload through environment files, local configuration, secret databases, or structured provider configuration.
Backend implementation
Remove secret-sync models
In src/suzent/sync/models.py, remove these fields from SyncProfile:
encrypted_secret_sync_enabled
secret_sync_available
synced_keys
Remove the secret-specific models:
ShibbolethKdfParams
MnemonicKdfParams
DeviceRegistration
EncryptedSecretBundle
SecretBundlesFile
Remove their exports from src/suzent/sync/__init__.py.
Existing profile JSON may contain obsolete fields. Pydantic should ignore those fields while loading; saving the profile again should write only the current schema.
Delete encryption modules
Delete:
src/suzent/sync/secrets.py
src/suzent/sync/shibboleth.py
src/suzent/sync/mnemonic.py
src/suzent/sync/bip39_english.txt
Remove dependencies used exclusively by these modules after verifying that they have no other consumers.
Keep planning externally read-only
Push and automatic-sync planning hash portable source trees directly and compare them with the committed payload. Tests must verify that repository status is identical before and after planning.
Remove SECRETS_DIR and encrypted-bundle preservation. Retain append-only
Markdown memory preservation. Do not copy non-Markdown memory attachments.
Do not generate _sync/manifest.json or _sync/presence/**. Git commits and
refs already provide revision identity, timestamps, and changed paths. Remove
the random revision UUID, device presence, metadata-only commit filtering, and
the corresponding status UI.
Simplify the sync service
Remove secret-specific service state and methods, including:
- Shibboleth and mnemonic lock/unlock state;
- mnemonic keyring storage;
- vault enable, disable, rotation, and device registration;
- key selection and vault-key removal;
- secret export, import, overwrite detection, and bundle inspection;
- secret-related fields in status and operation results.
Use simple file-only signatures:
async def pull(
profile_id: str | None = None,
*,
confirm_destructive: bool = False,
prefer_cloud: bool = False,
) -> dict:
...
async def push(
profile_id: str | None = None,
*,
confirm_destructive: bool = False,
) -> dict:
...
async def auto_sync(
profile_id: str | None = None,
*,
confirm_destructive: bool = False,
) -> dict:
...
Operation results must not contain secrets, imported_secret_keys, or
changed_secret_keys.
Keep planning file-only and read-only
SyncFileChange.category should support:
Literal["config", "skills", "memory", "other"]
Remove secret categories, vault-deletion warnings, and secret-specific diff handling.
Planning must not touch the Git working tree. Change lists must omit eager patch generation; the selected file's textual diff is loaded through a separate, locked request.
Destructive confirmation remains appropriate for:
- memory-file deletion;
- replacement of protected memory files;
- large deletion batches;
- removal of legacy
_sync/**content during the next payload build.
Use asymmetric, explicit resolution
Outgoing local changes may be discarded per file by restoring the selected path from the committed payload and applying that path locally. This does not advance repository state and must leave every other outgoing change pending.
Incoming changes remain bulk-only because Git fast-forward pull advances the repository as a whole. The review UI keeps files collapsed and loads only the selected file's diff on demand. Resolution is one of:
- pull the complete cloud payload;
- discard one outgoing local change;
- discard all outgoing changes and restore the committed payload locally;
- keep the plan unresolved.
Automatic sync must block whenever incoming and outgoing file changes coexist, even if destructive confirmation was supplied. It must never restore a complete local snapshot over newly pulled incoming files.
HTTP API cleanup
Remove the following route handlers and routes:
/sync/shibboleth/unlock
/sync/shibboleth/lock
/sync/secrets/enable
/sync/secrets/disable
/sync/secrets/unlock
/sync/secrets/rotate
/sync/secrets/register-device
/sync/secrets/generate-mnemonic
/sync/secrets/check-mnemonic
/sync/secrets/synced-keys
/sync/secrets/remove-keys
Remove _shibboleth_from_payload. Pull, push, and automatic-sync requests must
not accept passphrases or mnemonics.
Also remove the unused /sync/ahead-behind and conflict-preview endpoints.
Mixed changes are handled solely by the explicit review flow; no agent conflict
resolver or auto_resolve_enabled profile option remains.
Keep the file-sync routes:
/sync/status
/sync/plan
/sync/pull
/sync/push
/sync/auto
/sync/auto/run
/sync/discard-outgoing
Frontend cleanup
Delete:
frontend/src/components/settings/ShibbolethPanel.tsx
From GitHubSyncSection.tsx, remove:
- the shared-key-vault panel;
- unlock requirements before file operations;
- secret categories and labels;
- key import/export notifications;
- vault-inventory and key-selection state;
- Shibboleth and mnemonic API calls.
The GitHub sync card should state, through i18n:
Syncs config, skills, and memory. API keys and credentials stay on this device.
The action bar should follow the file plan: show Pull only when incoming files exist, show Push only when outgoing files exist, and hide both when the repository is current. Do not retain a separate combined Sync button; mixed changes belong in the explicit review flow.
From frontend/src/lib/dataApi.ts, remove secret-vault types, secret-sync profile
fields, secret-management functions, and Shibboleth arguments on pull and push.
Remove the payload inventory and revision UUID display; both duplicate Git state
and require unnecessary full-payload work during status loading.
Remove obsolete Shibboleth translations from the English and Chinese message files. Add translations for the file-only scope statement and any legacy-bundle warning.
Legacy encrypted bundles
The new sync workflow must never decrypt, import, update, or create
_sync/secrets/**.
This feature has not been part of the stable file-sync contract, so payload construction removes a legacy encrypted bundle directly. Documentation must explain that deleting the current file does not remove ciphertext from Git history and should recommend rotating provider keys if the repository or recovery phrase may have been exposed.
Tests
Delete tests dedicated to the removed implementation:
tests/sync/test_encrypted_secret_sync.py
tests/sync/test_shibboleth.py
Update route, payload, service, profile, and frontend tests for the reduced schema and API.
Add real temporary-Git-repository coverage for:
- Pushing local config, skill, and memory changes.
- Pulling remote portable-file changes.
- Excluding API keys and all machine-local secret files.
- Ensuring planning does not change repository status.
- Simultaneous incoming and outgoing changes.
- Per-file outgoing discard preserving other outgoing changes.
- Bulk pull and bulk discard resolution.
- Requiring confirmation for protected memory deletion.
- Removing a legacy encrypted bundle from newly built payloads.
- Preventing auto-sync from overwriting unresolved incoming files.
Provider mocks remain useful for error-path unit tests, but reconciliation semantics must be verified against real Git repositories.
Suggested commit sequence
test(sync): define file-only sync and secret exclusionrefactor(sync): remove encrypted key models and servicesrefactor(sync): make file planning read-onlyrefactor(sync): simplify file sync routes and responsesrefactor(frontend): remove shared key vault UItest(sync): cover mixed Git reconciliation and outgoing file discarddocs(sync): document device-local credentials
Acceptance criteria
- No API-key value or encrypted key bundle is created by sync.
- No Shibboleth, mnemonic, vault, or encrypted-secret runtime code remains.
- Planning is read-only.
- GitHub sync does not require an unlock step.
- The UI accurately describes the synchronized file categories.
- Mixed incoming and outgoing changes remain blocked until explicitly resolved.
- Mixed incoming and outgoing changes are covered by real Git tests.
- The full non-sandbox test suite passes.
- Ruff, the frontend production build, and pre-commit pass.
- A repository-wide search for removed terminology returns only intentional migration documentation.
Follow-up
Add a separate secret-provider subsystem after this pull request. The first optional implementation may use the official Bitwarden CLI with a configurable server URL, allowing both Bitwarden and Vaultwarden deployments. Only secret references should participate in portable-file sync.