switchroom 0.14.34 → 0.14.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/agent-scheduler/index.js +82 -81
- package/dist/auth-broker/index.js +82 -81
- package/dist/cli/drive-write-pretool.mjs +10 -10
- package/dist/cli/notion-write-pretool.mjs +86 -84
- package/dist/cli/skill-validate-pretool.mjs +72 -72
- package/dist/cli/switchroom.js +507 -371
- package/dist/host-control/main.js +150 -149
- package/dist/vault/approvals/kernel-server.js +106 -90
- package/dist/vault/broker/server.js +177 -90
- package/package.json +2 -2
- package/telegram-plugin/dist/bridge/bridge.js +112 -112
- package/telegram-plugin/dist/gateway/gateway.js +227 -195
- package/telegram-plugin/dist/server.js +160 -160
- package/telegram-plugin/gateway/gateway.ts +41 -1
|
@@ -420,6 +420,7 @@ import {
|
|
|
420
420
|
approvalRecord,
|
|
421
421
|
} from '../../src/vault/approvals/client.js'
|
|
422
422
|
import { resolveVaultApprovalPosture } from '../vault-approval-posture.js'
|
|
423
|
+
import { matchesAdminOnlyKey } from '../../src/vault/admin-only-keys.js'
|
|
423
424
|
import {
|
|
424
425
|
openTurnsDb,
|
|
425
426
|
markOrphanedWithTimeoutClassification,
|
|
@@ -2690,6 +2691,15 @@ const VAULT_PASSPHRASE_TTL_MS = 30 * 60 * 1000
|
|
|
2690
2691
|
*/
|
|
2691
2692
|
let VAULT_APPROVAL_AUTH_MODE: 'passphrase' | 'telegram-id' = 'passphrase'
|
|
2692
2693
|
|
|
2694
|
+
/**
|
|
2695
|
+
* Admin-only vault keys (`vault.broker.adminOnlyKeys`). A grant for one
|
|
2696
|
+
* of these may be approved ONLY by the admin operator
|
|
2697
|
+
* (`access.allowFrom[0]`) and is always minted via the operator
|
|
2698
|
+
* passphrase — never posture, even under telegram-id mode (the broker
|
|
2699
|
+
* enforces the same rule). Cached at boot alongside the posture mode.
|
|
2700
|
+
*/
|
|
2701
|
+
let ADMIN_ONLY_KEYS: string[] = []
|
|
2702
|
+
|
|
2693
2703
|
export function initVaultApprovalPosture(): void {
|
|
2694
2704
|
let cfg: ReturnType<typeof loadSwitchroomConfig>
|
|
2695
2705
|
try {
|
|
@@ -2722,6 +2732,13 @@ export function initVaultApprovalPosture(): void {
|
|
|
2722
2732
|
`(single-factor — broker mediates attestation via attest_via_posture)\n`,
|
|
2723
2733
|
)
|
|
2724
2734
|
}
|
|
2735
|
+
ADMIN_ONLY_KEYS = cfg.vault?.broker?.adminOnlyKeys ?? []
|
|
2736
|
+
if (ADMIN_ONLY_KEYS.length > 0) {
|
|
2737
|
+
process.stderr.write(
|
|
2738
|
+
`telegram gateway: ${ADMIN_ONLY_KEYS.length} admin-only vault key pattern(s) ` +
|
|
2739
|
+
`— grants approved by allowFrom[0] + operator passphrase only\n`,
|
|
2740
|
+
)
|
|
2741
|
+
}
|
|
2725
2742
|
}
|
|
2726
2743
|
/**
|
|
2727
2744
|
* Gateway-side guard on vault-key shape — UX gate, not a security
|
|
@@ -13851,11 +13868,31 @@ async function handleVaultRequestAccessCallback(ctx: Context, data: string): Pro
|
|
|
13851
13868
|
}
|
|
13852
13869
|
|
|
13853
13870
|
if (action === 'approve') {
|
|
13871
|
+
// Admin-only credentials (`vault.broker.adminOnlyKeys`) are held to a
|
|
13872
|
+
// higher bar: ONLY the admin operator (allowFrom[0]) may approve, and
|
|
13873
|
+
// the grant must be minted with the operator passphrase — never
|
|
13874
|
+
// posture, even under telegram-id mode (the broker enforces the same
|
|
13875
|
+
// rule, so a posture mint would just be rejected). So for an
|
|
13876
|
+
// admin-only key we (a) reject taps from any non-admin allowFrom
|
|
13877
|
+
// member, and (b) skip the telegram-id posture branch below, falling
|
|
13878
|
+
// through to the passphrase-prompt path. The card + buttons stay
|
|
13879
|
+
// intact on a non-admin tap so the admin can still approve.
|
|
13880
|
+
const isAdminOnly = matchesAdminOnlyKey(pending.key, ADMIN_ONLY_KEYS)
|
|
13881
|
+
if (isAdminOnly && senderId !== access.allowFrom[0]) {
|
|
13882
|
+
await ctx
|
|
13883
|
+
.answerCallbackQuery({
|
|
13884
|
+
text: '🔒 Admin-only credential — only the owner can approve this.',
|
|
13885
|
+
})
|
|
13886
|
+
.catch(() => {})
|
|
13887
|
+
return
|
|
13888
|
+
}
|
|
13889
|
+
|
|
13854
13890
|
// Posture: telegram-id (opt-in single-factor). The broker is
|
|
13855
13891
|
// auto-unlocked and we silently hold the passphrase in memory; skip
|
|
13856
13892
|
// the passphrase-cache lookup + prompt entirely and mint directly.
|
|
13857
13893
|
// Allowlist check above already attested the operator's Telegram ID.
|
|
13858
|
-
|
|
13894
|
+
// Admin-only keys are excluded — they take the passphrase path below.
|
|
13895
|
+
if (!isAdminOnly && VAULT_APPROVAL_AUTH_MODE === 'telegram-id') {
|
|
13859
13896
|
const username = ctx.from?.username ?? ctx.from?.first_name ?? `id=${senderId}`
|
|
13860
13897
|
if (pending.card_message_id != null) {
|
|
13861
13898
|
await ctx.api
|
|
@@ -13920,6 +13957,9 @@ async function handleVaultRequestAccessCallback(ctx: Context, data: string): Pro
|
|
|
13920
13957
|
pending.card_message_id,
|
|
13921
13958
|
joiningBatch
|
|
13922
13959
|
? `🔐 <b>Queued behind an earlier card.</b> Type your passphrase as your next message — it covers <b>${items.length}</b> pending approvals in this chat (one entry mints all grants, no re-type per card).`
|
|
13960
|
+
: isAdminOnly
|
|
13961
|
+
? `🔒 <b>Admin-only credential.</b> <code>${escapeHtmlForTg(pending.key)}</code> requires your vault passphrase to grant — reply with it as your next message and we'll mint the grant for <b>${escapeHtmlForTg(pending.agent)}</b>, then delete the passphrase message.\n\n` +
|
|
13962
|
+
`<i>The passphrase is what proves it's you: an agent can never mint this key on its own.</i>`
|
|
13923
13963
|
: `🔐 <b>Vault is locked.</b> Reply with your passphrase as your next message — we'll unlock, mint the grant for <b>${escapeHtmlForTg(pending.agent)}</b>, and delete the passphrase message in one step.\n\n` +
|
|
13924
13964
|
`<i>Mint authority stays operator-only: the broker only accepts the grant when the passphrase matches.</i>`,
|
|
13925
13965
|
{ parse_mode: 'HTML', reply_markup: { inline_keyboard: [] } },
|