@volter/teams 0.2.18 → 0.2.20

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/board-name.mjs ADDED
@@ -0,0 +1,14 @@
1
+ // A board's name, the last part of its address (sc:<machine>:board:<name>): the one rule both the board (which names
2
+ // itself) and a machine's daemon (which finds a board among the homes registered with it) read. A home's own board is
3
+ // named for the folder that owns the home (`…/volter-org/home` is `volter-org`), a named board of it `<that>/<board>`;
4
+ // any character other than A-Z a-z 0-9 _ . - in the name becomes `-`.
5
+ import { basename, dirname } from 'node:path';
6
+
7
+ /** The board's label as people read it: `volter-org`, or `volter-org/<board>` for a named board. */
8
+ export function boardLabel(root, slug) {
9
+ const home = basename(root) === 'home' ? basename(dirname(root)) : basename(root);
10
+ return slug && slug !== 'default' ? `${home}/${slug}` : home;
11
+ }
12
+
13
+ /** The label as an address carries it. */
14
+ export const boardName = (label) => label.replace(/[^A-Za-z0-9_.-]/g, '-');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter/teams",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "type": "module",
5
5
  "description": "Volter Teams, the neutral team plane: machine identity and daemon core, the local door and server link, admission from machine grants, product doors, and the Teams server's neutral routes (company RFC 0008).",
6
6
  "license": "MIT",
@@ -53,7 +53,8 @@
53
53
  "./redact": {
54
54
  "default": "./redact.mjs"
55
55
  },
56
- "./checkout": "./checkout.mjs"
56
+ "./checkout": "./checkout.mjs",
57
+ "./board-name": "./board-name.mjs"
57
58
  },
58
59
  "files": [
59
60
  "LICENSE-MIT",
package/redact.mjs CHANGED
@@ -3,7 +3,8 @@
3
3
  // - the values this machine keeps in its custody files (KEY=value lines), which no detector knows; the files are named
4
4
  // in `<supercode home>/custody.json` ({ "files": [...] }) and in SUPERCODE_CUSTODY_FILES (path-separated);
5
5
  // - gitleaks' default rule set, vendored unchanged as redact/gitleaks-rules.json (its source and sha256 inside): each
6
- // rule's keywords gate it, its secret group is what is replaced, and its entropy floor is kept.
6
+ // rule's keywords gate it, its secret group is what is replaced, and its entropy floor is kept;
7
+ // - the tokens a Teams server mints itself (TEAM_TOKENS), which no gitleaks rule knows.
7
8
  // Local files are never touched; only what is sent is redacted.
8
9
  import { readFileSync, existsSync, statSync } from 'node:fs';
9
10
  import { createHash } from 'node:crypto';
@@ -103,6 +104,13 @@ function longestMatch(source) {
103
104
  const RULES = vendored.rules.map((rule) => { const regex = jsPattern(rule.regex); return { id: rule.id, regex, longest: regex ? longestMatch(regex.source) : Infinity, group: rule.secretGroup ?? null, keywords: (rule.keywords ?? []).map((k) => k.toLowerCase()), entropy: rule.entropy ?? null }; }).filter((rule) => rule.regex);
104
105
  export const RULESET = { source: vendored.source, sha256: vendored.sha256, rules: RULES.length };
105
106
 
107
+ // The tokens a Teams server mints (sdk/volter-teams/server, sdk/teams/server): a credential `sct_<id>.<32 bytes>`
108
+ // (`sct_cred_<hex>.…`, the one three Codex session copies uploaded on 2026-09-30/10-01 carried, purged under D126), an
109
+ // invitation or install code `sci_[<id>.]<32 bytes>`, an app secret `sca_<32 bytes>`, an attachment token
110
+ // `sat_<id>.<32 bytes>`, each base64url, and a webhook signing secret `whsec_<32 bytes, base64>`. Each is replaced
111
+ // whole, by a marker naming its kind.
112
+ const TEAM_TOKENS = /\b(sct|sci|sca|sat)_(?:[A-Za-z]+_[0-9a-f]{32}\.)?[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])|\b(whsec)_[A-Za-z0-9+/]{43}=/g;
113
+
106
114
  // Where each of many literals occurs, in one pass over the text (gitleaks finds its keywords the same way, with one
107
115
  // automaton): a lookahead finds every position some literal starts at, and only those are compared literal by literal.
108
116
  // Hundreds of separate includes() read a 5 MB tool result hundreds of times.
@@ -182,6 +190,7 @@ export function redactText(text, env = process.env) {
182
190
  // Custody values are random, so one alternation of them gives the regex engine nothing to skip on: each is looked
183
191
  // for on its own, which reads a 5 MB text in a few dozen milliseconds.
184
192
  for (const { value, name } of custodyValues(env)) if (out.includes(value)) out = out.split(value).join(`[redacted:${name}]`);
193
+ if (/(?:sct|sci|sca|sat|whsec)_/.test(out)) out = out.replace(TEAM_TOKENS, (_, kind, hook) => `[redacted:teams-${kind ?? hook}-token]`);
185
194
  // Every rule has keywords, so a text holding none of them is done (the one test is cheap; finding where each keyword
186
195
  // occurs is not, and most texts are short and hold none).
187
196
  let lower = out.toLowerCase();