@decentnetwork/lan 0.1.273 → 0.1.274
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/dist/cli/commands.d.ts +18 -0
- package/dist/cli/commands.js +94 -1
- package/dist/cli/index.js +4 -1
- package/dist/config/loader.js +7 -5
- package/dist/console/console.js +7 -5
- package/dist/daemon/server.js +5 -2
- package/dist/ui/desktop/app.js +1 -1
- package/package.json +1 -1
package/dist/cli/commands.d.ts
CHANGED
|
@@ -486,6 +486,24 @@ export declare function windowsServiceLauncherScript(args: {
|
|
|
486
486
|
configDir: string;
|
|
487
487
|
logPath: string;
|
|
488
488
|
}): string;
|
|
489
|
+
/**
|
|
490
|
+
* Key-only SSH for connections arriving over the AgentNet subnet.
|
|
491
|
+
*
|
|
492
|
+
* Runs automatically whenever the user turns on the TUN / virtual LAN
|
|
493
|
+
* (`agentnet up --real-tun`, `agentnet service install`) — a daemon friend
|
|
494
|
+
* gets an L3 route to this machine, and password-guessable SSH over that
|
|
495
|
+
* route is the #1 exposure (docs/SECURITY-L3-HARDENING.md).
|
|
496
|
+
*
|
|
497
|
+
* STRICTLY SCOPED (product rule 2026-08-06): appends a
|
|
498
|
+
* `Match Address 10.86.0.0/16` block to the END of sshd_config — the
|
|
499
|
+
* user's existing physical-LAN/WAN login policy is untouched, and we
|
|
500
|
+
* never rewrite anything they configured. Idempotent via a marker line;
|
|
501
|
+
* original config backed up once at sshd_config.bak.agentnet; validated
|
|
502
|
+
* with `sshd -t` and rolled back if the daemon rejects it.
|
|
503
|
+
*/
|
|
504
|
+
export declare function cmdHardenSsh(args?: {
|
|
505
|
+
quiet?: boolean;
|
|
506
|
+
}): Promise<void>;
|
|
489
507
|
export declare function cmdServiceInstall(args: {
|
|
490
508
|
uninstall?: boolean;
|
|
491
509
|
configDir?: string;
|
package/dist/cli/commands.js
CHANGED
|
@@ -449,6 +449,17 @@ export async function cmdUp(args) {
|
|
|
449
449
|
// full disk stops the daemon outright. Covers the SDK's [peer-debug] output
|
|
450
450
|
// too, since this wraps the process console rather than our Logger.
|
|
451
451
|
installLogThrottle();
|
|
452
|
+
// Turning on the real TUN = giving friends an L3 route here. Close the
|
|
453
|
+
// password-SSH door on that route first (scoped, idempotent, no-op when
|
|
454
|
+
// already done or when there is no sshd).
|
|
455
|
+
if (args.realTun) {
|
|
456
|
+
try {
|
|
457
|
+
await cmdHardenSsh({ quiet: true });
|
|
458
|
+
}
|
|
459
|
+
catch (err) {
|
|
460
|
+
console.warn(String(err.message ?? err));
|
|
461
|
+
}
|
|
462
|
+
}
|
|
452
463
|
const daemon = new DaemonServer({
|
|
453
464
|
config,
|
|
454
465
|
configDir: dir,
|
|
@@ -867,7 +878,7 @@ export async function cmdFriendsAutoAccept(args) {
|
|
|
867
878
|
const configPath = resolve(dir, "config.yaml");
|
|
868
879
|
const config = await ConfigLoader.load(configPath);
|
|
869
880
|
if (!args.mode) {
|
|
870
|
-
const cur = config.friends?.autoAccept ??
|
|
881
|
+
const cur = config.friends?.autoAccept ?? false;
|
|
871
882
|
console.log(`friends.autoAccept: ${cur ? "on" : "off"}`);
|
|
872
883
|
console.log(cur
|
|
873
884
|
? " Incoming friend-requests are accepted automatically."
|
|
@@ -2195,6 +2206,76 @@ while ($true) {
|
|
|
2195
2206
|
}
|
|
2196
2207
|
`;
|
|
2197
2208
|
}
|
|
2209
|
+
/**
|
|
2210
|
+
* Key-only SSH for connections arriving over the AgentNet subnet.
|
|
2211
|
+
*
|
|
2212
|
+
* Runs automatically whenever the user turns on the TUN / virtual LAN
|
|
2213
|
+
* (`agentnet up --real-tun`, `agentnet service install`) — a daemon friend
|
|
2214
|
+
* gets an L3 route to this machine, and password-guessable SSH over that
|
|
2215
|
+
* route is the #1 exposure (docs/SECURITY-L3-HARDENING.md).
|
|
2216
|
+
*
|
|
2217
|
+
* STRICTLY SCOPED (product rule 2026-08-06): appends a
|
|
2218
|
+
* `Match Address 10.86.0.0/16` block to the END of sshd_config — the
|
|
2219
|
+
* user's existing physical-LAN/WAN login policy is untouched, and we
|
|
2220
|
+
* never rewrite anything they configured. Idempotent via a marker line;
|
|
2221
|
+
* original config backed up once at sshd_config.bak.agentnet; validated
|
|
2222
|
+
* with `sshd -t` and rolled back if the daemon rejects it.
|
|
2223
|
+
*/
|
|
2224
|
+
export async function cmdHardenSsh(args = {}) {
|
|
2225
|
+
const MARK = "agentnet L3 hardening";
|
|
2226
|
+
const say = (m) => { if (!args.quiet)
|
|
2227
|
+
console.log(m); };
|
|
2228
|
+
if (process.platform === "win32") {
|
|
2229
|
+
say("[harden-ssh] Windows: append to C:\\ProgramData\\ssh\\sshd_config:\n" +
|
|
2230
|
+
" Match Address 10.86.0.0/16\n PasswordAuthentication no\n" +
|
|
2231
|
+
"then Restart-Service sshd (as Administrator).");
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
const cfg = "/etc/ssh/sshd_config";
|
|
2235
|
+
if (!existsSync(cfg)) {
|
|
2236
|
+
say("[harden-ssh] no /etc/ssh/sshd_config (no OpenSSH server) — nothing to do");
|
|
2237
|
+
return;
|
|
2238
|
+
}
|
|
2239
|
+
if (readFileSync(cfg, "utf-8").includes(MARK)) {
|
|
2240
|
+
say("[harden-ssh] already hardened — nothing to do");
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
if (typeof process.getuid === "function" && process.getuid() !== 0) {
|
|
2244
|
+
// Not fatal: the caller may be a non-root `up` on a mock TUN box.
|
|
2245
|
+
console.log("[harden-ssh] needs root — run: sudo agentnet harden-ssh");
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
const { execSync } = await import("child_process");
|
|
2249
|
+
const { copyFileSync, appendFileSync } = await import("fs");
|
|
2250
|
+
const bak = `${cfg}.bak.agentnet`;
|
|
2251
|
+
if (!existsSync(bak))
|
|
2252
|
+
copyFileSync(cfg, bak);
|
|
2253
|
+
appendFileSync(cfg, `\n# ${MARK}: key-only SSH from the virtual net (10.86/16 only)\n` +
|
|
2254
|
+
`Match Address 10.86.0.0/16\n PasswordAuthentication no\n KbdInteractiveAuthentication no\n`);
|
|
2255
|
+
try {
|
|
2256
|
+
execSync("/usr/sbin/sshd -t", { stdio: "pipe" });
|
|
2257
|
+
}
|
|
2258
|
+
catch (err) {
|
|
2259
|
+
copyFileSync(bak, cfg);
|
|
2260
|
+
throw new Error(`[harden-ssh] sshd rejected the change — restored original config: ${err.message}`);
|
|
2261
|
+
}
|
|
2262
|
+
if (process.platform === "darwin") {
|
|
2263
|
+
try {
|
|
2264
|
+
execSync("launchctl kickstart -k system/com.openssh.sshd", { stdio: "pipe" });
|
|
2265
|
+
}
|
|
2266
|
+
catch { /* socket-activated: new connections read the config anyway */ }
|
|
2267
|
+
}
|
|
2268
|
+
else {
|
|
2269
|
+
for (const c of ["systemctl reload ssh", "systemctl reload sshd", "service ssh reload"]) {
|
|
2270
|
+
try {
|
|
2271
|
+
execSync(c, { stdio: "pipe" });
|
|
2272
|
+
break;
|
|
2273
|
+
}
|
|
2274
|
+
catch { /* try the next spelling */ }
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
console.log(`[harden-ssh] SSH from 10.86.0.0/16 is now key-only. Physical-network logins unchanged; backup: ${bak}`);
|
|
2278
|
+
}
|
|
2198
2279
|
export async function cmdServiceInstall(args) {
|
|
2199
2280
|
// Detect sudo's $HOME=/root trap. When `service install` is run via
|
|
2200
2281
|
// sudo and no --config-dir is provided, derive the home dir from
|
|
@@ -2337,6 +2418,12 @@ WantedBy=multi-user.target
|
|
|
2337
2418
|
}
|
|
2338
2419
|
execSync("systemctl daemon-reload");
|
|
2339
2420
|
execSync("systemctl enable --now agentnet");
|
|
2421
|
+
try {
|
|
2422
|
+
await cmdHardenSsh({ quiet: true });
|
|
2423
|
+
}
|
|
2424
|
+
catch (err) {
|
|
2425
|
+
console.warn(String(err.message ?? err));
|
|
2426
|
+
}
|
|
2340
2427
|
console.log(`Installed ${unitPath} and started agentnet.service.`);
|
|
2341
2428
|
console.log(`Logs: journalctl -u agentnet -f`);
|
|
2342
2429
|
console.log(`Optional — watch China video (CCTV etc.): agentnet proxy trust-ca && agentnet proxy router --hls-accel (docs/CCTV-VIEWING.md)`);
|
|
@@ -2414,6 +2501,12 @@ WantedBy=multi-user.target
|
|
|
2414
2501
|
console.log(`Note: could not install the newsyslog rotation rule (${err instanceof Error ? err.message : err}). The log is still throttled in-process.`);
|
|
2415
2502
|
}
|
|
2416
2503
|
execSync(`launchctl load ${plistPath}`);
|
|
2504
|
+
try {
|
|
2505
|
+
await cmdHardenSsh({ quiet: true });
|
|
2506
|
+
}
|
|
2507
|
+
catch (err) {
|
|
2508
|
+
console.warn(String(err.message ?? err));
|
|
2509
|
+
}
|
|
2417
2510
|
console.log(`Installed ${plistPath} and started com.decentlan.agentnet.`);
|
|
2418
2511
|
console.log(`Optional — watch China video (CCTV etc.): agentnet proxy trust-ca && agentnet proxy router --hls-accel (docs/CCTV-VIEWING.md)`);
|
|
2419
2512
|
console.log(`Logs: tail -f /var/log/agentnet.log`);
|
package/dist/cli/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { hideBin } from "yargs/helpers";
|
|
|
10
10
|
// Belt-and-braces — also raise it here in case the CLI is run directly
|
|
11
11
|
// (e.g. `node dist/cli/index.js` rather than via dist/index.js).
|
|
12
12
|
EventEmitter.defaultMaxListeners = 100;
|
|
13
|
-
import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdFriendsPending, cmdFriendsAccept, cmdFriendsAutoAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyWho, cmdProxyAccess, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyWhitelist, cmdProxyFileWhitelist, cmdProxyBusyMbps, cmdProxyUse, cmdProxyRouter, cmdProxyTrustCa, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdBootstrapShow, cmdBootstrapUpdate, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, cmdServiceInstall, cmdRestart, cmdServiceStatus, cmdServiceRestart, cmdUi, cmdConsole, cmdFileSend, cmdChatSend, cmdChatHistory, cmdFriendRemove, cmdFriendAlias, } from "./commands.js";
|
|
13
|
+
import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdFriendsPending, cmdFriendsAccept, cmdFriendsAutoAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyWho, cmdProxyAccess, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyWhitelist, cmdProxyFileWhitelist, cmdProxyBusyMbps, cmdProxyUse, cmdProxyRouter, cmdProxyTrustCa, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdBootstrapShow, cmdBootstrapUpdate, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, cmdHardenSsh, cmdServiceInstall, cmdRestart, cmdServiceStatus, cmdServiceRestart, cmdUi, cmdConsole, cmdFileSend, cmdChatSend, cmdChatHistory, cmdFriendRemove, cmdFriendAlias, } from "./commands.js";
|
|
14
14
|
async function main() {
|
|
15
15
|
await yargs(hideBin(process.argv))
|
|
16
16
|
.scriptName("agentnet")
|
|
@@ -149,6 +149,9 @@ async function main() {
|
|
|
149
149
|
})
|
|
150
150
|
.demandCommand(1, "Specify a dns subcommand (run 'agentnet dns --help')"), () => {
|
|
151
151
|
// parent handler — never invoked because demandCommand
|
|
152
|
+
})
|
|
153
|
+
.command("harden-ssh", "Make SSH key-only for connections from the AgentNet subnet (10.86/16 only; physical-network logins unchanged)", (y) => y, async () => {
|
|
154
|
+
await cmdHardenSsh();
|
|
152
155
|
})
|
|
153
156
|
// Persistent system service — wraps systemctl (Linux), launchctl
|
|
154
157
|
// (macOS), or a highest-privilege startup task (Windows) so a new operator runs ONE command to install
|
package/dist/config/loader.js
CHANGED
|
@@ -301,12 +301,14 @@ export class ConfigLoader {
|
|
|
301
301
|
enabled: false,
|
|
302
302
|
port: 8888,
|
|
303
303
|
},
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
304
|
+
// Manual friend approval by default (2026-08-06 security decision).
|
|
305
|
+
// A daemon friend gets an L3 route to this machine, and addresses
|
|
306
|
+
// are now publicly listed (Beagle discover/names directories) — so
|
|
307
|
+
// auto-accept + TUN would let strangers onto your virtual LAN.
|
|
308
|
+
// Infra nodes (doras/exits) that must accept strangers set
|
|
309
|
+
// `friends.autoAccept: true` explicitly.
|
|
308
310
|
friends: {
|
|
309
|
-
autoAccept:
|
|
311
|
+
autoAccept: false,
|
|
310
312
|
},
|
|
311
313
|
// Dora integration is ON by default and points at the public
|
|
312
314
|
// canonical dora — `agentnet init` follows up with a one-time
|
package/dist/console/console.js
CHANGED
|
@@ -883,12 +883,14 @@ var ConfigLoader = class {
|
|
|
883
883
|
enabled: false,
|
|
884
884
|
port: 8888
|
|
885
885
|
},
|
|
886
|
-
//
|
|
887
|
-
//
|
|
888
|
-
//
|
|
889
|
-
//
|
|
886
|
+
// Manual friend approval by default (2026-08-06 security decision).
|
|
887
|
+
// A daemon friend gets an L3 route to this machine, and addresses
|
|
888
|
+
// are now publicly listed (Beagle discover/names directories) — so
|
|
889
|
+
// auto-accept + TUN would let strangers onto your virtual LAN.
|
|
890
|
+
// Infra nodes (doras/exits) that must accept strangers set
|
|
891
|
+
// `friends.autoAccept: true` explicitly.
|
|
890
892
|
friends: {
|
|
891
|
-
autoAccept:
|
|
893
|
+
autoAccept: false
|
|
892
894
|
},
|
|
893
895
|
// Dora integration is ON by default and points at the public
|
|
894
896
|
// canonical dora — `agentnet init` follows up with a one-time
|
package/dist/daemon/server.js
CHANGED
|
@@ -919,7 +919,7 @@ export class DaemonServer {
|
|
|
919
919
|
statusMessage: this.config.node.statusMessage ?? "",
|
|
920
920
|
// Beagle's Profile tab renders the auto-accept toggle only when
|
|
921
921
|
// the backend reports the current state.
|
|
922
|
-
autoAccept: this.config.friends?.autoAccept ??
|
|
922
|
+
autoAccept: this.config.friends?.autoAccept ?? false,
|
|
923
923
|
},
|
|
924
924
|
tun: this.tunDevice?.getConfig(),
|
|
925
925
|
// When Dora is unavailable, config.network.ip can still be the
|
|
@@ -1255,7 +1255,10 @@ export class DaemonServer {
|
|
|
1255
1255
|
this.ipcEvents.emit("event", { type: "request", userid });
|
|
1256
1256
|
// Read the flag LIVE (not captured at start) so `agentnet friends
|
|
1257
1257
|
// autoaccept off` takes effect immediately, no daemon restart.
|
|
1258
|
-
|
|
1258
|
+
// Default flipped to false 2026-08-06: friend = L3 route, and
|
|
1259
|
+
// addresses are public in the Beagle directories now. Infra
|
|
1260
|
+
// (doras/exits) opts back in explicitly in its config.
|
|
1261
|
+
if (this.config.friends?.autoAccept ?? false) {
|
|
1259
1262
|
this.logger.info(`Friend request from ${who}${hello} — auto-accepting`);
|
|
1260
1263
|
this.peerManager?.acceptFriendRequest(req.pubkey).catch((err) => {
|
|
1261
1264
|
this.logger.warn(`Auto-accept failed for ${who}: ${err}`);
|
package/dist/ui/desktop/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
window.__DK_UI_VERSION="0.1.
|
|
1
|
+
window.__DK_UI_VERSION="0.1.274";
|
|
2
2
|
const ICON_PATHS = {
|
|
3
3
|
// ---- tab bar (the four must feel like one set) ----
|
|
4
4
|
users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decentnetwork/lan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.274",
|
|
4
4
|
"description": "Private virtual LAN for self-hosted services and AI agents, built on Elastos Carrier. NAT-traversal, name service, ACL, all over a peer-to-peer mesh — no public IP required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|