fix(channels): address PR review — security, bugs, and reliability

- fix: sanitize remote filenames with basename() and isolate uploads
  in UUID subdirs to prevent path traversal and collision (#2-4, #27)
- fix: use crypto.randomInt() for pairing codes instead of Math.random() (#5)
- fix: pass config.sessionScope instead of hardcoded 'user' (#6);
  add per-channel scope overrides via setChannelScope() for startAll (#7)
- fix: removeSession now returns removed session IDs and persists
  when chatId is provided (#8)
- fix: /clear only removes the cleared session from instructedSessions,
  not all sessions (#9)
- fix: DingTalk @mention stripping now removes only the first mention
  instead of all mentions (#10)
- fix: remove dead TELEGRAF_COMMANDS Set and its guard (#13)
- fix: WeChat cursor saved after message processing, not before (#14)
- fix: crash recovery uses time-window counting instead of resettable
  counter to prevent infinite restart loops (#17)
- fix: call channel.disconnect() before exit on crash exhaustion (#18)
This commit is contained in:
tanzhenxin 2026-03-31 00:57:59 +00:00
parent 2ca45b72f5
commit 7bbd5e6471
9 changed files with 152 additions and 74 deletions

View file

@ -1,5 +1,6 @@
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { mkdirSync, writeFileSync } from 'node:fs';
import { randomUUID } from 'node:crypto';
import { basename, join } from 'node:path';
import { tmpdir } from 'node:os';
import { DWClient, TOPIC_ROBOT, EventAck } from 'dingtalk-stream-sdk-nodejs';
import type { DWClientDownStream } from 'dingtalk-stream-sdk-nodejs';
@ -455,9 +456,10 @@ export class DingtalkChannel extends ChannelBase {
];
} else {
// Save non-image files to temp dir so the agent can read them
const dir = join(tmpdir(), 'channel-files');
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
const safeName = fileName || `dingtalk_${mediaType}_${Date.now()}`;
const dir = join(tmpdir(), 'channel-files', randomUUID());
mkdirSync(dir, { recursive: true });
const safeName =
basename(fileName || '') || `dingtalk_${mediaType}_${Date.now()}`;
const filePath = join(dir, safeName);
writeFileSync(filePath, media.buffer);
@ -520,9 +522,9 @@ export class DingtalkChannel extends ChannelBase {
const content = this.extractContent(data);
let cleanText = content.text;
// Strip @bot mention from text
// Strip first @mention (the bot) from text, keep other @mentions intact
if (isMentioned) {
cleanText = cleanText.replace(/@\S+/g, '').trim();
cleanText = cleanText.replace(/@\S+/, '').trim();
}
// Extract quoted message context