Skip to content

Add Decky SSH to Plugin Store - #1104

Open
zachlucas1 wants to merge 1 commit into
SteamDeckHomebrew:mainfrom
zachlucas1:add-decky-ssh
Open

Add Decky SSH to Plugin Store#1104
zachlucas1 wants to merge 1 commit into
SteamDeckHomebrew:mainfrom
zachlucas1:add-decky-ssh

Conversation

@zachlucas1

@zachlucas1 zachlucas1 commented Aug 11, 2026

Copy link
Copy Markdown

Add Decky SSH to Plugin Store

Decky SSH lets you enable/disable the sshd service and manage the SSH login password directly from the Quick Access Menu, without leaving Gaming Mode.

Features:

  • Toggle SSH on/off from the QAM (real root, via the root plugin flag, no polkit rule needed)
  • Set/change the deck account's SSH password from the QAM, masked input
  • Changing an existing password requires re-entering the current one, verified against the real system password via a direct PAM call (libpam through ctypes). the plugin can't be used to silently reset SSH access on an unlocked Deck without already knowing the current password
  • Shows the ready-to-use ssh deck@<ip> command once SSH is active

There's already an open PR for a similar plugin, Easy SSH (#1034), which also toggles SSH from the QAM and shows the local IP. The functionality that doesn't overlap is the in-QAM password management with current-password verification. Easy SSH's description doesn't mention handling the SSH password at all. I'm flagging this myself rather than waiting for a reviewer to point it out, and I'm open to this being redirected toward contributing that feature to Easy SSH instead, if that's preferred over having two similar plugins. It also looks like it was never merged so it is not present on the public store.

Task Checklist

Developer

  • I am the original author or an authorized maintainer of this plugin.
  • I have abided by the licenses of the libraries I am utilizing, including attaching license notices where appropriate.
  • Generative AI was NOT used to write a majority of the code I am submitting.

On the AI point, to be fully transparent since I know this is checked closely: I'm not a programmer. Claude (Anthropic) wrote essentially all of the actual code main.py and the React/TSX frontend. My role was directing what it should do, testing every change on my real Steam Deck over SSH, reading logs and pasting real error output back so bugs could actually get fixed (including two security issues I raised myself: that changing the SSH password should require the current one, and that an early implementation of that check turned out to always report "unable to verify" and needed a different approach entirely), and deciding on the UX. I did not write the code myself and can't honestly check this box. I understand this has been sufficient grounds to close every other new-plugin PR with a similar disclosure, and I'd rather be upfront about it than have it come up after the fact.

Plugin

  • I have verified that my plugin works properly on the Stable and Beta update channels of SteamOS.
  • I have verified my plugin is unique or provides more/alternative functionality to a plugin already on the store.

I attempted to also verify this on the Beta update channel, but after switching, Decky Loader's own QAM entry stopped appearing entirely (the backend service stayed running and healthy, just the UI injection point). This matches an existing, unrelated upstream issue (decky-loader#888) where a recent Steam Client update breaks Decky's QAM injection independent of any specific plugin. I wasn't able to verify decky-ssh specifically on Beta as a result, so I'm only checking the Stable box, which is what I've actually confirmed.

Backend

  • No: I am using a custom backend other than Python.
  • No: I am using a tool or software from a 3rd party FOSS project that does not have its dependencies statically linked.
  • No: I am using a custom binary that has all of its dependencies statically linked.

Community

  • I have tested and left feedback on two other pull requests for new or updating plugins.
  • I have commented links to my testing report in this PR.

Testing

  • Tested by a third party on SteamOS Stable or Beta update channel.

Adds decky-ssh as a submodule: toggle sshd on/off and manage the SSH
password from the Quick Access Menu, with PAM-verified current-password
checking before allowing a password change.
@zachlucas1
zachlucas1 requested a review from a team as a code owner August 11, 2026 23:34
@github-actions github-actions Bot added the plugin-addition Adding a plugin to the Plugin Store label Aug 11, 2026
@github-actions

Copy link
Copy Markdown

Issues Found

No issues with your PR description were found.

Next Steps

  1. If we found any issues above, please edit your pull request description to resolve them and leave a comment saying you've done so.
  2. For the quickest review, please see the Community section of the pull request template for how you can help other developers.
  3. Once your description is correct, a maintainer will review your pull request as soon as possible.

Thank you for your contribution! If you need any help, please reach out on our Discord server. ❤️

