@optimuslabs/harness-map-staging 1.5.35 → 1.5.36
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/approved_tenant_gate.js +62 -96
- package/dist/compliance_prompt_gate.js +15 -13
- package/package.json +1 -1
|
@@ -87,7 +87,7 @@ export function readClaudeSignedInTenant(env = process.env) {
|
|
|
87
87
|
/**
|
|
88
88
|
* Block messages, one shape for every agent (Optimus Labs' prompt-block wording):
|
|
89
89
|
* "This device only allows <agent> <under/on/in> the <approved> <kind>. You're signed in to
|
|
90
|
-
* <what>. <how to switch>." With nothing approved yet: "This device doesn't allow <agent> signed in
|
|
90
|
+
* <what>. <how to switch>." With nothing approved yet: "This device doesn't allow <agent> to be signed in
|
|
91
91
|
* to <what>." and the same last sentence.
|
|
92
92
|
*/
|
|
93
93
|
const TENANT_WORDS = {
|
|
@@ -96,6 +96,8 @@ const TENANT_WORDS = {
|
|
|
96
96
|
cursor: { name: 'Cursor', kind: 'team', prep: 'on' },
|
|
97
97
|
codex: { name: 'Codex', kind: 'workspace', prep: 'in' },
|
|
98
98
|
};
|
|
99
|
+
/** How a block that names the organization's own rule begins; such a block needs no title above it. */
|
|
100
|
+
export const ORGANIZATION_LEAD = 'Your organization';
|
|
99
101
|
/** A tenant's name as the admin approved it, or its id when it has none. */
|
|
100
102
|
function nameOf(t) {
|
|
101
103
|
return (t.tenant_name || '').trim() || t.tenant_id.trim();
|
|
@@ -108,35 +110,54 @@ function emailDomain(email) {
|
|
|
108
110
|
function capitalised(word) {
|
|
109
111
|
return word ? word[0].toUpperCase() + word.slice(1) : word;
|
|
110
112
|
}
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
+
/**
|
|
114
|
+
* What to pick to get back in: the one approved tenant, one of several, or the company's own,
|
|
115
|
+
* named after `company` when it is known ("your Optimus Labs organization").
|
|
116
|
+
*/
|
|
117
|
+
function choice(approved, kind, company) {
|
|
113
118
|
if (approved.length === 1)
|
|
114
119
|
return approved[0];
|
|
115
|
-
return approved.length > 1 ? 'one of them' : `your company's ${kind}`;
|
|
120
|
+
return approved.length > 1 ? 'one of them' : `your ${company || "company's"} ${kind}`;
|
|
116
121
|
}
|
|
117
|
-
function switchStep(agent, approved) {
|
|
122
|
+
function switchStep(agent, approved, company) {
|
|
118
123
|
const kind = TENANT_WORDS[agent].kind;
|
|
119
124
|
if (agent === 'claude_desktop')
|
|
120
|
-
return `Switch Claude Desktop to ${choice(approved, kind)}.`;
|
|
121
|
-
if (agent === 'codex')
|
|
122
|
-
return `Run codex logout, then codex login and choose ${choice(approved, kind)}.`;
|
|
125
|
+
return `Switch Claude Desktop to ${choice(approved, kind, company)}.`;
|
|
126
|
+
if (agent === 'codex') {
|
|
127
|
+
return `Run codex logout, then codex login and choose ${choice(approved, kind, company)}.`;
|
|
128
|
+
}
|
|
123
129
|
if (agent === 'cursor') {
|
|
124
|
-
|
|
125
|
-
return `Sign out of Cursor and sign in with your ${approved[0]} team account.`;
|
|
130
|
+
// Reached only without one account to name (see cursorTeamViolation).
|
|
126
131
|
return approved.length > 1
|
|
127
132
|
? 'Sign out of Cursor and sign in with an account on one of those teams.'
|
|
128
133
|
: "Sign out of Cursor and sign in with your company's team account.";
|
|
129
134
|
}
|
|
130
|
-
return `Run /login and choose ${choice(approved, kind)}.`;
|
|
135
|
+
return `Run /login and choose ${choice(approved, kind, company)}.`;
|
|
131
136
|
}
|
|
132
|
-
/**
|
|
133
|
-
|
|
137
|
+
/**
|
|
138
|
+
* The block message for `agent` signed in to `signedIn` (already worded) with these approved.
|
|
139
|
+
* `company` names the company when the agent has nothing approved of its own; '' says "company's".
|
|
140
|
+
*/
|
|
141
|
+
export function tenantBlockMessage(agent, approved, signedIn, company = '') {
|
|
134
142
|
const w = TENANT_WORDS[agent];
|
|
135
143
|
const lead = approved.length
|
|
136
144
|
? `This device only allows ${w.name} ${w.prep} the ${approved.join(' or ')} ${w.kind}. ` +
|
|
137
145
|
`You're signed in to ${signedIn}.`
|
|
138
|
-
: `This device doesn't allow ${w.name} signed in to ${signedIn}.`;
|
|
139
|
-
return `${lead} ${switchStep(agent, approved)}`;
|
|
146
|
+
: `This device doesn't allow ${w.name} to be signed in to ${signedIn}.`;
|
|
147
|
+
return `${lead} ${switchStep(agent, approved, company)}`;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The company's name for "your <company> team account": the one name its approved tenants go by,
|
|
151
|
+
* across every agent (a Cursor login on no approved team is pointed at the company's Claude
|
|
152
|
+
* organization's name). '' when there is none, or several. Personal accounts (`@<domain>`) and a
|
|
153
|
+
* person's own org ("ada@gmail.com's Organization") are not the company's.
|
|
154
|
+
*/
|
|
155
|
+
export function companyName(approved) {
|
|
156
|
+
const names = new Set(Object.values(approved ?? {})
|
|
157
|
+
.flatMap((policy) => tenantIds(policy?.tenants))
|
|
158
|
+
.filter((t) => !t.tenant_id.trim().startsWith('@') && !/'s organization$/i.test(nameOf(t)))
|
|
159
|
+
.map(nameOf));
|
|
160
|
+
return names.size === 1 ? [...names][0] : '';
|
|
140
161
|
}
|
|
141
162
|
const CLAUDE_PERSONAL_PLANS = new Set(['free', 'pro', 'max']);
|
|
142
163
|
/** "a personal Max account (gmail.com)" or "the Beta Corp organization". */
|
|
@@ -159,23 +180,18 @@ function tenantIds(tenants) {
|
|
|
159
180
|
/**
|
|
160
181
|
* The block message when `signedIn` may not be used for `agent`, else null.
|
|
161
182
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
183
|
+
* Only a tenant the organization blocked (marked unapproved) is refused. One never decided is
|
|
184
|
+
* awaiting review and gets through, as an approved one does. Ids compare exactly, as the backend
|
|
185
|
+
* does -- vendor ids are not ours to case-fold.
|
|
165
186
|
*/
|
|
166
187
|
export function approvedTenantViolation(agent, approved, signedIn) {
|
|
167
188
|
if (!signedIn)
|
|
168
189
|
return null;
|
|
169
190
|
const policy = approved?.[agent];
|
|
170
|
-
const approvedTenants = tenantIds(policy?.tenants);
|
|
171
|
-
const unapprovedTenants = tenantIds(policy?.unapproved_tenants);
|
|
172
191
|
const signedInId = signedIn.tenant_id.trim();
|
|
173
|
-
|
|
174
|
-
const blocked = isIn(unapprovedTenants) ||
|
|
175
|
-
((policy?.enforced || approvedTenants.length > 0) && !isIn(approvedTenants));
|
|
176
|
-
if (!blocked)
|
|
192
|
+
if (!tenantIds(policy?.unapproved_tenants).some((t) => t.tenant_id.trim() === signedInId))
|
|
177
193
|
return null;
|
|
178
|
-
return tenantBlockMessage(agent,
|
|
194
|
+
return tenantBlockMessage(agent, tenantIds(policy?.tenants).map(nameOf), claudeSignedInText(signedIn), companyName(approved));
|
|
179
195
|
}
|
|
180
196
|
/**
|
|
181
197
|
* The tenants a Cursor login is in: its teams, or, on no team, `@` and its email domain. Mirrors the
|
|
@@ -203,42 +219,30 @@ export function readCursorSignedInTeams(home = homedir()) {
|
|
|
203
219
|
/**
|
|
204
220
|
* Why a login in these tenants may not be used, or null when it may. Shared by every agent whose
|
|
205
221
|
* login is a list of tenants (Cursor's teams) or one tenant that may be a personal account's
|
|
206
|
-
* `@<domain>` (Codex)
|
|
207
|
-
*
|
|
208
|
-
*
|
|
222
|
+
* `@<domain>` (Codex). Blocked only when every tenant the login is in was blocked (marked
|
|
223
|
+
* unapproved): Cursor admits a login when any of its teams is allowed, and a tenant awaiting review
|
|
224
|
+
* is allowed, as an approved one is.
|
|
209
225
|
*/
|
|
210
226
|
function listedTenantViolation(policy, tenants) {
|
|
211
|
-
const
|
|
212
|
-
if (tenants.
|
|
227
|
+
const unapprovedList = tenantIds(policy?.unapproved_tenants);
|
|
228
|
+
if (!tenants.length || !tenants.every((id) => isListed(unapprovedList, id)))
|
|
213
229
|
return null;
|
|
214
|
-
|
|
215
|
-
if (!isRefused(tenants, enforced, tenantIds(policy?.unapproved_tenants)))
|
|
216
|
-
return null;
|
|
217
|
-
return { approved: approvedList.map(nameOf) };
|
|
230
|
+
return { approved: tenantIds(policy?.tenants).map(nameOf) };
|
|
218
231
|
}
|
|
219
232
|
function isListed(list, id) {
|
|
220
233
|
return list.some((t) => t.tenant_id.trim() === id);
|
|
221
234
|
}
|
|
222
|
-
/** Whether a login in these tenants is refused: one marked unapproved always; any other only once
|
|
223
|
-
* the agent is enforced (an older backend that sends no flag, with nothing approved, lets it pass). */
|
|
224
|
-
function isRefused(tenants, enforced, unapprovedList) {
|
|
225
|
-
if (tenants.some((id) => isListed(unapprovedList, id)))
|
|
226
|
-
return true;
|
|
227
|
-
return enforced;
|
|
228
|
-
}
|
|
229
235
|
/** A tenant's name from the organization's lists, or '' when it has none there. */
|
|
230
236
|
function knownName(policy, id) {
|
|
231
237
|
const all = [...tenantIds(policy?.tenants), ...tenantIds(policy?.unapproved_tenants)];
|
|
232
238
|
const hit = all.find((t) => t.tenant_id.trim() === id);
|
|
233
239
|
return (hit?.tenant_name || '').trim();
|
|
234
240
|
}
|
|
235
|
-
/** "a personal account
|
|
241
|
+
/** "a personal account", "the Beta team" or "team 9876". */
|
|
236
242
|
function cursorSignedInText(signedIn, policy) {
|
|
237
243
|
const tenants = cursorTenants(signedIn);
|
|
238
|
-
if (!tenants.length || tenants[0].startsWith('@'))
|
|
239
|
-
|
|
240
|
-
return `a personal account on no team${domain ? ` (${domain})` : ''}`;
|
|
241
|
-
}
|
|
244
|
+
if (!tenants.length || tenants[0].startsWith('@'))
|
|
245
|
+
return 'a personal account';
|
|
242
246
|
return tenants
|
|
243
247
|
.map((id) => (knownName(policy, id) ? `the ${knownName(policy, id)} team` : `team ${id}`))
|
|
244
248
|
.join(' and ');
|
|
@@ -255,13 +259,22 @@ function codexSignedInText(signedIn, tenants, policy) {
|
|
|
255
259
|
return name ? `the ${name} workspace` : `workspace ${tenant}`;
|
|
256
260
|
}
|
|
257
261
|
/** The block message when Cursor's login may not be used, else null. */
|
|
258
|
-
export function cursorTeamViolation(approved, signedIn) {
|
|
262
|
+
export function cursorTeamViolation(approved, signedIn, teamDomains = []) {
|
|
259
263
|
if (!signedIn)
|
|
260
264
|
return null;
|
|
261
265
|
const v = listedTenantViolation(approved?.cursor, cursorTenants(signedIn));
|
|
262
266
|
if (!v)
|
|
263
267
|
return null;
|
|
264
|
-
|
|
268
|
+
const signedInText = cursorSignedInText(signedIn, approved?.cursor);
|
|
269
|
+
// The one account to point at: the approved team, else the company's name. Several approved
|
|
270
|
+
// teams, or no name known, keep the generic message.
|
|
271
|
+
const account = v.approved.length === 1 ? v.approved[0] : v.approved.length ? '' : companyName(approved);
|
|
272
|
+
if (!account)
|
|
273
|
+
return tenantBlockMessage('cursor', v.approved, signedInText);
|
|
274
|
+
const domains = teamDomains.length ? ` (${teamDomains.join(', ')})` : '';
|
|
275
|
+
return (`${ORGANIZATION_LEAD} does not allow Cursor to be signed in to ${signedInText}. ` +
|
|
276
|
+
`The approved organization's team account for Cursor is ${account}${domains}. ` +
|
|
277
|
+
'Please sign out and sign in with the approved account to use Cursor.');
|
|
265
278
|
}
|
|
266
279
|
/**
|
|
267
280
|
* Codex plans that are personal, mirroring the backend's `CodexPlans.PERSONAL`. On one of these
|
|
@@ -307,34 +320,7 @@ export function codexTenantViolation(approved, signedIn) {
|
|
|
307
320
|
const v = listedTenantViolation(approved?.codex, tenants);
|
|
308
321
|
if (!v)
|
|
309
322
|
return null;
|
|
310
|
-
return tenantBlockMessage('codex', v.approved, codexSignedInText(signedIn, tenants, approved?.codex));
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Whether a personal account's `@<domain>` tenant is on a team domain: an allowlisted domain that
|
|
314
|
-
* someone on an approved team or enterprise tenant signs in with (any vendor's). A subdomain counts.
|
|
315
|
-
*/
|
|
316
|
-
export function onTeamDomain(tenant, teamDomains) {
|
|
317
|
-
if (!tenant.startsWith('@') || !teamDomains?.length)
|
|
318
|
-
return false;
|
|
319
|
-
const domain = tenant.slice(1);
|
|
320
|
-
return teamDomains.some((d) => domain === d || domain.endsWith(`.${d}`));
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* The block message for Cursor's login, else null. A login on a team is checked against the
|
|
324
|
-
* approved teams. A personal account is only checked once its email is on a team domain; then it
|
|
325
|
-
* is blocked unless an admin approved that personal tenant (`@<domain>`).
|
|
326
|
-
*/
|
|
327
|
-
export function cursorLoginViolation(approved, signedIn, teamDomains) {
|
|
328
|
-
if (!signedIn)
|
|
329
|
-
return null;
|
|
330
|
-
const checked = signedIn.team_ids.length > 0 || cursorTenants(signedIn).some((t) => onTeamDomain(t, teamDomains));
|
|
331
|
-
return checked ? cursorTeamViolation(approved, signedIn) : null;
|
|
332
|
-
}
|
|
333
|
-
/** The block message for Codex's login, else null; a personal plan is checked as Cursor's is. */
|
|
334
|
-
export function codexLoginViolation(approved, signedIn, teamDomains) {
|
|
335
|
-
const tenants = signedIn ? codexTenants(signedIn) : null;
|
|
336
|
-
const checked = tenants?.some((t) => !t.startsWith('@') || onTeamDomain(t, teamDomains));
|
|
337
|
-
return checked ? codexTenantViolation(approved, signedIn) : null;
|
|
323
|
+
return tenantBlockMessage('codex', v.approved, codexSignedInText(signedIn, tenants, approved?.codex), companyName(approved));
|
|
338
324
|
}
|
|
339
325
|
/**
|
|
340
326
|
* The org Claude Desktop is signed into now, for its Code tab's prompts, or null when unknown.
|
|
@@ -378,26 +364,6 @@ export function unapprovedEmailDomainViolation(email, unapproved) {
|
|
|
378
364
|
return (`This device doesn't allow accounts on ${hit}. You're signed in as ${address}. ` +
|
|
379
365
|
'Sign in with your work account.');
|
|
380
366
|
}
|
|
381
|
-
/**
|
|
382
|
-
* The block message when a login's email is not on the organization's email allowlist, else null.
|
|
383
|
-
*
|
|
384
|
-
* The allowlist is the organization's approved email domains; a domain covers its subdomains
|
|
385
|
-
* (`mail.acme.com` under `acme.com`). Null, so the login is allowed, when no allowlist has been
|
|
386
|
-
* synced to this machine yet or the login has no email we can read.
|
|
387
|
-
*/
|
|
388
|
-
export function emailAllowlistViolation(email, allowed) {
|
|
389
|
-
if (!Array.isArray(allowed))
|
|
390
|
-
return null;
|
|
391
|
-
const address = (email ?? '').trim().toLowerCase();
|
|
392
|
-
const at = address.lastIndexOf('@');
|
|
393
|
-
if (at < 0 || at === address.length - 1)
|
|
394
|
-
return null;
|
|
395
|
-
const domain = address.slice(at + 1);
|
|
396
|
-
if (allowed.some((d) => domain === d || domain.endsWith(`.${d}`)))
|
|
397
|
-
return null;
|
|
398
|
-
return (`This device only allows accounts on your organization's email domains. You're signed in as ${address}. ` +
|
|
399
|
-
'Sign in with your work account.');
|
|
400
|
-
}
|
|
401
367
|
/** The id an agent is approved under on the Agents page: Desktop's Code tab runs Claude Code. */
|
|
402
368
|
const APPROVAL_ID = { claude_desktop: 'claude' };
|
|
403
369
|
const AGENT_NAME = {
|
|
@@ -24,7 +24,7 @@ import { tryResolveHardwareUuid } from './log_config_files/runtime/hardware_uuid
|
|
|
24
24
|
import { cursorAutofixDefersInlineVerify, syncRemediations, } from './log_config_files/runtime/remediation_sync.js';
|
|
25
25
|
import { loadEndpointBase } from './log_config_files/sender/endpoint_config.js';
|
|
26
26
|
import { formatRemediationChangePreviewForApplied } from './remediation_change_preview.js';
|
|
27
|
-
import { agentApprovalViolation, approvedTenantViolation,
|
|
27
|
+
import { agentApprovalViolation, approvedTenantViolation, codexTenantViolation, cursorTeamViolation, ORGANIZATION_LEAD, readClaudeDesktopSignedInTenant, readClaudeSignedInTenant, readCodexSignedIn, readCursorSignedInTeams, unapprovedEmailDomainViolation, } from './approved_tenant_gate.js';
|
|
28
28
|
const MANIFEST_STALE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
29
29
|
/** After verified (or failed verify), push enforcement + session log immediately — do not wait for background runner. */
|
|
30
30
|
async function flushRemediationOutcomeToServer(complianceLogPath) {
|
|
@@ -89,8 +89,12 @@ function printAllowWithAdvisory(ide, advisoryMessage) {
|
|
|
89
89
|
}
|
|
90
90
|
/** Every block reads as an Optimus Labs notice: a title line, then the message. */
|
|
91
91
|
const BLOCK_TITLE = 'Optimus Labs\n\n';
|
|
92
|
+
/** The block as shown: titled, unless it already says it is the organization's rule. */
|
|
93
|
+
function titled(violationMessage) {
|
|
94
|
+
return violationMessage.startsWith(ORGANIZATION_LEAD) ? violationMessage : BLOCK_TITLE + violationMessage;
|
|
95
|
+
}
|
|
92
96
|
function blockPayload(ide, violationMessage) {
|
|
93
|
-
const text =
|
|
97
|
+
const text = titled(violationMessage);
|
|
94
98
|
if (ide === 'claude' || ide === 'copilot' || ide === 'opencode') {
|
|
95
99
|
return JSON.stringify({ decision: 'block', reason: text, systemMessage: text });
|
|
96
100
|
}
|
|
@@ -101,7 +105,7 @@ function blockPayload(ide, violationMessage) {
|
|
|
101
105
|
* warning, so the Claude-shaped payload (which carries both) prints the same sentence twice.
|
|
102
106
|
*/
|
|
103
107
|
function codexBlockPayload(violationMessage) {
|
|
104
|
-
return JSON.stringify({ decision: 'block', reason:
|
|
108
|
+
return JSON.stringify({ decision: 'block', reason: titled(violationMessage) });
|
|
105
109
|
}
|
|
106
110
|
function getManifestStalenessMs() {
|
|
107
111
|
try {
|
|
@@ -264,7 +268,7 @@ function loginEmail(agent) {
|
|
|
264
268
|
return readCursorSignedInTeams()?.email ?? '';
|
|
265
269
|
return null;
|
|
266
270
|
}
|
|
267
|
-
/** Why this agent's signed-in tenant is
|
|
271
|
+
/** Why this agent's signed-in tenant is blocked, or null when it is not (or cannot be read). */
|
|
268
272
|
function tenantViolation(agent) {
|
|
269
273
|
const { approved_tenants: approved, team_email_domains: teamDomains } = readRemediationInstructionsFile();
|
|
270
274
|
if (agent === 'claude')
|
|
@@ -272,12 +276,10 @@ function tenantViolation(agent) {
|
|
|
272
276
|
if (agent === 'claude_desktop') {
|
|
273
277
|
return approvedTenantViolation('claude_desktop', approved, readClaudeDesktopSignedInTenant());
|
|
274
278
|
}
|
|
275
|
-
// A personal account (Cursor on no team, Codex on a personal plan) is left to the email allowlist
|
|
276
|
-
// until its domain is a team domain; then it needs its personal tenant approved.
|
|
277
279
|
if (agent === 'codex')
|
|
278
|
-
return
|
|
280
|
+
return codexTenantViolation(approved, readCodexSignedIn());
|
|
279
281
|
if (agent === 'cursor')
|
|
280
|
-
return
|
|
282
|
+
return cursorTeamViolation(approved, readCursorSignedInTeams(), teamDomains);
|
|
281
283
|
return null;
|
|
282
284
|
}
|
|
283
285
|
/** The block for a login: which check refused it and why, or null when every check passes. */
|
|
@@ -288,16 +290,16 @@ function loginBlock(agent, email) {
|
|
|
288
290
|
return { event: 'prompt_gate_block_unapproved_agent', reason: agentMsg };
|
|
289
291
|
if (email === null)
|
|
290
292
|
return null;
|
|
291
|
-
const emailMsg =
|
|
293
|
+
const emailMsg = unapprovedEmailDomainViolation(email, synced.unapproved_email_domains);
|
|
292
294
|
if (emailMsg)
|
|
293
|
-
return { event: '
|
|
295
|
+
return { event: 'prompt_gate_block_unapproved_email_domain', reason: emailMsg };
|
|
294
296
|
const tenantMsg = tenantViolation(agent);
|
|
295
297
|
return tenantMsg ? { event: 'prompt_gate_block_unapproved_tenant', reason: tenantMsg } : null;
|
|
296
298
|
}
|
|
297
299
|
/**
|
|
298
|
-
* Block the prompt
|
|
299
|
-
*
|
|
300
|
-
*
|
|
300
|
+
* Block the prompt when the agent is not approved, or the login's email domain or its tenant was
|
|
301
|
+
* blocked (all as remediation sync stored them), printing the block payload; true when it did. A
|
|
302
|
+
* domain or tenant awaiting review gets through. A login whose email or tenant cannot be read passes that check, and a
|
|
301
303
|
* machine that has synced nothing yet is not blocked.
|
|
302
304
|
*/
|
|
303
305
|
function blockedByLogin(agent, ide) {
|