@decentnetwork/lan 0.1.218 → 0.1.220

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.
@@ -90,6 +90,8 @@ export declare class PeerManager extends EventEmitter {
90
90
  isNativeFriend(userid: string): boolean;
91
91
  /** Accept an incoming file offer. */
92
92
  acceptFile(userid: string, fileNumber: number): void;
93
+ /** Reject/cancel an incoming file offer before accepting it. */
94
+ cancelFile(userid: string, fileNumber: number): void;
93
95
  /** Cancel an in-flight send by content fileId. Returns true if it was found. */
94
96
  cancelSend(userid: string, fileId: string): boolean;
95
97
  /** Cancel in-flight sends matching size/name when activeSends lost the fileId. */
@@ -205,6 +205,10 @@ export class PeerManager extends EventEmitter {
205
205
  acceptFile(userid, fileNumber) {
206
206
  this.peer?.acceptFile(userid, fileNumber);
207
207
  }
208
+ /** Reject/cancel an incoming file offer before accepting it. */
209
+ cancelFile(userid, fileNumber) {
210
+ this.peer?.cancelFile(userid, fileNumber, false);
211
+ }
208
212
  /** Cancel an in-flight send by content fileId. Returns true if it was found. */
209
213
  cancelSend(userid, fileId) {
210
214
  return this.peer?.cancelFileById(userid, fileId, true) ?? false;
@@ -215,6 +215,15 @@ export declare function cmdProxyWhitelist(args: {
215
215
  userid?: string;
216
216
  configDir?: string;
217
217
  }): Promise<void>;
218
+ /**
219
+ * Exit bandwidth guard for Messenger file receiving. Default is OFF: friends can
220
+ * send files normally. When ON, this node only accepts incoming file offers from
221
+ * peers present in the existing proxy whitelist. Applies live to the daemon.
222
+ */
223
+ export declare function cmdProxyFileWhitelist(args: {
224
+ mode: "on" | "off" | "status";
225
+ configDir?: string;
226
+ }): Promise<void>;
218
227
  /**
219
228
  * Set (or clear, with 0) the exit's busy threshold in Mbps. Above this smoothed
220
229
  * egress rate the exit serves only whitelisted peers; 0 = free for all.
@@ -1004,6 +1004,9 @@ export async function cmdProxyWhitelist(args) {
1004
1004
  console.log(busy > 0
1005
1005
  ? `Busy threshold: ${busy}Mbps (above this, only whitelisted peers get new tunnels).`
1006
1006
  : `Busy threshold: OFF — exit is free for all. Set one with 'agentnet proxy busy-mbps <N>'.`);
1007
+ console.log(proxy.fileWhitelistOnly === true
1008
+ ? `File receive guard: ON — incoming files are accepted only from this whitelist.`
1009
+ : `File receive guard: OFF — friend file receiving is open. Enable with 'agentnet proxy file-whitelist on'.`);
1007
1010
  return;
1008
1011
  }
1009
1012
  if (!args.userid) {
@@ -1025,6 +1028,35 @@ export async function cmdProxyWhitelist(args) {
1025
1028
  await ConfigLoader.save(config, configPath);
1026
1029
  await applyProxyReloadIfRunning(config);
1027
1030
  }
1031
+ /**
1032
+ * Exit bandwidth guard for Messenger file receiving. Default is OFF: friends can
1033
+ * send files normally. When ON, this node only accepts incoming file offers from
1034
+ * peers present in the existing proxy whitelist. Applies live to the daemon.
1035
+ */
1036
+ export async function cmdProxyFileWhitelist(args) {
1037
+ const dir = args.configDir || ConfigLoader.defaultConfigDir();
1038
+ const configPath = resolve(dir, "config.yaml");
1039
+ const config = await ConfigLoader.load(configPath);
1040
+ const proxy = config.proxy ?? { enabled: false, port: 8888 };
1041
+ const whitelist = proxy.whitelist ?? [];
1042
+ const enabled = proxy.fileWhitelistOnly === true;
1043
+ if (args.mode === "status") {
1044
+ console.log(enabled
1045
+ ? `File receive guard: ON — incoming files are accepted only from proxy.whitelist (${whitelist.length} entr${whitelist.length === 1 ? "y" : "ies"}).`
1046
+ : `File receive guard: OFF — friend file receiving is open.`);
1047
+ return;
1048
+ }
1049
+ const next = args.mode === "on";
1050
+ config.proxy = { ...proxy, fileWhitelistOnly: next };
1051
+ await ConfigLoader.save(config, configPath);
1052
+ console.log(next
1053
+ ? `File receive guard ON — incoming files from friends outside proxy.whitelist will be rejected.`
1054
+ : `File receive guard OFF — friends can send files normally.`);
1055
+ if (next && whitelist.length === 0) {
1056
+ console.log(`Warning: proxy.whitelist is empty, so all incoming file offers will be rejected until you add peers.`);
1057
+ }
1058
+ await applyProxyReloadIfRunning(config);
1059
+ }
1028
1060
  /**
1029
1061
  * Set (or clear, with 0) the exit's busy threshold in Mbps. Above this smoothed
1030
1062
  * egress rate the exit serves only whitelisted peers; 0 = free for all.
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, cmdProxyBusyMbps, cmdProxyUse, cmdProxyRouter, cmdProxyTrustCa, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, 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, cmdDiag, cmdDoctor, cmdDnsInstall, cmdDnsHosts, 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")
@@ -327,6 +327,15 @@ async function main() {
327
327
  return;
328
328
  }
329
329
  await cmdProxyWhitelist({ action: argv.action, userid: argv.userid, configDir: argv["config-dir"] });
330
+ })
331
+ .command("file-whitelist <mode>", "Incoming files: accept only from proxy whitelist when on (on|off|status)", (yy) => yy
332
+ .positional("mode", { type: "string", demandOption: true })
333
+ .option("config-dir", { type: "string" }), async (argv) => {
334
+ if (argv.mode !== "on" && argv.mode !== "off" && argv.mode !== "status") {
335
+ console.log("Usage: agentnet proxy file-whitelist <on|off|status>");
336
+ return;
337
+ }
338
+ await cmdProxyFileWhitelist({ mode: argv.mode, configDir: argv["config-dir"] });
330
339
  })
331
340
  .command("busy-mbps <mbps>", "Set the exit's busy threshold (Mbps); above it, only whitelisted peers get new tunnels. 0 = free for all", (yy) => yy
332
341
  .positional("mbps", { type: "number", demandOption: true })
@@ -82,6 +82,7 @@ export declare class DaemonServer {
82
82
  * so a helper that keeps dying immediately doesn't tight-loop. */
83
83
  private attachTunRecovery;
84
84
  private rebuildTun;
85
+ private isFileSenderWhitelisted;
85
86
  start(): Promise<void>;
86
87
  stop(): Promise<void>;
87
88
  /**
@@ -207,6 +207,20 @@ export class DaemonServer {
207
207
  }
208
208
  this.tunRebuildInProgress = false;
209
209
  }
210
+ isFileSenderWhitelisted(userid) {
211
+ const proxy = this.config.proxy;
212
+ if (!proxy?.fileWhitelistOnly)
213
+ return true;
214
+ const whitelist = new Set((proxy.whitelist ?? []).map((s) => String(s).trim()).filter(Boolean));
215
+ if (whitelist.size === 0)
216
+ return false;
217
+ if (whitelist.has(userid))
218
+ return true;
219
+ const rec = this.ipam?.resolveCarrierId(userid);
220
+ if (!rec)
221
+ return false;
222
+ return whitelist.has(rec.virtualIp) || whitelist.has(rec.name) || whitelist.has(rec.carrierId);
223
+ }
210
224
  async start() {
211
225
  if (this.isRunning) {
212
226
  throw new Error("Daemon already running");
@@ -704,6 +718,7 @@ export class DaemonServer {
704
718
  // and `proxy busy-mbps` apply live without a restart.
705
719
  const whitelist = fresh.proxy?.whitelist ?? [];
706
720
  const busyMbps = fresh.proxy?.busyMbps;
721
+ const fileWhitelistOnly = fresh.proxy?.fileWhitelistOnly === true;
707
722
  this.connectProxy.updateAdmission({ whitelist, busyMbps });
708
723
  // Keep the in-memory config in sync so a later diag reflects it.
709
724
  if (this.config.proxy) {
@@ -712,8 +727,9 @@ export class DaemonServer {
712
727
  this.config.proxy.allowConnectPorts = allowConnectPorts;
713
728
  this.config.proxy.whitelist = whitelist;
714
729
  this.config.proxy.busyMbps = busyMbps;
730
+ this.config.proxy.fileWhitelistOnly = fileWhitelistOnly;
715
731
  }
716
- return { applied: true, allowHosts, whitelist, busyMbps };
732
+ return { applied: true, allowHosts, whitelist, busyMbps, fileWhitelistOnly };
717
733
  },
718
734
  proxyAccess: async (userid, allow) => {
719
735
  // Live block/allow a peer's access to THIS node's exit proxy port.
@@ -1014,6 +1030,13 @@ export class DaemonServer {
1014
1030
  // File transfer (toxcore-standard): auto-accept offers from friends and
1015
1031
  // save completed files under <configDir>/downloads/.
1016
1032
  this.peerManager.on("file-offer", (o) => {
1033
+ if (!this.isFileSenderWhitelisted(o.friendId)) {
1034
+ const rec = this.ipam?.resolveCarrierId(o.friendId);
1035
+ const who = rec ? `${rec.name}/${rec.virtualIp}` : o.friendId.slice(0, 8);
1036
+ this.logger.warn(`Incoming file "${o.name}" (${o.size}B) from ${who} rejected — proxy.fileWhitelistOnly=true and sender is not in proxy.whitelist`);
1037
+ this.peerManager?.cancelFile(o.friendId, o.fileNumber);
1038
+ return;
1039
+ }
1017
1040
  this.logger.info(`Incoming file "${o.name}" (${o.size}B) from ${o.friendId.slice(0, 8)} — accepting`);
1018
1041
  this.peerManager?.acceptFile(o.friendId, o.fileNumber);
1019
1042
  });
package/dist/types.d.ts CHANGED
@@ -141,6 +141,7 @@ export interface ProxyConfig {
141
141
  egress?: string;
142
142
  busyMbps?: number;
143
143
  whitelist?: string[];
144
+ fileWhitelistOnly?: boolean;
144
145
  }
145
146
  export interface FriendsConfig {
146
147
  /** When true (default), the running daemon auto-accepts incoming
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.218",
3
+ "version": "0.1.220",
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",
@@ -84,7 +84,7 @@
84
84
  },
85
85
  "dependencies": {
86
86
  "@decentnetwork/dora": "^0.1.14",
87
- "@decentnetwork/peer": "^0.1.110",
87
+ "@decentnetwork/peer": "^0.1.111",
88
88
  "@decentnetwork/peer-webrtc": "^0.2.10",
89
89
  "ink": "^5.2.1",
90
90
  "js-yaml": "^4.1.0",