Synchronize notes with remote GitHub repo#194
Conversation
adibhanna
left a comment
There was a problem hiding this comment.
Thank you for this, @tintinthedev — this is a genuinely useful feature and a strong first cut. The structure is clean (nice separation of github-sync.ts from the IPC wiring), you used execFile with array args so there's no shell-injection surface, the PAT goes into the OS secret store, the git errors are mapped to friendly user-facing messages, the web build correctly no-ops, and 15 vitest cases is excellent coverage for a PR like this. Really good instincts throughout.
Because the whole job of this feature is handling a GitHub credential, I'm holding it to a high bar on secret-handling. A few things need to change before it can merge — happy to help with any of them.
🔴 Security (must-fix)
-
The PAT is persisted in plaintext on disk.
setupRemote()bakes the token into the remote URL (https://${pat}:x-oauth-basic@github.com/${repo}.git) andgit remote set-urlwrites that to<vault>/.git/configin cleartext. That defeats the point of storing it in the secret store — anyone with filesystem/backup access can read it. Please inject the token ephemerally instead, e.g. aGIT_ASKPASShelper or-c http.extraHeader=\"Authorization: Bearer …\"per invocation, and keeporiginset to the plainhttps://github.com/owner/repo.git. -
The raw PAT is sent to the renderer.
handleGetConfig()returns{ pat, repo }and that flows over IPC to the renderer (getGithubConfig()). Since the renderer displays user-authored Markdown, any renderer-side code execution could callwindow.zen.getGithubConfig()and exfiltrate the token. Please return{ hasPat: boolean, repo }and keep the secret main-side only — the config UI only needs to know whether a token is set, not its value. -
The PAT can leak into error messages.
runGit()rejects with rawstderr, and git prints the tokenized remote URL on failure (fatal: unable to access 'https://TOKEN@github.com/…'). That string is returned straight to the UI viaGithubSyncResult.error. Please redact anything matching the token (or theuser:pass@portion of a URL) before surfacing it.
🟠 Correctness
-
Wrong conflict recovery.
pullRemote()runspull --rebase --autostash, but on a conflict it callsgit merge --abort. Mid-rebase there is no merge to abort, so that command errors and is swallowed by.catch(() => {}), leaving the repo stuck mid-rebase with the autostash unapplied. It should begit rebase --abort. -
git add -Astages the entire vault. With no.gitignore, this commits and pushes everything in the vault root —.obsidian/, attachments, large binaries, anything private the user keeps alongside their notes. Consider scaffolding a sensible.gitignoreand/or scoping what gets staged, and documenting that the whole vault is pushed. -
Hardcoded
maincan hijack existing repos.prepareRepo()doescheckout -B main, force-moving a vault that's already a git repo ontomain. Users onmasteror a feature branch would be surprised. Consider detecting/using the existing branch, or making it configurable.
🟡 Diff hygiene
- A large part of the
apps/desktop/src/main/index.tsdiff is unrelated Prettier reformatting (the mac window-options indentation,catch(() => { }), the deep-link block, etc.) — looks like a local editor/Prettier config that differs from the repo's. Could you revert those so the diff is just the GitHub-sync changes? It'll be much easier to review and won't conflict.
Thanks again — the bones here are good, and items 1–3 are the main thing standing between this and merge. Glad to pair on the credential-injection approach if that's helpful.
|
Thank you for the review! Really good to see that it's really a good improvement. I think I just need some more clarification about certain points you mentioned so I can perform the changes. About injecting the PAT ephemerally, it would still need to be stored somewhere? If not, then how would the push be made? Could you describe kind of the flow the app would have to take so the PAT can be injected ephemerally? Any representation, like "step 1 -> step 2 -> ..." is already good enough. Also, about avoiding the staging of the entire vault (point 5), what do you think is a good tactic? Just having a About choosing the correct branch, I think the best way is to update the feature so it can support an user-selected branch (that can be modified later, just like the repository and PAT). What do you think? I think that, with these things clear, I can perform the changes. I'm sorry I didn't consider many of the security aspects and conflict recovery. Also, I'm very sorry about the diff looking messy. It really is something to do with my local neovim setup. I will fix that ASAP and use the project's config. |
Adds GitHub synchronization for the desktop build.
Configure a Github PAT + repo in the UI, then sync notes via pull --rebase → add -A → commit → push.
New command palette entries (Ctrl+Shift+P):
IPC — 4 new channels (github:get-config, github:set-config, github:list-repos, github:sync) flowing through bridge.ts -> preload -> main process. Web build returns no-ops with an unavailable message.
Config — PAT stored in secret-store (keytar/safeStorage), repo name in PersistedConfig.githubRepo.
Test coverage — 15 vitest cases: listRepos (happy path + API errors), sync (no git, merge conflict, no changes, success, push rejection, connection failure), handleSync (missing PAT/repo).
Code reviews are welcome. Also, this was created based on #189