sudoplz 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sudoplz-0.2.0/.gitignore +48 -0
- sudoplz-0.2.0/CLAUDE.md +32 -0
- sudoplz-0.2.0/LICENSE +21 -0
- sudoplz-0.2.0/PKG-INFO +179 -0
- sudoplz-0.2.0/README.md +149 -0
- sudoplz-0.2.0/askpass-config.json +15 -0
- sudoplz-0.2.0/pyproject.toml +66 -0
- sudoplz-0.2.0/src/sudoplz/__init__.py +3 -0
- sudoplz-0.2.0/src/sudoplz/askpass.py +448 -0
- sudoplz-0.2.0/src/sudoplz/core.py +173 -0
- sudoplz-0.2.0/src/sudoplz/manager.py +300 -0
- sudoplz-0.2.0/uv.lock +514 -0
sudoplz-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
venv/
|
|
25
|
+
env/
|
|
26
|
+
ENV/
|
|
27
|
+
.venv/
|
|
28
|
+
|
|
29
|
+
# Tool caches
|
|
30
|
+
.mypy_cache/
|
|
31
|
+
.ruff_cache/
|
|
32
|
+
|
|
33
|
+
# IDE
|
|
34
|
+
.vscode/
|
|
35
|
+
.idea/
|
|
36
|
+
*.swp
|
|
37
|
+
*.swo
|
|
38
|
+
|
|
39
|
+
# Logs
|
|
40
|
+
*.log
|
|
41
|
+
|
|
42
|
+
# Named pipes (created at runtime)
|
|
43
|
+
/tmp/sudo_command
|
|
44
|
+
/tmp/sudo_response
|
|
45
|
+
|
|
46
|
+
# System
|
|
47
|
+
.DS_Store
|
|
48
|
+
Thumbs.db
|
sudoplz-0.2.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# sudoplz Project Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
sudoplz gives AI coding agents (Claude Code, Cursor, etc.) the ability to run `sudo` commands with case-by-case GUI approval. The sudo password is encrypted with the user's SSH key. The project ships two entry points:
|
|
5
|
+
- `askpass` - Invoked by `sudo -A` via `SUDO_ASKPASS`; performs security checks and prints the decrypted password on approval
|
|
6
|
+
- `sudoplz` - Management CLI (set/get/clear/test/audit, plus TOTP for headless sessions)
|
|
7
|
+
|
|
8
|
+
## Development Guidelines
|
|
9
|
+
|
|
10
|
+
1. **Tool Usage**:
|
|
11
|
+
- Use `bash` commands directly for basic operations
|
|
12
|
+
- For complex searches, use the `Task` tool
|
|
13
|
+
- When making multiple bash calls, use `Batch` for parallel execution
|
|
14
|
+
|
|
15
|
+
2. **Code Style**:
|
|
16
|
+
- Follow existing conventions in the askpass scripts
|
|
17
|
+
- Keep scripts minimal and focused on single responsibilities
|
|
18
|
+
- No unnecessary comments unless explicitly requested
|
|
19
|
+
|
|
20
|
+
3. **Testing**:
|
|
21
|
+
- Test both `askpass` and `sudoplz` components
|
|
22
|
+
- Verify SSH key encryption/decryption functionality
|
|
23
|
+
- Ensure secure password handling
|
|
24
|
+
|
|
25
|
+
4. **Security**:
|
|
26
|
+
- Never log or expose passwords in plain text
|
|
27
|
+
- Verify SSH key permissions are properly restricted
|
|
28
|
+
- Follow secure coding practices for password handling
|
|
29
|
+
|
|
30
|
+
5. **Commits**:
|
|
31
|
+
- Only commit when explicitly requested
|
|
32
|
+
- Follow existing commit style from git history
|
sudoplz-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
sudoplz-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sudoplz
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Give AI coding agents case-by-case sudo access via GUI approval, with SSH-key-encrypted password storage
|
|
5
|
+
Project-URL: Homepage, https://github.com/crypdick/sudoplz
|
|
6
|
+
Project-URL: Issues, https://github.com/crypdick/sudoplz/issues
|
|
7
|
+
Project-URL: Source, https://github.com/crypdick/sudoplz
|
|
8
|
+
Author-email: Ricardo Decal <biz@ricardodecal.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-agents,askpass,claude-code,cursor,security,ssh,sudo
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Security
|
|
25
|
+
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
|
|
26
|
+
Classifier: Topic :: Utilities
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: keyring>=24
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# sudoplz
|
|
32
|
+
|
|
33
|
+
Give Claude Code, Cursor, and other AI coding agents the ability to run `sudo` — with case-by-case GUI approval, no passwordless sudo, no `/etc/sudoers` allowlists.
|
|
34
|
+
|
|
35
|
+
Your sudo password is encrypted with your SSH private key and only decrypted after you approve a dialog showing the exact command about to run. Deny the dialog and nothing happens.
|
|
36
|
+
|
|
37
|
+
## Why
|
|
38
|
+
|
|
39
|
+
Coding agents can't handle interactive terminal prompts. Ask Claude Code to run `sudo apt install foo` and you get `sudo: Authentication failed`. The common workarounds all have problems:
|
|
40
|
+
|
|
41
|
+
- **Passwordless sudo** gives the agent — and anything else running as your user — unrestricted root.
|
|
42
|
+
- **`/etc/sudoers` allowlists** require predicting every command the agent will ever need. No case-by-case review.
|
|
43
|
+
- **Manual copy-paste** is tedious and breaks the agent's flow.
|
|
44
|
+
|
|
45
|
+
`sudoplz` plugs into `sudo -A`, so the agent runs `sudo -A <command>`, you see a dialog with the exact command, and you click Allow or Deny. Works for any command without pre-declaring what's permitted.
|
|
46
|
+
|
|
47
|
+
This threat model assumes a personal workstation with an encrypted disk and a passphrase-protected SSH key. Not appropriate for shared or production systems.
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
1. **Use traditional `sudo`, not `sudo-rs`.** `sudo-rs` doesn't support askpass. Check with `sudo --version` — it should say "Sudo version 1.x.x". If you're on `sudo-rs`, switch:
|
|
52
|
+
```bash
|
|
53
|
+
sudo update-alternatives --install /usr/bin/sudo sudo /usr/bin/sudo.ws 100
|
|
54
|
+
sudo update-alternatives --config sudo # pick sudo.ws
|
|
55
|
+
```
|
|
56
|
+
2. Make sure you have an SSH key (ed25519, ecdsa, rsa, or dsa).
|
|
57
|
+
3. Install system dependencies:
|
|
58
|
+
- [`age`](https://github.com/FiloSottile/age) — required if your SSH key is Ed25519 (the most common case today). `sudo pacman -S age` / `sudo apt install age` / `brew install age`.
|
|
59
|
+
- `zenity` on Linux — provides the GUI approval dialog. Pre-installed on most GNOME-based distros; `sudo apt install zenity` if missing. Not needed on macOS (uses AppleScript).
|
|
60
|
+
4. Install the tools with [`uv`](https://docs.astral.sh/uv/):
|
|
61
|
+
```bash
|
|
62
|
+
uv tool install .
|
|
63
|
+
```
|
|
64
|
+
This puts `askpass` and `sudoplz` on your PATH.
|
|
65
|
+
5. Point `SUDO_ASKPASS` at the installed binary (add to `~/.bashrc`, `~/.zshrc`, etc.):
|
|
66
|
+
```bash
|
|
67
|
+
export SUDO_ASKPASS="$(which askpass)"
|
|
68
|
+
```
|
|
69
|
+
6. Store your sudo password:
|
|
70
|
+
```bash
|
|
71
|
+
sudoplz set
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
Your agent (or you) runs `sudo -A <command>`. A dialog pops up showing the command. You approve or deny.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
sudo -A apt install foo
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Gotcha: `sudo -n` explicitly disallows prompting and will never trigger askpass. Always use `-A`.
|
|
83
|
+
|
|
84
|
+
Test the integration with:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
sudoplz test
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Security
|
|
91
|
+
|
|
92
|
+
### Encryption at rest
|
|
93
|
+
|
|
94
|
+
Passwords are encrypted with your SSH key:
|
|
95
|
+
|
|
96
|
+
- **Ed25519**: `age` encryption, stored at `~/.sudo_askpass.age`
|
|
97
|
+
- **RSA/ECDSA/DSA**: OpenSSL asymmetric encryption, stored at `~/.sudo_askpass.ssh`
|
|
98
|
+
|
|
99
|
+
Encrypted files have 600 permissions. Key preference: ed25519 > ecdsa > rsa > dsa. Falls back to the system keyring if available. Refuses plain text storage.
|
|
100
|
+
|
|
101
|
+
### Defense in depth
|
|
102
|
+
|
|
103
|
+
Encryption alone doesn't cover every abuse path — anything running as your user can in principle request decryption. The askpass script runs these checks on every invocation; any failure means no decryption:
|
|
104
|
+
|
|
105
|
+
- **Caller path whitelist.** Only decrypts when the caller's working directory is on an allowlist (home, `/tmp`, etc.). Blocks invocations from unexpected locations like `/var/tmp/malicious`.
|
|
106
|
+
- **Caller process whitelist.** Parent process must be on an allowlist (sudo, your shell, your IDE, your deploy tool). Keeps arbitrary binaries from invoking askpass directly.
|
|
107
|
+
- **User confirmation.** A GUI dialog asks for approval on each decryption, so any sudo elevation you didn't initiate is visible and can be denied.
|
|
108
|
+
- **Rate limiting.** Configurable max-attempts-per-hour and lockout window. Contains runaway scripts and brute-force attempts.
|
|
109
|
+
- **Password expiration.** Stored passwords age out automatically (default: 1 week). A stolen blob becomes useless once it expires, even with your SSH key.
|
|
110
|
+
|
|
111
|
+
Configure these in `~/.config/sudoplz/config.json` — an example is shipped as `askpass-config.json` in the repo; copy it and edit.
|
|
112
|
+
|
|
113
|
+
### Why age for Ed25519?
|
|
114
|
+
|
|
115
|
+
Ed25519 is a signing algorithm (EdDSA), not encryption. OpenSSL handles RSA encryption directly, but Ed25519 keys can't do asymmetric encryption at all. `age` was designed to work with SSH keys including Ed25519.
|
|
116
|
+
|
|
117
|
+
### SSH key unlocking
|
|
118
|
+
|
|
119
|
+
If your SSH key has a passphrase (recommended), the askpass tool will:
|
|
120
|
+
|
|
121
|
+
1. Check whether the key is loaded in ssh-agent
|
|
122
|
+
2. Prompt for the passphrase via GUI if it isn't
|
|
123
|
+
3. Load the key into ssh-agent for the session
|
|
124
|
+
|
|
125
|
+
You enter the passphrase once per session. After that, sudo commands only need the confirmation dialog. You need a running ssh-agent — most desktop environments start one on login; if not, `eval "$(ssh-agent -s)"` in your shell startup.
|
|
126
|
+
|
|
127
|
+
This works under `sudo -A` even though sudo strips `SSH_AUTH_SOCK`: the script reconnects to your running ssh-agent.
|
|
128
|
+
|
|
129
|
+
## Commands
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
sudoplz set # Store password (terminal prompt; expires per config, 1 week default)
|
|
133
|
+
sudoplz set-totp # Store password with TOTP verification (headless)
|
|
134
|
+
sudoplz totp-setup # Set up TOTP for headless sessions
|
|
135
|
+
sudoplz get # Check if password exists
|
|
136
|
+
sudoplz clear # Remove password
|
|
137
|
+
sudoplz test # Test sudo integration
|
|
138
|
+
sudoplz audit # Show recent askpass usage
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Headless/SSH usage with TOTP
|
|
142
|
+
|
|
143
|
+
For servers or SSH sessions without a display, authenticate with TOTP.
|
|
144
|
+
|
|
145
|
+
### Initial setup (run once from a GUI session)
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
sudoplz totp-setup
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Prints a TOTP secret and an `otpauth://` URL to add to your authenticator app.
|
|
152
|
+
|
|
153
|
+
### Setting a password from a headless session
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
sudoplz set-totp
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Enter your 6-digit TOTP code, then your password.
|
|
160
|
+
|
|
161
|
+
### Using sudo with TOTP
|
|
162
|
+
|
|
163
|
+
When `DISPLAY` isn't set, askpass prompts for a TOTP code:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Interactive — prompts for TOTP code
|
|
167
|
+
sudo -A command
|
|
168
|
+
|
|
169
|
+
# Non-interactive — pass TOTP via environment
|
|
170
|
+
TOTP="123456" sudo -A command
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Credits
|
|
174
|
+
|
|
175
|
+
The idea — an SSH-key-encrypted sudo password served via `SUDO_ASKPASS`, gated by a confirmation dialog — is from [GlassOnTin/secure-askpass](https://github.com/GlassOnTin/secure-askpass). That project is dormant; `sudoplz` is a substantially rewritten and cleaned up fork. Thanks to [@GlassOnTin](https://github.com/GlassOnTin) for the original idea.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT — see LICENSE.
|
sudoplz-0.2.0/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# sudoplz
|
|
2
|
+
|
|
3
|
+
Give Claude Code, Cursor, and other AI coding agents the ability to run `sudo` — with case-by-case GUI approval, no passwordless sudo, no `/etc/sudoers` allowlists.
|
|
4
|
+
|
|
5
|
+
Your sudo password is encrypted with your SSH private key and only decrypted after you approve a dialog showing the exact command about to run. Deny the dialog and nothing happens.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Coding agents can't handle interactive terminal prompts. Ask Claude Code to run `sudo apt install foo` and you get `sudo: Authentication failed`. The common workarounds all have problems:
|
|
10
|
+
|
|
11
|
+
- **Passwordless sudo** gives the agent — and anything else running as your user — unrestricted root.
|
|
12
|
+
- **`/etc/sudoers` allowlists** require predicting every command the agent will ever need. No case-by-case review.
|
|
13
|
+
- **Manual copy-paste** is tedious and breaks the agent's flow.
|
|
14
|
+
|
|
15
|
+
`sudoplz` plugs into `sudo -A`, so the agent runs `sudo -A <command>`, you see a dialog with the exact command, and you click Allow or Deny. Works for any command without pre-declaring what's permitted.
|
|
16
|
+
|
|
17
|
+
This threat model assumes a personal workstation with an encrypted disk and a passphrase-protected SSH key. Not appropriate for shared or production systems.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
1. **Use traditional `sudo`, not `sudo-rs`.** `sudo-rs` doesn't support askpass. Check with `sudo --version` — it should say "Sudo version 1.x.x". If you're on `sudo-rs`, switch:
|
|
22
|
+
```bash
|
|
23
|
+
sudo update-alternatives --install /usr/bin/sudo sudo /usr/bin/sudo.ws 100
|
|
24
|
+
sudo update-alternatives --config sudo # pick sudo.ws
|
|
25
|
+
```
|
|
26
|
+
2. Make sure you have an SSH key (ed25519, ecdsa, rsa, or dsa).
|
|
27
|
+
3. Install system dependencies:
|
|
28
|
+
- [`age`](https://github.com/FiloSottile/age) — required if your SSH key is Ed25519 (the most common case today). `sudo pacman -S age` / `sudo apt install age` / `brew install age`.
|
|
29
|
+
- `zenity` on Linux — provides the GUI approval dialog. Pre-installed on most GNOME-based distros; `sudo apt install zenity` if missing. Not needed on macOS (uses AppleScript).
|
|
30
|
+
4. Install the tools with [`uv`](https://docs.astral.sh/uv/):
|
|
31
|
+
```bash
|
|
32
|
+
uv tool install .
|
|
33
|
+
```
|
|
34
|
+
This puts `askpass` and `sudoplz` on your PATH.
|
|
35
|
+
5. Point `SUDO_ASKPASS` at the installed binary (add to `~/.bashrc`, `~/.zshrc`, etc.):
|
|
36
|
+
```bash
|
|
37
|
+
export SUDO_ASKPASS="$(which askpass)"
|
|
38
|
+
```
|
|
39
|
+
6. Store your sudo password:
|
|
40
|
+
```bash
|
|
41
|
+
sudoplz set
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
Your agent (or you) runs `sudo -A <command>`. A dialog pops up showing the command. You approve or deny.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
sudo -A apt install foo
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Gotcha: `sudo -n` explicitly disallows prompting and will never trigger askpass. Always use `-A`.
|
|
53
|
+
|
|
54
|
+
Test the integration with:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
sudoplz test
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Security
|
|
61
|
+
|
|
62
|
+
### Encryption at rest
|
|
63
|
+
|
|
64
|
+
Passwords are encrypted with your SSH key:
|
|
65
|
+
|
|
66
|
+
- **Ed25519**: `age` encryption, stored at `~/.sudo_askpass.age`
|
|
67
|
+
- **RSA/ECDSA/DSA**: OpenSSL asymmetric encryption, stored at `~/.sudo_askpass.ssh`
|
|
68
|
+
|
|
69
|
+
Encrypted files have 600 permissions. Key preference: ed25519 > ecdsa > rsa > dsa. Falls back to the system keyring if available. Refuses plain text storage.
|
|
70
|
+
|
|
71
|
+
### Defense in depth
|
|
72
|
+
|
|
73
|
+
Encryption alone doesn't cover every abuse path — anything running as your user can in principle request decryption. The askpass script runs these checks on every invocation; any failure means no decryption:
|
|
74
|
+
|
|
75
|
+
- **Caller path whitelist.** Only decrypts when the caller's working directory is on an allowlist (home, `/tmp`, etc.). Blocks invocations from unexpected locations like `/var/tmp/malicious`.
|
|
76
|
+
- **Caller process whitelist.** Parent process must be on an allowlist (sudo, your shell, your IDE, your deploy tool). Keeps arbitrary binaries from invoking askpass directly.
|
|
77
|
+
- **User confirmation.** A GUI dialog asks for approval on each decryption, so any sudo elevation you didn't initiate is visible and can be denied.
|
|
78
|
+
- **Rate limiting.** Configurable max-attempts-per-hour and lockout window. Contains runaway scripts and brute-force attempts.
|
|
79
|
+
- **Password expiration.** Stored passwords age out automatically (default: 1 week). A stolen blob becomes useless once it expires, even with your SSH key.
|
|
80
|
+
|
|
81
|
+
Configure these in `~/.config/sudoplz/config.json` — an example is shipped as `askpass-config.json` in the repo; copy it and edit.
|
|
82
|
+
|
|
83
|
+
### Why age for Ed25519?
|
|
84
|
+
|
|
85
|
+
Ed25519 is a signing algorithm (EdDSA), not encryption. OpenSSL handles RSA encryption directly, but Ed25519 keys can't do asymmetric encryption at all. `age` was designed to work with SSH keys including Ed25519.
|
|
86
|
+
|
|
87
|
+
### SSH key unlocking
|
|
88
|
+
|
|
89
|
+
If your SSH key has a passphrase (recommended), the askpass tool will:
|
|
90
|
+
|
|
91
|
+
1. Check whether the key is loaded in ssh-agent
|
|
92
|
+
2. Prompt for the passphrase via GUI if it isn't
|
|
93
|
+
3. Load the key into ssh-agent for the session
|
|
94
|
+
|
|
95
|
+
You enter the passphrase once per session. After that, sudo commands only need the confirmation dialog. You need a running ssh-agent — most desktop environments start one on login; if not, `eval "$(ssh-agent -s)"` in your shell startup.
|
|
96
|
+
|
|
97
|
+
This works under `sudo -A` even though sudo strips `SSH_AUTH_SOCK`: the script reconnects to your running ssh-agent.
|
|
98
|
+
|
|
99
|
+
## Commands
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
sudoplz set # Store password (terminal prompt; expires per config, 1 week default)
|
|
103
|
+
sudoplz set-totp # Store password with TOTP verification (headless)
|
|
104
|
+
sudoplz totp-setup # Set up TOTP for headless sessions
|
|
105
|
+
sudoplz get # Check if password exists
|
|
106
|
+
sudoplz clear # Remove password
|
|
107
|
+
sudoplz test # Test sudo integration
|
|
108
|
+
sudoplz audit # Show recent askpass usage
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Headless/SSH usage with TOTP
|
|
112
|
+
|
|
113
|
+
For servers or SSH sessions without a display, authenticate with TOTP.
|
|
114
|
+
|
|
115
|
+
### Initial setup (run once from a GUI session)
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
sudoplz totp-setup
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Prints a TOTP secret and an `otpauth://` URL to add to your authenticator app.
|
|
122
|
+
|
|
123
|
+
### Setting a password from a headless session
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
sudoplz set-totp
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Enter your 6-digit TOTP code, then your password.
|
|
130
|
+
|
|
131
|
+
### Using sudo with TOTP
|
|
132
|
+
|
|
133
|
+
When `DISPLAY` isn't set, askpass prompts for a TOTP code:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Interactive — prompts for TOTP code
|
|
137
|
+
sudo -A command
|
|
138
|
+
|
|
139
|
+
# Non-interactive — pass TOTP via environment
|
|
140
|
+
TOTP="123456" sudo -A command
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Credits
|
|
144
|
+
|
|
145
|
+
The idea — an SSH-key-encrypted sudo password served via `SUDO_ASKPASS`, gated by a confirmation dialog — is from [GlassOnTin/secure-askpass](https://github.com/GlassOnTin/secure-askpass). That project is dormant; `sudoplz` is a substantially rewritten and cleaned up fork. Thanks to [@GlassOnTin](https://github.com/GlassOnTin) for the original idea.
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT — see LICENSE.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sudoplz"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Give AI coding agents case-by-case sudo access via GUI approval, with SSH-key-encrypted password storage"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "MIT" }
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Ricardo Decal", email = "biz@ricardodecal.com" },
|
|
10
|
+
]
|
|
11
|
+
keywords = ["sudo", "askpass", "ssh", "ai-agents", "claude-code", "cursor", "security"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Intended Audience :: System Administrators",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: MacOS",
|
|
19
|
+
"Operating System :: POSIX :: Linux",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Security",
|
|
26
|
+
"Topic :: System :: Systems Administration :: Authentication/Directory",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"keyring>=24",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/crypdick/sudoplz"
|
|
35
|
+
Issues = "https://github.com/crypdick/sudoplz/issues"
|
|
36
|
+
Source = "https://github.com/crypdick/sudoplz"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
askpass = "sudoplz.askpass:main"
|
|
40
|
+
sudoplz = "sudoplz.manager:main"
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["hatchling"]
|
|
44
|
+
build-backend = "hatchling.build"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.wheel]
|
|
47
|
+
packages = ["src/sudoplz"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 100
|
|
51
|
+
target-version = "py310"
|
|
52
|
+
src = ["src"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint]
|
|
55
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
56
|
+
|
|
57
|
+
[tool.mypy]
|
|
58
|
+
python_version = "3.10"
|
|
59
|
+
strict = true
|
|
60
|
+
files = ["src"]
|
|
61
|
+
|
|
62
|
+
[dependency-groups]
|
|
63
|
+
dev = [
|
|
64
|
+
"ruff>=0.6",
|
|
65
|
+
"mypy>=1.10",
|
|
66
|
+
]
|