Skip to content

Add option to hijack SSH Askpass to prompt for passphrase/pin#423

Merged
idursun merged 8 commits intoidursun:mainfrom
oliverpool:askpass
Dec 16, 2025
Merged

Add option to hijack SSH Askpass to prompt for passphrase/pin#423
idursun merged 8 commits intoidursun:mainfrom
oliverpool:askpass

Conversation

@oliverpool
Copy link
Copy Markdown
Contributor

@oliverpool oliverpool commented Dec 13, 2025

Fixes #100

As proposed in #100 (comment), this adds a [ssh] hijack_askpass setting to prompt the user for the ssh passphrase/pin when needed.

The backend should works on linux and macos (platform support is limited by what https://github.com/tailscale/peercred supports) and is properly tested.

image

UI tasks:

  • display a the passphrase prompt and capture the user input
  • on enter return the captured password to the handler
  • hide the passphrase prompt when cancelled or done
  • override passphrase prompt on new request

@oliverpool oliverpool force-pushed the askpass branch 3 times, most recently from fef551a to 2062342 Compare December 13, 2025 11:14
done <-chan struct{}
}

func ensureConnFromDescendant(conn net.Conn, parentPID int) error {
Copy link
Copy Markdown
Contributor Author

@oliverpool oliverpool Dec 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a security measure, only descendants from already known PIDs can prompt the user for a password (since any other process could connect to the socket and send a password-retrieving message).

Copy link
Copy Markdown
Owner

@idursun idursun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey thanks for this.

I am definitely not an expert in this field, so might have missed actual workflow.

I left a couple of comments just after skimming the code briefly.

Additionally, I see that the askpass hook is only implemented for RunCommand which is basically runs the command in a separate go routine, so that's why it didn't deadlock.

However, we have a lot of usages of RunImmediate which runs in the UI thread in places. (e.g. loading the description of the selected revision), and I am sure this would deadlock in those places as the UI thread is waiting for a password to be entered and won't respond to any other messages until it gets past it.

I also don't know what jj commands would trigger entering a passphrase. I am guessing only the ones that creates or modifies commits. However, would it ask for passphrase if the user configured the jj log to show signed commits. I am guessing it would so we cannot safely assume this applies only to the write operations.

Is there a path where we can check if a passphrase is needed at the start and don't care about it for the rest of the app? Even if we can do it, is there a scenario where the passphrase is needed again due to some expiry, etc?

Comment on lines +110 to +116
cmd := exec.Command("ssh", "git@"+host, "-p", port,
"-o", "StrictHostKeyChecking=yes",
"-o", "UserKnownHostsFile="+knownHost,
"-o", "PubkeyAuthentication=no",
"-o", "PreferredAuthentications=password",
"-o", "PKCS11Provider=none",
)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried running the tests through nix and this failed. This will fail on every system that doesn't have ssh available, including package builds, possible windows builds as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I added an error check: if ssh client is not available, this test is skipped.

}

