dsh-remote 0.8.45 → 0.8.47
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.
- package/LICENSE +21 -21
- package/README.en.md +361 -361
- package/README.md +368 -368
- package/cordis.patch.yml +9 -9
- package/lib/binding.js +172 -172
- package/lib/client.js +3637 -3623
- package/lib/credential.js +177 -177
- package/lib/deploy-tasks.js +142 -142
- package/lib/errors.js +87 -87
- package/lib/file-reference.js +457 -457
- package/lib/forwards.js +258 -258
- package/lib/hostkey.js +108 -108
- package/lib/http-transport.js +196 -196
- package/lib/ignore.js +156 -156
- package/lib/index.js +3771 -3747
- package/lib/openssh.js +838 -838
- package/lib/paths.js +225 -225
- package/lib/pool.js +757 -754
- package/lib/registry.js +95 -95
- package/lib/remote-fs.js +148 -143
- package/lib/routes-fs.js +233 -233
- package/lib/search.js +230 -230
- package/lib/sftp-surface.js +76 -76
- package/lib/sshconfig.js +454 -454
- package/lib/sync.js +275 -275
- package/lib/tasks.js +95 -95
- package/lib/telemetry.js +213 -213
- package/lib/update.js +472 -472
- package/lib/web-attach.js +850 -846
- package/lib/web-deploy-skill.js +205 -205
- package/lib/web-deploy.js +694 -694
- package/node_modules/asn1/Jenkinsfile +65 -0
- package/node_modules/asn1/LICENSE +19 -0
- package/node_modules/asn1/README.md +50 -0
- package/node_modules/asn1/lib/ber/errors.js +13 -0
- package/node_modules/asn1/lib/ber/index.js +27 -0
- package/node_modules/asn1/lib/ber/reader.js +262 -0
- package/node_modules/asn1/lib/ber/types.js +36 -0
- package/node_modules/asn1/lib/ber/writer.js +317 -0
- package/node_modules/asn1/lib/index.js +20 -0
- package/node_modules/asn1/package.json +31 -0
- package/node_modules/bcrypt-pbkdf/CONTRIBUTING.md +13 -0
- package/node_modules/bcrypt-pbkdf/LICENSE +66 -0
- package/node_modules/bcrypt-pbkdf/README.md +45 -0
- package/node_modules/bcrypt-pbkdf/index.js +556 -0
- package/node_modules/bcrypt-pbkdf/package.json +15 -0
- package/node_modules/buildcheck/.eslintrc.js +5 -0
- package/node_modules/buildcheck/.github/workflows/ci.yml +88 -0
- package/node_modules/buildcheck/.github/workflows/lint.yml +27 -0
- package/node_modules/buildcheck/.npmignore +3 -0
- package/node_modules/buildcheck/LICENSE +19 -0
- package/node_modules/buildcheck/README.md +106 -0
- package/node_modules/buildcheck/deps/Find-VisualStudio.cs +250 -0
- package/node_modules/buildcheck/lib/findvs.js +361 -0
- package/node_modules/buildcheck/lib/index.js +738 -0
- package/node_modules/buildcheck/package.json +36 -0
- package/node_modules/buildcheck/test/test.js +8 -0
- package/node_modules/cpu-features/.eslintrc.js +5 -0
- package/node_modules/cpu-features/.github/workflows/ci.yml +74 -0
- package/node_modules/cpu-features/.github/workflows/lint.yml +27 -0
- package/node_modules/cpu-features/LICENSE +19 -0
- package/node_modules/cpu-features/README.md +59 -0
- package/node_modules/cpu-features/binding.gyp +16 -0
- package/node_modules/cpu-features/buildcheck.js +32 -0
- package/node_modules/cpu-features/deps/cpu_features/.clang-format +4 -0
- package/node_modules/cpu-features/deps/cpu_features/.dockerignore +31 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/Dockerfile +5 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/aarch64_linux_cmake.yml +30 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_freebsd_cmake.yml +22 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_linux_bazel.yml +26 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_linux_cmake.yml +31 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_macos_cmake.yml +43 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/amd64_windows_cmake.yml +25 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/arm_linux_cmake.yml +31 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/clang_format.yml +24 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/mips_linux_cmake.yml +30 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/power_linux_cmake.yml +29 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/riscv_linux_cmake.yml +28 -0
- package/node_modules/cpu-features/deps/cpu_features/.github/workflows/s390x_linux_cmake.yml +27 -0
- package/node_modules/cpu-features/deps/cpu_features/.grenrc.yml +21 -0
- package/node_modules/cpu-features/deps/cpu_features/BUILD.bazel +329 -0
- package/node_modules/cpu-features/deps/cpu_features/CMakeLists.txt +261 -0
- package/node_modules/cpu-features/deps/cpu_features/CONTRIBUTING.md +23 -0
- package/node_modules/cpu-features/deps/cpu_features/LICENSE +230 -0
- package/node_modules/cpu-features/deps/cpu_features/README.md +272 -0
- package/node_modules/cpu-features/deps/cpu_features/WORKSPACE +19 -0
- package/node_modules/cpu-features/deps/cpu_features/bazel/ci/README.md +5 -0
- package/node_modules/cpu-features/deps/cpu_features/bazel/platforms.bzl +11 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/CpuFeaturesConfig.cmake.in +3 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/CpuFeaturesNdkCompatConfig.cmake.in +3 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/README.md +30 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/Makefile +252 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/README.md +40 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/docker.dot +64 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/docker.svg +312 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/doc/generate_image.sh +7 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/docker/amd64/Dockerfile +48 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/docker/toolchain/Dockerfile +34 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/sample/CMakeLists.txt +22 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/sample/main.cpp +11 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/ci/vagrant/freebsd/Vagrantfile +107 -0
- package/node_modules/cpu-features/deps/cpu_features/cmake/googletest.CMakeLists.txt.in +15 -0
- package/node_modules/cpu-features/deps/cpu_features/cpu_features.gyp +105 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpu_features_cache_info.h +54 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpu_features_macros.h +384 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_aarch64.h +259 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_arm.h +121 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_mips.h +74 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_ppc.h +149 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_riscv.h +72 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_s390x.h +108 -0
- package/node_modules/cpu-features/deps/cpu_features/include/cpuinfo_x86.h +288 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/bit_utils.h +40 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/cpuid_x86.h +37 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/filesystem.h +39 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/hwcaps.h +240 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/stack_line_reader.h +49 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/string_view.h +110 -0
- package/node_modules/cpu-features/deps/cpu_features/include/internal/windows_utils.h +70 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/CMakeLists.txt +60 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/README.md +4 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/cpu-features.c +205 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/cpu-features.h +320 -0
- package/node_modules/cpu-features/deps/cpu_features/ndk_compat/ndk-compat-test.c +12 -0
- package/node_modules/cpu-features/deps/cpu_features/patches/0001-Add-Apple-Silicon-Support.patch +456 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/generate_badges.d +165 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/make_release.sh +75 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/run_integration.sh +490 -0
- package/node_modules/cpu-features/deps/cpu_features/scripts/test_integration.sh +79 -0
- package/node_modules/cpu-features/deps/cpu_features/src/copy.inl +19 -0
- package/node_modules/cpu-features/deps/cpu_features/src/define_introspection.inl +86 -0
- package/node_modules/cpu-features/deps/cpu_features/src/define_introspection_and_hwcaps.inl +26 -0
- package/node_modules/cpu-features/deps/cpu_features/src/equals.inl +22 -0
- package/node_modules/cpu-features/deps/cpu_features/src/filesystem.c +62 -0
- package/node_modules/cpu-features/deps/cpu_features/src/hwcaps.c +169 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64__base_implementation.inl +88 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_linux_or_android.c +79 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_macos_or_iphone.c +82 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_aarch64_windows.c +138 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_arm_linux_or_android.c +212 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_mips_linux_or_android.c +93 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_ppc_linux.c +163 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_riscv_linux.c +111 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_s390x_linux.c +120 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86__base_implementation.inl +2055 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_freebsd.c +68 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_linux_or_android.c +58 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_macos.c +57 -0
- package/node_modules/cpu-features/deps/cpu_features/src/impl_x86_windows.c +58 -0
- package/node_modules/cpu-features/deps/cpu_features/src/stack_line_reader.c +132 -0
- package/node_modules/cpu-features/deps/cpu_features/src/string_view.c +192 -0
- package/node_modules/cpu-features/deps/cpu_features/src/utils/list_cpu_features.c +461 -0
- package/node_modules/cpu-features/deps/cpu_features/test/CMakeLists.txt +114 -0
- package/node_modules/cpu-features/deps/cpu_features/test/bit_utils_test.cc +53 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_aarch64_test.cc +396 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_arm_test.cc +384 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_mips_test.cc +176 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_ppc_test.cc +131 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_riscv_test.cc +180 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_s390x_test.cc +82 -0
- package/node_modules/cpu-features/deps/cpu_features/test/cpuinfo_x86_test.cc +1729 -0
- package/node_modules/cpu-features/deps/cpu_features/test/filesystem_for_testing.cc +103 -0
- package/node_modules/cpu-features/deps/cpu_features/test/filesystem_for_testing.h +61 -0
- package/node_modules/cpu-features/deps/cpu_features/test/hwcaps_for_testing.cc +52 -0
- package/node_modules/cpu-features/deps/cpu_features/test/hwcaps_for_testing.h +31 -0
- package/node_modules/cpu-features/deps/cpu_features/test/stack_line_reader_test.cc +132 -0
- package/node_modules/cpu-features/deps/cpu_features/test/string_view_test.cc +202 -0
- package/node_modules/cpu-features/lib/index.js +5 -0
- package/node_modules/cpu-features/package.json +40 -0
- package/node_modules/cpu-features/src/binding.cc +151 -0
- package/node_modules/cpu-features/test/test.js +12 -0
- package/node_modules/nan/.github/workflows/ci.yml +46 -0
- package/node_modules/nan/.pre-commit-config.yaml +8 -0
- package/node_modules/nan/CHANGELOG.md +605 -0
- package/node_modules/nan/CMakeLists.txt +138 -0
- package/node_modules/nan/LICENSE.md +9 -0
- package/node_modules/nan/README.md +457 -0
- package/node_modules/nan/doc/asyncworker.md +146 -0
- package/node_modules/nan/doc/buffers.md +54 -0
- package/node_modules/nan/doc/callback.md +76 -0
- package/node_modules/nan/doc/converters.md +41 -0
- package/node_modules/nan/doc/errors.md +226 -0
- package/node_modules/nan/doc/json.md +62 -0
- package/node_modules/nan/doc/maybe_types.md +583 -0
- package/node_modules/nan/doc/methods.md +689 -0
- package/node_modules/nan/doc/new.md +147 -0
- package/node_modules/nan/doc/node_misc.md +123 -0
- package/node_modules/nan/doc/object_wrappers.md +263 -0
- package/node_modules/nan/doc/persistent.md +296 -0
- package/node_modules/nan/doc/scopes.md +73 -0
- package/node_modules/nan/doc/script.md +58 -0
- package/node_modules/nan/doc/string_bytes.md +81 -0
- package/node_modules/nan/doc/v8_internals.md +211 -0
- package/node_modules/nan/doc/v8_misc.md +85 -0
- package/node_modules/nan/include_dirs.js +1 -0
- package/node_modules/nan/nan.h +3202 -0
- package/node_modules/nan/nan_callbacks.h +141 -0
- package/node_modules/nan/nan_callbacks_12_inl.h +730 -0
- package/node_modules/nan/nan_callbacks_pre_12_inl.h +528 -0
- package/node_modules/nan/nan_converters.h +72 -0
- package/node_modules/nan/nan_converters_43_inl.h +68 -0
- package/node_modules/nan/nan_converters_pre_43_inl.h +42 -0
- package/node_modules/nan/nan_define_own_property_helper.h +29 -0
- package/node_modules/nan/nan_implementation_12_inl.h +446 -0
- package/node_modules/nan/nan_implementation_pre_12_inl.h +263 -0
- package/node_modules/nan/nan_json.h +166 -0
- package/node_modules/nan/nan_maybe_43_inl.h +360 -0
- package/node_modules/nan/nan_maybe_pre_43_inl.h +268 -0
- package/node_modules/nan/nan_new.h +340 -0
- package/node_modules/nan/nan_object_wrap.h +156 -0
- package/node_modules/nan/nan_persistent_12_inl.h +132 -0
- package/node_modules/nan/nan_persistent_pre_12_inl.h +242 -0
- package/node_modules/nan/nan_private.h +73 -0
- package/node_modules/nan/nan_scriptorigin.h +97 -0
- package/node_modules/nan/nan_string_bytes.h +305 -0
- package/node_modules/nan/nan_typedarray_contents.h +96 -0
- package/node_modules/nan/nan_weak.h +453 -0
- package/node_modules/nan/package.json +38 -0
- package/node_modules/nan/tools/1to2.js +412 -0
- package/node_modules/nan/tools/README.md +14 -0
- package/node_modules/nan/tools/package.json +19 -0
- package/node_modules/safer-buffer/LICENSE +21 -0
- package/node_modules/safer-buffer/Porting-Buffer.md +268 -0
- package/node_modules/safer-buffer/Readme.md +156 -0
- package/node_modules/safer-buffer/dangerous.js +58 -0
- package/node_modules/safer-buffer/package.json +34 -0
- package/node_modules/safer-buffer/safer.js +77 -0
- package/node_modules/safer-buffer/tests.js +406 -0
- package/node_modules/ssh2/.eslintignore +4 -0
- package/node_modules/ssh2/.eslintrc.js +5 -0
- package/node_modules/ssh2/.github/workflows/ci.yml +110 -0
- package/node_modules/ssh2/.github/workflows/lint.yml +27 -0
- package/node_modules/ssh2/LICENSE +19 -0
- package/node_modules/ssh2/README.md +1529 -0
- package/node_modules/ssh2/SFTP.md +413 -0
- package/node_modules/ssh2/examples/server-chat.js +238 -0
- package/node_modules/ssh2/examples/sftp-server-download-only.js +134 -0
- package/node_modules/ssh2/install.js +27 -0
- package/node_modules/ssh2/lib/Channel.js +295 -0
- package/node_modules/ssh2/lib/agent.js +1123 -0
- package/node_modules/ssh2/lib/client.js +2176 -0
- package/node_modules/ssh2/lib/http-agents.js +84 -0
- package/node_modules/ssh2/lib/index.js +44 -0
- package/node_modules/ssh2/lib/keygen.js +582 -0
- package/node_modules/ssh2/lib/protocol/Protocol.js +2136 -0
- package/node_modules/ssh2/lib/protocol/SFTP.js +4052 -0
- package/node_modules/ssh2/lib/protocol/constants.js +356 -0
- package/node_modules/ssh2/lib/protocol/crypto/binding.gyp +23 -0
- package/node_modules/ssh2/lib/protocol/crypto/poly1305.js +43 -0
- package/node_modules/ssh2/lib/protocol/crypto/src/binding.cc +2300 -0
- package/node_modules/ssh2/lib/protocol/crypto.js +1602 -0
- package/node_modules/ssh2/lib/protocol/handlers.js +16 -0
- package/node_modules/ssh2/lib/protocol/handlers.misc.js +1285 -0
- package/node_modules/ssh2/lib/protocol/kex.js +1908 -0
- package/node_modules/ssh2/lib/protocol/keyParser.js +1484 -0
- package/node_modules/ssh2/lib/protocol/node-fs-compat.js +115 -0
- package/node_modules/ssh2/lib/protocol/utils.js +356 -0
- package/node_modules/ssh2/lib/protocol/zlib.js +255 -0
- package/node_modules/ssh2/lib/server.js +1380 -0
- package/node_modules/ssh2/lib/utils.js +336 -0
- package/node_modules/ssh2/package.json +49 -0
- package/node_modules/ssh2/test/common.js +316 -0
- package/node_modules/ssh2/test/fixtures/bad_rsa_private_key +26 -0
- package/node_modules/ssh2/test/fixtures/https_cert.pem +33 -0
- package/node_modules/ssh2/test/fixtures/https_key.pem +52 -0
- package/node_modules/ssh2/test/fixtures/id_dsa +12 -0
- package/node_modules/ssh2/test/fixtures/id_ecdsa +5 -0
- package/node_modules/ssh2/test/fixtures/id_rsa +15 -0
- package/node_modules/ssh2/test/fixtures/id_rsa.ppk +26 -0
- package/node_modules/ssh2/test/fixtures/id_rsa_enc +30 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa +21 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc +22 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm +23 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_dsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa +9 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ecdsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519 +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_ed25519.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc +28 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm +29 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_new_rsa_enc_gcm.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa +12 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc +15 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa +5 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_ecdsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc +30 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.pub.result +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256 +54 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub +1 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.pub.result +6 -0
- package/node_modules/ssh2/test/fixtures/keyParser/openssh_old_rsa_enc_aes256.result +6 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc +17 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_dsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa +26 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc +18 -0
- package/node_modules/ssh2/test/fixtures/keyParser/ppk_rsa_enc.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub +9 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub +10 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa2.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub +11 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa3.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub +11 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa4.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub +8 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa5.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub +13 -0
- package/node_modules/ssh2/test/fixtures/keyParser/rfc4716_rsa6.pub.result +7 -0
- package/node_modules/ssh2/test/fixtures/openssh_new_rsa +27 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_dsa_key +12 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_ecdsa_key +5 -0
- package/node_modules/ssh2/test/fixtures/ssh_host_rsa_key +15 -0
- package/node_modules/ssh2/test/test-exec.js +578 -0
- package/node_modules/ssh2/test/test-integration-openssh.js +486 -0
- package/node_modules/ssh2/test/test-keygen.js +60 -0
- package/node_modules/ssh2/test/test-misc-client-server.js +1460 -0
- package/node_modules/ssh2/test/test-openssh.js +261 -0
- package/node_modules/ssh2/test/test-protocol-crypto.js +631 -0
- package/node_modules/ssh2/test/test-protocol-keyparser.js +177 -0
- package/node_modules/ssh2/test/test-server-hostkeys.js +138 -0
- package/node_modules/ssh2/test/test-sftp.js +842 -0
- package/node_modules/ssh2/test/test-shell.js +109 -0
- package/node_modules/ssh2/test/test-userauth-agent-openssh.js +110 -0
- package/node_modules/ssh2/test/test-userauth-agent.js +171 -0
- package/node_modules/ssh2/test/test-userauth.js +611 -0
- package/node_modules/ssh2/test/test-worker-imports.js +25 -0
- package/node_modules/ssh2/test/test.js +20 -0
- package/node_modules/ssh2/util/build_pagent.bat +2 -0
- package/node_modules/ssh2/util/pagent.c +88 -0
- package/node_modules/ssh2/util/pagent.exe +0 -0
- package/node_modules/tweetnacl/.npmignore +4 -0
- package/node_modules/tweetnacl/AUTHORS.md +28 -0
- package/node_modules/tweetnacl/CHANGELOG.md +221 -0
- package/node_modules/tweetnacl/LICENSE +24 -0
- package/node_modules/tweetnacl/PULL_REQUEST_TEMPLATE.md +20 -0
- package/node_modules/tweetnacl/README.md +459 -0
- package/node_modules/tweetnacl/nacl-fast.js +2388 -0
- package/node_modules/tweetnacl/nacl-fast.min.js +2 -0
- package/node_modules/tweetnacl/nacl.d.ts +98 -0
- package/node_modules/tweetnacl/nacl.js +1175 -0
- package/node_modules/tweetnacl/nacl.min.js +1 -0
- package/node_modules/tweetnacl/package.json +58 -0
- package/package.json +95 -89
package/lib/hostkey.js
CHANGED
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
// dsh-remote — host-key fingerprint helpers (TOFU). Pure functions split out
|
|
2
|
-
// of lib/index.js so the ssh2 hostVerifier contract (raw Buffer blob since
|
|
3
|
-
// ssh2 v1.17) is unit-tested against the REAL wire shape — the v0.6.7 bug
|
|
4
|
-
// class ("mock object hid a contract drift") must not regress.
|
|
5
|
-
import { createHash } from 'node:crypto'
|
|
6
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
7
|
-
import path from 'node:path'
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Extract the SSH host-key algorithm name from a raw SSH host-key blob
|
|
11
|
-
* (SSH wire format: `uint32 len` + algorithm string + key data).
|
|
12
|
-
*/
|
|
13
|
-
export function blobAlgorithm(blob) {
|
|
14
|
-
if (!Buffer.isBuffer(blob) || blob.length < 4) return ''
|
|
15
|
-
try {
|
|
16
|
-
const len = blob.readUInt32BE(0)
|
|
17
|
-
return blob.toString('utf8', 4, 4 + len)
|
|
18
|
-
} catch {
|
|
19
|
-
return ''
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* SHA-256 fingerprint (base64) of an ssh2 host-key blob, like `SHA256:…`
|
|
25
|
-
* in a known_hosts file (without the `SHA256:` prefix).
|
|
26
|
-
*
|
|
27
|
-
* ssh2 v1.17 passes hostVerifier the RAW host-key blob Buffer (the SSH
|
|
28
|
-
* wire-format `string(algo) string(keydata)`), NOT the old `{ algo, hash }`
|
|
29
|
-
* object. Fingerprinting `key.hash` on a raw Buffer crashed with "The 'data'
|
|
30
|
-
* argument must be of type string or an instance of Buffer…" on EVERY connect
|
|
31
|
-
* (v0.6.6 bug). Accept both shapes defensively.
|
|
32
|
-
*/
|
|
33
|
-
export function keyFingerprint(key) {
|
|
34
|
-
const blob = Buffer.isBuffer(key) ? key : (key && key.hash)
|
|
35
|
-
if (!blob) throw new Error('host key missing (hostVerifier received no key blob)')
|
|
36
|
-
return createHash('sha256').update(blob).digest('base64')
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function loadKnownHosts(file) {
|
|
40
|
-
if (!file) return {}
|
|
41
|
-
try {
|
|
42
|
-
const j = JSON.parse(readFileSync(file, 'utf8'))
|
|
43
|
-
if (j && typeof j === 'object' && !Array.isArray(j)) return j
|
|
44
|
-
} catch {}
|
|
45
|
-
return {}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function isHostKeyKnown(file, host, port) {
|
|
49
|
-
return Object.prototype.hasOwnProperty.call(loadKnownHosts(file), `${host}:${port}`)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Build an ssh2 `hostVerifier` bound to the current config + known_hosts file. */
|
|
53
|
-
export function createHostKeyGuard(config, knownHostsFile) {
|
|
54
|
-
const id = () => `${config.host}:${config.port}`
|
|
55
|
-
const mode = config.hostKeyMode === 'verify' || config.hostKeyMode === 'off'
|
|
56
|
-
? config.hostKeyMode
|
|
57
|
-
: 'accept-new'
|
|
58
|
-
const read = () => loadKnownHosts(knownHostsFile)
|
|
59
|
-
const write = (kh) => {
|
|
60
|
-
if (!knownHostsFile) return
|
|
61
|
-
try { mkdirSync(path.dirname(knownHostsFile), { recursive: true }) } catch {}
|
|
62
|
-
writeFileSync(knownHostsFile, JSON.stringify(kh, null, 2))
|
|
63
|
-
}
|
|
64
|
-
const guard = {
|
|
65
|
-
mode,
|
|
66
|
-
lastError: null,
|
|
67
|
-
knownHosts: read,
|
|
68
|
-
forgetHost() {
|
|
69
|
-
const kh = read()
|
|
70
|
-
delete kh[id()]
|
|
71
|
-
write(kh)
|
|
72
|
-
},
|
|
73
|
-
verifier(key) {
|
|
74
|
-
if (mode === 'off') return true
|
|
75
|
-
const fp = keyFingerprint(key)
|
|
76
|
-
const kh = read()
|
|
77
|
-
const stored = kh[id()]
|
|
78
|
-
if (stored) {
|
|
79
|
-
if (stored.fingerprint === fp) return true
|
|
80
|
-
guard.lastError =
|
|
81
|
-
`host key for ${id()} CHANGED (stored ${stored.fingerprint}, received ${fp}) — ` +
|
|
82
|
-
'possible man-in-the-middle; run /remote-forget-key to re-trust if this is expected'
|
|
83
|
-
return false
|
|
84
|
-
}
|
|
85
|
-
if (mode === 'verify') {
|
|
86
|
-
guard.lastError = `unknown host key for ${id()} (hostKeyMode=verify) — trust it first with accept-new`
|
|
87
|
-
return false
|
|
88
|
-
}
|
|
89
|
-
kh[id()] = { algo: blobAlgorithm(key) || (key && key.algo) || 'unknown', fingerprint: fp, firstSeen: new Date().toISOString() }
|
|
90
|
-
write(kh)
|
|
91
|
-
return true
|
|
92
|
-
},
|
|
93
|
-
}
|
|
94
|
-
return guard
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** Build a fake-but-wire-shaped host-key blob for tests:
|
|
98
|
-
* `string(algo) string(32 bytes)`. */
|
|
99
|
-
export function makeKeyBlob(algo, seed) {
|
|
100
|
-
const algoBuf = Buffer.from(algo, 'utf8')
|
|
101
|
-
const data = Buffer.alloc(32, seed || 1)
|
|
102
|
-
const blob = Buffer.alloc(4 + algoBuf.length + 4 + data.length)
|
|
103
|
-
blob.writeUInt32BE(algoBuf.length, 0)
|
|
104
|
-
algoBuf.copy(blob, 4)
|
|
105
|
-
blob.writeUInt32BE(data.length, 4 + algoBuf.length)
|
|
106
|
-
data.copy(blob, 8 + algoBuf.length)
|
|
107
|
-
return blob
|
|
108
|
-
}
|
|
1
|
+
// dsh-remote — host-key fingerprint helpers (TOFU). Pure functions split out
|
|
2
|
+
// of lib/index.js so the ssh2 hostVerifier contract (raw Buffer blob since
|
|
3
|
+
// ssh2 v1.17) is unit-tested against the REAL wire shape — the v0.6.7 bug
|
|
4
|
+
// class ("mock object hid a contract drift") must not regress.
|
|
5
|
+
import { createHash } from 'node:crypto'
|
|
6
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
7
|
+
import path from 'node:path'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extract the SSH host-key algorithm name from a raw SSH host-key blob
|
|
11
|
+
* (SSH wire format: `uint32 len` + algorithm string + key data).
|
|
12
|
+
*/
|
|
13
|
+
export function blobAlgorithm(blob) {
|
|
14
|
+
if (!Buffer.isBuffer(blob) || blob.length < 4) return ''
|
|
15
|
+
try {
|
|
16
|
+
const len = blob.readUInt32BE(0)
|
|
17
|
+
return blob.toString('utf8', 4, 4 + len)
|
|
18
|
+
} catch {
|
|
19
|
+
return ''
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* SHA-256 fingerprint (base64) of an ssh2 host-key blob, like `SHA256:…`
|
|
25
|
+
* in a known_hosts file (without the `SHA256:` prefix).
|
|
26
|
+
*
|
|
27
|
+
* ssh2 v1.17 passes hostVerifier the RAW host-key blob Buffer (the SSH
|
|
28
|
+
* wire-format `string(algo) string(keydata)`), NOT the old `{ algo, hash }`
|
|
29
|
+
* object. Fingerprinting `key.hash` on a raw Buffer crashed with "The 'data'
|
|
30
|
+
* argument must be of type string or an instance of Buffer…" on EVERY connect
|
|
31
|
+
* (v0.6.6 bug). Accept both shapes defensively.
|
|
32
|
+
*/
|
|
33
|
+
export function keyFingerprint(key) {
|
|
34
|
+
const blob = Buffer.isBuffer(key) ? key : (key && key.hash)
|
|
35
|
+
if (!blob) throw new Error('host key missing (hostVerifier received no key blob)')
|
|
36
|
+
return createHash('sha256').update(blob).digest('base64')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function loadKnownHosts(file) {
|
|
40
|
+
if (!file) return {}
|
|
41
|
+
try {
|
|
42
|
+
const j = JSON.parse(readFileSync(file, 'utf8'))
|
|
43
|
+
if (j && typeof j === 'object' && !Array.isArray(j)) return j
|
|
44
|
+
} catch {}
|
|
45
|
+
return {}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isHostKeyKnown(file, host, port) {
|
|
49
|
+
return Object.prototype.hasOwnProperty.call(loadKnownHosts(file), `${host}:${port}`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Build an ssh2 `hostVerifier` bound to the current config + known_hosts file. */
|
|
53
|
+
export function createHostKeyGuard(config, knownHostsFile) {
|
|
54
|
+
const id = () => `${config.host}:${config.port}`
|
|
55
|
+
const mode = config.hostKeyMode === 'verify' || config.hostKeyMode === 'off'
|
|
56
|
+
? config.hostKeyMode
|
|
57
|
+
: 'accept-new'
|
|
58
|
+
const read = () => loadKnownHosts(knownHostsFile)
|
|
59
|
+
const write = (kh) => {
|
|
60
|
+
if (!knownHostsFile) return
|
|
61
|
+
try { mkdirSync(path.dirname(knownHostsFile), { recursive: true }) } catch {}
|
|
62
|
+
writeFileSync(knownHostsFile, JSON.stringify(kh, null, 2))
|
|
63
|
+
}
|
|
64
|
+
const guard = {
|
|
65
|
+
mode,
|
|
66
|
+
lastError: null,
|
|
67
|
+
knownHosts: read,
|
|
68
|
+
forgetHost() {
|
|
69
|
+
const kh = read()
|
|
70
|
+
delete kh[id()]
|
|
71
|
+
write(kh)
|
|
72
|
+
},
|
|
73
|
+
verifier(key) {
|
|
74
|
+
if (mode === 'off') return true
|
|
75
|
+
const fp = keyFingerprint(key)
|
|
76
|
+
const kh = read()
|
|
77
|
+
const stored = kh[id()]
|
|
78
|
+
if (stored) {
|
|
79
|
+
if (stored.fingerprint === fp) return true
|
|
80
|
+
guard.lastError =
|
|
81
|
+
`host key for ${id()} CHANGED (stored ${stored.fingerprint}, received ${fp}) — ` +
|
|
82
|
+
'possible man-in-the-middle; run /remote-forget-key to re-trust if this is expected'
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
if (mode === 'verify') {
|
|
86
|
+
guard.lastError = `unknown host key for ${id()} (hostKeyMode=verify) — trust it first with accept-new`
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
kh[id()] = { algo: blobAlgorithm(key) || (key && key.algo) || 'unknown', fingerprint: fp, firstSeen: new Date().toISOString() }
|
|
90
|
+
write(kh)
|
|
91
|
+
return true
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
return guard
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Build a fake-but-wire-shaped host-key blob for tests:
|
|
98
|
+
* `string(algo) string(32 bytes)`. */
|
|
99
|
+
export function makeKeyBlob(algo, seed) {
|
|
100
|
+
const algoBuf = Buffer.from(algo, 'utf8')
|
|
101
|
+
const data = Buffer.alloc(32, seed || 1)
|
|
102
|
+
const blob = Buffer.alloc(4 + algoBuf.length + 4 + data.length)
|
|
103
|
+
blob.writeUInt32BE(algoBuf.length, 0)
|
|
104
|
+
algoBuf.copy(blob, 4)
|
|
105
|
+
blob.writeUInt32BE(data.length, 4 + algoBuf.length)
|
|
106
|
+
data.copy(blob, 8 + algoBuf.length)
|
|
107
|
+
return blob
|
|
108
|
+
}
|
package/lib/http-transport.js
CHANGED
|
@@ -1,196 +1,196 @@
|
|
|
1
|
-
// The routes in index.js are bounded JSON handlers, not arbitrary HTTP proxy
|
|
2
|
-
// handlers. Keep their implementation shared with legacy Web hosts while using
|
|
3
|
-
// Connection's authenticated, carrier-neutral channel on portless Desktop.
|
|
4
|
-
import { Readable } from 'node:stream'
|
|
5
|
-
|
|
6
|
-
const MAX_BODY_BYTES = 1024 * 1024
|
|
7
|
-
|
|
8
|
-
export function connectionRoute(route) {
|
|
9
|
-
// Only the prefix is a real invariant here. `kind`/`exact` belongs to
|
|
10
|
-
// dsh-host-webserver's registry (WebRouteKind); Connection's
|
|
11
|
-
// ConnectionFetchRoute has no `kind` field and assertFetchRoute never reads
|
|
12
|
-
// one, so requiring it would let a future route that omits it silently lose
|
|
13
|
-
// itself and every route registered after it.
|
|
14
|
-
if (!route.path.startsWith('/dsh-remote/')) {
|
|
15
|
-
throw new Error('dsh-remote: expected a plugin route under /dsh-remote/')
|
|
16
|
-
}
|
|
17
|
-
return {
|
|
18
|
-
path: '/api' + route.path,
|
|
19
|
-
methods: ['GET', 'POST'],
|
|
20
|
-
requestBody: 'buffered',
|
|
21
|
-
async fetch(request) {
|
|
22
|
-
const chunks = []
|
|
23
|
-
let size = 0
|
|
24
|
-
// Desktop's IPC carrier need not impose the Web carrier's JSON cap.
|
|
25
|
-
// Enforce our own cap before dispatch: oversized input must never become
|
|
26
|
-
// an empty object that could accidentally trigger a default operation.
|
|
27
|
-
const reader = request.body?.getReader()
|
|
28
|
-
try {
|
|
29
|
-
if (reader) {
|
|
30
|
-
while (true) {
|
|
31
|
-
request.signal.throwIfAborted()
|
|
32
|
-
const { done, value } = await reader.read()
|
|
33
|
-
if (done) break
|
|
34
|
-
size += value.byteLength
|
|
35
|
-
if (size > MAX_BODY_BYTES) {
|
|
36
|
-
await reader.cancel()
|
|
37
|
-
return Response.json({ ok: false, error: 'request body too large' }, { status: 413 })
|
|
38
|
-
}
|
|
39
|
-
chunks.push(Buffer.from(value))
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
} finally {
|
|
43
|
-
reader?.releaseLock()
|
|
44
|
-
}
|
|
45
|
-
request.signal.throwIfAborted()
|
|
46
|
-
const url = new URL(request.url)
|
|
47
|
-
// The legacy JSON reader joins chunks as strings. Supply one bounded
|
|
48
|
-
// buffer so UTF-8 characters split by the IPC stream remain intact.
|
|
49
|
-
const req = Readable.from(size ? [Buffer.concat(chunks, size)] : [])
|
|
50
|
-
req.method = request.method
|
|
51
|
-
req.url = route.path + url.search
|
|
52
|
-
const headers = new Headers()
|
|
53
|
-
let response
|
|
54
|
-
const res = {
|
|
55
|
-
statusCode: 200,
|
|
56
|
-
setHeader(key, value) { headers.set(key, value) },
|
|
57
|
-
end(body) { response = new Response(body, { status: this.statusCode, headers }) },
|
|
58
|
-
}
|
|
59
|
-
try {
|
|
60
|
-
await route.handler(req, res)
|
|
61
|
-
if (!response) throw new Error('JSON handler did not finish its response')
|
|
62
|
-
return response
|
|
63
|
-
} finally {
|
|
64
|
-
req.destroy()
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Reject a browser-shaped request that is not same-origin ours.
|
|
72
|
-
*
|
|
73
|
-
* This is the FALLBACK fence, used only when the Connection service (which owns
|
|
74
|
-
* DSH's authoritative `requestRejection`) is absent — an old composition that
|
|
75
|
-
* has a `webServer` but no browser-authentication owner. It is deliberately
|
|
76
|
-
* fail-closed on the three browser vectors our own routes would otherwise
|
|
77
|
-
* accept:
|
|
78
|
-
* • `sec-fetch-site: cross-site` — a page on any origin driving our API;
|
|
79
|
-
* • an `Origin` that is not our own authority — the same, with CORS markers;
|
|
80
|
-
* • a non-loopback `Host` — DNS rebinding, where a name the attacker controls
|
|
81
|
-
* resolves to 127.0.0.1 so even `Origin === Host` looks "same-origin".
|
|
82
|
-
* Non-browser callers (curl, scripts) carry no `Sec-Fetch-*`/`Origin` and a
|
|
83
|
-
* loopback `Host`, so they keep working; that is the same shape DSH's own fence
|
|
84
|
-
* implements for `/api`.
|
|
85
|
-
*
|
|
86
|
-
* A request with no HTTP headers at all is not a browser request — it comes
|
|
87
|
-
* from an in-process caller (tests, and the Desktop carrier's IPC path, which
|
|
88
|
-
* authenticates before dispatch) — so there is no browser layer to defend.
|
|
89
|
-
*
|
|
90
|
-
* @param {import('node:http').IncomingMessage} req - the request under test.
|
|
91
|
-
* @returns {number|undefined} 403 to reject, undefined to proceed.
|
|
92
|
-
*/
|
|
93
|
-
function fallbackRejection(req) {
|
|
94
|
-
const headers = req && req.headers
|
|
95
|
-
if (!headers || typeof headers !== 'object') return undefined
|
|
96
|
-
const header = (name) => {
|
|
97
|
-
const value = headers[name]
|
|
98
|
-
return typeof value === 'string' ? value : Array.isArray(value) ? value[0] : undefined
|
|
99
|
-
}
|
|
100
|
-
if (header('sec-fetch-site') === 'cross-site') return 403
|
|
101
|
-
const host = header('host')
|
|
102
|
-
const origin = header('origin')
|
|
103
|
-
if (origin !== undefined && host !== undefined) {
|
|
104
|
-
try {
|
|
105
|
-
if (new URL(origin).host !== host) return 403
|
|
106
|
-
} catch {
|
|
107
|
-
return 403
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
if (host !== undefined) {
|
|
111
|
-
const hostname = host.replace(/:\d+$/, '').toLowerCase()
|
|
112
|
-
const loopback = hostname === 'localhost' || hostname === '[::1]'
|
|
113
|
-
|| /^127(?:\.\d{1,3}){3}$/.test(hostname)
|
|
114
|
-
if (!loopback) return 403
|
|
115
|
-
}
|
|
116
|
-
return undefined
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Wrap one route so every request passes DSH's browser trust fence first.
|
|
121
|
-
*
|
|
122
|
-
* The legacy `/dsh-remote/*` routes are registered straight on `webServer`, which
|
|
123
|
-
* applies no Host/Origin check and no browser authentication — `requestRejection`
|
|
124
|
-
* lives on the Connection service and is only wired to `/api` and the
|
|
125
|
-
* `/api/remote.mux` upgrade. Without this wrapper our mutating routes were
|
|
126
|
-
* reachable cross-site as "simple requests" (no preflight: a `text/plain` POST
|
|
127
|
-
* needs no CORS approval), so any page the user visited could define a tunnel,
|
|
128
|
-
* rewrite the machine registry, or drive `update-apply`. The model tools were
|
|
129
|
-
* never exposed that way; only these JSON routes were.
|
|
130
|
-
*
|
|
131
|
-
* The lookup is deferred to request time because `webServer` and `connection`
|
|
132
|
-
* arrive independently: the fence must engage as soon as authentication exists,
|
|
133
|
-
* and must not be baked in from whichever service happened to be present when
|
|
134
|
-
* the route was registered.
|
|
135
|
-
*
|
|
136
|
-
* @param {object} route - a webServer route definition.
|
|
137
|
-
* @param {() => object|undefined} connectionOf - resolves the live Connection service.
|
|
138
|
-
* @returns {object} a route whose handler enforces the fence before dispatching.
|
|
139
|
-
*/
|
|
140
|
-
export function guardRoute(route, connectionOf) {
|
|
141
|
-
const inner = route.handler
|
|
142
|
-
return {
|
|
143
|
-
...route,
|
|
144
|
-
handler: async (req, res) => {
|
|
145
|
-
const connection = connectionOf()
|
|
146
|
-
const rejection = typeof connection?.requestRejection === 'function'
|
|
147
|
-
? connection.requestRejection(req)
|
|
148
|
-
: fallbackRejection(req)
|
|
149
|
-
if (rejection !== undefined) {
|
|
150
|
-
res.writeHead(rejection, { 'content-type': 'text/plain; charset=utf-8' })
|
|
151
|
-
res.end(rejection === 401 ? 'unauthorized' : 'forbidden')
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
return inner(req, res)
|
|
155
|
-
},
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function registerHttpTransports(ctx, routes) {
|
|
160
|
-
// Reactive injection matters: either service can arrive after the plugin.
|
|
161
|
-
// Neither optional transport is a hard prerequisite for the SSH tools.
|
|
162
|
-
ctx.inject(['webServer'], (inner) => {
|
|
163
|
-
// Apply DSH's own browser fence to every legacy route; the Connection
|
|
164
|
-
// service is read per request so a late-arriving authenticator still counts.
|
|
165
|
-
const guarded = routes.map((route) => guardRoute(route, () => ctx.get('connection')))
|
|
166
|
-
const disposers = guarded.map((route) => inner.get('webServer').register(route))
|
|
167
|
-
inner.effect(() => () => disposers.forEach((dispose) => dispose()), 'dsh-remote.web-routes')
|
|
168
|
-
})
|
|
169
|
-
ctx.inject(['connection'], (inner) => {
|
|
170
|
-
const connection = inner.get('connection')
|
|
171
|
-
// Older Web compositions may have Connection without exact Fetch routes.
|
|
172
|
-
// dsh < 0.1.2-rc.1 is such a composition: the Desktop transport is simply
|
|
173
|
-
// absent there, and the legacy /dsh-remote/* routes above still serve.
|
|
174
|
-
if (typeof connection.fetch?.register !== 'function') return
|
|
175
|
-
// Register one route at a time. A single failing route must not abort the
|
|
176
|
-
// rest of the list (which would silently drop every later route) nor leak
|
|
177
|
-
// the routes already registered, because this callback is a ctx.inject
|
|
178
|
-
// child fiber: its throw is caught and logged by the loader, so the parent
|
|
179
|
-
// plugin stays ACTIVE and the failure is easy to miss.
|
|
180
|
-
const disposers = []
|
|
181
|
-
const failed = []
|
|
182
|
-
for (const route of routes) {
|
|
183
|
-
try {
|
|
184
|
-
disposers.push(connection.fetch.register(connectionRoute(route)))
|
|
185
|
-
} catch (err) {
|
|
186
|
-
failed.push(route.path + ': ' + String((err && err.message) || err))
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
inner.effect(() => () => Promise.all(disposers.map((dispose) => dispose())), 'dsh-remote.fetch-routes')
|
|
190
|
-
if (failed.length) {
|
|
191
|
-
// Surface a partial registration instead of swallowing it; the Web
|
|
192
|
-
// transport is unaffected, so this is a warning rather than a failure.
|
|
193
|
-
console.warn('[dsh-remote] some Connection Fetch routes did not register:', failed.join('; '))
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
}
|
|
1
|
+
// The routes in index.js are bounded JSON handlers, not arbitrary HTTP proxy
|
|
2
|
+
// handlers. Keep their implementation shared with legacy Web hosts while using
|
|
3
|
+
// Connection's authenticated, carrier-neutral channel on portless Desktop.
|
|
4
|
+
import { Readable } from 'node:stream'
|
|
5
|
+
|
|
6
|
+
const MAX_BODY_BYTES = 1024 * 1024
|
|
7
|
+
|
|
8
|
+
export function connectionRoute(route) {
|
|
9
|
+
// Only the prefix is a real invariant here. `kind`/`exact` belongs to
|
|
10
|
+
// dsh-host-webserver's registry (WebRouteKind); Connection's
|
|
11
|
+
// ConnectionFetchRoute has no `kind` field and assertFetchRoute never reads
|
|
12
|
+
// one, so requiring it would let a future route that omits it silently lose
|
|
13
|
+
// itself and every route registered after it.
|
|
14
|
+
if (!route.path.startsWith('/dsh-remote/')) {
|
|
15
|
+
throw new Error('dsh-remote: expected a plugin route under /dsh-remote/')
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
path: '/api' + route.path,
|
|
19
|
+
methods: ['GET', 'POST'],
|
|
20
|
+
requestBody: 'buffered',
|
|
21
|
+
async fetch(request) {
|
|
22
|
+
const chunks = []
|
|
23
|
+
let size = 0
|
|
24
|
+
// Desktop's IPC carrier need not impose the Web carrier's JSON cap.
|
|
25
|
+
// Enforce our own cap before dispatch: oversized input must never become
|
|
26
|
+
// an empty object that could accidentally trigger a default operation.
|
|
27
|
+
const reader = request.body?.getReader()
|
|
28
|
+
try {
|
|
29
|
+
if (reader) {
|
|
30
|
+
while (true) {
|
|
31
|
+
request.signal.throwIfAborted()
|
|
32
|
+
const { done, value } = await reader.read()
|
|
33
|
+
if (done) break
|
|
34
|
+
size += value.byteLength
|
|
35
|
+
if (size > MAX_BODY_BYTES) {
|
|
36
|
+
await reader.cancel()
|
|
37
|
+
return Response.json({ ok: false, error: 'request body too large' }, { status: 413 })
|
|
38
|
+
}
|
|
39
|
+
chunks.push(Buffer.from(value))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
} finally {
|
|
43
|
+
reader?.releaseLock()
|
|
44
|
+
}
|
|
45
|
+
request.signal.throwIfAborted()
|
|
46
|
+
const url = new URL(request.url)
|
|
47
|
+
// The legacy JSON reader joins chunks as strings. Supply one bounded
|
|
48
|
+
// buffer so UTF-8 characters split by the IPC stream remain intact.
|
|
49
|
+
const req = Readable.from(size ? [Buffer.concat(chunks, size)] : [])
|
|
50
|
+
req.method = request.method
|
|
51
|
+
req.url = route.path + url.search
|
|
52
|
+
const headers = new Headers()
|
|
53
|
+
let response
|
|
54
|
+
const res = {
|
|
55
|
+
statusCode: 200,
|
|
56
|
+
setHeader(key, value) { headers.set(key, value) },
|
|
57
|
+
end(body) { response = new Response(body, { status: this.statusCode, headers }) },
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
await route.handler(req, res)
|
|
61
|
+
if (!response) throw new Error('JSON handler did not finish its response')
|
|
62
|
+
return response
|
|
63
|
+
} finally {
|
|
64
|
+
req.destroy()
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Reject a browser-shaped request that is not same-origin ours.
|
|
72
|
+
*
|
|
73
|
+
* This is the FALLBACK fence, used only when the Connection service (which owns
|
|
74
|
+
* DSH's authoritative `requestRejection`) is absent — an old composition that
|
|
75
|
+
* has a `webServer` but no browser-authentication owner. It is deliberately
|
|
76
|
+
* fail-closed on the three browser vectors our own routes would otherwise
|
|
77
|
+
* accept:
|
|
78
|
+
* • `sec-fetch-site: cross-site` — a page on any origin driving our API;
|
|
79
|
+
* • an `Origin` that is not our own authority — the same, with CORS markers;
|
|
80
|
+
* • a non-loopback `Host` — DNS rebinding, where a name the attacker controls
|
|
81
|
+
* resolves to 127.0.0.1 so even `Origin === Host` looks "same-origin".
|
|
82
|
+
* Non-browser callers (curl, scripts) carry no `Sec-Fetch-*`/`Origin` and a
|
|
83
|
+
* loopback `Host`, so they keep working; that is the same shape DSH's own fence
|
|
84
|
+
* implements for `/api`.
|
|
85
|
+
*
|
|
86
|
+
* A request with no HTTP headers at all is not a browser request — it comes
|
|
87
|
+
* from an in-process caller (tests, and the Desktop carrier's IPC path, which
|
|
88
|
+
* authenticates before dispatch) — so there is no browser layer to defend.
|
|
89
|
+
*
|
|
90
|
+
* @param {import('node:http').IncomingMessage} req - the request under test.
|
|
91
|
+
* @returns {number|undefined} 403 to reject, undefined to proceed.
|
|
92
|
+
*/
|
|
93
|
+
function fallbackRejection(req) {
|
|
94
|
+
const headers = req && req.headers
|
|
95
|
+
if (!headers || typeof headers !== 'object') return undefined
|
|
96
|
+
const header = (name) => {
|
|
97
|
+
const value = headers[name]
|
|
98
|
+
return typeof value === 'string' ? value : Array.isArray(value) ? value[0] : undefined
|
|
99
|
+
}
|
|
100
|
+
if (header('sec-fetch-site') === 'cross-site') return 403
|
|
101
|
+
const host = header('host')
|
|
102
|
+
const origin = header('origin')
|
|
103
|
+
if (origin !== undefined && host !== undefined) {
|
|
104
|
+
try {
|
|
105
|
+
if (new URL(origin).host !== host) return 403
|
|
106
|
+
} catch {
|
|
107
|
+
return 403
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (host !== undefined) {
|
|
111
|
+
const hostname = host.replace(/:\d+$/, '').toLowerCase()
|
|
112
|
+
const loopback = hostname === 'localhost' || hostname === '[::1]'
|
|
113
|
+
|| /^127(?:\.\d{1,3}){3}$/.test(hostname)
|
|
114
|
+
if (!loopback) return 403
|
|
115
|
+
}
|
|
116
|
+
return undefined
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Wrap one route so every request passes DSH's browser trust fence first.
|
|
121
|
+
*
|
|
122
|
+
* The legacy `/dsh-remote/*` routes are registered straight on `webServer`, which
|
|
123
|
+
* applies no Host/Origin check and no browser authentication — `requestRejection`
|
|
124
|
+
* lives on the Connection service and is only wired to `/api` and the
|
|
125
|
+
* `/api/remote.mux` upgrade. Without this wrapper our mutating routes were
|
|
126
|
+
* reachable cross-site as "simple requests" (no preflight: a `text/plain` POST
|
|
127
|
+
* needs no CORS approval), so any page the user visited could define a tunnel,
|
|
128
|
+
* rewrite the machine registry, or drive `update-apply`. The model tools were
|
|
129
|
+
* never exposed that way; only these JSON routes were.
|
|
130
|
+
*
|
|
131
|
+
* The lookup is deferred to request time because `webServer` and `connection`
|
|
132
|
+
* arrive independently: the fence must engage as soon as authentication exists,
|
|
133
|
+
* and must not be baked in from whichever service happened to be present when
|
|
134
|
+
* the route was registered.
|
|
135
|
+
*
|
|
136
|
+
* @param {object} route - a webServer route definition.
|
|
137
|
+
* @param {() => object|undefined} connectionOf - resolves the live Connection service.
|
|
138
|
+
* @returns {object} a route whose handler enforces the fence before dispatching.
|
|
139
|
+
*/
|
|
140
|
+
export function guardRoute(route, connectionOf) {
|
|
141
|
+
const inner = route.handler
|
|
142
|
+
return {
|
|
143
|
+
...route,
|
|
144
|
+
handler: async (req, res) => {
|
|
145
|
+
const connection = connectionOf()
|
|
146
|
+
const rejection = typeof connection?.requestRejection === 'function'
|
|
147
|
+
? connection.requestRejection(req)
|
|
148
|
+
: fallbackRejection(req)
|
|
149
|
+
if (rejection !== undefined) {
|
|
150
|
+
res.writeHead(rejection, { 'content-type': 'text/plain; charset=utf-8' })
|
|
151
|
+
res.end(rejection === 401 ? 'unauthorized' : 'forbidden')
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
return inner(req, res)
|
|
155
|
+
},
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function registerHttpTransports(ctx, routes) {
|
|
160
|
+
// Reactive injection matters: either service can arrive after the plugin.
|
|
161
|
+
// Neither optional transport is a hard prerequisite for the SSH tools.
|
|
162
|
+
ctx.inject(['webServer'], (inner) => {
|
|
163
|
+
// Apply DSH's own browser fence to every legacy route; the Connection
|
|
164
|
+
// service is read per request so a late-arriving authenticator still counts.
|
|
165
|
+
const guarded = routes.map((route) => guardRoute(route, () => ctx.get('connection')))
|
|
166
|
+
const disposers = guarded.map((route) => inner.get('webServer').register(route))
|
|
167
|
+
inner.effect(() => () => disposers.forEach((dispose) => dispose()), 'dsh-remote.web-routes')
|
|
168
|
+
})
|
|
169
|
+
ctx.inject(['connection'], (inner) => {
|
|
170
|
+
const connection = inner.get('connection')
|
|
171
|
+
// Older Web compositions may have Connection without exact Fetch routes.
|
|
172
|
+
// dsh < 0.1.2-rc.1 is such a composition: the Desktop transport is simply
|
|
173
|
+
// absent there, and the legacy /dsh-remote/* routes above still serve.
|
|
174
|
+
if (typeof connection.fetch?.register !== 'function') return
|
|
175
|
+
// Register one route at a time. A single failing route must not abort the
|
|
176
|
+
// rest of the list (which would silently drop every later route) nor leak
|
|
177
|
+
// the routes already registered, because this callback is a ctx.inject
|
|
178
|
+
// child fiber: its throw is caught and logged by the loader, so the parent
|
|
179
|
+
// plugin stays ACTIVE and the failure is easy to miss.
|
|
180
|
+
const disposers = []
|
|
181
|
+
const failed = []
|
|
182
|
+
for (const route of routes) {
|
|
183
|
+
try {
|
|
184
|
+
disposers.push(connection.fetch.register(connectionRoute(route)))
|
|
185
|
+
} catch (err) {
|
|
186
|
+
failed.push(route.path + ': ' + String((err && err.message) || err))
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
inner.effect(() => () => Promise.all(disposers.map((dispose) => dispose())), 'dsh-remote.fetch-routes')
|
|
190
|
+
if (failed.length) {
|
|
191
|
+
// Surface a partial registration instead of swallowing it; the Web
|
|
192
|
+
// transport is unaffected, so this is a warning rather than a failure.
|
|
193
|
+
console.warn('[dsh-remote] some Connection Fetch routes did not register:', failed.join('; '))
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
}
|