calendar-ics-normalizer-security.test.mjs
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠ breaking
1 day ago
| 1 | /** |
| 2 | * Tier 7 — SECURITY: injection fixtures, payload bounds, tier-0 agent isolation. |
| 3 | * Reference: docs/CALENDAR-EVENTS-V0-SPEC.md — Security checklist |
| 4 | */ |
| 5 | import { describe, it } from 'node:test'; |
| 6 | import assert from 'node:assert/strict'; |
| 7 | import fs from 'node:fs'; |
| 8 | import path from 'node:path'; |
| 9 | import { fileURLToPath } from 'node:url'; |
| 10 | import { parseIcsToEvents } from '../lib/calendar/ics-normalizer.mjs'; |
| 11 | import { buildSourceCalendarDefaults, isAgentTierAllowed } from '../lib/calendar/source-calendar-defaults.mjs'; |
| 12 | import { redactEventForAgentTier } from '../lib/calendar/agent-context-tier.mjs'; |
| 13 | |
| 14 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| 15 | const fixtureDir = path.join(__dirname, 'fixtures', 'calendar'); |
| 16 | |
| 17 | describe('Security — prompt injection in SUMMARY is stored verbatim but tier-redacted', () => { |
| 18 | it('preserves escaped injection text in normalized summary', () => { |
| 19 | const ics = fs.readFileSync(path.join(fixtureDir, 'injection-summary.ics'), 'utf8'); |
| 20 | const [event] = parseIcsToEvents(ics); |
| 21 | assert.match(event.summary ?? '', /Ignore prior instructions/); |
| 22 | assert.match(event.summary ?? '', /delete vault/); |
| 23 | }); |
| 24 | |
| 25 | it('tier 0 default calendar exposes no agent fields', () => { |
| 26 | const ics = fs.readFileSync(path.join(fixtureDir, 'injection-summary.ics'), 'utf8'); |
| 27 | const [event] = parseIcsToEvents(ics); |
| 28 | const cal = buildSourceCalendarDefaults(); |
| 29 | assert.equal(isAgentTierAllowed(cal, 1), false); |
| 30 | assert.equal(redactEventForAgentTier(event, 0), null); |
| 31 | const tier1 = redactEventForAgentTier(event, 1); |
| 32 | assert.ok(tier1); |
| 33 | assert.equal(tier1.summary, undefined); |
| 34 | }); |
| 35 | }); |
| 36 | |
| 37 | describe('Security — payload size limits', () => { |
| 38 | it('rejects ICS text above MAX_ICS_BYTES', () => { |
| 39 | const big = `BEGIN:VCALENDAR\nBEGIN:VEVENT\nUID:x\nDTSTART:20260101T000000Z\nDTEND:20260101T010000Z\nSUMMARY:${'A'.repeat(6 * 1024 * 1024)}\nEND:VEVENT\nEND:VCALENDAR`; |
| 40 | assert.throws(() => parseIcsToEvents(big), /exceeds/); |
| 41 | }); |
| 42 | |
| 43 | it('rejects non-string input', () => { |
| 44 | assert.throws(() => parseIcsToEvents(/** @type {*} */ (null)), /must be a string/); |
| 45 | }); |
| 46 | }); |
| 47 | |
| 48 | describe('Security — no OAuth or network surface in normalizer module', () => { |
| 49 | it('parseIcsToEvents accepts only in-memory string (no URL fetch API)', () => { |
| 50 | assert.deepEqual(parseIcsToEvents(''), []); |
| 51 | const events = parseIcsToEvents(`BEGIN:VCALENDAR |
| 52 | BEGIN:VEVENT |
| 53 | UID:local@only |
| 54 | DTSTART:20260101T000000Z |
| 55 | DTEND:20260101T010000Z |
| 56 | END:VEVENT |
| 57 | END:VCALENDAR`); |
| 58 | assert.equal(events.length, 1); |
| 59 | }); |
| 60 | }); |
| 61 | |
| 62 | describe('Security — display vs agent toggles are independent', () => { |
| 63 | it('display-on agents-off default allows UI but blocks tier > 0', () => { |
| 64 | const cal = buildSourceCalendarDefaults({ |
| 65 | enabled_for_display: true, |
| 66 | enabled_for_agents: false, |
| 67 | }); |
| 68 | assert.equal(cal.enabled_for_display, true); |
| 69 | assert.equal(isAgentTierAllowed(cal, 2), false); |
| 70 | }); |
| 71 | }); |
File History
1 commit
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd
feat(calendar): enforce agent context tiers in retrieval AP…
Human
minor
⚠
1 day ago