func readPPid(pid int) (int, error) {
f, err := os.Open(fmt.Sprintf("/proc/%d/status", pid))
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is unix specific, will probably fail on other systems.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why askpass performs a smoke test in StartListening: smokeTestPeerCred. It checks that peercred and readPPid work for a simple case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

partially addressed in 96948b0

@oliverpool oliverpool changed the title WIP: Add option to hijack SSH Askpass to promp for passphrase/pin Add option to hijack SSH Askpass to promp for passphrase/pin Dec 13, 2025
@oliverpool
Copy link
Copy Markdown
Contributor Author

oliverpool commented Dec 13, 2025

Thanks for the quick review!

I managed to hack a password prompt UI within the codebase. This can be tested by uncommenting a line in main.go (the result is discarded: it only allows to test the UI).


In my case I only need the password prompt when performing remote operations.

I think that for commit signing, you only need the private key (i.e. the passphrase), when writing (checking the signature only needs access to the public key, which should not require the passphrase).

my last push (with ssh pin prompt) happened using jjui 🎉

@vic
Copy link
Copy Markdown
Collaborator

vic commented Dec 13, 2025

my last pushed happened using jjui 🎉

we should all sign our commits with -- pushed from my jjui haha, like the old marketing strategy "sent from my iphone" to make more people look for jjui :D

@oliverpool
Copy link
Copy Markdown
Contributor Author

Is there a path where we can check if a passphrase is needed at the start and don't care about it for the rest of the app?

This wouldn't address my usecase of a tpm-based ssh key. Each usage prompts for the PIN.

@idursun
Copy link
Copy Markdown
Owner

idursun commented Dec 13, 2025

Hey, thanks! I am glad you could implement the UI part as well. Very much how I would implement it myself.

Please go ahead with this approach, as long as everything is behind a feature flag and no-op when the flag is off then I am happy to review it one more time once it is ready for review and merge.

Tests will fail because nix vendor hash won't match, I can fix it for you.

@oliverpool oliverpool force-pushed the askpass branch 3 times, most recently from 7929915 to 5d5ebb3 Compare December 14, 2025 21:50
@baggiiiie
Copy link
Copy Markdown
Collaborator

we should all sign our commits with -- pushed from my jjui haha, like the old marketing strategy "sent from my iphone" to make more people look for jjui :D

i second this 😆 🫡

@oliverpool oliverpool marked this pull request as ready for review December 15, 2025 08:10
@oliverpool oliverpool changed the title Add option to hijack SSH Askpass to promp for passphrase/pin Add option to hijack SSH Askpass to prompt for passphrase/pin Dec 15, 2025
err = errors.New(output.String())
msg := output.String()
if len(env) == 0 && slices.Contains([]string{"linux", "darwin"}, runtime.GOOS) {
msg += "\nHint: enable ssh.hijack_askpass if you expected a password prompt (e.g. ssh passphrase)"
Copy link
Copy Markdown
Contributor Author

@oliverpool oliverpool Dec 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a "just-in-time" hint: if a command fails and ssh.hijack_askpass is not enabled, inform the user about the existence of this setting.

The goal is to not clutter the interface for normal use (having an error here is unusual), while giving the user a chance to fix its setting quickly (without having to dig too deep in the docs).

@idursun
Copy link
Copy Markdown
Owner

idursun commented Dec 15, 2025

Hey thanks for the updates.

Test suite is failing on my mac. I don't know why though. I have ssh but that's about it.

In any case, can we scope these tests to linux only?

?       github.com/idursun/jjui/cmd/jjui        [no test files]
--- FAIL: TestServerListening (0.00s)
    server_test.go:56: unix.GetsockoptInt: socket is not connected
--- FAIL: TestServerAskpass (0.02s)
    server_test.go:103: unix.GetsockoptInt: socket is not connected
FAIL
FAIL    github.com/idursun/jjui/internal/askpass        1.320s

Copy link
Copy Markdown
Owner

@idursun idursun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey thanks for working on this. I am glad it is working for you.

As I mentioned in the comment, the tests are failing on my machine (Mac). It would be nice if we can scope those tests only to linux as they are passing in the CI.

I also added a comment about a possible rename.

I am happy to merge this if you can address those. Thanks!

@oliverpool
Copy link
Copy Markdown
Contributor Author

the tests are failing on my machine (Mac)

Indeed, macos needed some minor adjustments. Since I don't have a mac, I enabled the CI tests for macos and iterated until they passed: oliverpool#1

They should now succeed on your computer.

Copy link
Copy Markdown
Owner

@idursun idursun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this!

Obviously, I was not able to test this but since this is working for you and it is behind a configuration option, I am happy to merge it.

@idursun idursun merged commit 2df06f3 into idursun:main Dec 16, 2025
4 checks passed
@oliverpool oliverpool deleted the askpass branch December 16, 2025 20:47
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Jan 9, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [idursun/jjui](https://github.com/idursun/jjui) | patch | `v0.9.8` → `v0.9.9` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>idursun/jjui (idursun/jjui)</summary>

### [`v0.9.9`](https://github.com/idursun/jjui/releases/tag/v0.9.9)

[Compare Source](idursun/jjui@v0.9.8...v0.9.9)

### Release Notes

Another release with small improvements and bug fixes. Thanks to all contributors!

#### 🎉 New Features

##### Custom Commands & Lua API Enhancements

- **Custom Commands with Sequence Keys** ([#&#8203;420](idursun/jjui#420))
  - Added `key_sequence` property allowing custom commands to be invoked with multiple key presses in sequence
  - Added `desc` property for command descriptions
  - Introduced sequence overlay UI showing available key sequences when first key is pressed
  - Example: `key_sequence = ["w", "b", "l"]`

- **Lua API: Choose Method and UI** ([#&#8203;427](idursun/jjui#427)) ([#&#8203;442](idursun/jjui#442))
  - New `choose()` function for interactive selection prompts in Lua scripts
  - New `input()` function to prompt users for text input with customizable title and prompt
  - New `split_lines()` function for text processing

- **Lua API: Await on Operation Results** ([#&#8203;422](idursun/jjui#422))
  - `start_inline_describe()` now returns boolean indicating if operation was applied or cancelled
  - Enables conditional command execution based on user actions
  - Fixes [#&#8203;310](idursun/jjui#310)

- **Lua API: Interactive Commands** ([commit 8b257263](idursun/jjui@8b257263))
  - Added `jj_interactive` Lua function for interactive jj command execution

##### Navigation & UI Improvements

- **Ace Jump for Operations** ([#&#8203;445](idursun/jjui#445))
  - Pressing 'f' in set\_parents/duplicate/rebase/squash modes now triggers ace jump
  - After jump completes, returns to the original operation mode instead of normal mode
  - Closes [#&#8203;394](idursun/jjui#394)

- **Preview Width Variable** ([#&#8203;452](idursun/jjui#452))
  - Added `$preview_width` placeholder variable for preview commands
  - Exposes actual view width (in columns) to enable tools like delta to use `--side-by-side` correctly
  - Width updates dynamically when preview pane is resized
  - Similar to fzf's `$FZF_PREVIEW_COLUMNS`

- **Configurable Flash Message Display Time** ([#&#8203;456](idursun/jjui#456))
  - New config key: `ui.flash_message_display_seconds` (default: 4)
  - Special value `0` means messages display until manually dismissed
  - Fixes [#&#8203;455](idursun/jjui#455)

- **Page Up/Down Key Configuration** ([#&#8203;437](idursun/jjui#437))
  - ScrollUp/Down keys now registered in config instead of hardcoded
  - Keys exposed to configuration for customization
  - Fixes [#&#8203;360](idursun/jjui#360)

##### SSH & Authentication

- **SSH Askpass Support** ([#&#8203;423](idursun/jjui#423))
  - New `[ssh] hijack_askpass` setting to prompt for SSH passphrases/PINs within jjui
  - Works on Linux and macOS
  - Properly handles prompt overriding and cancellation
  - Fixes [#&#8203;100](idursun/jjui#100)

#### 🐛 Bug Fixes

- **Exec Command History** ([#&#8203;458](idursun/jjui#458))
  - Fixed issue where selected command history wasn't applied in exec mode
  - Input value now properly updated when selecting from fuzzy/regex suggestions
  - Selected commands correctly saved to history

- **Menu Pagination Display** ([#&#8203;446](idursun/jjui#446))
  - Fixed incorrect `%d/%d` pagination display
  - Height now calculated before pagination render
  - Added tab/shift+tab to short help menu
  - Fixes [#&#8203;444](idursun/jjui#444)

- **Flash Message Width** ([#&#8203;432](idursun/jjui#432))
  - Added maxWidth (50% of screen) to flash message rendering
  - Messages now properly line-wrap instead of extending beyond window width

- **Operation Log Refresh** ([#&#8203;431](idursun/jjui#431))
  - Operation log now returns Refresh and SelectionChanged messages upon closing
  - Fixes [#&#8203;430](idursun/jjui#430)

- **Custom Commands List Sorting** ([commit 3fa9783a](idursun/jjui@3fa9783a))
  - Fixed custom commands list to use stable sort
  - Fixes [#&#8203;424](idursun/jjui#424)

- **JJ Error Pass-through** ([#&#8203;421](idursun/jjui#421))
  - jjui now properly passes through stderr from jj commands
  - Error messages are more informative and show actual jj errors

- **Navigation Message Display** ([commit 94a4a874](idursun/jjui@94a4a874))
  - Navigation messages now only shown for paged scrolls

#### What's Changed

- main: pass through jj error by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;421](idursun/jjui#421)
- feat(lua): add ability to await on operation results (cancelled/applied) by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;422](idursun/jjui#422)
- oplog: return Refresh and SelectionChanged upon oplog closing by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;431](idursun/jjui#431)
- feat(nix): add comprehensive nix flake by [@&#8203;doprz](https://github.com/doprz) in [#&#8203;426](idursun/jjui#426)
- Add option to hijack SSH Askpass to prompt for passphrase/pin by [@&#8203;oliverpool](https://github.com/oliverpool) in [#&#8203;423](idursun/jjui#423)
- feat(lua): add choose method and ui by [@&#8203;idursun](https://github.com/idursun) in [#&#8203;427](idursun/jjui#427)
- flash: add maxWidth to flash msg rendering by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;432](idursun/jjui#432)
- keys: add pageup/down to keys config by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;437](idursun/jjui#437)
- chore(github): Add Adda0 as an automatic reviewer for Nix-related changes by [@&#8203;Adda0](https://github.com/Adda0) in [#&#8203;440](idursun/jjui#440)
- feat: add .editorconfig by [@&#8203;doprz](https://github.com/doprz) in [#&#8203;434](idursun/jjui#434)
- Add input and log to custom\_commands API by [@&#8203;ArnaudBger](https://github.com/ArnaudBger) in [#&#8203;442](idursun/jjui#442)
- oplog,list: refactor scrolling with Scrollable/StreamableList interface by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;429](idursun/jjui#429)
- menu: calculate height before pagination render by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;446](idursun/jjui#446)
- operations: enable ace jump for set\_parents/duplicate/rebase/squash by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;445](idursun/jjui#445)
- feat: make flash message display time configurable by [@&#8203;living180](https://github.com/living180) in [#&#8203;456](idursun/jjui#456)
- feat: add $width variable for preview commands by [@&#8203;pablospe](https://github.com/pablospe) in [#&#8203;452](idursun/jjui#452)
- status: fix exec command history not applied by [@&#8203;baggiiiie](https://github.com/baggiiiie) in [#&#8203;458](idursun/jjui#458)

#### New Contributors

- [@&#8203;doprz](https://github.com/doprz) made their first contribution in [#&#8203;426](idursun/jjui#426)
- [@&#8203;oliverpool](https://github.com/oliverpool) made their first contribution in [#&#8203;423](idursun/jjui#423)
- [@&#8203;living180](https://github.com/living180) made their first contribution in [#&#8203;456](idursun/jjui#456)
- [@&#8203;pablospe](https://github.com/pablospe) made their first contribution in [#&#8203;452](idursun/jjui#452)

**Full Changelog**: <idursun/jjui@v0.9.8...v0.9.9>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi43NS4xIiwidXBkYXRlZEluVmVyIjoiNDIuNzUuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6OnBhdGNoIl19-->
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.

Passphrase input for SSH remotes doesn't work?

4 participants