mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 01:44:33 +00:00
fix(telegram): resolve status SecretRefs with provider-safe env checks
Landed from #39130 by @neocody. Co-authored-by: Cody <25426121+neocody@users.noreply.github.com>
This commit is contained in:
@@ -275,6 +275,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Gateway/session bootstrap cache invalidation ordering: clear bootstrap snapshots only after active embedded-run shutdown wait completes, preventing dying runs from repopulating stale cache between `/new`/`sessions.reset` turns. (#38873) Thanks @MumuTW.
|
- Gateway/session bootstrap cache invalidation ordering: clear bootstrap snapshots only after active embedded-run shutdown wait completes, preventing dying runs from repopulating stale cache between `/new`/`sessions.reset` turns. (#38873) Thanks @MumuTW.
|
||||||
- Browser/dispatcher error clarity: preserve dispatcher-side failure context in browser fetch errors while still appending operator guidance and explicit no-retry model hints, preventing misleading `"Can't reach service"` wrapping and avoiding LLM retry loops. (#39090) Thanks @NewdlDewdl.
|
- Browser/dispatcher error clarity: preserve dispatcher-side failure context in browser fetch errors while still appending operator guidance and explicit no-retry model hints, preventing misleading `"Can't reach service"` wrapping and avoiding LLM retry loops. (#39090) Thanks @NewdlDewdl.
|
||||||
- Telegram/polling offset safety: confirm persisted offsets before polling startup while validating stored `lastUpdateId` values as non-negative safe integers (with overflow guards) so malformed offset state cannot cause update skipping/dropping. (#39111) Thanks @MumuTW.
|
- Telegram/polling offset safety: confirm persisted offsets before polling startup while validating stored `lastUpdateId` values as non-negative safe integers (with overflow guards) so malformed offset state cannot cause update skipping/dropping. (#39111) Thanks @MumuTW.
|
||||||
|
- Telegram/status SecretRef read-only resolution: resolve env-backed bot-token SecretRefs in config-only/status inspection while respecting provider source/defaults and env allowlists, so status no longer crashes or reports false-ready tokens for disallowed providers. (#39130) Thanks @neocody.
|
||||||
|
|
||||||
## 2026.3.2
|
## 2026.3.2
|
||||||
|
|
||||||
|
|||||||
79
src/telegram/account-inspect.test.ts
Normal file
79
src/telegram/account-inspect.test.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
|
import { withEnv } from "../test-utils/env.js";
|
||||||
|
import { inspectTelegramAccount } from "./account-inspect.js";
|
||||||
|
|
||||||
|
describe("inspectTelegramAccount SecretRef resolution", () => {
|
||||||
|
it("resolves default env SecretRef templates in read-only status paths", () => {
|
||||||
|
withEnv({ TG_STATUS_TOKEN: "123:token" }, () => {
|
||||||
|
const cfg: OpenClawConfig = {
|
||||||
|
channels: {
|
||||||
|
telegram: {
|
||||||
|
botToken: "${TG_STATUS_TOKEN}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const account = inspectTelegramAccount({ cfg, accountId: "default" });
|
||||||
|
expect(account.tokenSource).toBe("env");
|
||||||
|
expect(account.tokenStatus).toBe("available");
|
||||||
|
expect(account.token).toBe("123:token");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("respects env provider allowlists in read-only status paths", () => {
|
||||||
|
withEnv({ TG_NOT_ALLOWED: "123:token" }, () => {
|
||||||
|
const cfg: OpenClawConfig = {
|
||||||
|
secrets: {
|
||||||
|
defaults: {
|
||||||
|
env: "secure-env",
|
||||||
|
},
|
||||||
|
providers: {
|
||||||
|
"secure-env": {
|
||||||
|
source: "env",
|
||||||
|
allowlist: ["TG_ALLOWED"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channels: {
|
||||||
|
telegram: {
|
||||||
|
botToken: "${TG_NOT_ALLOWED}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const account = inspectTelegramAccount({ cfg, accountId: "default" });
|
||||||
|
expect(account.tokenSource).toBe("env");
|
||||||
|
expect(account.tokenStatus).toBe("configured_unavailable");
|
||||||
|
expect(account.token).toBe("");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not read env values for non-env providers", () => {
|
||||||
|
withEnv({ TG_EXEC_PROVIDER: "123:token" }, () => {
|
||||||
|
const cfg: OpenClawConfig = {
|
||||||
|
secrets: {
|
||||||
|
defaults: {
|
||||||
|
env: "exec-provider",
|
||||||
|
},
|
||||||
|
providers: {
|
||||||
|
"exec-provider": {
|
||||||
|
source: "exec",
|
||||||
|
command: "/usr/bin/env",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channels: {
|
||||||
|
telegram: {
|
||||||
|
botToken: "${TG_EXEC_PROVIDER}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const account = inspectTelegramAccount({ cfg, accountId: "default" });
|
||||||
|
expect(account.tokenSource).toBe("env");
|
||||||
|
expect(account.tokenStatus).toBe("configured_unavailable");
|
||||||
|
expect(account.token).toBe("");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import type { OpenClawConfig } from "../config/config.js";
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
import { hasConfiguredSecretInput, normalizeSecretInputString } from "../config/types.secrets.js";
|
import {
|
||||||
|
coerceSecretRef,
|
||||||
|
hasConfiguredSecretInput,
|
||||||
|
normalizeSecretInputString,
|
||||||
|
} from "../config/types.secrets.js";
|
||||||
import type { TelegramAccountConfig } from "../config/types.telegram.js";
|
import type { TelegramAccountConfig } from "../config/types.telegram.js";
|
||||||
import { resolveAccountWithDefaultFallback } from "../plugin-sdk/account-resolution.js";
|
import { resolveAccountWithDefaultFallback } from "../plugin-sdk/account-resolution.js";
|
||||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||||
|
import { resolveDefaultSecretProviderAlias } from "../secrets/ref-contract.js";
|
||||||
import {
|
import {
|
||||||
mergeTelegramAccountConfig,
|
mergeTelegramAccountConfig,
|
||||||
resolveDefaultTelegramAccountId,
|
resolveDefaultTelegramAccountId,
|
||||||
@@ -55,12 +60,58 @@ function inspectTokenFile(pathValue: unknown): {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function inspectTokenValue(value: unknown): {
|
function canResolveEnvSecretRefInReadOnlyPath(params: {
|
||||||
|
cfg: OpenClawConfig;
|
||||||
|
provider: string;
|
||||||
|
id: string;
|
||||||
|
}): boolean {
|
||||||
|
const providerConfig = params.cfg.secrets?.providers?.[params.provider];
|
||||||
|
if (!providerConfig) {
|
||||||
|
return params.provider === resolveDefaultSecretProviderAlias(params.cfg, "env");
|
||||||
|
}
|
||||||
|
if (providerConfig.source !== "env") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const allowlist = providerConfig.allowlist;
|
||||||
|
return !allowlist || allowlist.includes(params.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function inspectTokenValue(params: { cfg: OpenClawConfig; value: unknown }): {
|
||||||
token: string;
|
token: string;
|
||||||
tokenSource: "config" | "none";
|
tokenSource: "config" | "env" | "none";
|
||||||
tokenStatus: TelegramCredentialStatus;
|
tokenStatus: TelegramCredentialStatus;
|
||||||
} | null {
|
} | null {
|
||||||
const token = normalizeSecretInputString(value);
|
// Try to resolve env-based SecretRefs from process.env for read-only inspection
|
||||||
|
const ref = coerceSecretRef(params.value, params.cfg.secrets?.defaults);
|
||||||
|
if (ref?.source === "env") {
|
||||||
|
if (
|
||||||
|
!canResolveEnvSecretRefInReadOnlyPath({
|
||||||
|
cfg: params.cfg,
|
||||||
|
provider: ref.provider,
|
||||||
|
id: ref.id,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
token: "",
|
||||||
|
tokenSource: "env",
|
||||||
|
tokenStatus: "configured_unavailable",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const envValue = process.env[ref.id];
|
||||||
|
if (envValue && envValue.trim()) {
|
||||||
|
return {
|
||||||
|
token: envValue.trim(),
|
||||||
|
tokenSource: "env",
|
||||||
|
tokenStatus: "available",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
token: "",
|
||||||
|
tokenSource: "env",
|
||||||
|
tokenStatus: "configured_unavailable",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const token = normalizeSecretInputString(params.value);
|
||||||
if (token) {
|
if (token) {
|
||||||
return {
|
return {
|
||||||
token,
|
token,
|
||||||
@@ -68,7 +119,7 @@ function inspectTokenValue(value: unknown): {
|
|||||||
tokenStatus: "available",
|
tokenStatus: "available",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (hasConfiguredSecretInput(value)) {
|
if (hasConfiguredSecretInput(params.value, params.cfg.secrets?.defaults)) {
|
||||||
return {
|
return {
|
||||||
token: "",
|
token: "",
|
||||||
tokenSource: "config",
|
tokenSource: "config",
|
||||||
@@ -102,7 +153,7 @@ function inspectTelegramAccountPrimary(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountToken = inspectTokenValue(accountConfig?.botToken);
|
const accountToken = inspectTokenValue({ cfg: params.cfg, value: accountConfig?.botToken });
|
||||||
if (accountToken) {
|
if (accountToken) {
|
||||||
return {
|
return {
|
||||||
accountId,
|
accountId,
|
||||||
@@ -130,7 +181,10 @@ function inspectTelegramAccountPrimary(params: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const channelToken = inspectTokenValue(params.cfg.channels?.telegram?.botToken);
|
const channelToken = inspectTokenValue({
|
||||||
|
cfg: params.cfg,
|
||||||
|
value: params.cfg.channels?.telegram?.botToken,
|
||||||
|
});
|
||||||
if (channelToken) {
|
if (channelToken) {
|
||||||
return {
|
return {
|
||||||
accountId,
|
accountId,
|
||||||
|
|||||||
Reference in New Issue
Block a user