sshscript 4.0.2__tar.gz → 5.0.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.
Files changed (116) hide show
  1. sshscript-5.0.0/AI_GUIDE.md +58 -0
  2. {sshscript-4.0.2 → sshscript-5.0.0}/API_GUIDE.md +144 -11
  3. {sshscript-4.0.2 → sshscript-5.0.0}/CHANGELOG.md +12 -0
  4. {sshscript-4.0.2 → sshscript-5.0.0}/MANIFEST.in +3 -0
  5. {sshscript-4.0.2/src/sshscript.egg-info → sshscript-5.0.0}/PKG-INFO +64 -8
  6. {sshscript-4.0.2 → sshscript-5.0.0}/README.md +63 -7
  7. {sshscript-4.0.2 → sshscript-5.0.0}/SECURITY.md +11 -2
  8. {sshscript-4.0.2 → sshscript-5.0.0}/VERSIONING.md +18 -1
  9. sshscript-5.0.0/skills/sshscript/SKILL.md +76 -0
  10. sshscript-5.0.0/skills/sshscript/references/agent-guide.md +360 -0
  11. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/_version.py +1 -1
  12. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/session.py +100 -2
  13. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/session.pyi +15 -3
  14. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/sessionsettings.py +17 -1
  15. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_command_job.py +5 -1
  16. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_logger_integration.py +16 -3
  17. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_openssh_integration.py +30 -0
  18. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_recommended_examples.py +22 -1
  19. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_ssh_config.py +7 -1
  20. sshscript-5.0.0/src/sshscript/unittest/test_ssh_security.py +531 -0
  21. {sshscript-4.0.2 → sshscript-5.0.0/src/sshscript.egg-info}/PKG-INFO +64 -8
  22. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript.egg-info/SOURCES.txt +4 -0
  23. {sshscript-4.0.2 → sshscript-5.0.0}/tools/check_public_types.py +14 -5
  24. {sshscript-4.0.2 → sshscript-5.0.0}/tools/prepare_release.py +3 -0
  25. sshscript-5.0.0/tools/sync_ai_docs.py +97 -0
  26. sshscript-4.0.2/src/sshscript/unittest/test_ssh_security.py +0 -295
  27. {sshscript-4.0.2 → sshscript-5.0.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  28. {sshscript-4.0.2 → sshscript-5.0.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  29. {sshscript-4.0.2 → sshscript-5.0.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  30. {sshscript-4.0.2 → sshscript-5.0.0}/.github/dependabot.yml +0 -0
  31. {sshscript-4.0.2 → sshscript-5.0.0}/.github/pull_request_template.md +0 -0
  32. {sshscript-4.0.2 → sshscript-5.0.0}/.github/workflows/ci.yml +0 -0
  33. {sshscript-4.0.2 → sshscript-5.0.0}/.github/workflows/codeql.yml +0 -0
  34. {sshscript-4.0.2 → sshscript-5.0.0}/.github/workflows/release.yml +0 -0
  35. {sshscript-4.0.2 → sshscript-5.0.0}/.gitignore +0 -0
  36. {sshscript-4.0.2 → sshscript-5.0.0}/CODE_OF_CONDUCT.md +0 -0
  37. {sshscript-4.0.2 → sshscript-5.0.0}/CONTRIBUTING.md +0 -0
  38. {sshscript-4.0.2 → sshscript-5.0.0}/EXCEPTIONS.md +0 -0
  39. {sshscript-4.0.2 → sshscript-5.0.0}/LICENSE.txt +0 -0
  40. {sshscript-4.0.2 → sshscript-5.0.0}/RELEASING.md +0 -0
  41. {sshscript-4.0.2 → sshscript-5.0.0}/SUPPORT.md +0 -0
  42. {sshscript-4.0.2 → sshscript-5.0.0}/pyproject.toml +0 -0
  43. {sshscript-4.0.2 → sshscript-5.0.0}/setup.cfg +0 -0
  44. {sshscript-4.0.2 → sshscript-5.0.0}/src/_sshscript_cli.py +0 -0
  45. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/__init__.py +0 -0
  46. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/__init__.pyi +0 -0
  47. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/_runtime.py +0 -0
  48. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/channelgeneric.py +0 -0
  49. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/channelssh.py +0 -0
  50. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/channelsubprocess.py +0 -0
  51. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/channelutils.py +0 -0
  52. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/commandjob.py +0 -0
  53. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/commandjob.pyi +0 -0
  54. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/commandresult.py +0 -0
  55. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/commandresult.pyi +0 -0
  56. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/consolejob.py +0 -0
  57. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/dollar.py +0 -0
  58. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/dollarchanger.py +0 -0
  59. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/dollarparser.py +0 -0
  60. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/errorutils.py +0 -0
  61. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/patching.py +0 -0
  62. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/py.typed +0 -0
  63. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/sessionwrapper.py +0 -0
  64. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/sessionwrapper.pyi +0 -0
  65. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/spyimporter.py +0 -0
  66. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/sshconfig.py +0 -0
  67. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/sshscript.py +0 -0
  68. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/stdio.py +0 -0
  69. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/tokenparser.py +0 -0
  70. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/README.console-authentication.md +0 -0
  71. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/README.console-results.md +0 -0
  72. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/README.dollar-syntax-tests.md +0 -0
  73. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/README.module-tests.md +0 -0
  74. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/check_package_asserts.py +0 -0
  75. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/dollar_syntax.spy +0 -0
  76. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/dollar_syntax_fixture.spy +0 -0
  77. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/language_fixture.spy +0 -0
  78. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_channelgeneric_expect.py +0 -0
  79. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_check_file.py +0 -0
  80. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_command_api.py +0 -0
  81. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_console_authentication.py +0 -0
  82. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_console_job.py +0 -0
  83. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_console_results.py +0 -0
  84. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_dollar_command_escapes.py +0 -0
  85. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_file_transfer.py +0 -0
  86. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_logger_api.py +0 -0
  87. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_native_auth_runner.py +0 -0
  88. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_native_console_authentication.py +0 -0
  89. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_production_contract.py +0 -0
  90. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_public_types.py +0 -0
  91. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_python_version.py +0 -0
  92. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_release_inputs.py +0 -0
  93. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_session_close_reporting.py +0 -0
  94. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_session_proxy_cleanup.py +0 -0
  95. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_session_settings.py +0 -0
  96. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_spy_source_mapping.py +0 -0
  97. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_spy_thread_session.py +0 -0
  98. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_sshscript_dollar_syntax.py +0 -0
  99. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_sshscript_module.py +0 -0
  100. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_stdin_contract.py +0 -0
  101. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_stdio_dynamic_string.py +0 -0
  102. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_syntax_error.spy +0 -0
  103. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_update_check.py +0 -0
  104. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript/unittest/test_version_policy.py +0 -0
  105. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript.egg-info/dependency_links.txt +0 -0
  106. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript.egg-info/entry_points.txt +0 -0
  107. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript.egg-info/requires.txt +0 -0
  108. {sshscript-4.0.2 → sshscript-5.0.0}/src/sshscript.egg-info/top_level.txt +0 -0
  109. {sshscript-4.0.2 → sshscript-5.0.0}/tools/check_baseline_examples.py +0 -0
  110. {sshscript-4.0.2 → sshscript-5.0.0}/tools/check_release.py +0 -0
  111. {sshscript-4.0.2 → sshscript-5.0.0}/tools/publish_release.py +0 -0
  112. {sshscript-4.0.2 → sshscript-5.0.0}/tools/run_checks.py +0 -0
  113. {sshscript-4.0.2 → sshscript-5.0.0}/tools/run_native_auth_ci.py +0 -0
  114. {sshscript-4.0.2 → sshscript-5.0.0}/tools/setup_openssh_ci.sh +0 -0
  115. {sshscript-4.0.2 → sshscript-5.0.0}/tools/sync_api_guide.py +0 -0
  116. {sshscript-4.0.2 → sshscript-5.0.0}/tools/version_policy.py +0 -0
@@ -0,0 +1,58 @@
1
+ # SSHScript for AI agents
2
+
3
+ **Documentation contract: SSHScript 5.0.0; Python 3.11+.**
4
+
5
+ Start with the [agent guide](skills/sshscript/references/agent-guide.md).
6
+ It explains version checks, API selection, command results, input, deadlines,
7
+ authentication and cleanup. Read only the task-relevant sections of the
8
+ [canonical API guide](API_GUIDE.md) and [version policy](VERSIONING.md).
9
+
10
+ The [SSHScript skill](skills/sshscript/SKILL.md) supplies a compact operating
11
+ workflow. Its folder contains the supporting guide; copy the entire
12
+ `skills/sshscript/` folder when installing it in an agent's skill directory.
13
+ It teaches SSHScript usage, not development of SSHScript itself or a particular
14
+ recipe library. Agents without skill support can read these Markdown files.
15
+
16
+ ## Share one URL
17
+
18
+ The website entry is [SSHScript for AI Agents](https://iapyeh.github.io/sshscript/v3a/ai-agents/).
19
+ After these files are published, users can give that URL and a task to an agent:
20
+
21
+ > Read the SSHScript AI agent entry at
22
+ > https://iapyeh.github.io/sshscript/v3a/ai-agents/ and follow its reading order.
23
+ > Check the SSHScript version in my environment before selecting APIs. Help me
24
+ > complete this task: [describe the task, target hosts and expected output].
25
+ > If you cannot read a required document, report that limitation rather than
26
+ > guessing its contents. Explain the proposed commands before execution and
27
+ > stay within the operations I authorize.
28
+
29
+ A URL supplies reference material for the current task. It does not install
30
+ SSHScript, install a skill, grant execution permission, or guarantee persistent
31
+ memory. An agent needs URL-reading tools, or the user must provide the files.
32
+
33
+ ## Maintain the source and website
34
+
35
+ Maintain this entry, `skills/sshscript/SKILL.md`, and its reference guide in
36
+ the designated `working_branch` checkout on branch `working_branch`.
37
+ The landing page is maintained in `gh-pages/v3a/ai-agents/index.md` on branch
38
+ `gh-pages`. The API guide remains authoritative for public behavior.
39
+
40
+ From `working_branch`, synchronize the generated website guide, plain-text
41
+ files and portable skill ZIP with:
42
+
43
+ ```sh
44
+ python3 tools/sync_ai_docs.py ../gh-pages/v3a
45
+ python3 tools/sync_ai_docs.py ../gh-pages/v3a --check
46
+ ```
47
+
48
+ The ZIP includes the skill and its reference guide, plus snapshots of
49
+ `API_GUIDE.md` and `VERSIONING.md` for offline use. These snapshots are generated
50
+ from the source documents; do not edit the ZIP or generated website copies.
51
+ The sync command checks the destination branch before writing. Website pages
52
+ keep their Last Updated timestamp aligned with their mtime.
53
+
54
+ When the execution contract changes, review the skill and guide, update their
55
+ contract labels and the landing page, then regenerate the website assets.
56
+ Validate links, skill structure, example behavior, source/ZIP equality and
57
+ the rendered website. Source and website publishing are separate operations;
58
+ copying files does not publish either of them.
@@ -1,8 +1,8 @@
1
1
  # Recommended SSHScript API
2
2
 
3
3
  This is the canonical entry point for new code and AI-generated examples.
4
- **Current contract: 4.0.2**, Python 3.11+. Sections marked **4.0** require
5
- SSHScript 4.0.2; installing `sshscript==3.1.5` does not provide them.
4
+ **Current contract: 5.0.0**, Python 3.11+. Sections marked **4.0** require
5
+ SSHScript 4.0.2 or newer; installing `sshscript==3.1.5` does not provide them.
6
6
  Examples labelled 3.1.5 remain verified compatibility examples for that baseline.
7
7
  Pin the exact artifact version and record it with execution diagnostics when
8
8
  handing off automation. See [version policy and migration](VERSIONING.md).
@@ -31,7 +31,7 @@ identifiers and handoff reports are future work, not a current API guarantee.
31
31
 
32
32
  ## One command, one retained result (4.0; compatible with 3.1.5)
33
33
 
34
- Install the current release with `python -m pip install "sshscript==4.0.2"`.
34
+ Install the current release with `python -m pip install "sshscript==5.0.0"`.
35
35
  For reproducing only the historical compatibility baseline, explicitly install
36
36
  `sshscript==3.1.5`; the 4.0 sections require the current release.
37
37
  An argv list is one command, not a batch. Shell-looking argument text stays data.
@@ -88,6 +88,139 @@ with closing(Session()) as local:
88
88
  print(result.stdout)
89
89
  ```
90
90
 
91
+ ### Private-key paths and nested connections
92
+
93
+ `parent.connect(..., pkey_path=path)` loads an RSA private key from the
94
+ **calling parent Session's host**. A local Session reads the file on localhost;
95
+ a Session connected to host1 reads it on host1 through SFTP before connecting
96
+ to host2. `connect()` returns a child Session and leaves the parent on its
97
+ original host. Calling the original local Session again therefore still reads
98
+ the key on localhost.
99
+
100
+ For example, `local.connect("host1", pkey_path="/local/key_rsa")` reads
101
+ `/local/key_rsa` locally and returns `host1`. Then
102
+ `host1.connect("host2", pkey_path="/remote/key_rsa")` reads `/remote/key_rsa`
103
+ on host1. In `.spy`, an inner `with $.connect(...)` uses the outer scope's
104
+ current Session as its parent.
105
+
106
+ `key_filename=` is forwarded to Paramiko and reads from the local Python
107
+ process's filesystem even for nested connections; it supports additional key
108
+ formats. For an encrypted RSA key, use
109
+ `pkey=parent.pkey(path, password=key_passphrase)` to retain the same host-based
110
+ reading rule. Supplying both `pkey_path` and `pkey` raises `ValueError`.
111
+ See the [agent guide's complete nested example](https://iapyeh.github.io/sshscript/v3a/ai-agents/guide/#ssh-connections-and-private-key-paths).
112
+
113
+ <a id="host-key-sources-unreleased"></a>
114
+
115
+ ### Host-key sources (5.0)
116
+
117
+ This section requires SSHScript 5.0.0 or newer.
118
+ `known_hosts` selects the host-key trust source independently of `policy`:
119
+
120
+ | Value | Sources, in order |
121
+ | --- | --- |
122
+ | `"parent"` (new default) | Only the calling Session's host |
123
+ | `"local"` | Only the root localhost Session |
124
+ | `"chain"` | Calling Session, then each ancestor through localhost |
125
+
126
+ For localhost → host1 → host2 → host3, `host2.connect("host3",
127
+ known_hosts="chain")` consults host2, host1, then localhost. The nearest source
128
+ with a record for the resolved target hostname and port is authoritative.
129
+ A matching key is accepted; a changed key is rejected even if a more distant
130
+ source matches. Only absence of a target record permits continuing the chain.
131
+ Different key algorithms do not permit falling back to another source.
132
+
133
+ Each source reads its own `known_hosts_path`, or `~/.ssh/known_hosts` if unset.
134
+ Remote files are read over SFTP as that Session's original SSH login account;
135
+ SSH handshakes, user authentication and host-key verification still run in the
136
+ local Python process. Neither sudo/su nor a nested connection changes the SFTP
137
+ account. Remote `~/` paths refer to the login account's SFTP home; `~user` paths
138
+ are unsupported. Relative remote paths use the SFTP working directory.
139
+
140
+ ```py
141
+ local.set(known_hosts="chain")
142
+ # After connecting to host1 and host2:
143
+ host1.set(known_hosts_path="/home/ops/.ssh/known_hosts")
144
+ host2.set(known_hosts_path="/etc/ssh/ssh_known_hosts")
145
+ with host2.connect("deploy@host3", pkey_path="/home/ops/.ssh/host3_rsa") as host3:
146
+ result = host3.exec_command(["hostname"], check=True, command_timeout=10)
147
+ ```
148
+
149
+ The strategy is inherited by future child Sessions; the path is host-specific
150
+ and is **not inherited**. `known_hosts_path=None` restores the standard path.
151
+ A `connect(known_hosts=...)` override affects only that connection; it does not
152
+ change the parent setting or the child's inherited strategy. `set()`, `get()`
153
+ and matching properties expose both settings. `known_hosts_path` accepts text
154
+ paths and `PathLike[str]`; it is a Session setting, not a `connect()` keyword.
155
+
156
+ `pkey_path` always reads only from the calling Session's host, including with
157
+ `known_hosts="chain"`; private keys are never searched through ancestors.
158
+
159
+ An absent trust file, permission failure, read failure, invalid UTF-8 or invalid
160
+ entry raises an error; no distant source or permissive policy bypasses it.
161
+ Empty files, comments, plain host-key entries (including comma-separated names)
162
+ and hashed hostnames are supported. OpenSSH markers such as `@cert-authority`
163
+ and `@revoked`, wildcard/negated host patterns and unsupported key types raise
164
+ an error rather than being silently ignored. A source file is fully parsed
165
+ before its records are used. Nonstandard ports use `[hostname]:port` records.
166
+
167
+ When every selected source lacks the target, `policy` handles the unknown key.
168
+ `AutoAddPolicy` accepts it into the new client's memory only; these trust files
169
+ are read-only and are not automatically updated locally or remotely.
170
+
171
+ **Migration from 4.0.2:** earlier releases always load localhost host keys. The new default
172
+ `"parent"` makes nested host-key sources consistent with `pkey_path`. To retain
173
+ the previous behavior, set `local.set(known_hosts="local")` before connecting.
174
+ All strategies require a readable, valid file, including the default localhost
175
+ path. Unlike 4.0.2, a missing local file is an error; create an empty
176
+ file if the configured missing-key policy should handle unknown hosts.
177
+
178
+ ### Private keys readable only in a privileged console
179
+
180
+ Remote `pkey()` and `pkey_path` use SFTP with the original SSH login account.
181
+ Entering `sudo()` or `su()` does not change that identity, so even
182
+ `$.pkey("/root/.ssh/id_rsa")` inside a root console can fail with a permission
183
+ error. When authorized, read the file with a console command and construct
184
+ the Paramiko key from its captured text:
185
+
186
+ This manual example requires a real privileged console and SSH endpoints;
187
+ it is excluded from credential-free executable examples.
188
+
189
+ ```py
190
+ from contextlib import closing
191
+ from io import StringIO
192
+ import paramiko
193
+ from sshscript import Session
194
+
195
+ with closing(Session()) as local:
196
+ with local.connect("ops@host1.example.net", timeout=10,
197
+ banner_timeout=10, auth_timeout=10) as host1:
198
+ with host1.sudo() as root:
199
+ key_result = root("cat /root/.ssh/id_rsa", check=True, command_timeout=10)
200
+ key = paramiko.RSAKey.from_private_key(StringIO(key_result.stdout))
201
+ with host1.connect("deploy@host2.example.net", pkey=key, timeout=10,
202
+ banner_timeout=10, auth_timeout=10) as host2:
203
+ result = host2.exec_command(["hostname"], check=True, command_timeout=10)
204
+ print(result.stdout)
205
+ ```
206
+
207
+ The example requires authorized sudo/SSH access and verified host keys.
208
+ Configure output logging/display to keep private-key text out of logs before
209
+ running the read; do not print or report `key_result` or its stdout. Key text
210
+ also remains in console buffers. Use the matching Paramiko key class for
211
+ other formats; encrypted RSA keys need `password=` on `from_private_key()`.
212
+ An authorized `su()` console can supply the text in the same way.
213
+
214
+ The console returned by `shell()`/`sudo()`/`su()`/`enter()` has no `connect()`
215
+ method, so `$.connect()` is unavailable while a console is current in `.spy`.
216
+ A retained Python Session can still call `connect()` during an active console;
217
+ the connection uses its original SSH transport and does not inherit console
218
+ privilege. The example leaves the console first and calls the retained
219
+ `host1.connect(pkey=key)`, which needs no SFTP key read. See the
220
+ [agent guide's `.spy` equivalent](https://iapyeh.github.io/sshscript/v3a/ai-agents/guide/#keys-readable-only-after-sudosu).
221
+
222
+ ### Optional dollar syntax
223
+
91
224
  Save dollar syntax as `.spy`, run with `sshscript file.spy`, and syntax-check
92
225
  without execution using `sshscript --check file.spy`.
93
226
 
@@ -101,7 +234,7 @@ print(result.stdout)
101
234
 
102
235
  ## 4.0: settings and managed jobs
103
236
 
104
- Use SSHScript 4.0.2 for the following examples. CLI `-v`, `--stderr`, and
237
+ Use SSHScript 5.0.0 for the following examples. CLI `-v`, `--stderr`, and
105
238
  `--debug` override root defaults only for that execution. Child Sessions copy
106
239
  their parent's settings. `set()` validates before changing anything; `get()`
107
240
  returns a copy. Explicit command options override Session policy.
@@ -120,7 +253,7 @@ the settings dictionary at creation; the policy object itself is shared, so
120
253
  custom policies with mutable state must account for reuse. Changing the setting
121
254
  does not reconfigure existing connections.
122
255
 
123
- <!-- example: {"id":"settings", "profile":"4.0.2", "stdout":"True\n1\n"} -->
256
+ <!-- example: {"id":"settings", "profile":"5.0.0", "stdout":"True\n1\n"} -->
124
257
  ```python
125
258
  from contextlib import closing
126
259
  import paramiko
@@ -142,7 +275,7 @@ semantics; do not combine it with `command_timeout`.
142
275
  For an indefinite command such as tcpdump, use `start(timeout=None)`. This safe
143
276
  local example uses a small Python process instead of capturing network traffic:
144
277
 
145
- <!-- example: {"id":"stop", "profile":"4.0.2", "stdout":"cancelled confirmed\n"} -->
278
+ <!-- example: {"id":"stop", "profile":"5.0.0", "stdout":"cancelled confirmed\n"} -->
146
279
  ```python
147
280
  from contextlib import closing
148
281
  import sys
@@ -181,7 +314,7 @@ legacy SSH behavior which appended a newline to strings: if a consumer
181
314
  needs a line, migrate `input=password` to `input=password + "\n"` explicitly.
182
315
  Published 3.1.5 still has the legacy SSH behavior.
183
316
 
184
- <!-- example: {"id":"exact-stdin", "profile":"4.0.2", "stdout":"'abc'\n'abc\\n'\n"} -->
317
+ <!-- example: {"id":"exact-stdin", "profile":"5.0.0", "stdout":"'abc'\n'abc\\n'\n"} -->
185
318
  ```python
186
319
  from contextlib import closing
187
320
  import sys
@@ -250,7 +383,7 @@ or unpack exactly `stdout, stderr, exitcode`. This changes the released 3.1.5
250
383
  console contract: old `stdout, stderr = shell(command)` must be migrated.
251
384
  Shell calls accept command strings; argv execution belongs to Session.
252
385
 
253
- <!-- example: {"id":"shell-result", "profile":"4.0.2", "stdout":"first 0\n"} -->
386
+ <!-- example: {"id":"shell-result", "profile":"5.0.0", "stdout":"first 0\n"} -->
254
387
  ```python
255
388
  from contextlib import closing
256
389
  from sshscript import Session
@@ -306,7 +439,7 @@ matching: `iter_stdout()` yields chunks, not packets or lines. Put `stop()` in
306
439
  `finally`, so condition matches, exceptions, and KeyboardInterrupt all trigger
307
440
  cleanup. This credential-free example uses a continuous Python process:
308
441
 
309
- <!-- example: {"id":"stream-condition", "profile":"4.0.2", "stdout":"cancelled confirmed\n"} -->
442
+ <!-- example: {"id":"stream-condition", "profile":"5.0.0", "stdout":"cancelled confirmed\n"} -->
310
443
  ```python
311
444
  from contextlib import closing
312
445
  import sys
@@ -371,7 +504,7 @@ the current Bash console's channel, cwd, environment and identity. It does not
371
504
  launch through the underlying Session. Ordinary `console(command)` / `$command`
372
505
  still waits and returns `CommandResult`; no migration is needed for finite commands.
373
506
 
374
- <!-- example: {"id":"console-stream", "profile":"4.0.2", "stdout":"cancelled confirmed\nconsole-alive\n"} -->
507
+ <!-- example: {"id":"console-stream", "profile":"5.0.0", "stdout":"cancelled confirmed\nconsole-alive\n"} -->
375
508
  ```python
376
509
  from contextlib import closing
377
510
  import shlex
@@ -511,7 +644,7 @@ remains available for other authentication prompt formats.
511
644
  These transcripts require a real local account and its authentication policy;
512
645
  they are manual examples, separate from the credential-free CI examples above.
513
646
  Read passwords with `getpass` rather than storing them in the script. The new
514
- behavior requires 4.0.2 and does not apply to the installed 3.1.5 release.
647
+ behavior requires 4.0.2 or newer and does not apply to the installed 3.1.5 release.
515
648
 
516
649
  Open a root console through sudo and allow up to 15 seconds for entry:
517
650
 
@@ -3,6 +3,18 @@
3
3
  This project records user-visible changes here. Release artifacts and their
4
4
  provenance are available from the linked GitHub Release and PyPI pages.
5
5
 
6
+ ## 5.0.0 — 2026-10-10
7
+
8
+ - Add `known_hosts="parent"` (new default), `"local"` and `"chain"` strategies.
9
+ Nested connections now use the calling host's trust file by default, matching
10
+ `pkey_path` ownership. Set `known_hosts="local"` for previous behavior.
11
+ - Add per-Session `known_hosts_path` without path inheritance. Chain lookup uses
12
+ the nearest target record; changed keys and read/parse errors never fall back.
13
+ Authentication and verification still run locally; trust files are read-only.
14
+ - Isolate connection-timeout tests from runner HOME and verify client cleanup.
15
+ - Document host-key source selection, strict file errors and migration in the
16
+ API, website and portable AI agent skill.
17
+
6
18
  ## 4.0.2 — 2026-10-09
7
19
 
8
20
  - Align the README, current installation commands, website title, navigation,
@@ -1,4 +1,7 @@
1
1
  include README.md LICENSE.txt RELEASING.md
2
+ include AI_GUIDE.md
3
+ include skills/sshscript/SKILL.md
4
+ include skills/sshscript/references/agent-guide.md
2
5
  include CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md EXCEPTIONS.md SECURITY.md SUPPORT.md
3
6
  recursive-include tools *.py
4
7
  include tools/setup_openssh_ci.sh
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sshscript
3
- Version: 4.0.2
3
+ Version: 5.0.0
4
4
  Summary: Python automation for local processes, SSH, and SSHScript .spy files
5
5
  Author-email: "Yeh, Hsin-Yuan" <iapyeh@gmail.com>
6
6
  License-Expression: MIT
@@ -37,9 +37,12 @@ Dynamic: license-file
37
37
  [![CodeQL](https://github.com/iapyeh/sshscript/actions/workflows/codeql.yml/badge.svg?branch=release)](https://github.com/iapyeh/sshscript/actions/workflows/codeql.yml)
38
38
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/iapyeh/sshscript/blob/release/LICENSE.txt)
39
39
 
40
+ **AI agent ready automation for local and remote commands.**
41
+
40
42
  SSHScript is a Python automation library for running commands locally and over
41
- SSH through one `Session` API. It also provides optional dollar syntax for
42
- compact `.spy` automation files.
43
+ SSH through one `Session` API. It provides an agent guide, a portable skill
44
+ bundle, and copyable prompts to help your AI agent use its execution contract.
45
+ Optional dollar syntax supports compact `.spy` automation files.
43
46
 
44
47
  SSHScript aims to help engineers and AI agents execute, understand, and hand
45
48
  off automation reliably: identify the execution host, retain each command's
@@ -47,7 +50,7 @@ output and status, distinguish failure from timeout, and make cleanup and
47
50
  unknown termination explicit. A successful command exit is evidence about
48
51
  that command; verify the deployment or other application outcome separately.
49
52
 
50
- **Current release:** [4.0.2](https://github.com/iapyeh/sshscript/releases/tag/v4.0.2)
53
+ **Current release:** [5.0.0](https://github.com/iapyeh/sshscript/releases/tag/v5.0.0)
51
54
  (Production/Stable) · **Python:** 3.11 or newer · **Tested:** Python
52
55
  3.11–3.14 on Linux and macOS
53
56
 
@@ -57,11 +60,45 @@ that command; verify the deployment or other application outcome separately.
57
60
  [Security](https://github.com/iapyeh/sshscript/security/policy) ·
58
61
  [Support](https://github.com/iapyeh/sshscript/blob/release/SUPPORT.md)
59
62
 
60
- **Version 4.0.2** maintains the 4.0 execution contract introduced in 4.0.1.
61
- This patch aligns the published documentation and installation links.
63
+ **Version 5.0.0** preserves the 4.0 command execution contract and changes the
64
+ default host-key source to the calling host. Provision its known_hosts file or
65
+ select `known_hosts="local"` when migrating existing nested connections.
62
66
  Upgrade 3.1.5 automation using the migration guide and application regressions.
63
67
  See [version policy and migration](VERSIONING.md) before upgrading automation.
64
68
 
69
+ ## AI agent ready
70
+
71
+ **Give your agent one URL:**
72
+ [SSHScript for AI Agents](https://iapyeh.github.io/sshscript/v3a/ai-agents/).
73
+ Prepare it for future automation or give it a task to complete now.
74
+
75
+ - **A guide for agents:** version-aware API selection, exact input handling,
76
+ retained command results, timeouts, authentication and cleanup.
77
+ - **A portable skill bundle:** `SKILL.md` with the agent guide and API and
78
+ version-policy references, ready to install through your agent's skill
79
+ mechanism.
80
+ - **Prompts for humans:** copy a setup prompt to request skill installation and
81
+ a saved tool preference, or a task prompt to describe hosts, inputs and
82
+ expected results.
83
+
84
+ Start with this prompt, even before you have a task:
85
+
86
+ ```text
87
+ Read https://iapyeh.github.io/sshscript/v3a/ai-agents/ and the Agent Guide.
88
+ If you support skills, download and install the complete skill bundle. If you
89
+ support persistent preferences, save this preference: for future local or
90
+ remote command execution and automation, first assess whether SSHScript is
91
+ suitable and use it when appropriate. Report whether the skill can be loaded
92
+ and the preference was saved, and explain any limits.
93
+ For now, do not connect to remote hosts or run demonstration commands.
94
+ ```
95
+
96
+ The guides are ordinary Markdown and web pages for agents that can read files
97
+ or URLs. Skill installation and persistent preferences depend on the agent's
98
+ capabilities. See the [repository AI entry](AI_GUIDE.md),
99
+ [agent guide](skills/sshscript/references/agent-guide.md) and
100
+ [skill instructions](skills/sshscript/SKILL.md) for the source documents.
101
+
65
102
  ## Recommended API
66
103
 
67
104
  Start with [the canonical API guide](API_GUIDE.md): version-labelled examples,
@@ -83,6 +120,7 @@ and [the cross-system test guide](src/sshscript/unittest/README.console-authenti
83
120
 
84
121
  ## Why SSHScript?
85
122
 
123
+ - Prepare your AI agent with a dedicated guide, portable skill and setup prompts.
86
124
  - Use the same interface for local subprocesses and remote SSH commands.
87
125
  - Traverse nested SSH connections without rebuilding connection logic.
88
126
  - Keep ordinary Python functions, packages, exceptions, data processing, and
@@ -115,7 +153,7 @@ pin SSHScript and all transitive dependencies in your application's lock file.
115
153
  To install this release explicitly:
116
154
 
117
155
  ```sh
118
- python3 -m pip install "sshscript==4.0.2"
156
+ python3 -m pip install "sshscript==5.0.0"
119
157
  ```
120
158
 
121
159
  Use `python3 -m pip install --upgrade sshscript` to upgrade. The optional
@@ -124,7 +162,7 @@ it never installs an update by itself.
124
162
 
125
163
  ## 60-second local quickstart
126
164
 
127
- This example runs on 4.0.2 and retains compatibility with the 3.1.5 command API.
165
+ This example runs on 5.0.0 and retains compatibility with the 3.1.5 command API.
128
166
  The metadata below records that older compatibility baseline.
129
167
 
130
168
  <!-- example: {"id":"readme-quickstart", "profile":"3.1.5", "stdout":"sshscript is ready\nexit code: 0\n"} -->
@@ -380,6 +418,24 @@ verified the key. See the
380
418
  nested connections, timeouts, file transfer, `sudo`, `su`, and interactive
381
419
  programs.
382
420
 
421
+ ## Host-key sources in 5.0
422
+
423
+ SSHScript 5.0.0 uses `known_hosts="parent"` as the default: a local
424
+ Session reads localhost's trust file; `host1.connect("host2")` reads host1's
425
+ trust file over SFTP. This matches the host ownership of `pkey_path`.
426
+ SSHScript 4.0.2 and earlier use localhost for all host-key checks.
427
+
428
+ Set `session.set(known_hosts="chain")` to search from the calling Session
429
+ through its ancestors to localhost. The nearest layer with a target record
430
+ wins; a mismatch or read/parse error stops the connection. Set
431
+ `session.set(known_hosts="local")` to preserve previous behavior.
432
+ Each Session can set its own `known_hosts_path`; this path is not inherited.
433
+ `connect(known_hosts=...)` overrides only one connection. `pkey_path` remains
434
+ on the calling host for every strategy. `policy` handles only unknown keys;
435
+ `AutoAddPolicy` does not write these trust files. See the
436
+ [host-key source contract](API_GUIDE.md#host-key-sources-unreleased) for supported
437
+ file formats, strict error handling and migration.
438
+
383
439
  ## Reusing SSH configuration
384
440
 
385
441
  Connections from a local session read `~/.ssh/config` if it exists. Supported
@@ -6,9 +6,12 @@
6
6
  [![CodeQL](https://github.com/iapyeh/sshscript/actions/workflows/codeql.yml/badge.svg?branch=release)](https://github.com/iapyeh/sshscript/actions/workflows/codeql.yml)
7
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/iapyeh/sshscript/blob/release/LICENSE.txt)
8
8
 
9
+ **AI agent ready automation for local and remote commands.**
10
+
9
11
  SSHScript is a Python automation library for running commands locally and over
10
- SSH through one `Session` API. It also provides optional dollar syntax for
11
- compact `.spy` automation files.
12
+ SSH through one `Session` API. It provides an agent guide, a portable skill
13
+ bundle, and copyable prompts to help your AI agent use its execution contract.
14
+ Optional dollar syntax supports compact `.spy` automation files.
12
15
 
13
16
  SSHScript aims to help engineers and AI agents execute, understand, and hand
14
17
  off automation reliably: identify the execution host, retain each command's
@@ -16,7 +19,7 @@ output and status, distinguish failure from timeout, and make cleanup and
16
19
  unknown termination explicit. A successful command exit is evidence about
17
20
  that command; verify the deployment or other application outcome separately.
18
21
 
19
- **Current release:** [4.0.2](https://github.com/iapyeh/sshscript/releases/tag/v4.0.2)
22
+ **Current release:** [5.0.0](https://github.com/iapyeh/sshscript/releases/tag/v5.0.0)
20
23
  (Production/Stable) · **Python:** 3.11 or newer · **Tested:** Python
21
24
  3.11–3.14 on Linux and macOS
22
25
 
@@ -26,11 +29,45 @@ that command; verify the deployment or other application outcome separately.
26
29
  [Security](https://github.com/iapyeh/sshscript/security/policy) ·
27
30
  [Support](https://github.com/iapyeh/sshscript/blob/release/SUPPORT.md)
28
31
 
29
- **Version 4.0.2** maintains the 4.0 execution contract introduced in 4.0.1.
30
- This patch aligns the published documentation and installation links.
32
+ **Version 5.0.0** preserves the 4.0 command execution contract and changes the
33
+ default host-key source to the calling host. Provision its known_hosts file or
34
+ select `known_hosts="local"` when migrating existing nested connections.
31
35
  Upgrade 3.1.5 automation using the migration guide and application regressions.
32
36
  See [version policy and migration](VERSIONING.md) before upgrading automation.
33
37
 
38
+ ## AI agent ready
39
+
40
+ **Give your agent one URL:**
41
+ [SSHScript for AI Agents](https://iapyeh.github.io/sshscript/v3a/ai-agents/).
42
+ Prepare it for future automation or give it a task to complete now.
43
+
44
+ - **A guide for agents:** version-aware API selection, exact input handling,
45
+ retained command results, timeouts, authentication and cleanup.
46
+ - **A portable skill bundle:** `SKILL.md` with the agent guide and API and
47
+ version-policy references, ready to install through your agent's skill
48
+ mechanism.
49
+ - **Prompts for humans:** copy a setup prompt to request skill installation and
50
+ a saved tool preference, or a task prompt to describe hosts, inputs and
51
+ expected results.
52
+
53
+ Start with this prompt, even before you have a task:
54
+
55
+ ```text
56
+ Read https://iapyeh.github.io/sshscript/v3a/ai-agents/ and the Agent Guide.
57
+ If you support skills, download and install the complete skill bundle. If you
58
+ support persistent preferences, save this preference: for future local or
59
+ remote command execution and automation, first assess whether SSHScript is
60
+ suitable and use it when appropriate. Report whether the skill can be loaded
61
+ and the preference was saved, and explain any limits.
62
+ For now, do not connect to remote hosts or run demonstration commands.
63
+ ```
64
+
65
+ The guides are ordinary Markdown and web pages for agents that can read files
66
+ or URLs. Skill installation and persistent preferences depend on the agent's
67
+ capabilities. See the [repository AI entry](AI_GUIDE.md),
68
+ [agent guide](skills/sshscript/references/agent-guide.md) and
69
+ [skill instructions](skills/sshscript/SKILL.md) for the source documents.
70
+
34
71
  ## Recommended API
35
72
 
36
73
  Start with [the canonical API guide](API_GUIDE.md): version-labelled examples,
@@ -52,6 +89,7 @@ and [the cross-system test guide](src/sshscript/unittest/README.console-authenti
52
89
 
53
90
  ## Why SSHScript?
54
91
 
92
+ - Prepare your AI agent with a dedicated guide, portable skill and setup prompts.
55
93
  - Use the same interface for local subprocesses and remote SSH commands.
56
94
  - Traverse nested SSH connections without rebuilding connection logic.
57
95
  - Keep ordinary Python functions, packages, exceptions, data processing, and
@@ -84,7 +122,7 @@ pin SSHScript and all transitive dependencies in your application's lock file.
84
122
  To install this release explicitly:
85
123
 
86
124
  ```sh
87
- python3 -m pip install "sshscript==4.0.2"
125
+ python3 -m pip install "sshscript==5.0.0"
88
126
  ```
89
127
 
90
128
  Use `python3 -m pip install --upgrade sshscript` to upgrade. The optional
@@ -93,7 +131,7 @@ it never installs an update by itself.
93
131
 
94
132
  ## 60-second local quickstart
95
133
 
96
- This example runs on 4.0.2 and retains compatibility with the 3.1.5 command API.
134
+ This example runs on 5.0.0 and retains compatibility with the 3.1.5 command API.
97
135
  The metadata below records that older compatibility baseline.
98
136
 
99
137
  <!-- example: {"id":"readme-quickstart", "profile":"3.1.5", "stdout":"sshscript is ready\nexit code: 0\n"} -->
@@ -349,6 +387,24 @@ verified the key. See the
349
387
  nested connections, timeouts, file transfer, `sudo`, `su`, and interactive
350
388
  programs.
351
389
 
390
+ ## Host-key sources in 5.0
391
+
392
+ SSHScript 5.0.0 uses `known_hosts="parent"` as the default: a local
393
+ Session reads localhost's trust file; `host1.connect("host2")` reads host1's
394
+ trust file over SFTP. This matches the host ownership of `pkey_path`.
395
+ SSHScript 4.0.2 and earlier use localhost for all host-key checks.
396
+
397
+ Set `session.set(known_hosts="chain")` to search from the calling Session
398
+ through its ancestors to localhost. The nearest layer with a target record
399
+ wins; a mismatch or read/parse error stops the connection. Set
400
+ `session.set(known_hosts="local")` to preserve previous behavior.
401
+ Each Session can set its own `known_hosts_path`; this path is not inherited.
402
+ `connect(known_hosts=...)` overrides only one connection. `pkey_path` remains
403
+ on the calling host for every strategy. `policy` handles only unknown keys;
404
+ `AutoAddPolicy` does not write these trust files. See the
405
+ [host-key source contract](API_GUIDE.md#host-key-sources-unreleased) for supported
406
+ file formats, strict error handling and migration.
407
+
352
408
  ## Reusing SSH configuration
353
409
 
354
410
  Connections from a local session read `~/.ssh/config` if it exists. Supported
@@ -65,11 +65,20 @@ termination. Verify application outcomes and reconcile unknown termination.
65
65
 
66
66
  ### SSH identity and host keys
67
67
 
68
- SSH connections load system host keys and reject unknown or changed keys by
69
- default. The application is responsible for provisioning `known_hosts` and
68
+ SSH connections reject unknown or changed keys by default. SSHScript 4.0.2 and earlier
69
+ load localhost system host keys. SSHScript 5.0.0 defaults to
70
+ `known_hosts="parent"`, reading the calling Session host's trust file. Remote
71
+ trust files are read over SFTP using the original SSH login account; verification
72
+ still runs locally. `"chain"` trusts the nearest source with a target record;
73
+ it never falls back after a mismatch or file error. Trusting a parent's records
74
+ also trusts that host/account to maintain them. Use `"local"` to retain the
75
+ previous source selection. See the [host-key source contract](API_GUIDE.md#host-key-sources-unreleased).
76
+
77
+ The application is responsible for provisioning `known_hosts` and
70
78
  verifying a new fingerprint through an independent channel. Supplying an
71
79
  automatic-acceptance Paramiko policy is an explicit opt-out from this default
72
80
  and is not recommended for production enrollment.
81
+ AutoAddPolicy does not persist keys into these local or remote trust files.
73
82
 
74
83
  SSH authentication ultimately follows Paramiko and server policy. Prefer an
75
84
  SSH agent, managed key, or secret manager. Never store passwords or private
@@ -9,7 +9,8 @@ claim that a command or deployment is safe or successful.
9
9
  | Version | Contract and status |
10
10
  | --- | --- |
11
11
  | 3.1.5 | Published baseline described in API_GUIDE.md; one-shot Session commands return CommandResult, but persistent console calls return two live buffers. |
12
- | 4.0.2 | Production contract: completed console results, settings, managed jobs and authenticated-console handshakes; incompatible with parts of 3.1.5. |
12
+ | 4.0.2 | Previous production contract: completed console results, settings, managed jobs and authenticated-console handshakes; incompatible with parts of 3.1.5. |
13
+ | 5.0.0 | Current production contract: calling-host trust by default, explicit local/chain strategies and strict known_hosts file errors; preserves the 4.0 command execution contract. |
13
14
 
14
15
  Version 4.0.1 introduced the published 4.0 contract after release validation.
15
16
  Version 4.0.2 is a documentation and link maintenance patch with the same public
@@ -17,6 +18,22 @@ execution contract. Native-system validation limits remain documented.
17
18
  Use the normal Python Session API as the canonical entry point; optional .spy
18
19
  syntax follows the same versioned execution contract.
19
20
 
21
+ ## 5.0 host-key source migration
22
+
23
+ SSHScript 5.0.0 defaults `known_hosts` to `"parent"`: nested
24
+ connections read the calling Session host's known_hosts through SFTP.
25
+ 4.0.2 reads localhost's known_hosts for every connection. Existing nested
26
+ workflows relying on localhost trust records must opt into
27
+ `session.set(known_hosts="local")`, or provision the parent's trust file.
28
+ The new default requires a readable, valid trust file, even for a local
29
+ Session; an empty file allows the configured missing-key policy to decide.
30
+ `"chain"` searches the nearest host first and falls back only when no target
31
+ record exists, never after a mismatch or file error. `pkey_path` ownership is
32
+ unchanged. See [host-key sources](API_GUIDE.md#host-key-sources-unreleased).
33
+ This default and the strict file-error behavior are incompatible with 4.0.2,
34
+ so they are introduced in a new major version. Explicit `known_hosts="local"`
35
+ preserves the previous source location, while still requiring a readable file.
36
+
20
37
  ## Numbering and compatibility
21
38
 
22
39
  Versions follow PEP 440 with major.minor.patch compatibility semantics: