@decentnetwork/lan 0.1.3 → 0.1.4

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.
Binary file
Binary file
Binary file
Binary file
@@ -254,3 +254,14 @@ export declare function cmdDoraDisable(args: {
254
254
  export declare function cmdDoraStatus(args: {
255
255
  configDir?: string;
256
256
  }): Promise<void>;
257
+ /**
258
+ * Set the dora auto-friend policy. Three modes:
259
+ * - 'none' (default): never auto-friend roster peers
260
+ * - 'all': friend every roster entry that carries an address
261
+ * - 'allow <id-or-name>...' / 'remove <id-or-name>...': whitelist
262
+ */
263
+ export declare function cmdDoraAutofriend(args: {
264
+ mode: "none" | "all" | "allow" | "remove" | "list";
265
+ values?: string[];
266
+ configDir?: string;
267
+ }): Promise<void>;
@@ -1045,6 +1045,65 @@ export async function cmdDoraStatus(args) {
1045
1045
  }
1046
1046
  const refresh = dora.refreshIntervalMs ?? 60_000;
1047
1047
  console.log(` refresh: every ${Math.round(refresh / 1000)}s`);
1048
+ const af = dora.autoFriend ?? "none";
1049
+ if (af === "none" || af === "all") {
1050
+ console.log(` auto-friend: ${af}`);
1051
+ }
1052
+ else {
1053
+ console.log(` auto-friend: whitelist [${af.length}]: ${af.join(", ")}`);
1054
+ }
1048
1055
  console.log("");
1049
1056
  console.log("Note: live state (allocated IP, last refresh) requires querying a running daemon — not yet wired.");
1050
1057
  }
1058
+ /**
1059
+ * Set the dora auto-friend policy. Three modes:
1060
+ * - 'none' (default): never auto-friend roster peers
1061
+ * - 'all': friend every roster entry that carries an address
1062
+ * - 'allow <id-or-name>...' / 'remove <id-or-name>...': whitelist
1063
+ */
1064
+ export async function cmdDoraAutofriend(args) {
1065
+ const dir = args.configDir || ConfigLoader.defaultConfigDir();
1066
+ const configPath = resolve(dir, "config.yaml");
1067
+ const config = await ConfigLoader.load(configPath);
1068
+ const dora = config.dora ?? { enabled: false, userids: [], refreshIntervalMs: 60_000 };
1069
+ const current = dora.autoFriend ?? "none";
1070
+ if (args.mode === "list") {
1071
+ if (current === "none" || current === "all") {
1072
+ console.log(`auto-friend: ${current}`);
1073
+ }
1074
+ else {
1075
+ console.log(`auto-friend: whitelist (${current.length})`);
1076
+ for (const t of current)
1077
+ console.log(` - ${t}`);
1078
+ }
1079
+ return;
1080
+ }
1081
+ let next;
1082
+ if (args.mode === "none" || args.mode === "all") {
1083
+ next = args.mode;
1084
+ }
1085
+ else if (args.mode === "allow") {
1086
+ const list = Array.isArray(current) ? new Set(current) : new Set();
1087
+ for (const v of args.values ?? [])
1088
+ list.add(v);
1089
+ next = [...list];
1090
+ }
1091
+ else if (args.mode === "remove") {
1092
+ const list = Array.isArray(current) ? new Set(current) : new Set();
1093
+ for (const v of args.values ?? [])
1094
+ list.delete(v);
1095
+ next = list.size === 0 ? "none" : [...list];
1096
+ }
1097
+ else {
1098
+ throw new Error(`unknown autofriend mode: ${args.mode}`);
1099
+ }
1100
+ config.dora = { ...dora, autoFriend: next };
1101
+ await ConfigLoader.save(config, configPath);
1102
+ if (next === "none" || next === "all") {
1103
+ console.log(`auto-friend set to '${next}'.`);
1104
+ }
1105
+ else {
1106
+ console.log(`auto-friend whitelist now (${next.length}): ${next.join(", ")}`);
1107
+ }
1108
+ console.log(`Takes effect on the next roster refresh (~60s) or daemon restart.`);
1109
+ }
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, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyUse, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDiag, cmdDnsInstall, cmdDnsHosts, } from "./commands.js";
13
+ import { cmdInit, cmdIdentityShow, cmdPeersList, cmdIpamAssign, cmdGrant, cmdRevoke, cmdResolve, cmdStatus, cmdUp, cmdAuditLog, cmdFriendRequest, cmdFriendAccept, cmdFriendsList, cmdFriendsPending, cmdFriendsAccept, cmdFriendsReject, cmdProxyEnable, cmdProxyDisable, cmdProxyStatus, cmdProxyAllowHost, cmdProxyRevokeHost, cmdProxyListHosts, cmdProxyUse, cmdDoraEnable, cmdDoraDisable, cmdDoraStatus, cmdDoraAutofriend, cmdDiag, cmdDnsInstall, cmdDnsHosts, } from "./commands.js";
14
14
  async function main() {
15
15
  await yargs(hideBin(process.argv))
16
16
  .scriptName("agentnet")
@@ -204,6 +204,25 @@ async function main() {
204
204
  })
