-
Notifications
You must be signed in to change notification settings - Fork 447
feat(vc): implement SSH commit signing (ENG-2002) #435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
hieuntg81
wants to merge
53
commits into
main
Choose a base branch
from
feat/ENG-2002
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
78fb480
feat(vc): implement SSH commit signing (ENG-2002)
hieuntg81 fb6667d
fix(vc): address PR #435 review issues in SSH commit signing (ENG-2002)
hieuntg81 48ac694
fix(vc): address remaining minor observations from PR #435 re-review …
hieuntg81 d90806b
Merge branch 'main' into feat/ENG-2002
hieuntg81 65618ac
fix(vc): extract public key from OpenSSH header without decryption (E…
hieuntg81 bdd6414
Merge branch 'main' into feat/ENG-2002
hieuntg81 d4821d5
feat: add support for SSH-agent signing, improved key parsing, and se…
hieuntg81 8880b5f
Merge branch 'main' into feat/ENG-2002
hieuntg81 36e2682
fix(ssh): use 6-byte SSHSIG preamble for signed data per PROTOCOL.sshsig
hieuntg81 fb1c71b
fix(ssh): drop 'unsupported' from passphrase-error regex (ENG-2002 B6)
hieuntg81 d7ceeec
test(ssh): add regression test confirming B6 closes ENG-2002 B7
hieuntg81 77464d7
fix(oclif): remove interactive passphrase prompt from vc commit (ENG-…
hieuntg81 608aa9f
docs(ssh): rewrite SSH signing guide in English with full coverage (E…
hieuntg81 c174c31
fix(ssh): narrow ERR_OSSL whitelist to passphrase-only codes (ENG-200…
hieuntg81 3b1d28c
refactor(ssh): consolidate SSHSIG_MAGIC + drop stale 7-byte comments …
hieuntg81 7c9ba89
test(ssh): plug tempdir leaks + tighten B7 assertion (ENG-2002 M2+M3+M5)
hieuntg81 be121dd
style(ssh): minor cleanups from code review (ENG-2002 m1+m2+m3+m4+m6)
hieuntg81 7b469a0
Merge remote-tracking branch 'origin/feat/ENG-2002' into feat/ENG-2002
hieuntg81 ab3787d
docs(ssh): correct misleading comment for ERR_OSSL_CRYPTO_INTERRUPTED…
hieuntg81 d02c5f8
refactor(ssh): extract SSHSIG_MAGIC into dedicated sshsig-constants m…
hieuntg81 0bf6b07
test(ssh): replace `as Error`, justify before-pattern, drop B0 refere…
hieuntg81 bb9dd0e
docs(ssh): align troubleshooting symptoms with actual error strings (…
hieuntg81 dd53ab9
test(ssh): proper narrowing instead of dead-guard `as Error` (review-3)
hieuntg81 ba0efb1
refactor(ssh): export SSHSIG preamble as immutable string, not shared…
hieuntg81 e5a8f99
chore(ssh): correct constants comment + consolidate dynamic import (r…
hieuntg81 c13397d
fix(ssh): user-grade errors for wrong format and wrong passphrase (EN…
hieuntg81 010e653
feat(tui): Ink passphrase prompt for encrypted SSH signing keys (ENG-…
hieuntg81 8feaa2c
refactor(vc): drop camel-case `user.signingKey` from exported union (…
hieuntg81 0b4f7a5
feat(signing-key): require --yes to confirm destructive remove (ENG-2…
hieuntg81 ce82e17
refactor(ssh): scope SigningKeyCache entries by (projectPath, keyPath…
hieuntg81 2e4d228
feat(ssh): add scrubPassphrase helper for safe payload logging (ENG-2…
hieuntg81 4adcbdc
refactor(ssh): consolidate SSHSIG_HASH_ALGORITHM constant + document …
hieuntg81 db56ec0
refactor(tui): drop dead passphraseRef in VcCommitFlow (ENG-2002 revi…
hieuntg81 00931f8
docs(oclif): warn about --passphrase visibility in process list (ENG-…
hieuntg81 2c510e4
Merge branch 'main' into feat/ENG-2002
hieuntg81 598837a
fix(ssh): actionable hint for encrypted PEM in extractPublicKey (ENG-…
hieuntg81 03a1f71
chore(ssh): remove unwired scrubPassphrase helper (ENG-2002 review #1)
hieuntg81 5c78743
test(signing-key): drop fake 'returns mapped key list' test (ENG-2002…
hieuntg81 7c04d97
fix(vc): invalidate cache entry for the previous signing-key on confi…
hieuntg81 e836706
fix(vc): reject non-absolute signing-key path in config (ENG-2002 rev…
hieuntg81 c368efd
fix(vc): distinguish passphrase vs other parse errors in config hint …
hieuntg81 29658cc
fix(tui): reset commit mutation to drop passphrase after terminal sta…
hieuntg81 c0b44fe
fix(ssh): guard agent response length and sigLen bounds (ENG-2002 rev…
hieuntg81 637a061
fix(vc): detect signing-key duplicate via VcErrorCode + check respons…
hieuntg81 6dc8df2
test(vc): handleImportGitSigning picks up user.signingKey from global…
hieuntg81 9f3225e
test(ssh): RSA-via-agent sets SSH_AGENT_RSA_SHA2_512 flag (ENG-2002 r…
hieuntg81 4b24422
refactor(ssh): type predicates in place of as/! casts (ENG-2002 revie…
hieuntg81 9c0a68e
refactor: convert data-only DTO interfaces to type (ENG-2002 review #…
hieuntg81 57db09e
refactor(ssh): internal null sentinels → undefined (ENG-2002 review #…
hieuntg81 6605747
refactor(ssh): extract TTL sweep for symmetric eviction (ENG-2002 rev…
hieuntg81 10b903f
Merge branch 'main' into feat/ENG-2002
hieuntg81 4be8e74
style(ssh): fix missing indent on SigningKeyCache.ttlMs field (ENG-20…
hieuntg81 cd420c0
Merge branch 'main' into feat/ENG-2002
hieuntg81 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| # SSH Commit Signing | ||
|
|
||
| ByteRover signs commits with an SSH key. When enabled, every commit carries a | ||
| cryptographic signature and shows as **Verified** in the ByteRover UI and via | ||
| `git verify-commit`. | ||
|
|
||
| ## Quick start | ||
|
|
||
| ```bash | ||
| # 1. Generate (skip if you already have a key) | ||
| ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519_signing | ||
|
|
||
| # 2. Register the public key with ByteRover | ||
| brv signing-key add --key ~/.ssh/id_ed25519_signing.pub --title "My laptop" | ||
|
|
||
| # 3. Tell brv where the private key lives | ||
| brv vc config user.signingkey ~/.ssh/id_ed25519_signing | ||
|
|
||
| # 4. Sign every commit automatically | ||
| brv vc config commit.sign true | ||
| ``` | ||
|
|
||
| From here `brv vc commit -m "..."` produces a signed commit. | ||
|
|
||
| --- | ||
|
|
||
| ## Supported key formats | ||
|
|
||
| | Key format | Direct (file) | Via ssh-agent | | ||
| | --------------------------------------------------- | :-----------: | :-----------: | | ||
| | Ed25519, OpenSSH format, **unencrypted** | ✅ | ✅ | | ||
| | Ed25519, OpenSSH format, passphrase-protected | ❌ | ✅ | | ||
| | RSA / ECDSA, any format | ❌ | ✅ | | ||
|
|
||
| If your key falls into a row that only supports the ssh-agent column, load it | ||
| into the agent before signing: | ||
|
|
||
| ```bash | ||
| ssh-add ~/.ssh/id_rsa # macOS / Linux / WSL | ||
| ``` | ||
|
|
||
| On Windows PowerShell with the OpenSSH agent service running: | ||
|
|
||
| ```powershell | ||
| Get-Service ssh-agent | Set-Service -StartupType Automatic | ||
| Start-Service ssh-agent | ||
| ssh-add $HOME\.ssh\id_rsa | ||
| ``` | ||
|
|
||
| `brv vc commit --sign` automatically prefers ssh-agent when it is available | ||
| (`SSH_AUTH_SOCK` set on Unix, `\\.\pipe\openssh-ssh-agent` on Windows) — you | ||
| do not need to change any brv config. | ||
|
|
||
| --- | ||
|
|
||
| ## Passphrase-protected keys (without ssh-agent) | ||
|
|
||
| For an unencrypted Ed25519 key on disk you do not need a passphrase. For any | ||
| other passphrase-protected key, **use ssh-agent** (above) — that is the | ||
| expected path for the vast majority of users. | ||
|
|
||
| The narrow exception is an Ed25519 key saved in legacy PEM/PKCS8 format | ||
| (rather than the modern OpenSSH format that `ssh-keygen` produces by | ||
| default). Keys in that format support direct passphrase entry: | ||
|
|
||
| ```bash | ||
| # Pass once via flag | ||
| brv vc commit -m "msg" --sign --passphrase "$MY_PASS" | ||
|
|
||
| # Or via env var (preferred for CI / scripts — keeps the secret out of shell history) | ||
| BRV_SSH_PASSPHRASE="$MY_PASS" brv vc commit -m "msg" --sign | ||
| ``` | ||
|
|
||
| `brv` does **not** prompt interactively for the passphrase — `brv vc` is a | ||
| non-interactive oclif command. If a passphrase is required and neither | ||
| `--passphrase` nor `BRV_SSH_PASSPHRASE` is provided, the command exits with a | ||
| clear error pointing to both options. | ||
|
|
||
| --- | ||
|
|
||
| ## Cross-platform notes | ||
|
|
||
| ### macOS / Linux | ||
|
|
||
| Default key location: `~/.ssh/id_ed25519`. ssh-agent is started by your shell | ||
| or DE; check with `ssh-add -l`. | ||
|
|
||
| ### Windows (PowerShell, native OpenSSH) | ||
|
|
||
| - Install OpenSSH from "Optional Features" if not present. | ||
| - Default key location: `$HOME\.ssh\id_ed25519`. | ||
| - The agent runs as a Windows service, not a per-shell process. | ||
|
|
||
| ### WSL | ||
|
|
||
| WSL has its own ssh-agent independent of the Windows agent. Either: | ||
|
|
||
| - Generate keys inside WSL and use `ssh-add` there, or | ||
| - Bridge to the Windows agent via `npiperelay` + `socat` (community guides | ||
| exist) — beyond this doc's scope. | ||
|
|
||
| When pointing brv at a key, use the WSL path (`/mnt/c/...` for Windows-side | ||
| files, plain `~/...` for WSL-side). | ||
|
|
||
| --- | ||
|
|
||
| ## Existing git SSH signing config | ||
|
|
||
| If you already configured `git config gpg.format ssh` and | ||
| `git config user.signingKey ...`, brv can import directly: | ||
|
|
||
| ```bash | ||
| brv vc config --import-git-signing | ||
| ``` | ||
|
|
||
| This reads `user.signingKey` and `commit.gpgSign` from your git config and | ||
| copies them into brv's project config — no manual setup. | ||
|
|
||
| --- | ||
|
|
||
| ## Verification | ||
|
|
||
| Verify any signed commit with the standard `git verify-commit`: | ||
|
|
||
| ```bash | ||
| # Build an allowed_signers file once | ||
| echo "you@example.com $(cat ~/.ssh/id_ed25519_signing.pub)" > ~/.config/brv/allowed_signers | ||
|
|
||
| # Verify | ||
| cd .brv/context-tree | ||
| git -c gpg.ssh.allowedSignersFile=~/.config/brv/allowed_signers verify-commit HEAD | ||
| ``` | ||
|
|
||
| Expected: `Good "git" signature for you@example.com with ED25519 key SHA256:...`. | ||
|
|
||
| --- | ||
|
|
||
| ## Removing a key | ||
|
|
||
| ```bash | ||
| brv signing-key list # list registered keys + IDs | ||
| brv signing-key remove <key-id> # remove from ByteRover | ||
| ``` | ||
|
|
||
| This deletes the public key from ByteRover only. The private key on disk and | ||
| any `brv vc config user.signingkey` setting are untouched. | ||
|
|
||
| --- | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| Symptom column shows the leading substring of each error — the actual | ||
| output continues with a key path or key-type detail. Match by prefix. | ||
|
|
||
| | Symptom (starts with) | Likely cause | Fix | | ||
| | --- | --- | --- | | ||
| | `Error: Encrypted OpenSSH private keys are not supported for direct signing. Load the key into ssh-agent first: ssh-add …` | brv cannot decrypt OpenSSH-format encrypted keys natively. | Run the `ssh-add` command from the error message, then retry. | | ||
| | `Error: Unsupported OpenSSH key type: …` | RSA / ECDSA / non-Ed25519 OpenSSH keys are not parsed natively. | Load the key into ssh-agent (`ssh-add <keypath>`). | | ||
| | `Error: Signing key requires a passphrase. Provide it via the --passphrase flag or the BRV_SSH_PASSPHRASE environment variable, then retry.` | PEM-format key needs a passphrase and none was supplied. | Pass `--passphrase` or set `BRV_SSH_PASSPHRASE`. | | ||
| | `Could not verify signature.` from `git verify-commit` | `allowed_signers` file missing or wrong fingerprint. | Re-create as shown in **Verification**. | |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (block):
I think this file should not exits, or if really needed, please make it 100% english.