Initialize bug- and wishlist
Marco Ricci

Marco Ricci commited on 2025-03-12 22:52:13
Zeige 23 geänderte Dateien mit 549 Einfügungen und 0 Löschungen.


Based (in spirit) on PuTTY's wishlist system.  Intended to be used as
a submodule within the documentation tree, separate from the code,
because the bug and wish data presumably changes at a different
timescale than the code does.
... ...
@@ -0,0 +1,24 @@
1
+# `derivepassphrase` bug allow-all-unicode-passphrases
2
+
3
+???+ success "Bug details: Allow all Unicode text strings as master passphrases"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 <b>0.1.3</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/aacd09bdcbdb01df7cb819396727d2427636b144">aacd09bdcbdb01df7cb819396727d2427636b144</a> (0.2.0)
8
+    </table>
9
+
10
+In v0.1.x, `derivepassphrase` will accept a textual master passphrase if and only if it has a unique Unicode normalization form, i.e. if the NFC- and NFD-normalized forms of the master passphrase agree. This check was intended to safeguard against a passphrase from the configuration file being interpreted incorrectly by the `Vault` constructor (and subsequently generating the wrong passphrase) because it derived the wrong normalized form as the binary input string to the `vault` algorithm.
11
+
12
+This understanding of the "derived the wrong normalized form" part turns out to be wrong: the encoding of textual string to binary string is unique, and the ambiguity in the textual master passphrase arises only when reading the master passphrase *as text*. No matter what text is stored as the master passphrase, its binary encoding is unique, and is valid input to the `vault` algorithm. However, there is value in warning the user that the stored textual passphrase may not be what they think it is, because they are being misled by their editor, or copy-pasting the configuration from somewhere else.
13
+
14
+<b>Next steps:</b>
15
+
16
+1. Remove the machinery that asserts a unique normalization form. In particular, remove the `Vault.AmbiguousByteRepresentationError` exception type.
17
+2. Remove the check for unique normalization form from the `Vault` constructor. Instead, add a warning during the `--import` and `--config` modes of operation when the config file has unnormalized or incorrectly normalized stored master passphrases.
18
+3. Add a new configuration item, presumably `.global.unicode_normalization_form` and defaulting to `NFC`, from which to obtain the correct normalization form.
19
+
20
+--------
21
+
22
+> 2. Remove the check for unique normalization form from the `Vault` constructor. Instead, add a warning during the `--import` and `--config` modes of operation when the config file has unnormalized or incorrectly normalized stored master passphrases.
23
+
24
+Actually, the warning is equally sensible when interactively entering a master passphrase, not just when updating the configuration.
... ...
@@ -0,0 +1,14 @@
1
+# `derivepassphrase` bug amend-vault-config
2
+
3
+???+ success "Bug details: `derivepassphrase vault --import` overwrites config instead of amending it"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 <b>0.2.0</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/7d6ac080e84b06a116063b3cfec9c40620242b94">7d6ac080e84b06a116063b3cfec9c40620242b94</a> (0.3.0)
8
+    </table>
9
+
10
+When importing a vault(1) configuration, `derivepassphrase` unconditionally overwrites the existing configuration with the imported one.
11
+
12
+vault(1) however overwrites the existing configuration section-wise: each named service, and the global configuration if mentioned, is overwritten in whole by the respective imported settings.  This means that unmentioned named services (and perhaps the global section) are *inherited* from before the import.  (This should probably be called “merging” instead of “importing”.)
13
+
14
+While I find `derivepassphrase`'s current import-without-merge behavior more intuitive than the import-with-merge behavior, vault(1) uses the latter.  <b>Therefore</b>, for compatibility with vault(1), implement the latter by default.
... ...
@@ -0,0 +1,68 @@
1
+# `derivepassphrase` bug better-error-messages
2
+
3
+???+ success "Bug details: Improve common error messages in the command-line interface"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 <b>0.1.2</b> 0.1.3
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/e662c2e71c50e57f465fdeb8efb403ed77147e8c">e662c2e71c50e57f465fdeb8efb403ed77147e8c</a> (0.2.0)
8
+    </table>
9
+
10
+Except for those related to command-line parsing, all error messages in `derivepassphrase` are written to be useful to API users, not command-line end users. Currently, the command-line interface passes those errors through without modification, either via [default behavior of `click`](https://click.palletsprojects.com/en/8.1.x/api/#click.BaseCommand.main "see exception handling, in parameter 'standalone_mode'"), or by explicitly via [`ctx.fail`](https://click.palletsprojects.com/en/8.1.x/api.html#click.Context.fail). At the same time, the API uses standard error types directly if possible, and omits context that is already encoded in the error type. But this context is no longer available on the command-line because only the error message (and not the type) is relayed.
11
+
12
+<details class="admonition example">
13
+<summary>Example (trying to set up SSH key use with derivepassphrase)</summary>
14
+
15
+````shell-session
16
+$ env -u SSH_AUTH_SOCK derivepassphrase -k --config
17
+Usage: derivepassphrase [OPTIONS] [SERVICE]
18
+Try 'derivepassphrase -h' for help.
19
+
20
+Error: 'SSH_AUTH_SOCK environment variable'
21
+````
22
+
23
+What's actually happening:
24
+
25
+````python
26
+>>> import os
27
+>>> del os.environ['SSH_AUTH_SOCK']
28
+>>> from derivepassphrase.cli import derivepassphrase as main
29
+>>> main.main(args=['-k', '--config'], standalone_mode=False)
30
+Traceback (most recent call last):
31
+  File ".../derivepassphrase/cli.py", line 996, in derivepassphrase
32
+    key = base64.standard_b64encode(_select_ssh_key()).decode(
33
+                                    ^^^^^^^^^^^^^^^^^
34
+  File ".../derivepassphrase/cli.py", line 297, in _select_ssh_key
35
+    suitable_keys = list(_get_suitable_ssh_keys(conn))
36
+                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
37
+  File ".../derivepassphrase/cli.py", line 169, in _get_suitable_ssh_keys
38
+    client = ssh_agent_client.SSHAgentClient(socket=conn)
39
+             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40
+  File ".../ssh_agent_client/__init__.py", line 95, in __init__
41
+    raise KeyError(msg) from None
42
+KeyError: 'SSH_AUTH_SOCK environment variable'
43
+
44
+During handling of the above exception, another exception occurred:
45
+
46
+Traceback (most recent call last):
47
+  File "<stdin>", line 1, in <module>
48
+  File ".../click/core.py", line 1078, in main
49
+    rv = self.invoke(ctx)
50
+         ^^^^^^^^^^^^^^^^
51
+  File ".../click/core.py", line 1434, in invoke
52
+    return ctx.invoke(self.callback, **ctx.params)
53
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
54
+  File ".../click/core.py", line 783, in invoke
55
+    return __callback(*args, **kwargs)
56
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
57
+  File ".../click/decorators.py", line 33, in new_func
58
+    return f(get_current_context(), *args, **kwargs)
59
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
60
+  File ".../derivepassphrase/cli.py", line 1002, in derivepassphrase
61
+    ctx.fail(str(e))
62
+  File ".../click/core.py", line 684, in fail
63
+    raise UsageError(message, self)
64
+click.exceptions.UsageError: 'SSH_AUTH_SOCK environment variable'
65
+````
66
+</details>
67
+
68
+I believe the solution is two-fold. First, the API should use proper error subtypes, even if they partially duplicate the error message. Second, the command-line interface should guard all calls into the `derivepassphrase` machinery with proper error checking, and emit more suitable error messages if necessary.
... ...
@@ -0,0 +1,22 @@
1
+# `derivepassphrase` bug concurrency-audit
2
+
3
+???+ bug "Bug details: Audit `derivepassphrase` for concurrency/thread-safety issues"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Priority<td><i>high</i><td>This should be fixed in the next release.
7
+        <tr><th scope=col>Difficulty<td><i>tricky</i><td>Needs many tuits.
8
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 0.2.0 0.3.0 0.3.1 0.3.2 0.3.3 <b>0.4.0</b>
9
+        <tr><th scope=col>Blocks<td colspan=2>[concurrency-testing-in-test-suite](concurrency-testing-in-test-suite.md)
10
+    </table>
11
+
12
+`derivepassphrase` is not explicitly written with concurrency in mind. This may come around to bite us when the “free-threaded“ build of Python no becomes a main feature.
13
+
14
+Irrespective of that, it is a good idea anyway to have a clear picture on which parts of `derivepassphrase` are not threadsafe, and to which degree.
15
+
16
+**Therefore**, audit `derivepassphrase` for concurrency/thread-safety issues.
17
+
18
+--------
19
+
20
+Off the top of my head, the main parts of `derivepassphrase` in the `export` and `vault` subcommands each construct their own data and only compute a result or read from the data, so there are no read-write or write-write dependencies.
21
+
22
+The exceptions to this are the global logging and warnings handlers, which modify global state, and the `--config` option to `vault`, which writes back file contents.
... ...
@@ -0,0 +1,14 @@
1
+# `derivepassphrase` bug concurrency-testing-in-test-suite
2
+
3
+???+ bug "Bug details: Test for concurrency and assert thread-safety in `derivepassphrase`&apos;s test suite"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Priority<td><i>high</i><td>This should be fixed in the next release.
7
+        <tr><th scope=col>Difficulty<td><i>tricky</i><td>Needs many tuits.
8
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 0.2.0 0.3.0 0.3.1 0.3.2 0.3.3 <b>0.4.0</b>
9
+        <tr><th scope=col>Depends<td colspan=2>[concurrency-audit](concurrency-audit.md)
10
+    </table>
11
+
12
+Once [concurrency-audit](concurrency-audit.md) is resolved, the thread-safety of `derivepassphrase` should be explicitly asserted in the test suite.
13
+
14
+**Therefore**, assert the thread-safety of `derivepassphrase` in the test suite.
... ...
@@ -0,0 +1,45 @@
1
+# `derivepassphrase` bug configuration-directory-must-exist
2
+
3
+???+ success "Bug details: `derivepassphrase --config` requires configuration directory to exist"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 <b>0.1.2</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/a980a643275de28f7715241790f199f947f637f4">a980a643275de28f7715241790f199f947f637f4</a> (0.1.3)
8
+    </table>
9
+
10
+````console-session
11
+$ derivepassphrase -k --config
12
+Suitable SSH keys:
13
+[1] ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBjuC9...
14
+[2] ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG/RzZ...
15
+Your selection? (1-2, leave empty to abort): 1
16
+Traceback (most recent call last):
17
+  File ".../derivepassphrase", line 8, in <module>
18
+    sys.exit(derivepassphrase())
19
+             ^^^^^^^^^^^^^^^^^^
20
+  File ".../click/core.py", line 1157, in __call__
21
+    return self.main(*args, **kwargs)
22
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^
23
+  File ".../click/core.py", line 1078, in main
24
+    rv = self.invoke(ctx)
25
+         ^^^^^^^^^^^^^^^^
26
+  File ".../click/core.py", line 1434, in invoke
27
+    return ctx.invoke(self.callback, **ctx.params)
28
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
29
+  File ".../click/core.py", line 783, in invoke
30
+    return __callback(*args, **kwargs)
31
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
32
+  File ".../click/decorators.py", line 33, in new_func
33
+    return f(get_current_context(), *args, **kwargs)
34
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35
+  File ".../derivepassphrase/cli.py", line 1030, in derivepassphrase
36
+    _save_config(configuration)
37
+  File ".../derivepassphrase/cli.py", line 119, in _save_config
38
+    with open(filename, 'w', encoding='UTF-8') as fileobj:
39
+         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
40
+FileNotFoundError: [Errno 2] No such file or directory: '.../.derivepassphrase/settings.json'
41
+````
42
+
43
+(Also demonstrated in [one-time-key-override-fails](one-time-key-override-fails.md).)
44
+
45
+I believe it makes sense to handle `FileNotFoundError` here, but probably no other `OSError` variant, since these are not generally encountered in normal circumstances. I think.
... ...
@@ -0,0 +1,15 @@
1
+# `derivepassphrase` wish export-vault-formats
2
+
3
+???+ success "Wish details: Support data export from vault v0.2, vault v0.3, and storeroom storage formats"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>wish</i><td>This is a request for an enhancement.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 <b>0.1.2</b> 0.1.3
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/b4d8439fa4207b665ad8ea2217f21f807f603734">b4d8439fa4207b665ad8ea2217f21f807f603734</a> (0.2.0)
8
+    </table>
9
+
10
+Support extracting stored configurations from [vault][] v0.2, from v0.3, and from [storeroom][]-backed configurations.
11
+
12
+v0.2 and v0.3 differ only in a technicality in how the encryption key for the stored data is derived. storeroom is a completely different design, but it is reasonably well documented in its readme.
13
+
14
+  [vault]: https://github.com/jcoglan/vault 'jcoglan/vault'
15
+  [storeroom]: https://www.npmjs.com/package/storeroom 'npm:storeroom'
... ...
@@ -0,0 +1,13 @@
1
+# `derivepassphrase` wish exporter-script-as-subcommand
2
+
3
+???+ success "Wish details: Make the exporter a subcommand of `derivepassphrase`"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>wish</i><td>This is a request for an enhancement.
6
+        <tr><th scope=col>Present-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/b4d8439fa4207b665ad8ea2217f21f807f603734">b4d8439fa4207b665ad8ea2217f21f807f603734</a> (0.2.0)
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/69cf6a48483555dbcb4c8506673ef942fb008e18">69cf6a48483555dbcb4c8506673ef942fb008e18</a> (0.2.0)
8
+        <tr><th scope=col>Depends<td colspan=2>[scheme-specific-cli-and-config](scheme-specific-cli-and-config.md){: .fixed }
9
+    </table>
10
+
11
+Turn the `derivepassphrase_export` command into the subcommand `derivepassphrase export`.
12
+
13
+Dependent on [scheme-specific-cli-and-config](scheme-specific-cli-and-config.md).
... ...
@@ -0,0 +1,12 @@
1
+# `derivepassphrase` bug fail-gracefully-without-af-unix
2
+
3
+???+ success "Bug details: Fail gracefully if support for UNIX domain sockets is unavailable"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 <b>0.2.0</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/ba27276a76a263a2d866bc55eca012f927c34877">ba27276a76a263a2d866bc55eca012f927c34877</a> (0.3.0)
8
+    </table>
9
+
10
+We generally support running `derivepassphrase` on systems where the SSH agent client is unusable because the system (or at least Python on that system) does not support UNIX domain sockets; see e.g. [windows-ssh-agent-support](windows-ssh-agent-support.md).  Currently, these fail with an `AttributeError` while resolving the `socket.AF_UNIX` symbol, instead of a more descriptive exception.
11
+
12
+<b>Therefore</b>, correctly diagnose if the Python installation is lacking the `socket.AF_UNIX` symbol, and fail in an orderly manner.
... ...
@@ -0,0 +1,16 @@
1
+# `derivepassphrase` bug falsy-vault-config-values
2
+
3
+???+ success "Bug details: `derivepassphrase vault` differs from vault(1) behavior with falsy stored configuration values"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 <b>0.2.0</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/7d2f2b1bda31ead428d3c009772aaf3d2261d60c">7d2f2b1bda31ead428d3c009772aaf3d2261d60c</a> (0.3.0)
8
+    </table>
9
+
10
+`derivepassphrase vault` uses a very strict validator to ensure that a configuration is valid, both its contents and its types.  For example, the configuration `{"global": {"phrase": null}, "services": {}}` is not valid according to `derivepassphrase`'s validator, because the `phrase` value must be a string.
11
+
12
+vault(1) however tests most of its parameters for falsy values (in the JavaScript sense), and so will accept the configuration `{"global": {"phrase": null, "upper": ""}, "services": {}}`, among others.
13
+
14
+<b>Therefore</b>, in the interest of compatibility with vault(1), convert all falsy values to their correctly typed equivalent before validating them.
15
+
16
+We shall still make sure that any configuration we write is valid according to our validator as well, not just vault(1)'s.
... ...
@@ -0,0 +1,12 @@
1
+# `derivepassphrase` bug no-stdlib-module-names
2
+
3
+???+ success "Bug details: Rename `types` submodules to `_types`"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 <b>0.1.2</b> 0.1.3
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/c4a57f311710768cb18df717a73fd48a8a3077fe">c4a57f311710768cb18df717a73fd48a8a3077fe</a> (0.2.0)
8
+    </table>
9
+
10
+It appears to be a *very* bad idea to name a submodule similar to a standard library module, as some tools, e.g. vim's keyword lookup (`K`), execute the code and then may run into "circular import" problems because the `types` submodule shadows the `types` standard library module.
11
+
12
+So, rename the `types` submodules to `_types`.
... ...
@@ -0,0 +1,51 @@
1
+# `derivepassphrase` bug one-time-key-override-fails
2
+
3
+???+ success "Bug details: `derivepassphrase -k` fails when overriding the chosen key on the command-line"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 <b>0.1.2</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/d000e7cbd2cfdfd86f614a9f65acea039baeff70">d000e7cbd2cfdfd86f614a9f65acea039baeff70</a> (0.1.3)
8
+    </table>
9
+
10
+````console-session
11
+$ mkdir -p ~/.derivepassphrase
12
+$ derivepassphrase -k --config
13
+Suitable SSH keys:
14
+[1] ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBjuC9...
15
+[2] ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG/RzZ...
16
+Your selection? (1-2, leave empty to abort): 1
17
+$ derivepassphrase -k -l 35 abc
18
+Suitable SSH keys:
19
+[1] ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBjuC9...
20
+[2] ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG/RzZ...
21
+Your selection? (1-2, leave empty to abort): 2
22
+Traceback (most recent call last):
23
+  File ".../derivepassphrase", line 8, in <module>
24
+    sys.exit(derivepassphrase())
25
+             ^^^^^^^^^^^^^^^^^^
26
+  File ".../click/core.py", line 1157, in __call__
27
+    return self.main(*args, **kwargs)
28
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^
29
+  File ".../click/core.py", line 1078, in main
30
+    rv = self.invoke(ctx)
31
+         ^^^^^^^^^^^^^^^^
32
+  File ".../click/core.py", line 1434, in invoke
33
+    return ctx.invoke(self.callback, **ctx.params)
34
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35
+  File ".../click/core.py", line 783, in invoke
36
+    return __callback(*args, **kwargs)
37
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
38
+  File ".../click/decorators.py", line 33, in new_func
39
+    return f(get_current_context(), *args, **kwargs)
40
+           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41
+  File ".../derivepassphrase/cli.py", line 1077, in derivepassphrase
42
+    vault = dpp.Vault(**kwargs)
43
+            ^^^^^^^^^^^^^^^^^^^
44
+TypeError: Vault.__init__() got an unexpected keyword argument 'key'
45
+````
46
+
47
+It works for stored settings, though.
48
+
49
+--------
50
+
51
+Actually, this concrete error requires a selected key in the settings, and a `-k` argument on the command-line. It is not triggered by a naïve call of `derivepassphrase -k`.
... ...
@@ -0,0 +1,21 @@
1
+# `derivepassphrase` wish other-derivation-schemes
2
+
3
+???+ question "Wish details: Consider implementing passphrase schemes other than vault&apos;s"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>wish</i><td>This is a request for an enhancement.
6
+        <tr><th scope=col>Priority<td><i>low</i><td>We aren&apos;t sure whether to fix this or not.
7
+        <tr><th scope=col>Difficulty<td><i>tricky</i><td>Needs many tuits.
8
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 <b>0.1.2</b> 0.1.3 0.2.0 0.3.0 0.3.1 0.3.2 0.3.3 0.4.0
9
+        <tr><th scope=col>Depends<td colspan=2>[scheme-specific-cli-and-config](scheme-specific-cli-and-config.md){: .fixed }
10
+    </table>
11
+
12
+Consider implementing other deterministic password/passphrase generation schemes, beyond vault.
13
+
14
+Some candidates:
15
+
16
+- [chriszarate/supergenpass](https://github.com/chriszarate/supergenpass)
17
+- [grempe/strongpass](https://github.com/grempe/strongpass)
18
+- [aprico-org/aprico-gen](https://github.com/aprico-org/aprico-gen)
19
+- [Master Passphrase/Spectre.app scheme](https://spectre.app/blog/2018-01-06-algorithm/)
20
+
21
+The hard part about these will probably not be the coding, but the correctness testing.
... ...
@@ -0,0 +1,14 @@
1
+# `derivepassphrase` wish pretty-print-json
2
+
3
+???+ success "Wish details: `derivepassphrase vault` should store and export the vault configuration in pretty-printed JSON"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>wish</i><td>This is a request for an enhancement.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 0.2.0 0.3.0 0.3.1 0.3.2 0.3.3 <b>0.4.0</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/b05668d5614d47158c8f8f0ef5145775d5ee40d8">b05668d5614d47158c8f8f0ef5145775d5ee40d8</a>
8
+    </table>
9
+
10
+`derivepassphrase vault` stores and exports the vault configuration using the Python standard library's `json` module, with default settings.  This leads to very terse output in the configuration, particularly if it stores notes or SSH key references.  This terse notation, as reported to me by a certain non-Github user, becomes an unnecessary obstacle when debugging the configuration or looking up information in it.
11
+
12
+I agree with this assessment when it comes to exports of the vault configuration, because this is an expression of the [transparency of the system](http://www.catb.org/~esr/writings/taoup/html/ch01s06.html#id2878054).  I do not agree for the *stored* vault configuration, which is a data file and which should not be touched by the user: that it uses the same JSON format as the export does is an implementation detail.  However, as a stopgap measure until better built-in configuration querying capabilities are available, this request is reasonable.
13
+
14
+**Therefore**, pretty-print the vault configuration in both exports and in the stored configuration files.  The latter may be retired at a later date if replaced by better querying capabilities.
... ...
@@ -0,0 +1,17 @@
1
+# `derivepassphrase` wish print-service-notes-above
2
+
3
+???+ success "Wish details: `derivepassphrase vault` should be able to print service notes *above* the passphrase"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>wish</i><td>This is a request for an enhancement.
6
+        <tr><th scope=col>Present-in<td colspan=2><b>0.4.0</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/9b5805eb652e97ee4b63f6afbcf9563aba3311f0">9b5805eb652e97ee4b63f6afbcf9563aba3311f0</a>
8
+        <tr><th scope=col>Depends<td colspan=2>[print-service-notes](print-service-notes.md){: .fixed }
9
+    </table>
10
+
11
+See [print-service-notes](print-service-notes.md) for context.
12
+
13
+vault(1) always prints the derived service passphrase first, and the notes (if any) second.  This is sometimes weird to read if the notes contain explanation or supplementary info concerning the passphrase.
14
+
15
+`derivepassphrase vault` should support printing the notes before the passphrase as well, not just after.
16
+
17
+**Therefore**, add support for printing the notes before the passphrase.
... ...
@@ -0,0 +1,17 @@
1
+# `derivepassphrase` bug print-service-notes
2
+
3
+???+ success "Bug details: `derivepassphrase vault` does not print service notes"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 0.2.0 0.3.0 0.3.1 0.3.2 0.3.3 <b>0.4.0</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/9b5805eb652e97ee4b63f6afbcf9563aba3311f0">9b5805eb652e97ee4b63f6afbcf9563aba3311f0</a>
8
+        <tr><th scope=col>Blocks<td colspan=2>[print-service-notes-above](print-service-notes-above.md){: .fixed }
9
+    </table>
10
+
11
+If vault(1) is asked to derive a passphrase for a service, and that service has associated notes, then vault(1) prints the notes (to standard error) after printing the derived passphrase.
12
+
13
+`derivepassphrase vault` currently doesn't do this; it stores the notes, but doesn't otherwise access or act on them (apart from importing or exporting the configuration).
14
+
15
+**Therefore**, make `derivepassphrase vault` also print the service notes when printing a derived passphrase.
16
+
17
+This is an easy change on the technical side, but a somewhat tedious one concerning the documentation and the tests that need updating and/or rewriting.
... ...
@@ -0,0 +1,20 @@
1
+# `derivepassphrase` bug remove-pageant-build-info-check
2
+
3
+???+ success "Bug details: Remove Pageant build info check"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2><b>0.2.0</b> 0.3.0 0.3.1 0.3.2 0.3.3
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/e97b966ecba87289b839e0fbac736f0b53782ed5">e97b966ecba87289b839e0fbac736f0b53782ed5</a> (0.4.0)
8
+    </table>
9
+
10
+In [test-suite-isolated-ssh-agent](test-suite-isolated-ssh-agent.md), we encountered a bug in Pageant where the agent socket becomes undiscoverable if Pageant is spawned as a subprocess, in debug mode.  We wrote and submitted a patch to upstream PuTTY, which will hopefully make it into PuTTY 0.82.  [63b51df7a39fd642ca079ac390014d23f617b972](https://github.com/the-13th-letter/derivepassphrase/commit/63b51df7a39fd642ca079ac390014d23f617b972) introduced a check for a fixed version of Pageant by asserting a version number 0.82 or greater, or by the presence of a specific build identifier.  This check further enables running Pageant in foreground mode, which is a small feature request related to and submitted together with the patch above.
11
+
12
+Of course, this version check in its current form is only a temporary measure, and there is no guarantee that 0.82 will be the first version where the bug is fixed or the foreground mode feature is implemented, if at all.  The local build identifier check furthermore assumes that Pageant's version number adheres to [PEP 440](https://peps.python.org/pep-0440/), which is not guaranteed by upstream PuTTY, neither in format nor in semantics.
13
+
14
+<b>Therefore</b>, once a fixed version of PuTTY has been released, remove the build identifier checks and update the minimum required version to whatever version number the fixed PuTTY version has.  If necessary, adapt the version comparison code as well.
15
+
16
+--------
17
+
18
+> In [test-suite-isolated-ssh-agent](test-suite-isolated-ssh-agent.md), we encountered a bug in Pageant where the agent socket becomes undiscoverable if Pageant is spawned as a subprocess, in debug mode. We wrote and submitted a patch to upstream PuTTY, which will hopefully make it into PuTTY 0.82. [63b51df](https://github.com/the-13th-letter/derivepassphrase/commit/63b51df7a39fd642ca079ac390014d23f617b972) introduced a check for a fixed version of Pageant by asserting a version number 0.82 or greater, or by the presence of a specific build identifier. This check further enables running Pageant in foreground mode, which is a small feature request related to and submitted together with the patch above.
19
+
20
+The "undiscoverable socket in debug mode" bug has been fixed upstream in [fca6ce10dbf01e57ec4777b87faae8b38e53ff43](https://git.tartarus.org/?p=simon/putty.git;a=commit;h=fca6ce10dbf01e57ec4777b87faae8b38e53ff43), and foreground mode has been introduced in [2b93417398f641e410f0b3564135508ebfb71ac0](https://git.tartarus.org/?p=simon/putty.git;a=commit;h=2b93417398f641e410f0b3564135508ebfb71ac0). Both commits should be included in PuTTY 0.82.
... ...
@@ -0,0 +1,18 @@
1
+# `derivepassphrase` wish report-build-flags-and-features
2
+
3
+???+ success "Wish details: `derivepassphrase` should report its build flags and supported features"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>wish</i><td>This is a request for an enhancement.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 0.2.0 0.3.0 0.3.1 0.3.2 0.3.3 <b>0.4.0</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/6741af2eaa6fba39717997292ec25baf5f1f4f20">6741af2eaa6fba39717997292ec25baf5f1f4f20</a>
8
+    </table>
9
+
10
+Currently, `derivepassphrase` does not report its “build flags“ or its supported optional features (passphrase derivation schemes, PEP 508 extras, etc.). So callers of `derivepassphrase` need to infer support for optional features through other means, such as trying out the desired feature directly, or observing support indirectly e.g. in the `--help` output.
11
+
12
+**Therefore**, `derivepassphrase` should include a way to report its build flags and supported features.
13
+
14
+A common way to implement this is to expand the `--version` output to include this information (in a structured tabular format, for machine-parsability).
15
+
16
+--------
17
+
18
+This was implemented in commit 6741af2eaa6fba39717997292ec25baf5f1f4f20.
... ...
@@ -0,0 +1,13 @@
1
+# `derivepassphrase` wish scheme-specific-cli-and-config
2
+
3
+???+ success "Wish details: Move `vault`-specific command-line interface into a separate CLI subcommand and matching configuration file"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>wish</i><td>This is a request for an enhancement.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 0.1.3 <b>0.2.0</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/69cf6a48483555dbcb4c8506673ef942fb008e18">69cf6a48483555dbcb4c8506673ef942fb008e18</a> (0.2.0)
8
+        <tr><th scope=col>Blocks<td colspan=2>[other-derivation-schemes](other-derivation-schemes.md)
9
+    </table>
10
+
11
+In preparation for [other-derivation-schemes](other-derivation-schemes.md), move the current `vault`-specific command-line interface into a subcommand `vault`, and a matching configuration file `vault.json` instead of `config.json`.
12
+
13
+Include machinery to automatically migrate to `vault.json`, with fallback to `config.json` if the former is not writable. (v1.0 will no longer support `config.json` or auto-migration.)
... ...
@@ -0,0 +1,17 @@
1
+# `derivepassphrase` bug single-toplevel-module
2
+
3
+???+ success "Bug details: Move `sequin` and `ssh_agent_client` modules into `derivepassphrase` package"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 <b>0.1.2</b> 0.1.3
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/c4a57f311710768cb18df717a73fd48a8a3077fe">c4a57f311710768cb18df717a73fd48a8a3077fe</a> (0.2.0)
8
+    </table>
9
+
10
+The current layout, using three top-level Python packages `derivepassphrase`, `sequin` and `ssh_agent_client`, is error-prone:
11
+
12
+- The `sequin` module and the `ssh_agent_client` package already are tightly coupled to the `derivepassphrase` package, insofar as their scope and functionality is solely dictated by the `derivepassphrase` package. `sequin` in particular is *very* special purpose, and unlikely to be useful in contexts other than passphrase generation.
13
+- For 0.1.0 and 0.1.1, the Python wheels forgot to include `sequin` and `ssh_agent_client`, which led to broken installations.
14
+- Version info via the `__version__` attribute needs to be replicated across all three top-level packages.
15
+- At least `ssh_agent_client` is likely enough a module/package name that we may expect name collisions in the future.
16
+
17
+Since the only argument I have for keeping the packages separate is decoupling and independent evolution, which I think is completely mitigated by the <i lang="la">de facto</i> coupling to the `derivepassphrase` package anyway, I wish to integrate the `sequin` module and the `ssh_agent_client` package into the `derivepassphrase` package in the long run (and perhaps consolidate both `types` submodules).
... ...
@@ -0,0 +1,14 @@
1
+# `derivepassphrase` bug test-filesystem-isolation
2
+
3
+???+ success "Bug details: Isolate tests properly from the filesystem"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 <b>0.1.2</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/a980a643275de28f7715241790f199f947f637f4">a980a643275de28f7715241790f199f947f637f4</a> (0.1.3)
8
+    </table>
9
+
10
+Tests for the `derivepassphrase` command-line interface interact with the user's configuration, and will in general fail, unrelatedly, if said user configuration is broken.
11
+
12
+While there *is* code to isolate the filesystem during tests, it is not yet consistently applied across all tests.
13
+
14
+(Discovered while working on [configuration-directory-must-exist](configuration-directory-must-exist.md).)
... ...
@@ -0,0 +1,48 @@
1
+# `derivepassphrase` wish test-suite-isolated-ssh-agent
2
+
3
+???+ success "Wish details: Support and isolate OpenSSH&apos;s `ssh-agent` and PuTTY&apos;s `pageant` in the test suite"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>wish</i><td>This is a request for an enhancement.
6
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 0.1.3 <b>0.2.0</b>
7
+        <tr><th scope=col>Fixed-in<td colspan=2><a href="https://github.com/the-13th-letter/derivepassphrase/commit/dd606b3a051f0ba3bc76d0ef6e14ba3fb5d87298">dd606b3a051f0ba3bc76d0ef6e14ba3fb5d87298</a> (0.3.0)
8
+    </table>
9
+
10
+When testing SSH agent-related functionality, currently the test suite will use whatever agent happens to be running (if any), and upload test keys to and issue test queries against said agent.  The test keys are re-uploaded in every test that uses them, with a fixed lifetime of 30 seconds.
11
+
12
+Of course, this setup muddles the agent's supported key list.  In extreme cases, it could even lead to legitimate SSH connections failing to authenticate because an OpenSSH client (with `IdentitiesOnly` off) naively tries out all of our test keys against a server, exhausting its number of authentication attempts.
13
+
14
+Additionally, this setup does not work for PuTTY's Pageant, because [Pageant does not support key lifetimes/timeouts](https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/pageant-timeout.html).  Furthermore, because key lifetime constraints are part of the core agent protocol, instead of a protocol extension, we cannot reliably query the support status of this feature.
15
+
16
+<b>Therefore</b>, instead of the test suite expecting to run alongside an already-running SSH agent, attempt to spawn one ourselves: try OpenSSH first, then PuTTY, else fall back (as before) to a running SSH agent, if any.
17
+
18
+(As an added bonus, if the agent is under our control, then we don't have to control the lifetime of the keys for isolation purposes anymore.   So Pageant's lack of support for this setting is then irrelevant.)
19
+
20
+Because of the standard calling conventions of both `ssh-agent` and `pageant`, if actually spawned by us, then we will be running the respective agent in the foreground, and have to do the necessary environment manipulation (`SSH_AUTH_SOCK` variable) ourselves.  We will then also need to ensure the agent is terminated when the test suite exits.
21
+
22
+--------
23
+
24
+> Therefore, instead of the test suite expecting to run alongside an already-running SSH agent, attempt to spawn one ourselves: try OpenSSH first, then PuTTY, else fall back (as before) to a running SSH agent, if any.
25
+
26
+I have since changed this approach for test coverage reasons.  Because there are only two agent implementations and rather few affected tests, it is still viable to attempt to test every combination.  A new pytest fixture `running_ssh_agent` loops through the known agent configurations and ensures that *some* agent is running, otherwise it skips the running test.  Another new parametrized pytest fixture `ssh_agent_client_with_test_keys_loaded` spawns a client for every known agent configuration and preloads all standard test keys, skipping only if the agent or the client didn't spawn or if all test keys failed to load in the agent.
27
+
28
+For PuTTY, this currently requires a patched Pageant version; see next point.
29
+
30
+> Because of the standard calling conventions of both `ssh-agent` and `pageant`, if actually spawned by us, then we will be running the respective agent in the foreground, and have to do the necessary environment manipulation (`SSH_AUTH_SOCK` variable) ourselves. We will then also need to ensure the agent is terminated when the test suite exits.
31
+
32
+As of version 0.81, Pageant has a problem with output buffering when run in debug mode, its standard foreground mode: to communicate with the test harness, Pageant's standard output is a pipe, which means the C `stdio` library uses fully buffered output by default.  Pageant therefore writes its `SSH_AUTH_SOCK` line to the buffer.  In general, the buffer isn't filled by this, so the `SSH_AUTH_SOCK` line doesn't flush to standard output yet. This is a deadlock: clients are waiting for Pageant to report its socket address so they can connect, and Pageant is waiting for connections to have something to report about.
33
+
34
+As a workaround, it is of course possible to run Pageant in one of its forking modes and track the PID of that instance directly, but this is inherently susceptible to race conditions because the PID might get silently reused, and it lacks a cross-platform Python API.
35
+
36
+A bug report and a patch has been submitted to PuTTY on 2024-09-18, and reception acknowledged on 2024-09-22.  A locally patched version with proper output flushing has been tested, and behaves very well in this usage.
37
+
38
+--------
39
+
40
+> As of version 0.81, Pageant has a problem with output buffering when run in debug mode, its standard foreground mode: to communicate with the test harness, Pageant's standard output is a pipe, which means the C `stdio` library uses fully buffered output by default. Pageant therefore writes its `SSH_AUTH_SOCK` line to the buffer. In general, the buffer isn't filled by this, so the `SSH_AUTH_SOCK` line doesn't flush to standard output yet. This is a deadlock: clients are waiting for Pageant to report its socket address so they can connect, and Pageant is waiting for connections to have something to report about.
41
+> 
42
+> […]
43
+> 
44
+> A bug report and a patch has been submitted to PuTTY on 2024-09-18, and reception acknowledged on 2024-09-22. A locally patched version with proper output flushing has been tested, and behaves very well in this usage.
45
+
46
+[The aforementioned patch has been added to PuTTY/Pageant](https://git.tartarus.org/?p=simon/putty.git;a=commit;h=fca6ce10dbf01e57ec4777b87faae8b38e53ff43), and will likely be part of PuTTY 0.82.
47
+
48
+Simon Tatham, PuTTY's principal author, has further clarified that he considers `derivepassphrase`'s handling to be an abuse of Pageant's debug mode, which was never intended to be machine-parsable. He is instead in favor of the new foreground mode for Pageant (as alluded to in https://github.com/the-13th-letter/derivepassphrase/issues/14#issue-2541165526) which is also due to become part of PuTTY 0.82.
... ...
@@ -0,0 +1,44 @@
1
+# `derivepassphrase` bug windows-ssh-agent-support
2
+
3
+???+ bug "Bug details: Support PuTTY/Pageant (and maybe OpenSSH/`ssh-agent`) on Windows"
4
+    <table id="bug-summary" markdown>
5
+        <tr><th scope=col>Class<td><i>bug</i><td>This is clearly an actual problem we want fixed.
6
+        <tr><th scope=col>Priority<td><i>medium</i><td>This should be fixed one day.
7
+        <tr><th scope=col>Difficulty<td><i>taxing</i><td>Needs external things we don't have: standards, users, et cetera.
8
+        <tr><th scope=col>Present-in<td colspan=2>0.1.0 0.1.1 0.1.2 0.1.3 <b>0.2.0</b> 0.3.0 0.3.1 0.3.2 0.3.3 0.4.0
9
+    </table>
10
+
11
+The SSH agent support in the default “vault” scheme assumes a UNIX host system, where all sensible SSH agent implementations use UNIX domain (`AF_UNIX`) sockets to connect the SSH client to the SSH agent, and expose the name of the socket in the `SSH_AUTH_SOCK` environment variable.
12
+
13
+Windows historically did not support UNIX domain sockets, so portable programs using UNIX domain sockets would need to resort to other inter-process communication designs when ported to Windows. (A TCP/IP port on `localhost` plus an authentication token seems to be a common design, e.g. [GnuPG 2.3](https://lists.gnupg.org/pipermail/gnupg-devel/2021-March/034795.html).)
14
+
15
+PuTTY/Pageant uses (Windows) named pipes, presumably with a fixed address.  Annoyingly, stock Python does not support connecting to Windows named pipes: while UNIX domain sockets can be opened by the standard C open(3) call, Windows named pipes need a special Win32 API call to open, which Python does not bind.
16
+
17
+OpenSSH for Windows uses yet other means of advertising and of connecting to the running agent, [seemingly incompatible with the UNIX domain socket support in Windows 10 and later](https://github.com/PowerShell/Win32-OpenSSH/issues/1761).
18
+
19
+As a result, while `derivepassphrase` does not actively use Windows-incompatible code for SSH agent handling, the two main Windows SSH agent implementations likely cannot be straightforwardly connected to `derivepassphrase`.
20
+
21
+<b>Therefore</b>, implement specific support on Windows to locate and connect to running Pageant or OpenSSH agent instances.
22
+
23
+---
24
+
25
+<strong>Help wanted!</strong> As we have neither Windows experience nor Windows hardware to test this on, please get in touch if you can
26
+
27
+- confirm that `derivepassphrase` cannot talk to either SSH agent even if their (socket) address is stored in `SSH_AUTH_SOCK`,
28
+- provide help with implementing the necessary code to talk to Pageant/OpenSSH agent in their default configurations.
29
+
30
+--------
31
+
32
+As far as I can tell – still without having tried this out on actual Windows hardware – the current situation is as follows:
33
+
34
+1. [Microsoft implemented support for UNIX domain sockets, in certain constellations.][ANNOUNCEMENT] (Corrections: https://github.com/microsoft/WSL/issues/4240.)  Such support is available with some versions of Windows 10, and all versions of Windows 11.  There appears to be no further effort by Microsoft to provide further parts of the UNIX domain functionality (datagram sockets, abstract sockets, etc.)
35
+2. Because not all constellations are supported, Python does not support `socket.AF_UNIX` on Windows: https://github.com/python/cpython/issues/77589. There is a suggestion, but no consensus, to add a symbol `socket.WIN_AF_UNIX` or `socket.AF_UNIX_PARTIALSUPPORT` to officially expose whatever support Windows currently *does* have for UNIX domain sockets.
36
+3. PuTTY and OpenSSH for Windows predate such `AF_UNIX` support, and implement agent/client communication via Windows named pipes.  Windows named pipes are not compatible with the low-level UNIX I/O layer (`read`, `write`, etc.), and are not supported by the Python standard library in any form.  There appears to be no current PyPI package providing a useful interface to Windows named pipes—the pipes are not exposed as proper file objects or sockets, or they are not full duplex, or they cannot be explicitly named by the application.  This is usually because the implementation calls into the Windows kernel DLL directly, and wraps those specific functions necessary for the application to run; no attempt at providing a comprehensive and/or pythonic interface is made.
37
+4. PuTTY/Pageant uses [a default address for the named pipe, but that default address is *not constant*][PUTTY_PIPE_NAME]; it includes a personalized hash as a suffix.  This will be difficult to replicate in non-PuTTY code.  Accordingly, Pageant can be instructed to write out an OpenSSH-compatible config (the `IdentityAgent` line) for non-PuTTY clients, which we could then parse.
38
+5. OpenSSH for Windows offers the agent as a system-wide service.  There does not seem to be any support for spawning the agent outside of this system service context.  I do not know how the agent's socket address is communicated (if it is non-constant at all).
39
+6. Pageant on Windows *does* support exposing a UNIX domain stream socket, but because of (2), we cannot interact with it.  The support is limited to WSL 1; in WSL 2, UNIX domain sockets use a different namespace than Winsock sockets do, neither of which is accessible to the other.
40
+
41
+Given this situation, the most sensible thing to do is to give up on waiting for proper UNIX domain socket support in Windows/Python, and implement specific support for talking to an SSH agent via a Windows named pipe.  In particular, it also makes sense to correctly diagnose if the Python installation is lacking the `socket.AF_UNIX` symbol, and fail in an orderly manner.
42
+
43
+[ANNOUNCEMENT]: https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/
44
+[PUTTY_PIPE_NAME]: https://git.tartarus.org/?p=simon/putty.git;a=blob;f=windows/utils/agent_named_pipe_name.c;h=aa64b3f60df455e06d6bc1b6c47923143b7a2dda;hb=a8601a72a918dfc2a8e8536a77139d7f37700044
0 45