205
205
  .command("status", "Show dora configuration", (yy) => yy.option("config-dir", { type: "string" }), async (argv) => {
206
206
  await cmdDoraStatus({ configDir: argv["config-dir"] });
207
+ })
208
+ .command("autofriend <mode> [values..]", "Auto-friend policy: 'none' (default), 'all', 'allow <name|userid>...', 'remove <name|userid>...', or 'list'", (yy) => yy
209
+ .positional("mode", {
210
+ type: "string",
211
+ demandOption: true,
212
+ choices: ["none", "all", "allow", "remove", "list"],
213
+ describe: "Policy mode",
214
+ })
215
+ .positional("values", {
216
+ type: "string",
217
+ array: true,
218
+ describe: "For 'allow'/'remove': one or more names or userids",
219
+ })
220
+ .option("config-dir", { type: "string" }), async (argv) => {
221
+ await cmdDoraAutofriend({
222
+ mode: argv.mode,
223
+ values: argv.values,
224
+ configDir: argv["config-dir"],
225
+ });
207
226
  })
208
227
  .demandCommand(1, "Specify a dora subcommand (run 'agentnet dora --help')"), () => {
209
228
  // parent handler — never invoked because demandCommand above
@@ -108,6 +108,11 @@ export class ConfigLoader {
108
108
  enabled: false,
109
109
  userids: [],
110
110
  refreshIntervalMs: 60_000,
111
+ // Default: do NOT auto-friend roster peers. Dora is a naming
112
+ // / IP service, not a trust statement. Operators opt in with
113
+ // `agentnet dora autofriend all` (closed labs) or
114
+ // `agentnet dora autofriend allow <name|userid>` (whitelist).
115
+ autoFriend: "none",
111
116
  },
112
117
  };
113
118
  }
@@ -87,4 +87,10 @@ export declare class DoraIntegration {
87
87
  * re-register the peer or fall back to a manual friend-request.
88
88
  */
89
89
  private maybeFriend;
90
+ /**
91
+ * Decide whether a roster entry is allowed by the autoFriend
92
+ * policy. Names match case-insensitively against the entry's
93
+ * `name`; userids match exact base58.
94
+ */
95
+ private policyAllows;
90
96
  }
@@ -304,6 +304,22 @@ export class DoraIntegration {
304
304
  this.friendRequested.add(entry.userid);
305
305
  return;
306
306
  }
307
+ // Policy gate. Dora is a name service, not a trust statement.
308
+ // Default ("none") means we DON'T auto-friend roster peers —
309
+ // an operator on a multi-tenant dora isn't asking to be
310
+ // mutually-friended with everyone else just because they share
311
+ // a name service. "all" reproduces the old behavior (closed
312
+ // labs). Otherwise the policy is a whitelist of names or
313
+ // userids; only matches are friended.
314
+ const policy = this.opts.config.autoFriend ?? "none";
315
+ if (!this.policyAllows(entry, policy)) {
316
+ // Mark as "seen" so we don't re-evaluate the policy every
317
+ // 60s for entries we deliberately skip — but DON'T mark as
318
+ // "friendRequested" since the operator may flip the policy
319
+ // later and we should pick them up on next refresh after a
320
+ // daemon restart.
321
+ return;
322
+ }
307
323
  if (!entry.address) {
308
324
  this.logger.warn(`Roster entry ${entry.name} (${entry.userid.slice(0, 12)}...) has no address — can't auto-friend. ` +
309
325
  `Have them re-register against a newer dora server, or run 'agentnet friend-request' manually.`);
@@ -322,4 +338,25 @@ export class DoraIntegration {
322
338
  this.friendRequested.delete(entry.userid);
323
339
  });
