feat(copilot): comprehensive copilot improvements#2285
feat(copilot): comprehensive copilot improvements#2285thesocialdev wants to merge 8 commits intostagefrom
Conversation
…ditor reports Phase 1 - Layout & UI polish: - Widen copilot panel from 350px to 50vw with 350px min-width - Fix CSS bugs (margin-left, word-break, z-index casing, debug border) - Add toolbar title with i18n, reduce input height, fix flex layout Phase 2 - Session persistence: - Add CopilotSession MongoDB schema and CRUD service - Add session endpoints (GET, POST, POST clear) to controller - Refactor CopilotChatService to load/persist messages from sessions - Fix concurrency bug: use local editorReportRef instead of instance variable - Persist editorReport in session so it survives page refresh - Refactor frontend API, types, and CopilotDrawer for session-based flow - Pass dataHash from ClaimReviewEditor to CopilotDrawer Also includes prior session fixes: - NDJSON streaming parser for Agencia responses - GPT-5-mini model upgrade and temperature adjustment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expand the context stored in copilot sessions with richer claim data: - All personality names (not just the first) - Content model type (Speech, Image, Debate, Unattributed) - Related topics from the claim's content (pulled from Redux store) - Claim title Restructure the system prompt with clear markdown sections so the LLM has full awareness of the claim being fact-checked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Process sources returned by Agencia (url, title, type) and persist them as Source documents in MongoDB via SourceService, with md5 deduplication. Normalizes Agencia's `url` field to `href` for compatibility with the Source schema and editor-parser. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Send session_id to Agencia on POST /invoke so executions are tracked. Parse the new "started" NDJSON line to extract execution_id and store it in the CopilotSessionMessage alongside the assistant response. Add GET endpoints to query Agencia executions by session and by specific execution ID for future frontend use. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rom user-controlled sources Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…rgery Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
CopilotSourceService now parses both source formats returned by Agencia: - Objects with url/title/type (web search) — existing behavior - Plain strings like "Porto Alegre (2024-06-11): https://..." (gazette search) — new Sources are appended as a numbered "Fontes:" reference list at the end of the report text, since Agencia doesn't provide the field/textRange metadata needed for inline editor citations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The GitHub code scanning autofix (32f0583) placed validation statements outside the method body, causing TypeScript compilation errors. Moved validateSessionId/validateExecutionId calls inside the try block. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| async getSessionById( | ||
| sessionId: string | ||
| ): Promise<CopilotSessionDocument | null> { | ||
| return this.copilotSessionModel.findById(sessionId).exec(); |
Check failure
Code scanning / CodeQL
Database query built from user-controlled sources High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 days ago
General approach: Ensure that user-controlled values used in MongoDB queries are validated and/or explicitly treated as literal identifiers, not arbitrary query objects. For IDs, we can (a) validate that the value is a string representing a valid ObjectId and cast it with Mongoose’s Types.ObjectId, or (b) at least enforce that it is a primitive string and reject any non-string/object-like values. This prevents NoSQL-style operator injection and also avoids unexpected behavior if malformed IDs are supplied.
Best fix here: strengthen getSessionById (the sink CodeQL flags) and also the other methods that take sessionId and use it in findByIdAndUpdate. We can:
- Import
Typesfrommongoose. - Add a small helper method
private isValidObjectId(id: any): booleanthat usesTypes.ObjectId.isValid. - In
getSessionById,addMessage, anddeactivateSession, check thatsessionIdis a string and a valid ObjectId before hitting the database. If invalid, log a warning and either returnnull(forgetSessionById) or throw an error / returnnullfor the update methods. - For the actual query, pass the validated string (or cast ObjectId) into
findById/findByIdAndUpdate, which is safe once we’ve ensured it’s a primitive in the right format.
This approach keeps the external API the same (still takes a string ID), keeps behavior for valid IDs unchanged, and adds clear, minimal validation to stop tainted, malformed, or object-like input from becoming a query object.
Concretely, in server/copilot/copilot-session.service.ts:
-
Update the import from
mongoosetoimport { Model, Types } from "mongoose";. -
Add a private helper
isValidObjectIdto theCopilotSessionServiceclass. -
Update
getSessionById,addMessage, anddeactivateSessionto:- Check
typeof sessionId === "string"andthis.isValidObjectId(sessionId). - Log a warning and return
null(or throw) if invalid. - Optionally cast:
const objectId = new Types.ObjectId(sessionId);and use that infindById/findByIdAndUpdate.
- Check
No other files need code changes for this fix.
| ): Promise<CopilotSessionDocument> { | ||
| return this.copilotSessionModel | ||
| .findByIdAndUpdate( | ||
| sessionId, |
Check failure
Code scanning / CodeQL
Database query built from user-controlled sources High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 days ago
In general, to fix this kind of issue with MongoDB/Mongoose, ensure that any user-controlled input used in a query is either: (1) strictly validated/typed before use (e.g., must be a string matching an ObjectId pattern), or (2) passed through a safe comparison operator like $eq when used inside a query object so that it cannot be interpreted as a query object itself.
For this codebase, the narrowest fix without changing behavior is to validate sessionId in addMessage and deactivateSession similarly to how getActiveSession validates claimReviewDataHash. Specifically:
- Check that
sessionIdis a string. - Optionally, log a warning and return
nullif it is not valid. - Use
this.copilotSessionModel.findOneAndUpdate({ _id: { $eq: sessionId } }, ...)instead offindByIdAndUpdate(sessionId, ...). This ensures that even if an attacker managed to send a non-literal value, MongoDB will treat it as a literal via$eq, closing off NoSQL injection vectors.
Concretely:
- In
server/copilot/copilot-session.service.ts, modifyaddMessageto:- Validate
sessionId’s type. - Use
findOneAndUpdatewith a query{ _id: { $eq: sessionId } }.
- Validate
- Similarly modify
deactivateSessionto:- Validate
sessionId’s type. - Use
findOneAndUpdatewith$eq.
No new imports are required; we just reuse the existingLoggerandcopilotSessionModel. The rest of the application code callingaddMessage/deactivateSessiondoes not need to change, since the methods’ signatures and return types remain the same.
- Validate
| @@ -63,9 +63,17 @@ | ||
| sessionId: string, | ||
| message: CopilotSessionMessage | ||
| ): Promise<CopilotSessionDocument> { | ||
| // Ensure sessionId is treated as a literal value and not a query object | ||
| if (typeof sessionId !== "string") { | ||
| this.logger.warn( | ||
| `Invalid sessionId type in addMessage: ${typeof sessionId}` | ||
| ); | ||
| return null; | ||
| } | ||
|
|
||
| return this.copilotSessionModel | ||
| .findByIdAndUpdate( | ||
| sessionId, | ||
| .findOneAndUpdate( | ||
| { _id: { $eq: sessionId } }, | ||
| { $push: { messages: message } }, | ||
| { new: true } | ||
| ) | ||
| @@ -75,9 +82,17 @@ | ||
| async deactivateSession( | ||
| sessionId: string | ||
| ): Promise<CopilotSessionDocument> { | ||
| // Ensure sessionId is treated as a literal value and not a query object | ||
| if (typeof sessionId !== "string") { | ||
| this.logger.warn( | ||
| `Invalid sessionId type in deactivateSession: ${typeof sessionId}` | ||
| ); | ||
| return null; | ||
| } | ||
|
|
||
| return this.copilotSessionModel | ||
| .findByIdAndUpdate( | ||
| sessionId, | ||
| .findOneAndUpdate( | ||
| { _id: { $eq: sessionId } }, | ||
| { isActive: false }, | ||
| { new: true } | ||
| ) |
Summary
Test plan
yarn build-ts— passes🤖 Generated with Claude Code