T02 · Agent Memory Poisoning
Error
- Location
- scripts/ber.js:645
- Finding
- Rejected, Quarantined, Superseded, or Unreviewed Lessons Can Be Promoted into Durable Agent Instructions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/ber.js:645-714` **Vulnerability Type**: Missing lifecycle-state authorization for durable instruction promotion **Risk Level**: High ### Vulnerable Code ```js function cmdCard(opts) { const id = opts._[0]; const targetType = opts.to; if (!PROMOTION_TARGETS.has(targetType)) { throw new Error(`--to must be one of: ${Array.from(PROMOTION_TARGETS).join(", ")}`); } const { targetPath, rel } = targetPathFor(opts.target); validatePromotionTarget(targetType, rel); const lessons = readJsonl(LESSONS_FILE); const lesson = findLesson(lessons, id); const rendered = promotionBlock(lesson, targetType, opts.note || ""); const scan = scanPromotion(lesson, targetType, rel, rendered); const targetHash = hashFile(targetPath); const plan = { targetType, target: rel, targetHash, scan, cardPath: path.relative(process.cwd(), cardPath(lesson.id)), createdAt: new Date().toISOString(), }; lesson.promotionPlan = plan; fs.writeFileSync(cardPath(lesson.id), lessonCardMarkdown(lesson, targetType, rel, targetHash, scan, opts.note || ""), "utf8"); writeJsonl(LESSONS_FILE, lessons); console.log(`# Lesson card written\n\n- ID: ${lesson.id}\n- Card: ${plan.cardPath}\n- To: ${targetType}\n- Target: ${rel}\n- Target SHA-256: ${targetHash}\n${scanLines(scan)}`); } function cmdPromote(opts) { const id = opts._[0]; const targetType = opts.to; if (!PROMOTION_TARGETS.has(targetType)) { throw new Error(`--to must be one of: ${Array.from(PROMOTION_TARGETS).join(", ")}`); } const { targetPath, rel } = targetPathFor(opts.target); validatePromotionTarget(targetType, rel); const lessons = readJsonl(LESSONS_FILE); const lesson = findLesson(lessons, id); const rendered = promotionBlock(lesson, targetType, opts.note || ""); const scan = scanPromotion(lesson, targetType, rel, rendered); if (scan.hard.length) { throw new Error(`Promotion blocked by BER scanner ...[truncated 4030 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Require an explicitly accepted and unexpired lesson before card creation and promotion: ```js function assertPromotableLesson(lesson) { if (lesson.status !== "accepted") { throw new Error(`Only accepted lessons may be promoted; current status: ${lesson.status}`); } if (isExpired(lesson)) { throw new Error("Expired lessons cannot be promoted."); } if (lesson.supersededBy) { throw new Error("Superseded lessons cannot be promoted."); } } ``` 2. Call this validation from both `cmdCard()` and `cmdPromote()`. 3. Store a separate approval record rather than treating card creation as implicit authorization. Bind the approval to: - Lesson ID - Hash of the complete lesson content - Target type - Canonical target path - Target file hash - Reviewer identity or trusted approval source - Approval timestamp 4. Revalidate the lesson hash, status, expiry, and approval immediately before writing the target. 5. Invalidate existing promotion plans whenever a lesson is rejected, quarantined, superseded, edited, or expires. 6. Add regression tests proving that proposed, rejected, quarantined, superseded, and expired lessons cannot be carded or promoted. ]]>