@zomars

zomars commented Aug 18, 2026

Copy link
Copy Markdown

Testing report — Decky SSH

Hardware: Steam Machine (not a Deck), SteamOS Stable, Decky Loader 3.2.6.
Build: built from submodule commit 369c89ff with ghcr.io/steamdeckhomebrew/builder:latest, installed through Decky's own install flow.
Caveat up front: this box already had sshd enabled and a password set on the deck account, and I was connected to it over SSH throughout. So I did not test the first-run "no password yet" path, and I did not test an actual password change — only the verification that guards it.

Works

  • Installs and loads cleanly. The root flag takes effect: ps shows the backend running as root.
  • get_status returns correct values in 9 ms. On a box that also runs Tailscale it reports the LAN address rather than the tailnet one, which is the right pick for the ssh deck@… hint.
  • The QAM panel renders correctly and shows the ready-to-use command.
  • The SSH toggle works in both directions, and the UI state is immediate and accurate — unlike a lot of QAM plugins it awaits the backend and re-renders from the returned status, so there's no stale row. The ssh deck@… line correctly disappears while SSH is off. Verified independently with a 2-second poller running outside the SSH session:
19:27:04  active=active    enabled=enabled
19:27:37  active=inactive  enabled=disabled
19:27:41  active=active    enabled=enabled
  • Note that makes the toggle persistent across reboots, since disable --now / enable --now change the boot-time enablement too. Probably the intent, but worth stating in the description — a user may read the toggle as session-only.
  • Turning SSH off does not drop SSH sessions that are already established; mine survived it.
  • A new password shorter than 8 characters is rejected in 1 ms, before any PAM call.
  • The current-password check genuinely works. A wrong current password is rejected via PAM in ~1.7 s — that delay is pam_unix's own failure backoff, not the plugin. This is the security property the PR description leans on and it holds up under test.
  • Repeated wrong attempts don't lock the account on stock SteamOS: /etc/security/faillock.conf ships deny = 0. Worth knowing that this is the only thing preventing a QAM typo storm from locking out deck, and it's a distro default rather than anything the plugin controls.

Issues

1. A newline in new_password injects a second chpasswd entry.

input=f"{decky.DECKY_USER}:{new_password}\n".encode()

chpasswd reads one user:password pair per line, so a new_password containing \n appends a second pair — root: included. The QAM TextField can't produce a newline, but the backend method is reachable by anything running in the Steam UI JS context, not only by this plugin's own frontend (that's how I drove these tests). Severity depends on machine state: where a password already exists an attacker needs the current one first, but on a device with no password set — the SteamOS default — _has_password() is false, the verification branch is skipped entirely, and this becomes an unauthenticated path to setting root's password. I did not exploit this, since doing so would have changed real credentials on a machine I depend on; it's read from the code plus the documented chpasswd input format. Rejecting \n and \r in new_password closes it.

2. auth_ret can be referenced before assignment. In _pam_verify_password the finally: block calls libpam.pam_end(pamh, auth_ret), but auth_ret is only bound inside the try. If pam_authenticate itself raises, the finally raises UnboundLocalError instead, masking the original error and leaking the PAM handle. The caller maps that to None so it does fail closed and the user sees "Unable to verify current password" — but the real cause is lost, which is exactly the failure mode the PR description says was already painful to debug once.

3. plugin.json ships "flags": ["debug", "root"]. The debug flag looks like a development leftover for a store submission.

4. backend/ is the untouched Decky template stub — a Hello World main.c, plus a Dockerfile and Makefile. The official builder rsyncs everything except src/, __pycache__ and node_modules, so this ships inside the store zip. Harmless, but it's dead weight and at a glance it reads as contradicting the "not using a custom backend" checkbox. Deleting it would save a reviewer the double-take.

5. Naming and version drift. plugin.json says Decky SSH, definePlugin says SSH Toggle (that's what shows in the QAM), and package.json is still at 0.0.1.

6. Minor UX. Disabling SSH doesn't warn that it also disables the service at boot. On a setup where SSH is the only remote way in, one tap from the couch means no remote access until someone touches the machine locally. The toggle description is a natural place to say so.

Not tested

  • An actual password change, and therefore the chpasswd path itself.
  • The first-run flow with no password set, including the toggle being gated on has_password.
  • Beta channel.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin-addition Adding a plugin to the Plugin Store

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants