fix: address security audit findings (ReDoS, prompt injection, supply chain, error leakage)#54
Conversation
|
Thanks for taking the time to put this together. It seems like a few of these point at real surfaces. I went through each finding against the code, and I think it's worth discussing before merging, because at least one of the fixes looks like it might change documented behavior and a couple seem to rest on premises that may not hold for this repo. M1 - ReDoS (
If we did want to bound backtracking while keeping the documented regex feature, I think capping input length and/or wrapping the match in a timeout (or something like M2 - Prompt injection ( L1 - Dependency pinning L2 - Error message sanitization Net: I think M2 could potentially stand on its own as incremental hardening, M1 seems like it may need a different approach since the current fix looks like it regresses documented behavior, and L1/L2 I'd probably hold unless we decide we want them as explicit project policy. Would it be possible to split these so the less contentious parts could be considered independently? Appreciate the contribution. |
M1 - ReDoS (searchProjects): Replace literal-escape approach with the re2 library, which provides a drop-in safe regex engine guaranteed to run in linear time. This preserves the documented regex semantics of name_pattern (callers can still pass patterns like "^Vi" or "proj.*2026") while eliminating catastrophic backtracking. Adds re2 as a production dependency and marks it external in the esbuild config. M2 - Prompt injection (task-summary): Wrap untrusted Asana content in BEGIN/END ASANA DATA delimiters to create a trust boundary, and add a sanitizeDataContent() helper that strips any occurrence of those delimiter sequences from task names, notes, custom fields, and story texts before interpolation. This closes the bypass where crafted task content could emit its own END marker to escape the data block. L1/L2 reverted: Accepted maintainer feedback — package-lock.json already provides equivalent pinning coverage, and error messages are useful for model self-correction given the single-trust-boundary context.
a1bbb4a to
462e1a5
Compare
|
Thanks for the thorough review — these are all fair points, and I appreciate you taking the time to work through each finding rather than just bouncing the PR. M1 (ReDoS): You're right that the literal-escape approach was a functional regression. I missed that M2 (Prompt injection): Good catch on the bypass — you're absolutely right that an injected L1 (Dependency pinning): Makes sense — L2 (Error sanitization): Accepted your trust-boundary argument. The error message goes to the model that already holds the token, and the detail genuinely helps with self-correction. Reverted. The updated commit keeps only M1 (re2) and M2 (hardened delimiters). Let me know if you'd like M2 split into a separate PR or if there's anything else you'd like adjusted. |
Summary
Security audit of the codebase identified four issues. This PR fixes all of them.
M1 — ReDoS (
src/asana-client-wrapper.ts:60):namePatternwas passed directly to theRegExpconstructor without escaping. A crafted pattern (e.g.(a+)+$) could cause catastrophic backtracking and peg the Node.js event loop. Fixed by escaping all regex metacharacters before constructing the pattern.M2 — Indirect prompt injection (
src/prompt-handler.ts): Task name, notes, custom field values, and story text were interpolated directly into the LLM message content for thetask-summaryprompt, with no trust-boundary markers. An actor with write access to any Asana task in the workspace could inject arbitrary instructions into the consuming model's context. Fixed by wrapping all inlined Asana data in explicit--- BEGIN/END ASANA DATA ---delimiters with an instruction to treat the content as data.L1 — Floating production dependency versions (
package.json,.npmrc): All three production dependencies used^caret ranges, meaning a supply-chain-compromised minor/patch release would be adopted automatically onnpm install. Fixed by pinning to exact versions (matching the currentpackage-lock.json) and adding.npmrcwithsave-exact=trueso future dependency additions are pinned by default.L2 — Raw API error details in LLM context (
src/tool-handler.ts): The catch-all error handler returnederror.messageverbatim to the calling model. Asana API errors frequently include workspace GIDs and field names in their messages. Fixed by returning only the HTTP status code for Asana API errors; the full error is still written to stderr for debugging.Test plan
npm run buildsucceeds with no TypeScript errorsasana_search_projectswith a regex metacharacter pattern (e.g.name_pattern: "(a+)+$") returns an empty list rather than hangingtask-summaryprompt output contains--- BEGIN ASANA DATA ---/--- END ASANA DATA ---delimiters around task content"Asana API error (status 404): request could not be completed"rather than the raw Asana error body🤖 Generated with Claude Code