fix: address Qodo code review findings
- Add missing snapshot-differ.test.ts to npm test script - Fix path traversal vulnerability in agent-mail.ts with message ID validation - Fix readLastTouchedVersion to log errors instead of silently swallowing them - Sanitize log statements to not leak full paths - Add projectRoot validation to all API routes - Fix activity persistence write race conditions with promise chaining Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
d1140c9809
commit
a3f2ceef52
8 changed files with 108 additions and 9 deletions
|
|
@ -98,6 +98,12 @@ function trimOrEmpty(value: unknown): string {
|
|||
return typeof value === 'string' ? value.trim() : '';
|
||||
}
|
||||
|
||||
function isValidMessageId(value: string): boolean {
|
||||
// Message IDs must be alphanumeric with underscores, hyphens, and colons
|
||||
// This prevents path traversal attacks
|
||||
return /^[a-zA-Z0-9_\-:]+$/.test(value);
|
||||
}
|
||||
|
||||
function success<T>(command: MailCommandName, data: T): MailCommandResponse<T> {
|
||||
return {
|
||||
ok: true,
|
||||
|
|
@ -330,6 +336,10 @@ export async function readAgentMessage(
|
|||
return invalid(command, 'MESSAGE_NOT_FOUND', 'Message id is required.');
|
||||
}
|
||||
|
||||
if (!isValidMessageId(messageId)) {
|
||||
return invalid(command, 'INVALID_MESSAGE_ID', 'Message id contains invalid characters.');
|
||||
}
|
||||
|
||||
try {
|
||||
const existing = await readMessageIndex(messageId);
|
||||
if (!existing) {
|
||||
|
|
@ -374,6 +384,10 @@ export async function ackAgentMessage(
|
|||
return invalid(command, 'MESSAGE_NOT_FOUND', 'Message id is required.');
|
||||
}
|
||||
|
||||
if (!isValidMessageId(messageId)) {
|
||||
return invalid(command, 'INVALID_MESSAGE_ID', 'Message id contains invalid characters.');
|
||||
}
|
||||
|
||||
try {
|
||||
const existing = await readMessageIndex(messageId);
|
||||
if (!existing) {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export class IssuesEventBus {
|
|||
private nextSubscriberId = 1;
|
||||
|
||||
emit(projectRoot: string, changedPath?: string, kind: IssuesChangeKind = 'changed'): IssuesChangedEvent {
|
||||
console.log(`[IssuesBus] Emitting event: ${kind} for ${projectRoot} (${changedPath})`);
|
||||
console.log(`[IssuesBus] Emitting event: ${kind} for project (${changedPath ? path.basename(changedPath) : 'unknown'})`);
|
||||
const canonicalProjectRoot = canonicalizeWindowsPath(projectRoot);
|
||||
const projectKey = windowsPathKey(canonicalProjectRoot);
|
||||
const event: IssuesChangedEvent = {
|
||||
|
|
@ -94,6 +94,7 @@ export class ActivityEventBus {
|
|||
private readonly history: ActivityEvent[] = [];
|
||||
private readonly MAX_HISTORY = 100;
|
||||
private initialized = false;
|
||||
private savePromise: Promise<void> | null = null;
|
||||
|
||||
private nextSubscriberId = 1;
|
||||
|
||||
|
|
@ -121,8 +122,22 @@ export class ActivityEventBus {
|
|||
this.history.pop();
|
||||
}
|
||||
|
||||
// Persist async
|
||||
void saveActivityHistory(this.history);
|
||||
// Persist async with deduplication - wait for any pending save to complete
|
||||
const currentHistory = [...this.history];
|
||||
const persist = async () => {
|
||||
try {
|
||||
await saveActivityHistory(currentHistory);
|
||||
} catch (error) {
|
||||
console.error('[ActivityEventBus] Failed to save history:', error);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.savePromise === null) {
|
||||
this.savePromise = persist();
|
||||
} else {
|
||||
// Chain to existing promise to prevent concurrent writes
|
||||
this.savePromise = this.savePromise.then(persist);
|
||||
}
|
||||
|
||||
for (const subscriber of this.subscribers.values()) {
|
||||
if (!subscriber.projectKey || subscriber.projectKey === projectKey) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue