switchroom 0.18.11 → 0.18.13
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 +37 -5
- package/dist/auth-broker/index.js +116 -78
- package/dist/cli/hindsight-mental-model-pretool.mjs +39 -0
- package/dist/cli/ms-365-write-pretool.mjs +31 -8
- package/dist/cli/notion-write-pretool.mjs +38 -6
- package/dist/cli/skill-validate-pretool.mjs +144 -2847
- package/dist/cli/switchroom.js +3529 -4543
- package/dist/cli/ui/index.html +163 -17
- package/dist/host-control/main.js +605 -2847
- package/dist/vault/approvals/kernel-server.js +120 -13
- package/dist/vault/broker/server.js +259 -157
- package/package.json +3 -4
- package/profiles/_base/start.sh.hbs +65 -0
- package/profiles/_shared/vault-protocol.md.hbs +3 -1
- package/profiles/coding/CLAUDE.md.hbs +1 -1
- package/profiles/default/CLAUDE.md.hbs +2 -2
- package/profiles/executive-assistant/CLAUDE.md.hbs +1 -1
- package/profiles/health-coach/CLAUDE.md.hbs +1 -1
- package/telegram-plugin/bridge/bridge.ts +37 -0
- package/telegram-plugin/bridge/inbound-dedup.ts +101 -0
- package/telegram-plugin/dist/bridge/bridge.js +122 -4
- package/telegram-plugin/dist/gateway/gateway.js +4213 -3288
- package/telegram-plugin/dist/server.js +139 -5
- package/telegram-plugin/flood-circuit-breaker.ts +493 -21
- package/telegram-plugin/format.ts +19 -0
- package/telegram-plugin/gateway/approval-hold.ts +602 -0
- package/telegram-plugin/gateway/auth-command.ts +92 -2
- package/telegram-plugin/gateway/auth-loopback-relay.ts +670 -0
- package/telegram-plugin/gateway/boot-card.ts +12 -5
- package/telegram-plugin/gateway/callback-query-handlers.ts +88 -1
- package/telegram-plugin/gateway/config-approval-handler.ts +6 -1
- package/telegram-plugin/gateway/disconnect-flush.ts +19 -0
- package/telegram-plugin/gateway/dm-pin-sweep.test.ts +251 -0
- package/telegram-plugin/gateway/dm-pin-sweep.ts +178 -0
- package/telegram-plugin/gateway/gateway.ts +1695 -230
- package/telegram-plugin/gateway/hostd-dispatch.ts +23 -0
- package/telegram-plugin/gateway/idle-clear.ts +90 -6
- package/telegram-plugin/gateway/inbound-delivery-machine-shadow.ts +26 -5
- package/telegram-plugin/gateway/inject-handler.ts +8 -0
- package/telegram-plugin/gateway/ipc-protocol.ts +46 -3
- package/telegram-plugin/gateway/ipc-server.ts +43 -0
- package/telegram-plugin/gateway/mental-model-propose-resolve.ts +145 -37
- package/telegram-plugin/gateway/model-command.ts +9 -3
- package/telegram-plugin/gateway/pending-session-command.ts +13 -1
- package/telegram-plugin/gateway/permission-ttl-sweep.ts +66 -0
- package/telegram-plugin/gateway/pre-approval-check.ts +74 -0
- package/telegram-plugin/gateway/queued-card-store.ts +217 -0
- package/telegram-plugin/gateway/session-model-file.ts +26 -1
- package/telegram-plugin/gateway/turn-end-gate-backstop.ts +59 -0
- package/telegram-plugin/gateway/turn-end-gate.ts +95 -0
- package/telegram-plugin/gateway/turn-typing-loop.ts +10 -2
- package/telegram-plugin/gateway/unhandled-rejection-policy.ts +13 -0
- package/telegram-plugin/history.ts +51 -0
- package/telegram-plugin/hooks/dispatch-claim-scan.mjs +259 -0
- package/telegram-plugin/hooks/dispatch-claim-stop.mjs +129 -0
- package/telegram-plugin/hooks/hooks.json +9 -0
- package/telegram-plugin/inline-keyboard-callbacks.ts +303 -2
- package/telegram-plugin/model-unavailable.ts +41 -11
- package/telegram-plugin/operator-events.ts +23 -0
- package/telegram-plugin/outbound-field-redact.ts +69 -0
- package/telegram-plugin/package.json +0 -1
- package/telegram-plugin/permission-rule.ts +1 -0
- package/telegram-plugin/permission-title.ts +1 -0
- package/telegram-plugin/render/render.ts +32 -14
- package/telegram-plugin/retry-api-call.ts +212 -2
- package/telegram-plugin/scoped-approval.ts +11 -2
- package/telegram-plugin/secret-detect/chunker.ts +18 -4
- package/telegram-plugin/secret-detect/index.ts +12 -56
- package/telegram-plugin/send-gate-degraded.test.ts +574 -0
- package/telegram-plugin/send-gate-observability.test.ts +470 -0
- package/telegram-plugin/send-gate-observability.ts +355 -0
- package/telegram-plugin/send-gate.test.ts +717 -0
- package/telegram-plugin/send-gate.ts +1056 -0
- package/telegram-plugin/session-tail.ts +82 -7
- package/telegram-plugin/shared/bot-runtime.ts +17 -5
- package/telegram-plugin/shared/gw-trace-gate.ts +105 -0
- package/telegram-plugin/status-pin-driver.ts +52 -7
- package/telegram-plugin/status-pin.ts +81 -0
- package/telegram-plugin/subagent-watcher.ts +173 -18
- package/telegram-plugin/tests/activity-card-wiring.test.ts +18 -5
- package/telegram-plugin/tests/approval-hold-harness.ts +425 -0
- package/telegram-plugin/tests/approval-hold-outcome.test.ts +327 -0
- package/telegram-plugin/tests/approval-hold-record.test.ts +531 -0
- package/telegram-plugin/tests/approval-hold-redeliver.test.ts +602 -0
- package/telegram-plugin/tests/auth-loopback-relay.test.ts +533 -0
- package/telegram-plugin/tests/boot-card-flood-suppress.test.ts +53 -7
- package/telegram-plugin/tests/busy-key-reaper.test.ts +1 -0
- package/telegram-plugin/tests/callback-query-handlers.test.ts +65 -0
- package/telegram-plugin/tests/dispatch-claim-scan.test.ts +250 -0
- package/telegram-plugin/tests/flood-breaker-blindness.test.ts +213 -0
- package/telegram-plugin/tests/flood-windows-persistence.test.ts +224 -0
- package/telegram-plugin/tests/gateway-boot-marker-clear.test.ts +3 -3
- package/telegram-plugin/tests/gateway-disconnect-flush.test.ts +29 -1
- package/telegram-plugin/tests/gateway-loopback-paste-redact.test.ts +66 -0
- package/telegram-plugin/tests/gateway-outbound-redact.test.ts +57 -0
- package/telegram-plugin/tests/gw-trace-gate.test.ts +105 -0
- package/telegram-plugin/tests/history.test.ts +115 -0
- package/telegram-plugin/tests/idle-clear.test.ts +233 -3
- package/telegram-plugin/tests/inbound-dedup.test.ts +93 -0
- package/telegram-plugin/tests/inbound-message-types.test.ts +5 -1
- package/telegram-plugin/tests/inline-keyboard-callbacks.test.ts +448 -0
- package/telegram-plugin/tests/ipc-server-check-pre-approved.test.ts +194 -0
- package/telegram-plugin/tests/mental-model-propose-resolve.test.ts +123 -0
- package/telegram-plugin/tests/missed-approvals-wiring.test.ts +1 -1
- package/telegram-plugin/tests/model-command.test.ts +14 -0
- package/telegram-plugin/tests/operator-events-session-tail.test.ts +74 -0
- package/telegram-plugin/tests/outbound-field-redact.test.ts +107 -0
- package/telegram-plugin/tests/pending-session-command.test.ts +21 -0
- package/telegram-plugin/tests/permission-card-routing.test.ts +30 -5
- package/telegram-plugin/tests/permission-no-repeat-wiring.test.ts +8 -7
- package/telegram-plugin/tests/permission-rearm-wiring.test.ts +1 -1
- package/telegram-plugin/tests/pre-approval-check.test.ts +148 -0
- package/telegram-plugin/tests/queued-card-store.test.ts +232 -0
- package/telegram-plugin/tests/reaction-flush-turn-gated.test.ts +100 -0
- package/telegram-plugin/tests/reaction-gate-routing.test.ts +173 -0
- package/telegram-plugin/tests/render/render.test.ts +88 -0
- package/telegram-plugin/tests/retry-api-call.test.ts +398 -0
- package/telegram-plugin/tests/scoped-approval.test.ts +27 -0
- package/telegram-plugin/tests/secret-detect-chunk-overlap.test.ts +65 -0
- package/telegram-plugin/tests/secret-detect-oauth-code.test.ts +5 -4
- package/telegram-plugin/tests/session-model-file.test.ts +50 -0
- package/telegram-plugin/tests/session-tail-sidecar-reap.test.ts +268 -0
- package/telegram-plugin/tests/status-pin-boot-recovery.test.ts +35 -14
- package/telegram-plugin/tests/status-pin.test.ts +275 -1
- package/telegram-plugin/tests/subagent-watcher-deferral-log-ratelimit.test.ts +316 -0
- package/telegram-plugin/tests/subagent-watcher-fd-leak.test.ts +275 -0
- package/telegram-plugin/tests/turn-end-gate-backstop.test.ts +92 -0
- package/telegram-plugin/tests/turn-end-gate.test.ts +137 -0
- package/telegram-plugin/tests/typing-emitter.test.ts +586 -0
- package/telegram-plugin/tests/unhandled-rejection-policy.test.ts +20 -0
- package/telegram-plugin/tests/worktree-watch-cwds.test.ts +215 -1
- package/telegram-plugin/typing-emitter.ts +224 -0
- package/telegram-plugin/uat/scenarios/jtbd-feel-like-a-colleague-dm.test.ts +136 -0
- package/telegram-plugin/welcome-text.ts +42 -0
- package/telegram-plugin/worktree-watch-cwds.ts +194 -5
- package/vendor/hindsight-memory/scripts/drain_pending.py +22 -6
- package/vendor/hindsight-memory/scripts/lib/client.py +12 -5
- package/vendor/hindsight-memory/scripts/lib/directives.py +38 -3
- package/vendor/hindsight-memory/scripts/lib/pending.py +36 -9
- package/vendor/hindsight-memory/scripts/session_end.py +14 -3
- package/vendor/hindsight-memory/scripts/session_start.py +21 -0
- package/vendor/hindsight-memory/scripts/tests/test_directives.py +38 -0
- package/vendor/hindsight-memory/tests/test_drain_pending.py +68 -0
- package/vendor/hindsight-memory/tests/test_pending.py +44 -0
- package/vendor/hindsight-memory/tests/test_session_end_pending.py +38 -0
- package/vendor/hindsight-memory/tests/test_session_start_drain.py +155 -0
- package/telegram-plugin/channel-envelope-safety.test.ts +0 -56
- package/telegram-plugin/channel-envelope-safety.ts +0 -56
- package/telegram-plugin/secret-detect/secretlint-source.ts +0 -95
- package/telegram-plugin/tests/secret-detect-secretlint.test.ts +0 -105
|
@@ -4116,7 +4116,12 @@ var init_schema = __esm(() => {
|
|
|
4116
4116
|
AgentSoulSchema = exports_external.object({
|
|
4117
4117
|
name: exports_external.string().describe("Agent persona name (e.g., 'Coach', 'Sage')"),
|
|
4118
4118
|
style: exports_external.string().describe("Communication style description"),
|
|
4119
|
-
|
|
4119
|
+
creature: exports_external.string().optional().describe("Persona creature/form (e.g., 'owl', 'octopus')"),
|
|
4120
|
+
vibe: exports_external.string().optional().describe("One-line personality vibe (e.g., 'calm, precise')"),
|
|
4121
|
+
expertise: exports_external.string().optional().describe("Domain expertise summary rendered into the persona"),
|
|
4122
|
+
emoji: exports_external.string().optional().describe("Signature emoji for the persona (e.g., '\uD83E\uDD89')"),
|
|
4123
|
+
boundaries: exports_external.string().optional().describe("Behavioral boundaries and disclaimers"),
|
|
4124
|
+
shape: exports_external.enum(["executive-assistant", "developer", "coach", "generalist"]).default("generalist").describe("Persona-shape discriminator the per-agent CLAUDE.md template can " + "branch on (e.g., 'you are an executive assistant' vs 'a senior " + "engineer'). No runtime behavior yet — template work lands in a " + "later issue. Cascade: override (per-agent wins over default).")
|
|
4120
4125
|
}).optional();
|
|
4121
4126
|
AgentToolsSchema = exports_external.object({
|
|
4122
4127
|
allow: exports_external.array(exports_external.string()).default([]).describe("Allowed tools (use ['all'] for unrestricted)"),
|
|
@@ -4281,6 +4286,11 @@ var init_schema = __esm(() => {
|
|
|
4281
4286
|
safe_boundary: exports_external.boolean().optional().describe("When true (the default), a `!`-prefix interrupt that arrives while " + "the agent is mid-tool-call is DEFERRED: the SIGINT and the " + "replacement turn wait until the in-flight tool call finishes (a " + "clean boundary) instead of C-c'ing the agent mid-write/mid-bash. If " + "no tool is in flight the interrupt still fires immediately. Bounded " + "by max_wait_ms so a long tool never strands the user. Set false to " + "fire synchronously the moment `!` is received (historical " + "behaviour). Rapid repeated `!` while one is pending coalesce into a " + "single deferred interrupt carrying the latest body."),
|
|
4282
4287
|
max_wait_ms: exports_external.number().int().positive().optional().describe("Upper bound (ms) the gateway waits for a safe boundary before firing " + "a deferred `!` interrupt anyway. Only consulted when safe_boundary is " + "true. Default 8000. Keep it short — the user explicitly asked to " + "interrupt, so a long in-flight tool shouldn't ghost them; the cap " + "trades a tiny risk of a mid-tool C-c for a guaranteed response.")
|
|
4283
4288
|
}).optional().describe("Interrupt timing — how a `!`-prefix interrupt behaves when it lands " + "mid-tool-call. Off by default (fire immediately). Cascades from " + "defaults.channels.telegram.interrupt."),
|
|
4289
|
+
button_choice_confirmation: exports_external.object({
|
|
4290
|
+
enabled: exports_external.boolean().default(false).describe("When true, tapping an agent-emitted inline_keyboard button annotates " + "the source message body with a '✅ You chose: <label> · HH:MM' line so " + "the chat surface is self-documenting. Only applies to single-use " + "keyboards (re-tappable keyboards are never annotated). Requires the " + "agent's parseMode to be the default 'html'. Opt-in (default false). " + "Limitation: the annotated body is rebuilt from the message's plain " + "text, so any formatting entities (bold, links, code, ...) on the " + "original message are lost when the annotation is applied."),
|
|
4291
|
+
format: exports_external.string().default("✅ You chose: {label} · {time}").describe("Template for the annotation line. `{label}` is replaced with the " + "tapped button's text (newlines stripped, HTML-escaped, trimmed to 60 " + "chars) and `{time}` with HH:MM in the chosen timezone. Note: retap " + "dedup (replacing a prior annotation instead of appending another " + "line) only recognizes the DEFAULT format shape — a custom format " + "accumulates one line per retap."),
|
|
4292
|
+
timezone: exports_external.enum(["gateway", "utc"]).default("gateway").describe("Timezone for the {time} placeholder. 'gateway' uses the gateway " + "process's local time; 'utc' uses UTC. Per-user timezones are out of " + "scope for v1.")
|
|
4293
|
+
}).optional().describe("Auto-confirm 'You chose: X' annotation on inline_keyboard taps (#789). " + "Cascades from defaults.channels.telegram.button_choice_confirmation."),
|
|
4284
4294
|
webhook_sources: exports_external.array(exports_external.enum(["github", "generic", "linear"])).optional().describe("External webhook sources allowed to ingest events into this agent's " + "log. POST /webhook/<agent>/<source> on the switchroom web server. " + "Each source has its own signature verification ('github' = " + "X-Hub-Signature-256 HMAC-SHA256, 'generic' = Bearer token, " + "'linear' = Linear-Signature bare-hex HMAC-SHA256 of the raw body). " + "Per-source secret read from ~/.switchroom/webhook-secrets.json " + "keyed by [agent][source]. Verified events append to " + "<agent>/telegram/webhook-events.jsonl for the agent to read on " + "demand. Off by default — webhook is the only untrusted-inbound " + "surface in the system, so opt-in is mandatory. " + "Cascades from defaults.channels.telegram.webhook_sources. " + "(Migrated from per-agent root in #596 — see #577.)"),
|
|
4285
4295
|
webhook_dispatch: exports_external.object({
|
|
4286
4296
|
github: exports_external.array(webhookDispatchRule).optional(),
|
|
@@ -4429,7 +4439,12 @@ var init_schema = __esm(() => {
|
|
|
4429
4439
|
soul: exports_external.object({
|
|
4430
4440
|
name: exports_external.string().optional(),
|
|
4431
4441
|
style: exports_external.string().optional(),
|
|
4432
|
-
|
|
4442
|
+
creature: exports_external.string().optional(),
|
|
4443
|
+
vibe: exports_external.string().optional(),
|
|
4444
|
+
expertise: exports_external.string().optional(),
|
|
4445
|
+
emoji: exports_external.string().optional(),
|
|
4446
|
+
boundaries: exports_external.string().optional(),
|
|
4447
|
+
shape: exports_external.enum(["executive-assistant", "developer", "coach", "generalist"]).optional()
|
|
4433
4448
|
}).optional(),
|
|
4434
4449
|
tools: exports_external.object({
|
|
4435
4450
|
allow: exports_external.array(exports_external.string()).optional(),
|
|
@@ -4626,7 +4641,8 @@ var init_schema = __esm(() => {
|
|
|
4626
4641
|
autoUnlockCredentialPath: exports_external.string().default("~/.switchroom/vault-auto-unlock").describe("Path to the machine-bound auto-unlock blob (see " + "src/vault/auto-unlock.ts for the format). Default lives under " + "~/.switchroom so it can be bind-mounted into the vault-broker " + "container by docker compose. Tilde-expansion happens " + "at read time."),
|
|
4627
4642
|
approvalAuth: exports_external.enum(["passphrase", "telegram-id"]).default("passphrase").describe("Posture for tap-to-Approve on vault grant cards. `passphrase` " + "(default) prompts the operator to type the vault passphrase on " + "every Approve — two-factor (Telegram ID + passphrase). " + "`telegram-id` mints immediately on Approve with no passphrase " + "prompt — single-factor (Telegram ID only); REQUIRES " + "`autoUnlock: true` so the broker already holds the passphrase. " + "Trades a factor of security for smoother UX; opt-in only."),
|
|
4628
4643
|
postureMintAgents: exports_external.array(exports_external.string().min(1)).default([]).describe("Per-agent opt-in for posture-attested broker calls (`mint_grant` / " + "`list_grants` / `put` with `attest_via_posture: true`). Only agents " + "whose names are in this list can use the silent-mint path under " + "`approvalAuth: telegram-id`. Default `[]` — no agent can self-mint " + "until the operator explicitly opts it in. The request's `agent` " + "field must also equal the calling peer's resolved agent name " + "(broker rejects cross-agent posture mints). When `approvalAuth` is " + "`passphrase` this list is ignored — passphrase attestation still " + "works as before. Each entry is an agent slug exactly as it appears " + "under `agents:` in this config."),
|
|
4629
|
-
adminOnlyKeys: exports_external.array(exports_external.string().min(1)).default([]).describe("Vault keys held to a higher approval bar: only the admin operator " + "(`access.allowFrom[0]`) may approve a grant for them, and they can " + "NEVER be minted via posture attestation — granting one requires the " + "operator passphrase (so an agent, even one on `postureMintAgents`, " + "cannot self-grant it). Entries are exact key names or `*` globs, " + "e.g. `stripe/*`, `*/oauth-token`, `microsoft/ken-tokens` (`*` matches " + "any run of characters incl. `/`; case-sensitive). Default `[]` — no " + "key is admin-only. Posture may RETAIN an admin-only key across a " + "union re-mint but never ADD one. Takes effect on broker + gateway " + "restart (broker has no ACL hot-reload).")
|
|
4644
|
+
adminOnlyKeys: exports_external.array(exports_external.string().min(1)).default([]).describe("Vault keys held to a higher approval bar: only the admin operator " + "(`access.allowFrom[0]`) may approve a grant for them, and they can " + "NEVER be minted via posture attestation — granting one requires the " + "operator passphrase (so an agent, even one on `postureMintAgents`, " + "cannot self-grant it). Entries are exact key names or `*` globs, " + "e.g. `stripe/*`, `*/oauth-token`, `microsoft/ken-tokens` (`*` matches " + "any run of characters incl. `/`; case-sensitive). Default `[]` — no " + "key is admin-only. Posture may RETAIN an admin-only key across a " + "union re-mint but never ADD one. Takes effect on broker + gateway " + "restart (broker has no ACL hot-reload)."),
|
|
4645
|
+
auditFailClosed: exports_external.boolean().default(false).describe("sec WS10-F3 (#1420) — fail-CLOSED on an audit-append failure. When " + "`false` (default, backward-compatible) the broker fails OPEN: if a " + "vault-audit.log append fails it logs to stderr, bumps the durable " + "fail-open counter, and STILL releases the secret. When `true`, a " + "failed audit append DENIES the secret release with " + "`AUDIT_UNAVAILABLE` — no secret leaves the broker without a durable " + "audit row. The env var `SWITCHROOM_VAULT_AUDIT_FAIL_CLOSED` " + "(`1`/`true` or `0`/`false`) overrides this at runtime. Either way " + "the fail-open counter is surfaced by `switchroom doctor`. Takes " + "effect on broker restart.")
|
|
4630
4646
|
}).default({}).superRefine((broker, ctx) => {
|
|
4631
4647
|
if (broker.approvalAuth === "telegram-id" && broker.autoUnlock !== true) {
|
|
4632
4648
|
ctx.addIssue({
|
|
@@ -4665,7 +4681,9 @@ var init_schema = __esm(() => {
|
|
|
4665
4681
|
});
|
|
4666
4682
|
HostdConfigSchema = exports_external.object({
|
|
4667
4683
|
config_edit_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the `config_propose_edit` hostd verb (RFC " + "admin-agent-config-edit §3). Default false — the verb returns " + "`E_CONFIG_EDIT_DISABLED` until the operator explicitly flips " + "this to true. When true, admin agents can propose unified-diff " + "patches against " + "`/state/config/switchroom.yaml`, gated by an operator approval " + "card in the primary chat. Same trust posture as `update_apply` " + "and `agent_restart`: the human-in-the-loop tap is the security " + "boundary, not the agent's judgement."),
|
|
4668
|
-
config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit §5). Default 3 cards/hour; min 1, " + "max 20. ENFORCED server-side: a caller exceeding this in a sliding " + "1-hour window is rejected with `E_RATE_LIMITED` (carrying a " + "`retry_after` fix) instead of posting another operator approval " + "card — so a looping agent is throttled rather than spamming the chat.")
|
|
4684
|
+
config_edit_rate_per_hour: exports_external.number().int().min(1).max(20).default(3).describe("Per-requesting-agent rate cap for `config_propose_edit` cards " + "(RFC admin-agent-config-edit §5). Default 3 cards/hour; min 1, " + "max 20. ENFORCED server-side: a caller exceeding this in a sliding " + "1-hour window is rejected with `E_RATE_LIMITED` (carrying a " + "`retry_after` fix) instead of posting another operator approval " + "card — so a looping agent is throttled rather than spamming the chat."),
|
|
4685
|
+
operator_attest_enabled: exports_external.boolean().default(false).describe("Opt-in toggle for the operator-passphrase 2nd factor on hostd's " + "mutating verbs (#1841, RFC host-control-daemon.md §5.4). Default " + "false — attestation is ACCEPTED-and-audited whenever present, but " + "no verb REQUIRES it (behaviour is byte-identical to today, so " + "existing fleets and the Telegram approval-card flow are unchanged). " + "When true, the verbs in `operator_attest_required_verbs` demand a " + "valid operator-passphrase attestation IN ADDITION to admin: hostd " + "forwards the passphrase to the vault broker over its own admin-client " + "connection (`/run/switchroom/broker/hostd/sock`) and treats a broker " + "DENIED as a gate failure. hostd never holds the passphrase. Leave " + "this off until BOTH the gateway passphrase-forward and the " + "cross-compose broker socket are deployed, or gated verbs will fail."),
|
|
4686
|
+
operator_attest_required_verbs: exports_external.array(exports_external.string().min(1)).default(["update_apply", "apply", "rollout"]).describe("Which mutating hostd verbs REQUIRE a valid operator-passphrase " + "attestation when `operator_attest_enabled` is true (#1841). Default " + "is the RFC §5.4 fleet-mutation set (`update_apply`, `apply`, " + "`rollout`). Verbs NOT in this list still accept-and-audit an " + "attestation when one is supplied, but never require it. Ignored " + "entirely when `operator_attest_enabled` is false.")
|
|
4669
4687
|
});
|
|
4670
4688
|
CronEgressSchema = exports_external.object({
|
|
4671
4689
|
allowed_hosts: exports_external.array(exports_external.string().min(1)).default([]).describe("Hosts a poll may reach (exact, https-only). loopback/private/IP-literal are always rejected."),
|
|
@@ -4760,6 +4778,13 @@ var init_schema = __esm(() => {
|
|
|
4760
4778
|
});
|
|
4761
4779
|
});
|
|
4762
4780
|
|
|
4781
|
+
// src/util/atomic.ts
|
|
4782
|
+
import { closeSync, constants, fsyncSync, openSync, renameSync, rmSync, writeSync } from "node:fs";
|
|
4783
|
+
var TMP_OPEN_FLAGS;
|
|
4784
|
+
var init_atomic = __esm(() => {
|
|
4785
|
+
TMP_OPEN_FLAGS = constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0);
|
|
4786
|
+
});
|
|
4787
|
+
|
|
4763
4788
|
// src/config/merge.ts
|
|
4764
4789
|
function dedupe(items) {
|
|
4765
4790
|
const seen = new Set;
|
|
@@ -4897,6 +4922,11 @@ function mergeAgentConfig(defaultsIn, agentIn) {
|
|
|
4897
4922
|
if (v !== undefined)
|
|
4898
4923
|
combined[k] = v;
|
|
4899
4924
|
}
|
|
4925
|
+
const baseSoul = base;
|
|
4926
|
+
const overrideSoul = override;
|
|
4927
|
+
if (baseSoul.shape !== undefined && (overrideSoul.shape === undefined || overrideSoul.shape === "generalist")) {
|
|
4928
|
+
combined.shape = baseSoul.shape;
|
|
4929
|
+
}
|
|
4900
4930
|
merged.soul = combined;
|
|
4901
4931
|
}
|
|
4902
4932
|
if (defaults.memory || merged.memory) {
|
|
@@ -18064,7 +18094,7 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
18064
18094
|
|
|
18065
18095
|
// src/vault/broker/server.ts
|
|
18066
18096
|
import * as net from "node:net";
|
|
18067
|
-
import { mkdirSync as mkdirSync6, chmodSync as chmodSync4, chownSync, existsSync as existsSync11, readFileSync as
|
|
18097
|
+
import { mkdirSync as mkdirSync6, chmodSync as chmodSync4, chownSync, existsSync as existsSync11, readFileSync as readFileSync10, readdirSync as readdirSync4, statSync as statSync6, unlinkSync as unlinkSync6, writeFileSync as writeFileSync3, renameSync as renameSync7 } from "node:fs";
|
|
18068
18098
|
|
|
18069
18099
|
// src/agents/compose.ts
|
|
18070
18100
|
import { createHash } from "node:crypto";
|
|
@@ -18072,6 +18102,7 @@ init_schema();
|
|
|
18072
18102
|
|
|
18073
18103
|
// src/agents/scaffold.ts
|
|
18074
18104
|
import { join as join4, resolve as resolve6 } from "node:path";
|
|
18105
|
+
init_atomic();
|
|
18075
18106
|
init_schema();
|
|
18076
18107
|
|
|
18077
18108
|
// src/config/users.ts
|
|
@@ -18154,22 +18185,6 @@ function isWebkiteCredentialKeyForAgent(agentConfig, key) {
|
|
|
18154
18185
|
return false;
|
|
18155
18186
|
return true;
|
|
18156
18187
|
}
|
|
18157
|
-
function parseCronUnit(unitName) {
|
|
18158
|
-
const m = unitName.match(/^switchroom-([a-zA-Z0-9_-]+)-cron-(\d+)\.service$/);
|
|
18159
|
-
if (!m)
|
|
18160
|
-
return null;
|
|
18161
|
-
const agentName = m[1];
|
|
18162
|
-
const index = parseInt(m[2], 10);
|
|
18163
|
-
if (!agentName)
|
|
18164
|
-
return null;
|
|
18165
|
-
return { agentName, index };
|
|
18166
|
-
}
|
|
18167
|
-
function agentSlugFromPeer(peer) {
|
|
18168
|
-
if (peer.systemdUnit === null)
|
|
18169
|
-
return null;
|
|
18170
|
-
const parsed = parseCronUnit(peer.systemdUnit);
|
|
18171
|
-
return parsed?.agentName ?? null;
|
|
18172
|
-
}
|
|
18173
18188
|
function checkEntryScope(scope, agentSlug) {
|
|
18174
18189
|
if (scope === undefined || scope === null) {
|
|
18175
18190
|
return { allow: true };
|
|
@@ -18192,42 +18207,6 @@ function checkEntryScope(scope, agentSlug) {
|
|
|
18192
18207
|
}
|
|
18193
18208
|
return { allow: true };
|
|
18194
18209
|
}
|
|
18195
|
-
function checkAcl(peer, config, key) {
|
|
18196
|
-
if (peer.systemdUnit !== null) {
|
|
18197
|
-
const parsed = parseCronUnit(peer.systemdUnit);
|
|
18198
|
-
if (parsed === null) {
|
|
18199
|
-
return {
|
|
18200
|
-
allow: false,
|
|
18201
|
-
reason: `systemd unit '${peer.systemdUnit}' does not match switchroom cron unit naming convention`
|
|
18202
|
-
};
|
|
18203
|
-
}
|
|
18204
|
-
const { agentName, index } = parsed;
|
|
18205
|
-
const agentConfig = config.agents?.[agentName];
|
|
18206
|
-
if (!agentConfig) {
|
|
18207
|
-
return { allow: false, reason: `agent '${agentName}' not found in config` };
|
|
18208
|
-
}
|
|
18209
|
-
const schedule = agentConfig.schedule ?? [];
|
|
18210
|
-
if (index >= schedule.length || index < 0) {
|
|
18211
|
-
return {
|
|
18212
|
-
allow: false,
|
|
18213
|
-
reason: `schedule index ${index} out of range for agent '${agentName}' (${schedule.length} entries)`
|
|
18214
|
-
};
|
|
18215
|
-
}
|
|
18216
|
-
const entry = schedule[index];
|
|
18217
|
-
const allowedKeys = entry.secrets ?? [];
|
|
18218
|
-
if (!allowedKeys.includes(key)) {
|
|
18219
|
-
return {
|
|
18220
|
-
allow: false,
|
|
18221
|
-
reason: `key '${key}' not in ACL for ${agentName}/schedule[${index}]`
|
|
18222
|
-
};
|
|
18223
|
-
}
|
|
18224
|
-
return { allow: true };
|
|
18225
|
-
}
|
|
18226
|
-
return {
|
|
18227
|
-
allow: false,
|
|
18228
|
-
reason: "caller is not a switchroom cron unit; use 'switchroom vault get --no-broker' for interactive access"
|
|
18229
|
-
};
|
|
18230
|
-
}
|
|
18231
18210
|
function checkAclByAgent(config, agentName, key) {
|
|
18232
18211
|
if (!agentName) {
|
|
18233
18212
|
return { allow: false, reason: "agent name unresolved" };
|
|
@@ -18361,18 +18340,22 @@ var tails = new Map;
|
|
|
18361
18340
|
|
|
18362
18341
|
// src/agents/inject.ts
|
|
18363
18342
|
var INJECT_COMMANDS = new Map([
|
|
18364
|
-
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true }],
|
|
18365
|
-
["/status", { description: "Show session status", expectsOutput: true, dialog: true }],
|
|
18366
|
-
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true }],
|
|
18367
|
-
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true }],
|
|
18368
|
-
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true }],
|
|
18369
|
-
["/
|
|
18343
|
+
["/cost", { description: "Show session cost", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18344
|
+
["/status", { description: "Show session status", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18345
|
+
["/usage", { description: "Show plan quota", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18346
|
+
["/hooks", { description: "List configured hooks", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18347
|
+
["/memory", { description: "Open memory picker", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18348
|
+
["/help", { description: "Show help / command discovery", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18349
|
+
["/context", { description: "Show context window usage", expectsOutput: true, argsAllowed: false }],
|
|
18350
|
+
["/release-notes", { description: "Show release-notes version list", expectsOutput: true, dialog: true, argsAllowed: false }],
|
|
18351
|
+
["/model", { description: "Open model picker", expectsOutput: true, argsAllowed: true }],
|
|
18370
18352
|
[
|
|
18371
18353
|
"/clear",
|
|
18372
18354
|
{
|
|
18373
18355
|
description: "Clear session screen",
|
|
18374
18356
|
expectsOutput: false,
|
|
18375
|
-
silentNote: "context cleared — fresh slate"
|
|
18357
|
+
silentNote: "context cleared — fresh slate",
|
|
18358
|
+
argsAllowed: false
|
|
18376
18359
|
}
|
|
18377
18360
|
],
|
|
18378
18361
|
[
|
|
@@ -18380,7 +18363,8 @@ var INJECT_COMMANDS = new Map([
|
|
|
18380
18363
|
{
|
|
18381
18364
|
description: "Compact conversation history",
|
|
18382
18365
|
expectsOutput: false,
|
|
18383
|
-
silentNote: "compaction runs silently"
|
|
18366
|
+
silentNote: "compaction runs silently",
|
|
18367
|
+
argsAllowed: false
|
|
18384
18368
|
}
|
|
18385
18369
|
]
|
|
18386
18370
|
]);
|
|
@@ -18390,6 +18374,15 @@ var INJECT_BLOCKED = new Map([
|
|
|
18390
18374
|
["/logout", { reason: "would terminate the agent's auth session" }],
|
|
18391
18375
|
["/exit", { reason: "would kill the agent process" }],
|
|
18392
18376
|
["/quit", { reason: "would kill the agent process" }],
|
|
18377
|
+
["/upgrade", { reason: "mutates the Claude Code installation" }],
|
|
18378
|
+
["/init", { reason: "generates/overwrites CLAUDE.md and runs a model turn" }],
|
|
18379
|
+
["/mcp", { reason: "opens an interactive MCP server management dialog" }],
|
|
18380
|
+
["/permissions", { reason: "opens an interactive permissions editor that mutates tool policy" }],
|
|
18381
|
+
["/install-github-app", { reason: "runs a network/OAuth install flow" }],
|
|
18382
|
+
["/add-dir", { reason: "mutates the session's working-directory set" }],
|
|
18383
|
+
["/terminal-setup", { reason: "mutates terminal keybinding configuration" }],
|
|
18384
|
+
["/privacy-settings", { reason: "opens an interactive privacy-settings dialog" }],
|
|
18385
|
+
["/bug", { reason: "submits a bug report over the network" }],
|
|
18393
18386
|
[
|
|
18394
18387
|
"/effort",
|
|
18395
18388
|
{
|
|
@@ -18407,6 +18400,7 @@ var HINDSIGHT_CONSUMER_NAME = "hindsight";
|
|
|
18407
18400
|
var HINDSIGHT_IMAGE_REPO = "ghcr.io/switchroom/switchroom-hindsight";
|
|
18408
18401
|
var HINDSIGHT_IMAGE = `${HINDSIGHT_IMAGE_REPO}:latest`;
|
|
18409
18402
|
var HINDSIGHT_BROKER_SOCK_VOLUME = `auth-broker-${HINDSIGHT_CONSUMER_NAME}-sock`;
|
|
18403
|
+
var HINDSIGHT_CREDS_MIRROR_VOLUME = `consumer-creds-${HINDSIGHT_CONSUMER_NAME}`;
|
|
18410
18404
|
var HINDSIGHT_HEALTHCHECK_PY = 'import urllib.request,sys; sys.exit(0 if urllib.request.urlopen("http://localhost:8888/health",timeout=4).getcode()==200 else 1)';
|
|
18411
18405
|
var HINDSIGHT_HEALTHCHECK_CMD = `python3 -c '${HINDSIGHT_HEALTHCHECK_PY}'`;
|
|
18412
18406
|
|
|
@@ -18438,14 +18432,14 @@ init_loader();
|
|
|
18438
18432
|
import { randomBytes, scryptSync, createCipheriv, createDecipheriv } from "node:crypto";
|
|
18439
18433
|
import {
|
|
18440
18434
|
readFileSync as readFileSync5,
|
|
18441
|
-
writeSync as
|
|
18435
|
+
writeSync as writeSync3,
|
|
18442
18436
|
existsSync as existsSync6,
|
|
18443
|
-
renameSync,
|
|
18437
|
+
renameSync as renameSync2,
|
|
18444
18438
|
mkdirSync as mkdirSync2,
|
|
18445
18439
|
unlinkSync as unlinkSync2,
|
|
18446
|
-
fsyncSync as
|
|
18447
|
-
openSync as
|
|
18448
|
-
closeSync as
|
|
18440
|
+
fsyncSync as fsyncSync3,
|
|
18441
|
+
openSync as openSync3,
|
|
18442
|
+
closeSync as closeSync3,
|
|
18449
18443
|
lstatSync,
|
|
18450
18444
|
realpathSync as realpathSync2
|
|
18451
18445
|
} from "node:fs";
|
|
@@ -18454,10 +18448,10 @@ import { dirname, basename as basename2, resolve as resolve5 } from "node:path";
|
|
|
18454
18448
|
// src/vault/flock.ts
|
|
18455
18449
|
import {
|
|
18456
18450
|
existsSync as existsSync5,
|
|
18457
|
-
openSync,
|
|
18458
|
-
closeSync,
|
|
18459
|
-
writeSync,
|
|
18460
|
-
fsyncSync,
|
|
18451
|
+
openSync as openSync2,
|
|
18452
|
+
closeSync as closeSync2,
|
|
18453
|
+
writeSync as writeSync2,
|
|
18454
|
+
fsyncSync as fsyncSync2,
|
|
18461
18455
|
unlinkSync,
|
|
18462
18456
|
readFileSync as readFileSync4,
|
|
18463
18457
|
readdirSync as readdirSync3,
|
|
@@ -18582,7 +18576,7 @@ function acquireLock(vaultPath, options = {}) {
|
|
|
18582
18576
|
while (true) {
|
|
18583
18577
|
let fd = null;
|
|
18584
18578
|
try {
|
|
18585
|
-
fd =
|
|
18579
|
+
fd = openSync2(lockPath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, 384);
|
|
18586
18580
|
} catch (err) {
|
|
18587
18581
|
const code = err?.code ?? "";
|
|
18588
18582
|
if (code === "EEXIST") {
|
|
@@ -18618,11 +18612,11 @@ function acquireLock(vaultPath, options = {}) {
|
|
|
18618
18612
|
${Date.now()}
|
|
18619
18613
|
${process.argv[1] ?? ""}
|
|
18620
18614
|
`;
|
|
18621
|
-
|
|
18622
|
-
|
|
18615
|
+
writeSync2(fd, meta);
|
|
18616
|
+
fsyncSync2(fd);
|
|
18623
18617
|
} catch {
|
|
18624
18618
|
try {
|
|
18625
|
-
|
|
18619
|
+
closeSync2(fd);
|
|
18626
18620
|
} catch {}
|
|
18627
18621
|
try {
|
|
18628
18622
|
unlinkSync(lockPath);
|
|
@@ -18633,7 +18627,7 @@ ${process.argv[1] ?? ""}
|
|
|
18633
18627
|
return {
|
|
18634
18628
|
release: () => {
|
|
18635
18629
|
try {
|
|
18636
|
-
|
|
18630
|
+
closeSync2(ownedFd);
|
|
18637
18631
|
} catch {}
|
|
18638
18632
|
try {
|
|
18639
18633
|
unlinkSync(lockPath);
|
|
@@ -18689,20 +18683,20 @@ function atomicWriteFileSync(path, data, mode) {
|
|
|
18689
18683
|
const dir = dirname(resolve5(effectivePath));
|
|
18690
18684
|
const tmp = resolve5(dir, `.${basename2(effectivePath)}.${process.pid}.${Date.now()}.tmp`);
|
|
18691
18685
|
try {
|
|
18692
|
-
const fd =
|
|
18686
|
+
const fd = openSync3(tmp, "wx", mode);
|
|
18693
18687
|
try {
|
|
18694
|
-
|
|
18695
|
-
|
|
18688
|
+
writeSync3(fd, data);
|
|
18689
|
+
fsyncSync3(fd);
|
|
18696
18690
|
} finally {
|
|
18697
|
-
|
|
18691
|
+
closeSync3(fd);
|
|
18698
18692
|
}
|
|
18699
|
-
|
|
18693
|
+
renameSync2(tmp, effectivePath);
|
|
18700
18694
|
try {
|
|
18701
|
-
const dirFd =
|
|
18695
|
+
const dirFd = openSync3(dir, "r");
|
|
18702
18696
|
try {
|
|
18703
|
-
|
|
18697
|
+
fsyncSync3(dirFd);
|
|
18704
18698
|
} finally {
|
|
18705
|
-
|
|
18699
|
+
closeSync3(dirFd);
|
|
18706
18700
|
}
|
|
18707
18701
|
} catch {}
|
|
18708
18702
|
} catch (err) {
|
|
@@ -19278,6 +19272,13 @@ var ApprovalLookupRequestSchema = exports_external.object({
|
|
|
19278
19272
|
action: exports_external.string().min(1),
|
|
19279
19273
|
current_approver_set: exports_external.array(exports_external.string())
|
|
19280
19274
|
});
|
|
19275
|
+
var ApprovalLookupByRequestRequestSchema = exports_external.object({
|
|
19276
|
+
v: exports_external.literal(1),
|
|
19277
|
+
op: exports_external.literal("approval_lookup_by_request"),
|
|
19278
|
+
agent_unit: exports_external.string().min(1),
|
|
19279
|
+
request_id: exports_external.string().regex(/^[0-9a-f]{32}$/),
|
|
19280
|
+
current_approver_set: exports_external.array(exports_external.string())
|
|
19281
|
+
});
|
|
19281
19282
|
var ApprovalConsumeRequestSchema = exports_external.object({
|
|
19282
19283
|
v: exports_external.literal(1),
|
|
19283
19284
|
op: exports_external.literal("approval_consume"),
|
|
@@ -19332,6 +19333,7 @@ var RequestSchema = exports_external.discriminatedUnion("op", [
|
|
|
19332
19333
|
RevokeGrantRequestSchema,
|
|
19333
19334
|
ApprovalRequestRequestSchema,
|
|
19334
19335
|
ApprovalLookupRequestSchema,
|
|
19336
|
+
ApprovalLookupByRequestRequestSchema,
|
|
19335
19337
|
ApprovalConsumeRequestSchema,
|
|
19336
19338
|
ApprovalRevokeRequestSchema,
|
|
19337
19339
|
ApprovalListRequestSchema,
|
|
@@ -19354,7 +19356,8 @@ var ErrorCode = exports_external.enum([
|
|
|
19354
19356
|
"DENIED",
|
|
19355
19357
|
"UNKNOWN_KEY",
|
|
19356
19358
|
"BAD_REQUEST",
|
|
19357
|
-
"INTERNAL"
|
|
19359
|
+
"INTERNAL",
|
|
19360
|
+
"AUDIT_UNAVAILABLE"
|
|
19358
19361
|
]);
|
|
19359
19362
|
var OkEntryResponseSchema = exports_external.object({
|
|
19360
19363
|
ok: exports_external.literal(true),
|
|
@@ -19542,6 +19545,7 @@ init_paths();
|
|
|
19542
19545
|
|
|
19543
19546
|
// src/agents/scaffold.ts
|
|
19544
19547
|
var REPO_ROOT = resolve6(import.meta.dirname, "../..");
|
|
19548
|
+
var CLAUDE_MD_YOURS_PLACEHOLDER = "This space is yours. Add per-agent rules, exceptions, or context the " + "Switchroom template doesn't capture. Everything above the marker line is " + "regenerated on every apply; this section is preserved.";
|
|
19545
19549
|
var SWITCHROOM_OWNED_SETTINGS_KEYS = new Set([
|
|
19546
19550
|
"permissions",
|
|
19547
19551
|
"mcpServers",
|
|
@@ -19582,13 +19586,13 @@ import {
|
|
|
19582
19586
|
copyFileSync as copyFileSync2,
|
|
19583
19587
|
chmodSync,
|
|
19584
19588
|
existsSync as existsSync7,
|
|
19585
|
-
fsyncSync as
|
|
19589
|
+
fsyncSync as fsyncSync4,
|
|
19586
19590
|
lstatSync as lstatSync2,
|
|
19587
19591
|
mkdirSync as mkdirSync3,
|
|
19588
|
-
openSync as
|
|
19589
|
-
closeSync as
|
|
19592
|
+
openSync as openSync4,
|
|
19593
|
+
closeSync as closeSync4,
|
|
19590
19594
|
readFileSync as readFileSync7,
|
|
19591
|
-
renameSync as
|
|
19595
|
+
renameSync as renameSync3,
|
|
19592
19596
|
statSync as statSync4,
|
|
19593
19597
|
symlinkSync,
|
|
19594
19598
|
unlinkSync as unlinkSync3
|
|
@@ -19654,7 +19658,7 @@ function runMigration(home2, opts) {
|
|
|
19654
19658
|
copyFileSync2(oldPath, tempNew);
|
|
19655
19659
|
chmodSync(tempNew, 384);
|
|
19656
19660
|
fsyncFile(tempNew);
|
|
19657
|
-
|
|
19661
|
+
renameSync3(tempNew, newPath);
|
|
19658
19662
|
fsyncDir(parent);
|
|
19659
19663
|
atomicReplaceWithSymlink(oldPath, "vault/vault.enc");
|
|
19660
19664
|
fsyncDir(switchroomRoot);
|
|
@@ -19688,22 +19692,22 @@ function atomicReplaceWithSymlink(target, linkTarget) {
|
|
|
19688
19692
|
} catch {}
|
|
19689
19693
|
}
|
|
19690
19694
|
symlinkSync(linkTarget, tmp);
|
|
19691
|
-
|
|
19695
|
+
renameSync3(tmp, target);
|
|
19692
19696
|
}
|
|
19693
19697
|
function fsyncFile(path) {
|
|
19694
|
-
const fd =
|
|
19698
|
+
const fd = openSync4(path, "r+");
|
|
19695
19699
|
try {
|
|
19696
|
-
|
|
19700
|
+
fsyncSync4(fd);
|
|
19697
19701
|
} finally {
|
|
19698
|
-
|
|
19702
|
+
closeSync4(fd);
|
|
19699
19703
|
}
|
|
19700
19704
|
}
|
|
19701
19705
|
function fsyncDir(path) {
|
|
19702
|
-
const fd =
|
|
19706
|
+
const fd = openSync4(path, "r");
|
|
19703
19707
|
try {
|
|
19704
|
-
|
|
19708
|
+
fsyncSync4(fd);
|
|
19705
19709
|
} finally {
|
|
19706
|
-
|
|
19710
|
+
closeSync4(fd);
|
|
19707
19711
|
}
|
|
19708
19712
|
}
|
|
19709
19713
|
|
|
@@ -19714,15 +19718,15 @@ init_loader();
|
|
|
19714
19718
|
import { createHmac, randomBytes as randomBytes2, createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2 } from "node:crypto";
|
|
19715
19719
|
import {
|
|
19716
19720
|
chmodSync as chmodSync2,
|
|
19717
|
-
closeSync as
|
|
19721
|
+
closeSync as closeSync5,
|
|
19718
19722
|
existsSync as existsSync8,
|
|
19719
|
-
fsyncSync as
|
|
19723
|
+
fsyncSync as fsyncSync5,
|
|
19720
19724
|
mkdirSync as mkdirSync4,
|
|
19721
|
-
openSync as
|
|
19725
|
+
openSync as openSync5,
|
|
19722
19726
|
readFileSync as readFileSync8,
|
|
19723
|
-
renameSync as
|
|
19727
|
+
renameSync as renameSync4,
|
|
19724
19728
|
unlinkSync as unlinkSync4,
|
|
19725
|
-
writeSync as
|
|
19729
|
+
writeSync as writeSync4
|
|
19726
19730
|
} from "node:fs";
|
|
19727
19731
|
var FORMAT_VERSION = 1;
|
|
19728
19732
|
var SALT_LEN = 16;
|
|
@@ -19815,7 +19819,7 @@ import * as path from "node:path";
|
|
|
19815
19819
|
|
|
19816
19820
|
// src/util/audit-hashchain.ts
|
|
19817
19821
|
import { createHash as createHash4 } from "node:crypto";
|
|
19818
|
-
import { openSync as
|
|
19822
|
+
import { openSync as openSync6, readSync, fstatSync as fstatSync2, closeSync as closeSync6 } from "node:fs";
|
|
19819
19823
|
var CHAIN_GENESIS = "GENESIS";
|
|
19820
19824
|
var SEP = "\x00";
|
|
19821
19825
|
function rowHash(prev, seq, rowJsonNoChain) {
|
|
@@ -19836,7 +19840,7 @@ function chainRow(state, entry) {
|
|
|
19836
19840
|
function readTail(path, tailBytes = 64 * 1024) {
|
|
19837
19841
|
let fd;
|
|
19838
19842
|
try {
|
|
19839
|
-
fd =
|
|
19843
|
+
fd = openSync6(path, "r");
|
|
19840
19844
|
} catch {
|
|
19841
19845
|
return null;
|
|
19842
19846
|
}
|
|
@@ -19852,7 +19856,7 @@ function readTail(path, tailBytes = 64 * 1024) {
|
|
|
19852
19856
|
return null;
|
|
19853
19857
|
} finally {
|
|
19854
19858
|
try {
|
|
19855
|
-
|
|
19859
|
+
closeSync6(fd);
|
|
19856
19860
|
} catch {}
|
|
19857
19861
|
}
|
|
19858
19862
|
}
|
|
@@ -19929,6 +19933,23 @@ function safeAuditLogPath(requestedPath) {
|
|
|
19929
19933
|
}
|
|
19930
19934
|
|
|
19931
19935
|
// src/vault/broker/audit-log.ts
|
|
19936
|
+
function failOpenStatePath(logPath) {
|
|
19937
|
+
return `${logPath}.failopen`;
|
|
19938
|
+
}
|
|
19939
|
+
function readFailOpenState(statePath) {
|
|
19940
|
+
try {
|
|
19941
|
+
const raw = fs.readFileSync(statePath, "utf8");
|
|
19942
|
+
const parsed = JSON.parse(raw);
|
|
19943
|
+
const count = Number(parsed.failOpenCount);
|
|
19944
|
+
return {
|
|
19945
|
+
failOpenCount: Number.isFinite(count) && count > 0 ? count : 0,
|
|
19946
|
+
lastFailureTs: typeof parsed.lastFailureTs === "string" ? parsed.lastFailureTs : undefined,
|
|
19947
|
+
lastError: typeof parsed.lastError === "string" ? parsed.lastError : undefined
|
|
19948
|
+
};
|
|
19949
|
+
} catch {
|
|
19950
|
+
return { failOpenCount: 0 };
|
|
19951
|
+
}
|
|
19952
|
+
}
|
|
19932
19953
|
var DEFAULT_AUDIT_MAX_BYTES = 32 * 1024 * 1024;
|
|
19933
19954
|
var DEFAULT_AUDIT_MAX_FILES = 5;
|
|
19934
19955
|
function resolveRotation(opts) {
|
|
@@ -20009,7 +20030,27 @@ function createAuditLogger(opts = {}) {
|
|
|
20009
20030
|
const logPath = safeAuditLogPath(opts.path ?? defaultAuditLogPath());
|
|
20010
20031
|
let chain = seedChain(logPath);
|
|
20011
20032
|
const rotation = resolveRotation(opts);
|
|
20033
|
+
const statePath = opts.statePath ?? failOpenStatePath(logPath);
|
|
20034
|
+
let failOpen = readFailOpenState(statePath).failOpenCount;
|
|
20035
|
+
const recordFailOpen = (err) => {
|
|
20036
|
+
failOpen += 1;
|
|
20037
|
+
const state = {
|
|
20038
|
+
failOpenCount: failOpen,
|
|
20039
|
+
lastFailureTs: new Date().toISOString(),
|
|
20040
|
+
lastError: String(err.message).slice(0, 300)
|
|
20041
|
+
};
|
|
20042
|
+
try {
|
|
20043
|
+
fs.writeFileSync(statePath, `${JSON.stringify(state)}
|
|
20044
|
+
`, { mode: 384 });
|
|
20045
|
+
} catch (persistErr) {
|
|
20046
|
+
process.stderr.write(`[vault-audit] ERROR: could not persist fail-open counter ${statePath}: ${persistErr.message}
|
|
20047
|
+
`);
|
|
20048
|
+
}
|
|
20049
|
+
};
|
|
20012
20050
|
return {
|
|
20051
|
+
failOpenCount() {
|
|
20052
|
+
return failOpen;
|
|
20053
|
+
},
|
|
20013
20054
|
write(entry) {
|
|
20014
20055
|
if (rotation.maxBytes > 0) {
|
|
20015
20056
|
try {
|
|
@@ -20026,14 +20067,18 @@ function createAuditLogger(opts = {}) {
|
|
|
20026
20067
|
} catch (err) {
|
|
20027
20068
|
process.stderr.write(`[vault-audit] ERROR: could not open audit log ${logPath}: ${err.message}
|
|
20028
20069
|
`);
|
|
20029
|
-
|
|
20070
|
+
recordFailOpen(err);
|
|
20071
|
+
return false;
|
|
20030
20072
|
}
|
|
20073
|
+
let durable = false;
|
|
20031
20074
|
try {
|
|
20032
20075
|
fs.writeSync(fd, line);
|
|
20033
20076
|
chain = next;
|
|
20077
|
+
durable = true;
|
|
20034
20078
|
} catch (err) {
|
|
20035
20079
|
process.stderr.write(`[vault-audit] ERROR: could not write to audit log ${logPath}: ${err.message}
|
|
20036
20080
|
`);
|
|
20081
|
+
recordFailOpen(err);
|
|
20037
20082
|
} finally {
|
|
20038
20083
|
try {
|
|
20039
20084
|
fs.closeSync(fd);
|
|
@@ -20042,6 +20087,7 @@ function createAuditLogger(opts = {}) {
|
|
|
20042
20087
|
`);
|
|
20043
20088
|
}
|
|
20044
20089
|
}
|
|
20090
|
+
return durable;
|
|
20045
20091
|
}
|
|
20046
20092
|
};
|
|
20047
20093
|
}
|
|
@@ -22166,6 +22212,9 @@ function lookupDecision(db, query, opts = {}, now = Date.now()) {
|
|
|
22166
22212
|
ORDER BY granted_at DESC LIMIT 1`).get(query.agent_unit, query.scope, query.action);
|
|
22167
22213
|
if (!row)
|
|
22168
22214
|
return { state: "no_decision" };
|
|
22215
|
+
return evaluateDecisionRow(db, row, currentCanonical, opts, now);
|
|
22216
|
+
}
|
|
22217
|
+
function evaluateDecisionRow(db, row, currentCanonical, opts, now) {
|
|
22169
22218
|
const decision = rowToDecision(row);
|
|
22170
22219
|
if (decision.decision === "deny_perm") {
|
|
22171
22220
|
return { state: "denied", decision };
|
|
@@ -22375,6 +22424,21 @@ class VaultBroker {
|
|
|
22375
22424
|
this.grantsDb = openGrantsDb();
|
|
22376
22425
|
}
|
|
22377
22426
|
}
|
|
22427
|
+
auditFailClosedEnabled() {
|
|
22428
|
+
const env = process.env.SWITCHROOM_VAULT_AUDIT_FAIL_CLOSED;
|
|
22429
|
+
if (env !== undefined && env !== "") {
|
|
22430
|
+
return env === "1" || env.toLowerCase() === "true";
|
|
22431
|
+
}
|
|
22432
|
+
return this.config?.vault?.broker?.auditFailClosed === true;
|
|
22433
|
+
}
|
|
22434
|
+
auditedReleaseOk(socket, writeAudit, entry) {
|
|
22435
|
+
const durable = writeAudit(entry);
|
|
22436
|
+
if (!durable && this.auditFailClosedEnabled()) {
|
|
22437
|
+
socket.write(encodeResponse(errorResponse("AUDIT_UNAVAILABLE", "secret release denied: audit append failed and broker is in fail-closed mode (vault.broker.auditFailClosed)")));
|
|
22438
|
+
return false;
|
|
22439
|
+
}
|
|
22440
|
+
return true;
|
|
22441
|
+
}
|
|
22378
22442
|
async start(socketPath, configPath, vaultPath) {
|
|
22379
22443
|
if (process.platform !== "linux" && process.env.SWITCHROOM_BROKER_ALLOW_NON_LINUX !== "1") {
|
|
22380
22444
|
throw new Error(`vault-broker is Linux-only (running on ${process.platform}). ` + `The broker's ACL relies on cgroup-based systemd unit identification, ` + `which is not available on this platform. ` + `Use 'switchroom vault get --no-broker' for direct vault access. ` + `If you need to run the broker for development on this platform, ` + `set SWITCHROOM_BROKER_ALLOW_NON_LINUX=1 — but understand that the ` + `broker will accept any same-user caller without per-cron ACL enforcement.`);
|
|
@@ -22442,7 +22506,7 @@ class VaultBroker {
|
|
|
22442
22506
|
return;
|
|
22443
22507
|
try {
|
|
22444
22508
|
if (ready) {
|
|
22445
|
-
|
|
22509
|
+
writeFileSync3(p, "", { mode: 384 });
|
|
22446
22510
|
} else if (existsSync11(p)) {
|
|
22447
22511
|
unlinkSync6(p);
|
|
22448
22512
|
}
|
|
@@ -22741,7 +22805,7 @@ class VaultBroker {
|
|
|
22741
22805
|
const auditPeerUid = peer?.uid;
|
|
22742
22806
|
const auditAgentName = agentName ?? undefined;
|
|
22743
22807
|
const writeAudit = (entry) => {
|
|
22744
|
-
this.auditLogger.write({
|
|
22808
|
+
return this.auditLogger.write({
|
|
22745
22809
|
...entry,
|
|
22746
22810
|
peer_uid: entry.peer_uid ?? auditPeerUid,
|
|
22747
22811
|
agent_name: entry.agent_name ?? auditAgentName
|
|
@@ -22850,8 +22914,8 @@ class VaultBroker {
|
|
|
22850
22914
|
return;
|
|
22851
22915
|
}
|
|
22852
22916
|
}
|
|
22853
|
-
if (!isOperator && agentName === null && process.platform === "linux"
|
|
22854
|
-
const reason = "Unable to identify caller (
|
|
22917
|
+
if (!isOperator && agentName === null && process.platform === "linux") {
|
|
22918
|
+
const reason = "Unable to identify caller (no per-agent socket identity); denying on Linux";
|
|
22855
22919
|
this.auditLogger.write({
|
|
22856
22920
|
ts: new Date().toISOString(),
|
|
22857
22921
|
op: "list",
|
|
@@ -22863,14 +22927,12 @@ class VaultBroker {
|
|
|
22863
22927
|
socket.write(encodeResponse(errorResponse("DENIED", reason)));
|
|
22864
22928
|
return;
|
|
22865
22929
|
}
|
|
22866
|
-
const listAgentSlug = agentName
|
|
22930
|
+
const listAgentSlug = agentName;
|
|
22867
22931
|
let visibleKeys;
|
|
22868
22932
|
if (isOperator) {
|
|
22869
22933
|
visibleKeys = Object.entries(this.secrets).filter(([, entry]) => checkEntryScope(entry.scope, "operator").allow).map(([k]) => k);
|
|
22870
22934
|
} else if (agentName !== null && this.config !== null) {
|
|
22871
22935
|
visibleKeys = Object.entries(this.secrets).filter(([key, entry]) => checkAclByAgent(this.config, agentName, key).allow && checkEntryScope(entry.scope, agentName).allow).map(([k]) => k);
|
|
22872
|
-
} else if (peer !== null && this.config !== null) {
|
|
22873
|
-
visibleKeys = Object.entries(this.secrets).filter(([key, entry]) => checkAcl(peer, this.config, key).allow && checkEntryScope(entry.scope, listAgentSlug).allow).map(([k]) => k);
|
|
22874
22936
|
} else {
|
|
22875
22937
|
visibleKeys = Object.entries(this.secrets).filter(([, entry]) => checkEntryScope(entry.scope, listAgentSlug).allow).map(([k]) => k);
|
|
22876
22938
|
}
|
|
@@ -22894,6 +22956,21 @@ class VaultBroker {
|
|
|
22894
22956
|
const grantResult = await validateGrant(this.grantsDb, req.token, req.key);
|
|
22895
22957
|
if (grantResult.ok) {
|
|
22896
22958
|
const grantId = grantResult.grant.id;
|
|
22959
|
+
if (agentName !== null && grantResult.grant.agent_slug !== agentName) {
|
|
22960
|
+
this.auditLogger.write({
|
|
22961
|
+
ts: new Date().toISOString(),
|
|
22962
|
+
op: "get",
|
|
22963
|
+
key: req.key,
|
|
22964
|
+
caller: auditCaller,
|
|
22965
|
+
pid: auditPid,
|
|
22966
|
+
cgroup: auditCgroup,
|
|
22967
|
+
result: "denied:grant-identity-mismatch",
|
|
22968
|
+
method: "grant",
|
|
22969
|
+
grant_id: grantId
|
|
22970
|
+
});
|
|
22971
|
+
socket.write(encodeResponse(errorResponse("DENIED", "grant-identity-mismatch")));
|
|
22972
|
+
return;
|
|
22973
|
+
}
|
|
22897
22974
|
const entry2 = this.secrets[req.key];
|
|
22898
22975
|
if (entry2 === undefined) {
|
|
22899
22976
|
this.auditLogger.write({
|
|
@@ -22910,7 +22987,23 @@ class VaultBroker {
|
|
|
22910
22987
|
socket.write(encodeResponse(errorResponse("UNKNOWN_KEY", `Key not found: ${req.key}`)));
|
|
22911
22988
|
return;
|
|
22912
22989
|
}
|
|
22913
|
-
|
|
22990
|
+
const tokenScope = checkEntryScope(entry2.scope, grantResult.grant.agent_slug);
|
|
22991
|
+
if (!tokenScope.allow) {
|
|
22992
|
+
this.auditLogger.write({
|
|
22993
|
+
ts: new Date().toISOString(),
|
|
22994
|
+
op: "get",
|
|
22995
|
+
key: req.key,
|
|
22996
|
+
caller: auditCaller,
|
|
22997
|
+
pid: auditPid,
|
|
22998
|
+
cgroup: auditCgroup,
|
|
22999
|
+
result: `denied:${tokenScope.reason}`,
|
|
23000
|
+
method: "grant",
|
|
23001
|
+
grant_id: grantId
|
|
23002
|
+
});
|
|
23003
|
+
socket.write(encodeResponse(errorResponse("DENIED", tokenScope.reason)));
|
|
23004
|
+
return;
|
|
23005
|
+
}
|
|
23006
|
+
if (!this.auditedReleaseOk(socket, writeAudit, {
|
|
22914
23007
|
ts: new Date().toISOString(),
|
|
22915
23008
|
op: "get",
|
|
22916
23009
|
key: req.key,
|
|
@@ -22920,7 +23013,9 @@ class VaultBroker {
|
|
|
22920
23013
|
result: "allowed",
|
|
22921
23014
|
method: "grant",
|
|
22922
23015
|
grant_id: grantId
|
|
22923
|
-
})
|
|
23016
|
+
})) {
|
|
23017
|
+
return;
|
|
23018
|
+
}
|
|
22924
23019
|
socket.write(encodeResponse(entryResponse(entry2)));
|
|
22925
23020
|
return;
|
|
22926
23021
|
} else if (grantResult.reason === "grant-revoked") {
|
|
@@ -22974,23 +23069,8 @@ class VaultBroker {
|
|
|
22974
23069
|
socket.write(encodeResponse(errorResponse("DENIED", aclResult.reason)));
|
|
22975
23070
|
return;
|
|
22976
23071
|
}
|
|
22977
|
-
} else if (
|
|
22978
|
-
const
|
|
22979
|
-
if (!aclResult.allow) {
|
|
22980
|
-
writeAudit({
|
|
22981
|
-
ts: new Date().toISOString(),
|
|
22982
|
-
op: "get",
|
|
22983
|
-
key: req.key,
|
|
22984
|
-
caller: auditCaller,
|
|
22985
|
-
pid: auditPid,
|
|
22986
|
-
cgroup: auditCgroup,
|
|
22987
|
-
result: `denied:${aclResult.reason}`
|
|
22988
|
-
});
|
|
22989
|
-
socket.write(encodeResponse(errorResponse("DENIED", aclResult.reason)));
|
|
22990
|
-
return;
|
|
22991
|
-
}
|
|
22992
|
-
} else if (process.platform === "linux" && peer === null) {
|
|
22993
|
-
const reason = "Unable to identify caller (peercred unavailable); denying on Linux";
|
|
23072
|
+
} else if (process.platform === "linux" && agentName === null) {
|
|
23073
|
+
const reason = "Unable to identify caller (no per-agent socket identity); denying on Linux";
|
|
22994
23074
|
this.auditLogger.write({
|
|
22995
23075
|
ts: new Date().toISOString(),
|
|
22996
23076
|
op: "get",
|
|
@@ -23017,7 +23097,7 @@ class VaultBroker {
|
|
|
23017
23097
|
socket.write(encodeResponse(errorResponse("UNKNOWN_KEY", `Key not found: ${req.key}`)));
|
|
23018
23098
|
return;
|
|
23019
23099
|
}
|
|
23020
|
-
const getAgentSlug = agentName
|
|
23100
|
+
const getAgentSlug = agentName;
|
|
23021
23101
|
const scopeResult = checkEntryScope(entry.scope, getAgentSlug);
|
|
23022
23102
|
if (!scopeResult.allow) {
|
|
23023
23103
|
this.auditLogger.write({
|
|
@@ -23032,7 +23112,7 @@ class VaultBroker {
|
|
|
23032
23112
|
socket.write(encodeResponse(errorResponse("DENIED", scopeResult.reason)));
|
|
23033
23113
|
return;
|
|
23034
23114
|
}
|
|
23035
|
-
this.
|
|
23115
|
+
if (!this.auditedReleaseOk(socket, writeAudit, {
|
|
23036
23116
|
ts: new Date().toISOString(),
|
|
23037
23117
|
op: "get",
|
|
23038
23118
|
key: req.key,
|
|
@@ -23040,7 +23120,9 @@ class VaultBroker {
|
|
|
23040
23120
|
pid: auditPid,
|
|
23041
23121
|
cgroup: auditCgroup,
|
|
23042
23122
|
result: "allowed"
|
|
23043
|
-
})
|
|
23123
|
+
})) {
|
|
23124
|
+
return;
|
|
23125
|
+
}
|
|
23044
23126
|
socket.write(encodeResponse(entryResponse(entry)));
|
|
23045
23127
|
return;
|
|
23046
23128
|
}
|
|
@@ -23140,6 +23222,41 @@ class VaultBroker {
|
|
|
23140
23222
|
if (req.token !== undefined && req.token !== "") {
|
|
23141
23223
|
const v = await validateGrantForWrite(this.grantsDb, req.token, req.key);
|
|
23142
23224
|
if (v.ok) {
|
|
23225
|
+
const writeGrantAgentSlug = v.grant.agent_slug;
|
|
23226
|
+
if (agentName !== null && writeGrantAgentSlug !== agentName) {
|
|
23227
|
+
this.auditLogger.write({
|
|
23228
|
+
ts: new Date().toISOString(),
|
|
23229
|
+
op: "put",
|
|
23230
|
+
key: req.key,
|
|
23231
|
+
caller: auditCaller,
|
|
23232
|
+
pid: auditPid,
|
|
23233
|
+
cgroup: auditCgroup,
|
|
23234
|
+
result: "denied:grant-identity-mismatch",
|
|
23235
|
+
method: "grant",
|
|
23236
|
+
grant_id: v.grant.id
|
|
23237
|
+
});
|
|
23238
|
+
socket.write(encodeResponse(errorResponse("DENIED", "grant-identity-mismatch")));
|
|
23239
|
+
return;
|
|
23240
|
+
}
|
|
23241
|
+
const existingForScope = this.secrets[req.key];
|
|
23242
|
+
if (existingForScope !== undefined) {
|
|
23243
|
+
const writeScope = checkEntryScope(existingForScope.scope, writeGrantAgentSlug);
|
|
23244
|
+
if (!writeScope.allow) {
|
|
23245
|
+
this.auditLogger.write({
|
|
23246
|
+
ts: new Date().toISOString(),
|
|
23247
|
+
op: "put",
|
|
23248
|
+
key: req.key,
|
|
23249
|
+
caller: auditCaller,
|
|
23250
|
+
pid: auditPid,
|
|
23251
|
+
cgroup: auditCgroup,
|
|
23252
|
+
result: `denied:${writeScope.reason}`,
|
|
23253
|
+
method: "grant",
|
|
23254
|
+
grant_id: v.grant.id
|
|
23255
|
+
});
|
|
23256
|
+
socket.write(encodeResponse(errorResponse("DENIED", writeScope.reason)));
|
|
23257
|
+
return;
|
|
23258
|
+
}
|
|
23259
|
+
}
|
|
23143
23260
|
writeGrantId = v.grant.id;
|
|
23144
23261
|
} else if (v.reason === "grant-expired" || v.reason === "grant-revoked") {
|
|
23145
23262
|
this.auditLogger.write({
|
|
@@ -23448,21 +23565,6 @@ class VaultBroker {
|
|
|
23448
23565
|
socket.write(encodeResponse(errorResponse("DENIED", "peercred unavailable; cannot verify operator identity")));
|
|
23449
23566
|
return;
|
|
23450
23567
|
}
|
|
23451
|
-
if (peer !== null && peer.systemdUnit !== null) {
|
|
23452
|
-
const parsed = parseCronUnit(peer.systemdUnit);
|
|
23453
|
-
if (parsed !== null) {
|
|
23454
|
-
this.auditLogger.write({
|
|
23455
|
-
ts: new Date().toISOString(),
|
|
23456
|
-
op: req.op,
|
|
23457
|
-
caller: auditCaller,
|
|
23458
|
-
pid: auditPid,
|
|
23459
|
-
cgroup: auditCgroup,
|
|
23460
|
-
result: "denied:cron-cannot-manage-grants"
|
|
23461
|
-
});
|
|
23462
|
-
socket.write(encodeResponse(errorResponse("DENIED", "Grant management ops are operator-only; cron units cannot mint, list, or revoke grants")));
|
|
23463
|
-
return;
|
|
23464
|
-
}
|
|
23465
|
-
}
|
|
23466
23568
|
}
|
|
23467
23569
|
}
|
|
23468
23570
|
if (req.op === "mint_grant") {
|
|
@@ -23501,8 +23603,8 @@ class VaultBroker {
|
|
|
23501
23603
|
mkdirSync6(tokenDir, { recursive: true });
|
|
23502
23604
|
const tokenPath = path3.join(tokenDir, ".vault-token");
|
|
23503
23605
|
const tmpPath = `${tokenPath}.tmp.${process.pid}`;
|
|
23504
|
-
|
|
23505
|
-
|
|
23606
|
+
writeFileSync3(tmpPath, mintResult.token, { mode: 384 });
|
|
23607
|
+
renameSync7(tmpPath, tokenPath);
|
|
23506
23608
|
try {
|
|
23507
23609
|
const uid = allocateAgentUid(agent);
|
|
23508
23610
|
chownSync(tokenPath, uid, uid);
|
|
@@ -23788,7 +23890,7 @@ class VaultBroker {
|
|
|
23788
23890
|
_writePidFile() {
|
|
23789
23891
|
try {
|
|
23790
23892
|
const pidPath = resolvePath(PID_FILE_DEFAULT);
|
|
23791
|
-
|
|
23893
|
+
writeFileSync3(pidPath, String(process.pid) + `
|
|
23792
23894
|
`, { mode: 384 });
|
|
23793
23895
|
} catch {}
|
|
23794
23896
|
}
|
|
@@ -23844,7 +23946,7 @@ class VaultBroker {
|
|
|
23844
23946
|
const credPath = `${dir}/vault-passphrase`;
|
|
23845
23947
|
let passphrase;
|
|
23846
23948
|
try {
|
|
23847
|
-
passphrase =
|
|
23949
|
+
passphrase = readFileSync10(credPath, "utf8").replace(/\n+$/, "");
|
|
23848
23950
|
} catch (err) {
|
|
23849
23951
|
const code = err.code;
|
|
23850
23952
|
if (code === "ENOENT") {
|