instar 1.3.704 → 1.3.705
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/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +216 -17
- package/dist/commands/server.js.map +1 -1
- package/dist/core/DeliverMessageHandler.d.ts +16 -0
- package/dist/core/DeliverMessageHandler.d.ts.map +1 -1
- package/dist/core/DeliverMessageHandler.js +0 -0
- package/dist/core/DeliverMessageHandler.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +15 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +108 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/QueueDrainLoop.d.ts +6 -2
- package/dist/core/QueueDrainLoop.d.ts.map +1 -1
- package/dist/core/QueueDrainLoop.js +0 -0
- package/dist/core/QueueDrainLoop.js.map +1 -1
- package/dist/core/SessionRouter.d.ts +24 -4
- package/dist/core/SessionRouter.d.ts.map +1 -1
- package/dist/core/SessionRouter.js +25 -2
- package/dist/core/SessionRouter.js.map +1 -1
- package/dist/core/meshRejectionLog.d.ts +37 -0
- package/dist/core/meshRejectionLog.d.ts.map +1 -0
- package/dist/core/meshRejectionLog.js +73 -0
- package/dist/core/meshRejectionLog.js.map +1 -0
- package/dist/core/registryHighWater.d.ts +59 -0
- package/dist/core/registryHighWater.d.ts.map +1 -0
- package/dist/core/registryHighWater.js +120 -0
- package/dist/core/registryHighWater.js.map +1 -0
- package/dist/core/senderRejectionNotice.d.ts +89 -0
- package/dist/core/senderRejectionNotice.d.ts.map +1 -0
- package/dist/core/senderRejectionNotice.js +149 -0
- package/dist/core/senderRejectionNotice.js.map +1 -0
- package/dist/core/senderValidationGate.d.ts +80 -0
- package/dist/core/senderValidationGate.d.ts.map +1 -0
- package/dist/core/senderValidationGate.js +133 -0
- package/dist/core/senderValidationGate.js.map +1 -0
- package/dist/core/types.d.ts +12 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/messaging/MessageProcessingLedger.d.ts +33 -5
- package/dist/messaging/MessageProcessingLedger.d.ts.map +1 -1
- package/dist/messaging/MessageProcessingLedger.js +50 -7
- package/dist/messaging/MessageProcessingLedger.js.map +1 -1
- package/dist/messaging/ingressDedup.d.ts.map +1 -1
- package/dist/messaging/ingressDedup.js +9 -0
- package/dist/messaging/ingressDedup.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +2 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +36 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/users/UserManager.d.ts +22 -1
- package/dist/users/UserManager.d.ts.map +1 -1
- package/dist/users/UserManager.js +87 -2
- package/dist/users/UserManager.js.map +1 -1
- package/dist/users/testIdentityMarkers.d.ts +94 -0
- package/dist/users/testIdentityMarkers.d.ts.map +1 -0
- package/dist/users/testIdentityMarkers.js +170 -0
- package/dist/users/testIdentityMarkers.js.map +1 -0
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates.ts +2 -1
- package/upgrades/1.3.705.md +79 -0
- package/upgrades/side-effects/silent-loss-refusal-conservation.md +124 -0
|
@@ -27,9 +27,30 @@ export declare class UserManager {
|
|
|
27
27
|
private users;
|
|
28
28
|
private channelIndex;
|
|
29
29
|
private usersFile;
|
|
30
|
+
private readonly stateDir;
|
|
31
|
+
/**
|
|
32
|
+
* silent-loss-refusal-conservation §2.D — the server-held HMAC key that
|
|
33
|
+
* verifies a signed `allowTestIdentity` marker on a legitimate fixture-collision
|
|
34
|
+
* profile. Loaded only by the server process (NOT authToken/dashboardPin).
|
|
35
|
+
* Absent (CLI / read-only probe / tests) → a fixture-collision profile is
|
|
36
|
+
* refused (write) / skipped (load) unless the double-keyed test escape is on —
|
|
37
|
+
* a marker can be neither minted nor verified without the key (safe direction).
|
|
38
|
+
*/
|
|
39
|
+
private readonly testIdentityKey;
|
|
30
40
|
/** WS2.6 user-record replication emitter (injected, dark by default). Absent ⇒ strict no-op. */
|
|
31
41
|
private userReplication;
|
|
32
|
-
constructor(stateDir: string, initialUsers?: UserProfile[]
|
|
42
|
+
constructor(stateDir: string, initialUsers?: UserProfile[], opts?: {
|
|
43
|
+
testIdentityKey?: string;
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* silent-loss-refusal-conservation §2.D — is a fixture-identity write/load
|
|
47
|
+
* PERMITTED for this profile? A match is permitted only when (a) the
|
|
48
|
+
* double-keyed test escape is active (env + on-disk test-home marker) OR (b) the
|
|
49
|
+
* profile carries a signed `allowTestIdentity` marker that VERIFIES under the
|
|
50
|
+
* server key (a legitimate name-collision, dashboard-PIN-minted). Returns the
|
|
51
|
+
* matched marker string when the write/load must be REFUSED, or null when it is
|
|
52
|
+
* permitted (either no match, or an accepted override). */
|
|
53
|
+
private refusedTestIdentity;
|
|
33
54
|
/**
|
|
34
55
|
* Late-bind the WS2.6 user-record replication emitter (server.ts constructs the journal/clock
|
|
35
56
|
* AFTER the manager). Idempotent; passing undefined/null detaches (back to single-machine
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserManager.d.ts","sourceRoot":"","sources":["../../src/users/UserManager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"UserManager.d.ts","sourceRoot":"","sources":["../../src/users/UserManager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAU1E;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,sBAAsB;IACrC,kFAAkF;IAClF,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IACnC,gFAAgF;IAChF,UAAU,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9D;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAqB;IACrD,gGAAgG;IAChG,OAAO,CAAC,eAAe,CAAuC;gBAElD,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,WAAW,EAAE,EAAE,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAA;KAAE;IAO/F;;;;;;;+DAO2D;IAC3D,OAAO,CAAC,mBAAmB;IAQ3B;;;;;OAKG;IACH,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAInF;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI;IAIxD;;OAEG;IACH,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,GAAG,IAAI;IAO5D;;;;;;;OAOG;IACH,yBAAyB,CAAC,cAAc,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAQrE;;;;;;;OAOG;IACH,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAW/D;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAI3C;;OAEG;IACH,SAAS,IAAI,WAAW,EAAE;IAI1B;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;IAmCtC;;OAEG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IA4BnC;;OAEG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAM1D;;;OAGG;IACH,kBAAkB,CAAC,cAAc,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW;IAYpG;;;OAGG;IACH,qBAAqB,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAQpF;;OAEG;IACH,SAAS,IAAI,WAAW,EAAE;IAI1B,OAAO,CAAC,eAAe;IAsBvB,OAAO,CAAC,SAAS;IAsEjB,OAAO,CAAC,YAAY;CA+BrB"}
|
|
@@ -8,16 +8,48 @@ import fs from 'node:fs';
|
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
import { randomBytes } from 'node:crypto';
|
|
10
10
|
import { SafeFsExecutor } from '../core/SafeFsExecutor.js';
|
|
11
|
+
import { matchTestIdentity, testIdentitiesAllowed, verifyAllowTestIdentity, TestIdentityRefusedError, } from './testIdentityMarkers.js';
|
|
12
|
+
import { setRegistryHighWater } from '../core/registryHighWater.js';
|
|
11
13
|
export class UserManager {
|
|
12
14
|
users = new Map();
|
|
13
15
|
channelIndex = new Map(); // "type:identifier" -> userId
|
|
14
16
|
usersFile;
|
|
17
|
+
stateDir;
|
|
18
|
+
/**
|
|
19
|
+
* silent-loss-refusal-conservation §2.D — the server-held HMAC key that
|
|
20
|
+
* verifies a signed `allowTestIdentity` marker on a legitimate fixture-collision
|
|
21
|
+
* profile. Loaded only by the server process (NOT authToken/dashboardPin).
|
|
22
|
+
* Absent (CLI / read-only probe / tests) → a fixture-collision profile is
|
|
23
|
+
* refused (write) / skipped (load) unless the double-keyed test escape is on —
|
|
24
|
+
* a marker can be neither minted nor verified without the key (safe direction).
|
|
25
|
+
*/
|
|
26
|
+
testIdentityKey;
|
|
15
27
|
/** WS2.6 user-record replication emitter (injected, dark by default). Absent ⇒ strict no-op. */
|
|
16
28
|
userReplication = null;
|
|
17
|
-
constructor(stateDir, initialUsers) {
|
|
29
|
+
constructor(stateDir, initialUsers, opts) {
|
|
18
30
|
this.usersFile = path.join(stateDir, 'users.json');
|
|
31
|
+
this.stateDir = stateDir;
|
|
32
|
+
this.testIdentityKey = opts?.testIdentityKey;
|
|
19
33
|
this.loadUsers(initialUsers);
|
|
20
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* silent-loss-refusal-conservation §2.D — is a fixture-identity write/load
|
|
37
|
+
* PERMITTED for this profile? A match is permitted only when (a) the
|
|
38
|
+
* double-keyed test escape is active (env + on-disk test-home marker) OR (b) the
|
|
39
|
+
* profile carries a signed `allowTestIdentity` marker that VERIFIES under the
|
|
40
|
+
* server key (a legitimate name-collision, dashboard-PIN-minted). Returns the
|
|
41
|
+
* matched marker string when the write/load must be REFUSED, or null when it is
|
|
42
|
+
* permitted (either no match, or an accepted override). */
|
|
43
|
+
refusedTestIdentity(profile) {
|
|
44
|
+
const marker = matchTestIdentity(profile);
|
|
45
|
+
if (!marker)
|
|
46
|
+
return null;
|
|
47
|
+
if (testIdentitiesAllowed(this.stateDir))
|
|
48
|
+
return null;
|
|
49
|
+
if (verifyAllowTestIdentity(this.testIdentityKey, profile.id, marker, profile.allowTestIdentity))
|
|
50
|
+
return null;
|
|
51
|
+
return marker;
|
|
52
|
+
}
|
|
21
53
|
/**
|
|
22
54
|
* Late-bind the WS2.6 user-record replication emitter (server.ts constructs the journal/clock
|
|
23
55
|
* AFTER the manager). Idempotent; passing undefined/null detaches (back to single-machine
|
|
@@ -120,6 +152,14 @@ export class UserManager {
|
|
|
120
152
|
this.channelIndex.set(`${channel.type}:${channel.identifier}`, profile.id);
|
|
121
153
|
}
|
|
122
154
|
this.persistUsers();
|
|
155
|
+
// silent-loss-refusal-conservation §2.D set-point: a successful register/upsert
|
|
156
|
+
// of a (validated non-fixture) user means the authoritative local registry now
|
|
157
|
+
// holds a real user — set the monotonic high-water marker so a later emptying
|
|
158
|
+
// classifies POPULATED (emptied-by-deletion), not never-populated.
|
|
159
|
+
try {
|
|
160
|
+
setRegistryHighWater(this.stateDir, 'user-registered');
|
|
161
|
+
}
|
|
162
|
+
catch { /* best-effort */ }
|
|
123
163
|
}
|
|
124
164
|
/**
|
|
125
165
|
* Remove a user.
|
|
@@ -201,6 +241,16 @@ export class UserManager {
|
|
|
201
241
|
if (!Array.isArray(profile.permissions)) {
|
|
202
242
|
throw new Error(`UserProfile(${profile.id}).permissions must be an array`);
|
|
203
243
|
}
|
|
244
|
+
// silent-loss-refusal-conservation §2.D — fixture refusal at the WRITE path
|
|
245
|
+
// (API/CLI/registration). "Test Identity Never Enters Production State": a
|
|
246
|
+
// typed throw so a fixture id can never be persisted into the production
|
|
247
|
+
// registry (the 2026-07-01 clobber's write side). A legitimate name-collision
|
|
248
|
+
// supplies a dashboard-PIN-minted signed `allowTestIdentity`; an isolated test
|
|
249
|
+
// home sets the double-keyed escape.
|
|
250
|
+
const refused = this.refusedTestIdentity(profile);
|
|
251
|
+
if (refused) {
|
|
252
|
+
throw new TestIdentityRefusedError(profile.id, refused);
|
|
253
|
+
}
|
|
204
254
|
}
|
|
205
255
|
loadUsers(initialUsers) {
|
|
206
256
|
// Load from file if exists
|
|
@@ -213,6 +263,19 @@ export class UserManager {
|
|
|
213
263
|
console.warn(`[UserManager] Skipping malformed user entry: ${JSON.stringify(user).slice(0, 100)}`);
|
|
214
264
|
continue;
|
|
215
265
|
}
|
|
266
|
+
// silent-loss-refusal-conservation §2.D — fixture refusal at the LOAD
|
|
267
|
+
// path. Refuse-and-skip-with-loud-alert (NEVER throw — a constructor
|
|
268
|
+
// throw fails boot). A fixture row that slipped into an already-polluted
|
|
269
|
+
// store is dropped from the in-memory registry so it can never resolve
|
|
270
|
+
// as a real sender; the §4 boot migration quarantines it off disk.
|
|
271
|
+
const refused = this.refusedTestIdentity(user);
|
|
272
|
+
if (refused) {
|
|
273
|
+
console.error(`[UserManager] REFUSING to load fixture/test identity "${user.id}" (matched marker "${refused}") from the user registry ` +
|
|
274
|
+
`— "Test Identity Never Enters Production State" (silent-loss-refusal-conservation §2.D). ` +
|
|
275
|
+
`The row is skipped in-memory; the boot migration quarantines it off disk. ` +
|
|
276
|
+
`If this is a legitimate user, register them via the dashboard-PIN-authed allow-identity override.`);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
216
279
|
this.users.set(user.id, user);
|
|
217
280
|
for (const channel of user.channels) {
|
|
218
281
|
if (channel.type && channel.identifier) {
|
|
@@ -235,10 +298,32 @@ export class UserManager {
|
|
|
235
298
|
if (initialUsers) {
|
|
236
299
|
for (const user of initialUsers) {
|
|
237
300
|
if (!this.users.has(user.id)) {
|
|
238
|
-
|
|
301
|
+
// upsertUser → validateProfile refuses a fixture initialUsers entry
|
|
302
|
+
// (typed throw). Guard so a fixture in config.users can't fail boot;
|
|
303
|
+
// a real initialUsers merge below sets the high-water marker.
|
|
304
|
+
try {
|
|
305
|
+
this.upsertUser(user);
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
if (err instanceof TestIdentityRefusedError) {
|
|
309
|
+
console.error(`[UserManager] Skipped fixture identity from initialUsers: ${err.message}`);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
throw err;
|
|
313
|
+
}
|
|
239
314
|
}
|
|
240
315
|
}
|
|
241
316
|
}
|
|
317
|
+
// silent-loss-refusal-conservation §2.D set-point: if the authoritative local
|
|
318
|
+
// registry holds ≥1 resolvable real user, this machine has "held a real user"
|
|
319
|
+
// — set the monotonic high-water marker so a LATER emptying classifies as
|
|
320
|
+
// POPULATED (emptied-by-deletion → keep rejecting), not never-populated.
|
|
321
|
+
if (this.users.size > 0) {
|
|
322
|
+
try {
|
|
323
|
+
setRegistryHighWater(this.stateDir, 'load-observed-real-user');
|
|
324
|
+
}
|
|
325
|
+
catch { /* best-effort */ }
|
|
326
|
+
}
|
|
242
327
|
}
|
|
243
328
|
persistUsers() {
|
|
244
329
|
const dir = path.dirname(this.usersFile);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserManager.js","sourceRoot":"","sources":["../../src/users/UserManager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"UserManager.js","sourceRoot":"","sources":["../../src/users/UserManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-Identity Refusal (silent-loss-refusal-conservation §2.D — "Test Identity
|
|
3
|
+
* Never Enters Production State"). The 2026-07-01 silent-loss incident began 19
|
|
4
|
+
* days earlier when a live-test / harness run clobbered a machine's `users.json`
|
|
5
|
+
* with FIXTURE identities. A registry populated with only fixtures rejects every
|
|
6
|
+
* real sender (including the operator) once sender re-validation arms.
|
|
7
|
+
*
|
|
8
|
+
* This module is the single source of truth for WHICH identities are fixtures
|
|
9
|
+
* and HOW a legitimate name-collision is overridden. It is pure + dependency-free
|
|
10
|
+
* so both the write-path guard (UserManager.validateProfile — typed throw) and the
|
|
11
|
+
* load-path guard (UserManager.loadUsers — refuse-and-skip, never throw) and the
|
|
12
|
+
* §4 boot remediation share ONE matcher and can never drift.
|
|
13
|
+
*
|
|
14
|
+
* MATCH RULE (round-2 adversarial #10): exact match on the known fixture platform
|
|
15
|
+
* IDS + reserved-token anchored prefixes on ids — DISPLAY NAME IS NEVER A MATCH
|
|
16
|
+
* CRITERION (a real user named "Olivia" registers fine). See §2.D / decision 3.
|
|
17
|
+
*/
|
|
18
|
+
import type { UserProfile } from '../core/types.js';
|
|
19
|
+
/**
|
|
20
|
+
* The closed fixture-identity set. IDS + reserved tokens only — never a
|
|
21
|
+
* display-name. Exact-match on the known platform ids; anchored-prefix match on
|
|
22
|
+
* the reserved `livetest`/`g3test` tokens.
|
|
23
|
+
*
|
|
24
|
+
* - `slackIds` — the demo Slack workspace user ids used by the live-test harness.
|
|
25
|
+
* - `harnessIds` — the `u-*` / `U_*` fixture user ids the integration harness writes.
|
|
26
|
+
* - `reservedTokenPrefixes` — anchored id/username prefixes the isolated test-home
|
|
27
|
+
* scaffold uses (`livetest…`, `g3test…`).
|
|
28
|
+
*/
|
|
29
|
+
export declare const TEST_IDENTITY_MARKERS: {
|
|
30
|
+
readonly slackIds: readonly ["U0BA7QGPBQS", "U0BA5NW9QA2", "U0B9SFJ7QAK", "U0B9SFV2BAT", "U0BA4L8RMFF"];
|
|
31
|
+
readonly harnessIds: readonly ["u-olivia", "u-adam", "u-mia", "u-cory", "u-oscar", "U_OLIVIA", "U_ADAM", "U_MIA", "U_CORY", "U_OSCAR"];
|
|
32
|
+
readonly reservedTokenPrefixes: readonly ["livetest", "g3test"];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Does a single string token match a fixture marker? Exact match against the
|
|
36
|
+
* known-id set, OR an anchored (^) reserved-token prefix. Case-sensitive on the
|
|
37
|
+
* exact set (platform ids are case-significant); the reserved-token prefix is
|
|
38
|
+
* matched case-INsensitively (a `LiveTest…` id is still a fixture).
|
|
39
|
+
*/
|
|
40
|
+
export declare function matchesTestIdentityToken(token: string | undefined | null): string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Classify a profile against the fixture-identity set. Checks the id, the Slack
|
|
43
|
+
* user id, and slack-typed channel identifiers — NEVER the display name.
|
|
44
|
+
* Returns the matched marker string (for the audit + the allow-marker) or null.
|
|
45
|
+
*/
|
|
46
|
+
export declare function matchTestIdentity(profile: Pick<UserProfile, 'id' | 'slackUserId' | 'channels'>): string | null;
|
|
47
|
+
/** Typed error thrown by the WRITE path (validateProfile) when a fixture identity
|
|
48
|
+
* is written without a verifying allow-marker + the double-keyed test escape. */
|
|
49
|
+
export declare class TestIdentityRefusedError extends Error {
|
|
50
|
+
readonly marker: string;
|
|
51
|
+
constructor(profileId: string, marker: string);
|
|
52
|
+
}
|
|
53
|
+
/** The on-disk marker a test home's scaffold writes to opt that home into
|
|
54
|
+
* fixture-identity writes. Its PRESENCE is the second key; contents are ignored. */
|
|
55
|
+
export declare const TEST_HOME_MARKER_FILENAME = ".instar-test-home";
|
|
56
|
+
/**
|
|
57
|
+
* The narrow escape is double-keyed (§2.D / decision 11): the env var
|
|
58
|
+
* `INSTAR_ALLOW_TEST_IDENTITIES=1` is honored ONLY when an on-disk test-home
|
|
59
|
+
* marker is ALSO present under the state dir. Env-set-but-marker-absent → refuse
|
|
60
|
+
* (a stray env var on a production box can never open the door).
|
|
61
|
+
*/
|
|
62
|
+
export declare function testIdentitiesAllowed(stateDir: string, env?: NodeJS.ProcessEnv): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* A profile that legitimately collides with a fixture marker carries a signed
|
|
65
|
+
* allow-marker `{ marker, sig }`. The PIN gates MINTING (the route); the LOAD
|
|
66
|
+
* path VERIFIES the HMAC with no PIN. `sig = HMAC-SHA256(serverKey, userId + ":" + marker)`
|
|
67
|
+
* where `serverKey` is a server-held vault secret loaded only by the server
|
|
68
|
+
* process — NOT the authToken/dashboardPin, NOT a state-dir file a plain
|
|
69
|
+
* users.json writer reads (§2.D / decision 23, honest threat model).
|
|
70
|
+
*/
|
|
71
|
+
export interface AllowTestIdentityMarker {
|
|
72
|
+
marker: string;
|
|
73
|
+
sig: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Derive the server-held allow-marker HMAC key from the machine SIGNING key PEM
|
|
77
|
+
* — a server-held secret loaded only by the server process, NOT the
|
|
78
|
+
* authToken/dashboardPin and NOT users.json (§2.D / decision 23, honest scope:
|
|
79
|
+
* a fully-FS-privileged process is out of scope). Returns undefined when no
|
|
80
|
+
* signing key exists (a single-machine non-mesh install) — then a fixture
|
|
81
|
+
* override can be neither minted nor verified (the safe direction). The raw PEM
|
|
82
|
+
* is never used directly; the key is a domain-separated hash of it.
|
|
83
|
+
*/
|
|
84
|
+
export declare function loadTestIdentityKey(stateDir: string): string | undefined;
|
|
85
|
+
/** Mint the HMAC signature for a legitimate fixture-collision override. */
|
|
86
|
+
export declare function signAllowTestIdentity(serverKey: string, userId: string, marker: string): string;
|
|
87
|
+
/**
|
|
88
|
+
* Verify a profile's signed allow-marker on the LOAD path (no PIN present).
|
|
89
|
+
* Constant-time compare. Returns true iff the profile carries an
|
|
90
|
+
* `allowTestIdentity` whose `marker` equals the matched marker AND whose `sig`
|
|
91
|
+
* recomputes under the server key. A missing/bogus sig → false → quarantine.
|
|
92
|
+
*/
|
|
93
|
+
export declare function verifyAllowTestIdentity(serverKey: string | undefined | null, userId: string, matchedMarker: string, allow: AllowTestIdentityMarker | undefined | null): boolean;
|
|
94
|
+
//# sourceMappingURL=testIdentityMarkers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testIdentityMarkers.d.ts","sourceRoot":"","sources":["../../src/users/testIdentityMarkers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB;;;;CAOxB,CAAC;AAOX;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAQxF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,aAAa,GAAG,UAAU,CAAC,GAAG,MAAM,GAAG,IAAI,CAc9G;AAED;kFACkF;AAClF,qBAAa,wBAAyB,SAAQ,KAAK;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAU9C;AAID;qFACqF;AACrF,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAE7D;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAOrG;AAID;;;;;;;GAOG;AACH,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACb;AAMD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUxE;AAED,2EAA2E;AAC3E,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE/F;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EACpC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,uBAAuB,GAAG,SAAS,GAAG,IAAI,GAChD,OAAO,CAaT"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test-Identity Refusal (silent-loss-refusal-conservation §2.D — "Test Identity
|
|
3
|
+
* Never Enters Production State"). The 2026-07-01 silent-loss incident began 19
|
|
4
|
+
* days earlier when a live-test / harness run clobbered a machine's `users.json`
|
|
5
|
+
* with FIXTURE identities. A registry populated with only fixtures rejects every
|
|
6
|
+
* real sender (including the operator) once sender re-validation arms.
|
|
7
|
+
*
|
|
8
|
+
* This module is the single source of truth for WHICH identities are fixtures
|
|
9
|
+
* and HOW a legitimate name-collision is overridden. It is pure + dependency-free
|
|
10
|
+
* so both the write-path guard (UserManager.validateProfile — typed throw) and the
|
|
11
|
+
* load-path guard (UserManager.loadUsers — refuse-and-skip, never throw) and the
|
|
12
|
+
* §4 boot remediation share ONE matcher and can never drift.
|
|
13
|
+
*
|
|
14
|
+
* MATCH RULE (round-2 adversarial #10): exact match on the known fixture platform
|
|
15
|
+
* IDS + reserved-token anchored prefixes on ids — DISPLAY NAME IS NEVER A MATCH
|
|
16
|
+
* CRITERION (a real user named "Olivia" registers fine). See §2.D / decision 3.
|
|
17
|
+
*/
|
|
18
|
+
import crypto from 'node:crypto';
|
|
19
|
+
import fs from 'node:fs';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
/**
|
|
22
|
+
* The closed fixture-identity set. IDS + reserved tokens only — never a
|
|
23
|
+
* display-name. Exact-match on the known platform ids; anchored-prefix match on
|
|
24
|
+
* the reserved `livetest`/`g3test` tokens.
|
|
25
|
+
*
|
|
26
|
+
* - `slackIds` — the demo Slack workspace user ids used by the live-test harness.
|
|
27
|
+
* - `harnessIds` — the `u-*` / `U_*` fixture user ids the integration harness writes.
|
|
28
|
+
* - `reservedTokenPrefixes` — anchored id/username prefixes the isolated test-home
|
|
29
|
+
* scaffold uses (`livetest…`, `g3test…`).
|
|
30
|
+
*/
|
|
31
|
+
export const TEST_IDENTITY_MARKERS = {
|
|
32
|
+
slackIds: ['U0BA7QGPBQS', 'U0BA5NW9QA2', 'U0B9SFJ7QAK', 'U0B9SFV2BAT', 'U0BA4L8RMFF'],
|
|
33
|
+
harnessIds: [
|
|
34
|
+
'u-olivia', 'u-adam', 'u-mia', 'u-cory', 'u-oscar',
|
|
35
|
+
'U_OLIVIA', 'U_ADAM', 'U_MIA', 'U_CORY', 'U_OSCAR',
|
|
36
|
+
],
|
|
37
|
+
reservedTokenPrefixes: ['livetest', 'g3test'],
|
|
38
|
+
};
|
|
39
|
+
const EXACT_IDS = new Set([
|
|
40
|
+
...TEST_IDENTITY_MARKERS.slackIds,
|
|
41
|
+
...TEST_IDENTITY_MARKERS.harnessIds,
|
|
42
|
+
]);
|
|
43
|
+
/**
|
|
44
|
+
* Does a single string token match a fixture marker? Exact match against the
|
|
45
|
+
* known-id set, OR an anchored (^) reserved-token prefix. Case-sensitive on the
|
|
46
|
+
* exact set (platform ids are case-significant); the reserved-token prefix is
|
|
47
|
+
* matched case-INsensitively (a `LiveTest…` id is still a fixture).
|
|
48
|
+
*/
|
|
49
|
+
export function matchesTestIdentityToken(token) {
|
|
50
|
+
if (typeof token !== 'string' || !token)
|
|
51
|
+
return null;
|
|
52
|
+
if (EXACT_IDS.has(token))
|
|
53
|
+
return token;
|
|
54
|
+
const lower = token.toLowerCase();
|
|
55
|
+
for (const prefix of TEST_IDENTITY_MARKERS.reservedTokenPrefixes) {
|
|
56
|
+
if (lower.startsWith(prefix))
|
|
57
|
+
return token;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Classify a profile against the fixture-identity set. Checks the id, the Slack
|
|
63
|
+
* user id, and slack-typed channel identifiers — NEVER the display name.
|
|
64
|
+
* Returns the matched marker string (for the audit + the allow-marker) or null.
|
|
65
|
+
*/
|
|
66
|
+
export function matchTestIdentity(profile) {
|
|
67
|
+
const idMatch = matchesTestIdentityToken(profile.id);
|
|
68
|
+
if (idMatch)
|
|
69
|
+
return idMatch;
|
|
70
|
+
const slackMatch = matchesTestIdentityToken(profile.slackUserId ?? null);
|
|
71
|
+
if (slackMatch)
|
|
72
|
+
return slackMatch;
|
|
73
|
+
if (Array.isArray(profile.channels)) {
|
|
74
|
+
for (const ch of profile.channels) {
|
|
75
|
+
if (ch && ch.type === 'slack') {
|
|
76
|
+
const chMatch = matchesTestIdentityToken(ch.identifier);
|
|
77
|
+
if (chMatch)
|
|
78
|
+
return chMatch;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/** Typed error thrown by the WRITE path (validateProfile) when a fixture identity
|
|
85
|
+
* is written without a verifying allow-marker + the double-keyed test escape. */
|
|
86
|
+
export class TestIdentityRefusedError extends Error {
|
|
87
|
+
marker;
|
|
88
|
+
constructor(profileId, marker) {
|
|
89
|
+
super(`Refusing to persist test/fixture identity "${profileId}" (matched marker "${marker}") into the production user registry ` +
|
|
90
|
+
`— "Test Identity Never Enters Production State" (silent-loss-refusal-conservation §2.D). ` +
|
|
91
|
+
`A legitimate name-collision uses the dashboard-PIN-authed X-Instar-Allow-Identity override; ` +
|
|
92
|
+
`an isolated test home sets INSTAR_ALLOW_TEST_IDENTITIES=1 plus the on-disk test-home marker.`);
|
|
93
|
+
this.name = 'TestIdentityRefusedError';
|
|
94
|
+
this.marker = marker;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// ── Double-keyed test escape (env + on-disk test-home marker) ────────────────
|
|
98
|
+
/** The on-disk marker a test home's scaffold writes to opt that home into
|
|
99
|
+
* fixture-identity writes. Its PRESENCE is the second key; contents are ignored. */
|
|
100
|
+
export const TEST_HOME_MARKER_FILENAME = '.instar-test-home';
|
|
101
|
+
/**
|
|
102
|
+
* The narrow escape is double-keyed (§2.D / decision 11): the env var
|
|
103
|
+
* `INSTAR_ALLOW_TEST_IDENTITIES=1` is honored ONLY when an on-disk test-home
|
|
104
|
+
* marker is ALSO present under the state dir. Env-set-but-marker-absent → refuse
|
|
105
|
+
* (a stray env var on a production box can never open the door).
|
|
106
|
+
*/
|
|
107
|
+
export function testIdentitiesAllowed(stateDir, env = process.env) {
|
|
108
|
+
if (env.INSTAR_ALLOW_TEST_IDENTITIES !== '1')
|
|
109
|
+
return false;
|
|
110
|
+
try {
|
|
111
|
+
return fs.existsSync(path.join(stateDir, TEST_HOME_MARKER_FILENAME));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function allowMarkerMessage(userId, marker) {
|
|
118
|
+
return `${userId}:${marker}`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Derive the server-held allow-marker HMAC key from the machine SIGNING key PEM
|
|
122
|
+
* — a server-held secret loaded only by the server process, NOT the
|
|
123
|
+
* authToken/dashboardPin and NOT users.json (§2.D / decision 23, honest scope:
|
|
124
|
+
* a fully-FS-privileged process is out of scope). Returns undefined when no
|
|
125
|
+
* signing key exists (a single-machine non-mesh install) — then a fixture
|
|
126
|
+
* override can be neither minted nor verified (the safe direction). The raw PEM
|
|
127
|
+
* is never used directly; the key is a domain-separated hash of it.
|
|
128
|
+
*/
|
|
129
|
+
export function loadTestIdentityKey(stateDir) {
|
|
130
|
+
for (const name of ['signing-key.pem', 'signing-private.pem']) {
|
|
131
|
+
try {
|
|
132
|
+
const pem = fs.readFileSync(path.join(stateDir, 'machine', name), 'utf-8');
|
|
133
|
+
if (pem && pem.trim()) {
|
|
134
|
+
return crypto.createHash('sha256').update(`instar-test-identity-allow:${pem}`).digest('hex');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch { /* try next / none */ }
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
/** Mint the HMAC signature for a legitimate fixture-collision override. */
|
|
142
|
+
export function signAllowTestIdentity(serverKey, userId, marker) {
|
|
143
|
+
return crypto.createHmac('sha256', serverKey).update(allowMarkerMessage(userId, marker)).digest('hex');
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Verify a profile's signed allow-marker on the LOAD path (no PIN present).
|
|
147
|
+
* Constant-time compare. Returns true iff the profile carries an
|
|
148
|
+
* `allowTestIdentity` whose `marker` equals the matched marker AND whose `sig`
|
|
149
|
+
* recomputes under the server key. A missing/bogus sig → false → quarantine.
|
|
150
|
+
*/
|
|
151
|
+
export function verifyAllowTestIdentity(serverKey, userId, matchedMarker, allow) {
|
|
152
|
+
if (!serverKey)
|
|
153
|
+
return false;
|
|
154
|
+
if (!allow || typeof allow.marker !== 'string' || typeof allow.sig !== 'string')
|
|
155
|
+
return false;
|
|
156
|
+
if (allow.marker !== matchedMarker)
|
|
157
|
+
return false;
|
|
158
|
+
const expected = signAllowTestIdentity(serverKey, userId, matchedMarker);
|
|
159
|
+
try {
|
|
160
|
+
const a = Buffer.from(expected, 'hex');
|
|
161
|
+
const b = Buffer.from(allow.sig, 'hex');
|
|
162
|
+
if (a.length !== b.length || a.length === 0)
|
|
163
|
+
return false;
|
|
164
|
+
return crypto.timingSafeEqual(a, b);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=testIdentityMarkers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testIdentityMarkers.js","sourceRoot":"","sources":["../../src/users/testIdentityMarkers.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED