fix(hermes): persist Slack signing secret to SLACK_SIGNING_SECRET env

Channel save removed signing_secret from YAML (matching app_token migration)
but never wrote it to ~/.hermes/.env. Saving Slack config from the panel
permanently wiped the signing secret, breaking webhook signature verification.

Read path now prefers SLACK_SIGNING_SECRET from env, matching bot/app tokens.

Co-authored-by: 晴天 <1186258278@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-06-05 11:13:50 +00:00
parent 6985bac56f
commit cef48c13ee
3 changed files with 61 additions and 0 deletions

View File

@@ -6313,6 +6313,7 @@ export function buildHermesChannelConfigValues(config = {}, envValues = {}) {
putHermesString(form, extra, 'app_token')
form.appToken = hermesEnvValue(envValues, 'SLACK_APP_TOKEN') || form.appToken || ''
putHermesString(form, extra, 'signing_secret')
form.signingSecret = hermesEnvValue(envValues, 'SLACK_SIGNING_SECRET') || form.signingSecret || ''
putHermesString(form, extra, 'webhook_path')
} else if (platform === 'feishu') {
for (const key of ['app_id', 'app_secret', 'domain', 'connection_mode', 'webhook_path', 'reaction_notifications']) {
@@ -6772,6 +6773,7 @@ export function buildHermesChannelEnvUpdates(platform, form = {}) {
} else if (platform === 'slack') {
updates.SLACK_BOT_TOKEN = String(form.botToken || '').trim()
updates.SLACK_APP_TOKEN = String(form.appToken || '').trim()
updates.SLACK_SIGNING_SECRET = String(form.signingSecret || '').trim()
updates.SLACK_ALLOWED_USERS = csvEnvValue(form.allowFrom)
if (Object.hasOwn(form, 'requireMention')) updates.SLACK_REQUIRE_MENTION = boolEnvValue(form.requireMention)
} else if (platform === 'feishu') {

View File

@@ -3402,6 +3402,13 @@ fn build_hermes_channel_config_values(
.unwrap_or_default();
form.insert("appToken".to_string(), Value::String(app_token));
insert_json_string_if_present(&mut form, &extra, "signing_secret", "signingSecret");
let signing_secret = hermes_env_value(env_values, "SLACK_SIGNING_SECRET")
.or_else(|| json_form_string(&form, "signingSecret"))
.unwrap_or_default();
form.insert(
"signingSecret".to_string(),
Value::String(signing_secret),
);
insert_json_string_if_present(&mut form, &extra, "webhook_path", "webhookPath");
}
"feishu" => {
@@ -11182,6 +11189,10 @@ fn build_hermes_channel_env_updates(platform: &str, form: &Value) -> Vec<(String
"SLACK_APP_TOKEN",
form_string(form, "appToken").unwrap_or_default(),
);
push(
"SLACK_SIGNING_SECRET",
form_string(form, "signingSecret").unwrap_or_default(),
);
push("SLACK_ALLOWED_USERS", csv_env_value(form, "allowFrom"));
if let Some(value) = form_bool(form, "requireMention") {
push("SLACK_REQUIRE_MENTION", bool_env_value(value));
@@ -11431,6 +11442,7 @@ fn write_hermes_channel_env(platform: &str, form: &Value) -> Result<(), String>
"slack" => vec![
"SLACK_BOT_TOKEN",
"SLACK_APP_TOKEN",
"SLACK_SIGNING_SECRET",
"SLACK_ALLOWED_USERS",
"SLACK_REQUIRE_MENTION",
],
@@ -24515,6 +24527,21 @@ platforms:
config["platforms"]["slack"]["extra"]["unknown_option"].as_str(),
Some("keep-me")
);
let env = build_hermes_channel_env_updates(
"slack",
&json!({
"enabled": true,
"botToken": "xoxb-new",
"appToken": "xapp-new",
"signingSecret": "new-signing-secret",
"webhookPath": "/slack/events",
}),
);
assert!(env.contains(&(
"SLACK_SIGNING_SECRET".to_string(),
"new-signing-secret".to_string()
)));
}
#[test]

View File

@@ -358,6 +358,38 @@ test('Hermes 渠道保存会从 YAML 清理旧凭证,避免覆盖 .env 运行
assert.equal(next.platforms.slack.extra.signing_secret, undefined)
assert.equal(next.platforms.slack.extra.webhook_path, '/slack/events')
assert.equal(next.platforms.slack.extra.unknown_option, 'keep-me')
const env = buildHermesChannelEnvUpdates('slack', {
enabled: true,
botToken: 'xoxb-new',
appToken: 'xapp-new',
signingSecret: 'new-signing-secret',
webhookPath: '/slack/events',
})
assert.equal(env.SLACK_BOT_TOKEN, 'xoxb-new')
assert.equal(env.SLACK_APP_TOKEN, 'xapp-new')
assert.equal(env.SLACK_SIGNING_SECRET, 'new-signing-secret')
})
test('Hermes Slack 读取会从 .env 回填 signing secret', () => {
const values = buildHermesChannelConfigValues({
platforms: {
slack: {
enabled: true,
extra: {
webhook_path: '/slack/events',
},
},
},
}, {
SLACK_SIGNING_SECRET: 'env-signing-secret',
SLACK_BOT_TOKEN: 'xoxb-env',
SLACK_APP_TOKEN: 'xapp-env',
})
assert.equal(values.slack.signingSecret, 'env-signing-secret')
assert.equal(values.slack.botToken, 'xoxb-env')
assert.equal(values.slack.appToken, 'xapp-env')
})
test('Hermes 钉钉保存会使用运行时实际读取的字段', () => {