324
340
  }
341
+ /**
342
+ * Decide whether a roster entry is allowed by the autoFriend
343
+ * policy. Names match case-insensitively against the entry's
344
+ * `name`; userids match exact base58.
345
+ */
346
+ policyAllows(entry, policy) {
347
+ if (policy === "all")
348
+ return true;
349
+ if (policy === "none")
350
+ return false;
351
+ if (!Array.isArray(policy) || policy.length === 0)
352
+ return false;
353
+ const nameLower = entry.name.toLowerCase();
354
+ for (const token of policy) {
355
+ if (token === entry.userid)
356
+ return true;
357
+ if (token.toLowerCase() === nameLower)
358
+ return true;
359
+ }
360
+ return false;
361
+ }
325
362
  }
package/dist/types.d.ts CHANGED
@@ -142,6 +142,25 @@ export interface DoraConfig {
142
142
  userids?: string[];
143
143
  /** How often to re-fetch the full roster from dora. Default 60_000ms. */
144
144
  refreshIntervalMs?: number;
145
+ /**
146
+ * Policy for auto-friending peers found in the dora roster. Dora is
147
+ * a NAMING and IP-ALLOCATION service — it does NOT imply trust.
148
+ * On a large dora (many tenants, customer machines, etc.) you do
149
+ * NOT want to auto-friend everyone.
150
+ *
151
+ * - `"none"` (default): never auto-friend roster peers. The operator
152
+ * initiates friend-requests explicitly with `agentnet
153
+ * friend-request --address <addr>`. The dora server itself is
154
+ * always friended out-of-band when you run `agentnet dora enable
155
+ * --address`.
156
+ * - `"all"`: friend every roster entry that carries an address.
157
+ * Useful for a closed lab of trusted peers; **dangerous on a
158
+ * shared / public dora.**
159
+ * - `string[]`: whitelist by peer NAME (matches `name` in the roster
160
+ * record) or by userid (base58). Matching entries are auto-
161
+ * friended on every refresh; everything else is left alone.
162
+ */
163
+ autoFriend?: "none" | "all" | string[];
145
164
  }
146
165
  export interface DecentAgentNetConfig {
147
166
  node: NodeConfig;
@@ -112,7 +112,27 @@ ACL-gated; see `agentnet proxy --help`.
112
112
  - **`userids`** — list of dora servers to try, in order. First
113
113
  responder wins.
114
114
  - **`refreshIntervalMs`** — how often to re-pull the roster from
115
- dora (default 60s). Drives auto-friend discovery of new peers.
115
+ dora (default 60s).
116
+ - **`autoFriend`** — policy for friending peers found in the dora
117
+ roster. Dora is a name service, NOT a trust statement; on a
118
+ multi-tenant dora you do not want the daemon to silently friend
119
+ every other peer.
120
+ - `"none"` (default) — never auto-friend roster peers; only the
121
+ dora server itself is friended (you do that explicitly with
122
+ `agentnet dora enable --address`).
123
+ - `"all"` — friend every roster entry that carries an address.
124
+ Reasonable for closed labs of mutually-trusted peers.
125
+ - `string[]` — whitelist by peer name OR userid. Only matching
126
+ entries get auto-friended.
127
+
128
+ CLI:
129
+ ```
130
+ agentnet dora autofriend none # default
131
+ agentnet dora autofriend all # closed lab
132
+ agentnet dora autofriend allow mac-dev snoopy peerX-userid # whitelist
133
+ agentnet dora autofriend remove snoopy
134
+ agentnet dora autofriend list
135
+ ```
116
136
 
117
137
  ## `policy.yaml` reference
118
138
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decentnetwork/lan",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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",