@1agh/maude 0.54.0 → 0.55.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.
@@ -17,10 +17,10 @@
17
17
 
18
18
  import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
19
19
  import { homedir } from 'node:os';
20
- import { dirname, join } from 'node:path';
20
+ import { basename, dirname, join } from 'node:path';
21
21
 
22
22
  import { saveHubCredential } from '../sync/hub-link.ts';
23
- import { normalizeUrl } from '../sync/hubs-config.ts';
23
+ import { getHubToken, normalizeUrl } from '../sync/hubs-config.ts';
24
24
 
25
25
  /**
26
26
  * Where Maude Cloud lives, resolved PER CALL rather than at module load.
@@ -113,14 +113,28 @@ interface Ctx {
113
113
 
114
114
  export function createCloudEndpoints(ctx: Ctx) {
115
115
  return {
116
- /** Signed in? Who? Cheap — reads the file, never the network. */
116
+ /**
117
+ * Signed in? Who? And WHICH folder is asking? Cheap — reads two files,
118
+ * never the network.
119
+ *
120
+ * `project` + `linkedHub` are the local half of the answer, and they exist
121
+ * for one reason: a maude:// link names a CLOUD project, and the person
122
+ * confirming it deserves to see which LOCAL folder is about to be attached
123
+ * to it. Both are already-public facts (a directory name, and the
124
+ * token-free `linkedHub` this same module writes into a committed
125
+ * config.json) — no credential material widens by being reported here.
126
+ */
117
127
  status(): CloudEndpointResult {
118
128
  const file = readCloudFile();
119
129
  return {
120
130
  status: 200,
121
- json: file
122
- ? { connected: true, email: file.email ?? null, url: file.url }
123
- : { connected: false, url: cloudUrl() },
131
+ json: {
132
+ connected: !!file,
133
+ email: file?.email ?? null,
134
+ url: file ? file.url : cloudUrl(),
135
+ project: basename(ctx.paths.repoRoot),
136
+ linkedHub: readLinkedHub(),
137
+ },
124
138
  };
125
139
  },
126
140
 
@@ -292,6 +306,34 @@ export function createCloudEndpoints(ctx: Ctx) {
292
306
  },
293
307
  };
294
308
 
309
+ /**
310
+ * The workspace this folder already answers to, if any. Address only.
311
+ *
312
+ * `credentialed` is the half that can be trusted. `config.json` is COMMITTED
313
+ * and travels with the repo, so a `linkedHub` in it is attacker-authorable:
314
+ * publish a template pointing at your own cell and the connect dialog would
315
+ * cheerfully print "this folder is already linked to <you>" — the strongest
316
+ * reassurance it can give, produced entirely by content the person merely
317
+ * opened (attacker pass 2026-08-04, B2). A stored hub credential for that
318
+ * address is the corroboration, because only a real sign-in writes one.
319
+ */
320
+ function readLinkedHub(): { url: string; credentialed: boolean } | null {
321
+ try {
322
+ const cfg = JSON.parse(readFileSync(join(ctx.paths.designRoot, 'config.json'), 'utf8'));
323
+ const url = cfg?.linkedHub?.url;
324
+ if (typeof url !== 'string' || !url) return null;
325
+ let credentialed = false;
326
+ try {
327
+ credentialed = !!getHubToken(normalizeUrl(url));
328
+ } catch {
329
+ /* unreadable credential store → treat as uncorroborated */
330
+ }
331
+ return { url, credentialed };
332
+ } catch {
333
+ return null; // absent/malformed → simply not linked
334
+ }
335
+ }
336
+
295
337
  /** The shared tail of every attach: cell exchange → credential + linkedHub. */
296
338
  async function linkToWorkspace({
297
339
  workspaceUrl,