ai-remote 0.5.0 → 0.6.0

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.
@@ -0,0 +1,72 @@
1
+ // src/cli/known-hosts.ts
2
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ function configDir() {
6
+ const base = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
7
+ return join(base, "ai-remote");
8
+ }
9
+ var storePath = () => join(configDir(), "known_hosts.json");
10
+ function read() {
11
+ try {
12
+ const parsed = JSON.parse(readFileSync(storePath(), "utf8"));
13
+ return parsed && typeof parsed === "object" ? parsed : {};
14
+ } catch {
15
+ return {};
16
+ }
17
+ }
18
+ function write(store) {
19
+ mkdirSync(configDir(), { recursive: true, mode: 448 });
20
+ writeFileSync(storePath(), `${JSON.stringify(store, null, 2)}
21
+ `, { mode: 384 });
22
+ chmodSync(storePath(), 384);
23
+ }
24
+ var hostAddress = (host, port) => `${host}:${port}`;
25
+ function checkHostKey(address, info, { insecure = false } = {}) {
26
+ const store = read();
27
+ const known = store[address];
28
+ if (known && known.fingerprint === info.fingerprint) {
29
+ return { accepted: true, status: "known", message: "" };
30
+ }
31
+ if (known && !insecure) {
32
+ return {
33
+ accepted: false,
34
+ status: "changed",
35
+ message: [
36
+ `The host key for ${address} has changed.`,
37
+ ` expected: ${known.fingerprint}`,
38
+ ` received: ${info.fingerprint} (${info.keyType})`,
39
+ "Someone may be intercepting this connection. If the machine was genuinely",
40
+ `rebuilt, run \`ai-remote forget-host ${address}\` and connect again.`
41
+ ].join("\n")
42
+ };
43
+ }
44
+ store[address] = {
45
+ fingerprint: info.fingerprint,
46
+ keyType: info.keyType,
47
+ firstSeen: (/* @__PURE__ */ new Date()).toISOString()
48
+ };
49
+ write(store);
50
+ return {
51
+ accepted: true,
52
+ status: known ? "known" : "new",
53
+ message: known ? `Host key for ${address} replaced at your request: ${info.fingerprint}` : `Recorded host key for ${address}: ${info.fingerprint} (${info.keyType})`
54
+ };
55
+ }
56
+ function forgetHostKey(address) {
57
+ const store = read();
58
+ if (!(address in store)) return false;
59
+ delete store[address];
60
+ write(store);
61
+ return true;
62
+ }
63
+ function knownHosts() {
64
+ return Object.entries(read()).map(([address, entry]) => ({ address, ...entry })).toSorted((a, b) => a.address < b.address ? -1 : a.address > b.address ? 1 : 0);
65
+ }
66
+
67
+ export {
68
+ hostAddress,
69
+ checkHostKey,
70
+ forgetHostKey,
71
+ knownHosts
72
+ };
@@ -1,11 +1,15 @@
1
1
  import {
2
2
  SshSession,
3
3
  TcpTransport
4
- } from "./cli-chunk-S2TWL5LF.mjs";
4
+ } from "./cli-chunk-UD2YES3P.mjs";
5
5
  import {
6
6
  SshReader,
7
7
  SshWriter
8
8
  } from "./cli-chunk-XT2FISR5.mjs";
9
+ import {
10
+ checkHostKey,
11
+ hostAddress
12
+ } from "./cli-chunk-WWORJ2NE.mjs";
9
13
 
10
14
  // src/cli/copy.ts
11
15
  import { open as openFile, mkdir, readdir, stat as statFile } from "node:fs/promises";
@@ -438,10 +442,19 @@ var SftpConnection = class {
438
442
  // and still redirectable on its own.
439
443
  log: (step, detail) => process.stderr.write(`[SSH] ${step} ${JSON.stringify(detail)}
440
444
  `),
441
- // The same reasoning as the terminal's: the person running the command
442
- // named the host, on their own network, and there is no known_hosts here
443
- // to compare against yet.
444
- verifyHost: async () => true,
445
+ // The same known_hosts file the terminal uses, on the same accept-new
446
+ // terms: a key that changed stops the copy rather than completing it.
447
+ verifyHost: (info) => {
448
+ const verdict = checkHostKey(
449
+ hostAddress(options.host, options.port),
450
+ info,
451
+ { insecure: options.insecureHostKey }
452
+ );
453
+ if (verdict.message) process.stderr.write(`${verdict.message}
454
+ `);
455
+ if (!verdict.accepted) this.#error = verdict.message;
456
+ return verdict.accepted;
457
+ },
445
458
  openTransport: () => new TcpTransport(options.host, options.port)
446
459
  });
447
460
  this.sftp = new Sftp({ write: (bytes) => this.#session.write(bytes) });