Commit graph

647 commits

Author SHA1 Message Date
eaee6292dc docs(17-01): record self-check result in SUMMARY.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 14:31:22 -04:00
2bb1817e8d docs(17-01): complete Mimecast blast-radius lookup plan
Mark BLAST-01/BLAST-02 complete in REQUIREMENTS.md and add the plan's
SUMMARY.md documenting getBlastRadius() delivery.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 14:31:10 -04:00
efbc437e2e feat(17-01): build mimecast-blast-radius.ts fan-out orchestration
- Add getBlastRadius(): never-throwing orchestration that fans out to
  searchDeliveredMessages + getHeldMessages + getThreatEvents (D-01,
  unconditional fan-out) and merges into normalized matched/delivered/
  held/rejected/clicked counts + perRecipient status array
- Config gate (BLAST-02): returns status:'unavailable' reason:'not_configured'
  synchronously when Mimecast is unconfigured, never constructs the client
- Redis-backed 5-min cache (D-04) via redis-client.ts, short-circuits before
  any MimecastClient call on hit
- clicked derived best-effort from getThreatEvents() analysis[] (D-02);
  documents the /api/ttp/url/get-logs limitation in code
- Documents D-05 known limitation: single global getMimecastClient() only,
  not per-company mimecast_tenants
- Unrecognized delivered-message status strings treated conservatively as
  non-rejected (A3 unconfirmed enum), raw values logged at debug level
- Add lib/services/mimecast-blast-radius.test.ts covering config gate,
  cache-hit short-circuit, fan-out merge, never-throw-on-error, and
  unknown-recipient classification
- Log pre-existing unrelated itglue-search.test.ts failures to
  deferred-items.md (out of scope for this plan)
2026-07-15 14:30:00 -04:00
8b032c3890 feat(17-01): add isMimecastConfigured() config gate + test seam
- Add isMimecastConfigured() to lib/services/mimecast-client.ts mirroring
  the pax8-factory.ts is<Name>Configured() convention
- Add _resetMimecastClient() test seam so tests can isolate env-var state
- Add lib/services/mimecast-client.test.ts covering config gate + throw/cache
  behavior of getMimecastClient()
2026-07-15 14:27:47 -04:00
683d90edd0 docs(state): record phase 17 planning session 2026-07-15 13:48:04 -04:00
3424812d60 docs(17): fix plan-checker warnings (merge formula clarity + resolved open questions) 2026-07-15 13:47:48 -04:00
7303b16cbb docs(17): create phase plan (1 plan, 1 wave) 2026-07-15 13:44:14 -04:00
78ff39fa48 docs(17): add pattern map 2026-07-15 13:39:21 -04:00
c702cb9b03 docs(phase-17): add validation strategy 2026-07-15 13:35:57 -04:00
9dbc1e7510 docs(17): add research findings + tenant-scope decision 2026-07-15 13:35:21 -04:00
cacf41a493 docs(17): research phase domain 2026-07-15 13:28:58 -04:00
e30d9af46d docs(state): record phase 17 context session 2026-07-15 13:13:37 -04:00
09ff5b9bb8 docs(17): capture phase context 2026-07-15 13:13:31 -04:00
50bb8e2ac9 docs(phase-16): mark phase complete 2026-07-15 10:45:47 -04:00
40268e709f docs(phase-16): update tracking after wave 2 2026-07-15 10:42:00 -04:00
cad1a63e3d chore: merge executor worktree (worktree-agent-adbfd43ed3e884d1d) 2026-07-15 10:41:39 -04:00
135d448127 docs(16-03): complete EML/MIME evidence orchestration service plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 10:41:15 -04:00
ce16e6747c test(16-03): phishing-eml-service.test.ts orchestration coverage (mocked I/O)
- mocks postgresClient, getAutotaskClient, and B2 client (isB2Configured/presignUpload); no real Postgres/Autotask/B2 credentials or network
- happy path: one messages insert with D-06 auth verdicts in headers, indicators inserted
- no-eml no-op: NO_EML_ATTACHMENTS -> stored:false, no messages insert
- B2 unconfigured: raw_ref null, no presignUpload/PUT
- B2 configured: raw_ref = phishing/{reportId}/{attachmentId}.eml, exactly one PUT
- no-network invariant: fetch never called with a message-body URL from the fixture
- attachment_hash indicator metadata carries filename/contentType/size/related
- all fixtures reused from eml-parser.fixtures.ts (synthetic only)
2026-07-15 10:39:59 -04:00
5088d8d511 feat(16-03): phishing-eml-service.ts orchestration (list->select->fetch->B2->parse->persist)
- parseAndStoreMessage lists ticket attachments, selects the original message via selectOriginalMessage
- fetches full content via getAttachmentContent, size-guards the decoded buffer against MAX_EML_BYTES
- uploads raw bytes to B2 under phishing/{reportId}/{attachmentId}.eml when isB2Configured(), gracefully degrading rawRef=null on failure/absence
- parses via parseEml and persists one messages row (headers incl. D-06 auth verdicts, urls, attachments, body_preview, raw_ref) plus indicators rows (attachment_hash/url/sender) carrying D-07 metadata JSONB
- never fetches any URL extracted from the message; the only outbound calls are the Autotask attachment GET and the B2 presigned PUT
2026-07-15 10:37:35 -04:00
36ef9e7ce3 docs(phase-16): update tracking after wave 1 2026-07-15 10:33:49 -04:00
f3ace33f86 chore: merge executor worktree (worktree-agent-a2b7d8cafac0f4270) 2026-07-15 10:32:42 -04:00
49dd22bbd6 chore: merge executor worktree (worktree-agent-a692342f48398a2c2) 2026-07-15 10:32:38 -04:00
d7c868a2d7 docs(16-01): complete EML/MIME evidence parser core plan
Summary of Plan 16-01: mailparser + linkify-it dependency install,
selectOriginalMessage (EVID-02 three-tier selection), and parseEml +
parseAuthResults + extractUrls + buildBodyPreview (EVID-03/EVID-04),
with a DoS size guard ahead of simpleParser. 26/26 tests pass, tsc
clean. Logs 2 pre-existing unrelated itglue-search.test.ts failures to
deferred-items.md (out of scope, not fixed).
2026-07-15 10:31:45 -04:00
654e624505 feat(16-01): implement parseEml, parseAuthResults, extractUrls, buildBodyPreview
GREEN: parseEml normalizes headers (From/Reply-To/Return-Path/To/Cc/
Subject/Date/Message-ID), builds the ordered Received chain from
mail.headerLines, and maps mailparser attachments to AttachmentMeta
(name/content-type/size/sha256 checksum, related flag preserved for
inline/CID parts per Pitfall 5).

parseAuthResults hand-rolls RFC 8601 Authentication-Results parsing
(spf/dkim/dmarc verdicts) rather than using mailauth, which performs
live DNS/HTTP verification (SC#3 violation). Both Authentication-Results
and Authentication-Results-Original are read via mail.headerLines
(Pitfall 4 — headers Map only exposes one occurrence of a repeated
header) and parsed into distinct authResults/authResultsOriginal fields.

extractUrls uses linkify-it with fuzzyLink enabled (scheme-less www.
URLs) scanning both text and html parts, deduped, never dereferenced.

buildBodyPreview prefers mail.text, falling back to a small hand-rolled
HTML-to-text stripper (not the undeclared transitive html-to-text
dependency — see SUMMARY deviations) when only HTML exists; truncated
to 500 chars.

MAX_EML_BYTES (10 MB, below B2's 25 MB cap) is enforced before
simpleParser is ever called (T-16-01 DoS guard).

26/26 tests pass; tsc clean for eml-parser files; full npm test run
confirms 2 pre-existing itglue-search.test.ts failures are unrelated
(logged to deferred-items.md).
2026-07-15 10:30:18 -04:00
4df4816b21 test(16-01): add failing tests for parseEml/parseAuthResults/extractUrls/buildBodyPreview
RED: covers EVID-03 (normalized headers, structured auth verdicts,
Received chain, URLs, attachment metadata incl. related flag),
EVID-04 (no-network spy, truncated body preview), and the DoS size
guard (oversized buffer rejected before simpleParser). New synthetic
fixtures: rich multipart, auth-results-original, inline/CID attachment,
long-body, fuzzy-URL, and an oversized-buffer generator. None of these
exports exist on eml-parser.ts yet.
2026-07-15 10:28:22 -04:00
094d34c2c8 docs(16-02): append self-check result to summary 2026-07-15 10:25:00 -04:00
3f55e145e3 docs(16-02): complete Autotask attachment content / B2 EML regex / indicators metadata plan
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 10:24:47 -04:00
0bf37cef0c feat(16-02): add migration 099 — indicators.metadata JSONB (D-07)
- Nullable JSONB column, no backfill needed (no rows exist yet)
- Applied to the running dev DB (docker exec against pulse-postgres);
  Postgres init only runs migrations on first volume boot
2026-07-15 10:23:55 -04:00
8630fd5151 feat(16-02): add EML_OBJECT_KEY_REGEX + parameterize B2 key validation (D-05)
- New EML_OBJECT_KEY_REGEX enforces phishing/<id>/<id>.eml, rejects traversal
- presignDownload/presignUpload/downloadToBuffer take optional keyRegex,
  defaulting to OBJECT_KEY_REGEX so existing LogLift call sites are unchanged
- OBJECT_KEY_REGEX itself left untouched (skill-doc rule)
2026-07-15 10:23:19 -04:00
e4718ae71a feat(16-01): implement selectOriginalMessage three-tier attachment selection
GREEN: rfc.eml exact match -> single non-OriginatingEmail message/rfc822
candidate (covers KnowBe4 versioned filenames) -> OriginatingEmail.eml
fallback -> null. Case-insensitive on both content-type and filename
(checked via title/fullPath basename). All 8 selectOriginalMessage tests
pass; tsc clean for eml-parser files.
2026-07-15 10:23:00 -04:00
6de92a507b test(16-02): add failing tests for EML_OBJECT_KEY_REGEX + parameterized B2 key validation
- EML_OBJECT_KEY_REGEX must match phishing/<id>/<id>.eml and reject traversal/wrong-ext/LogLift shapes
- presignUpload must accept an optional keyRegex arg, defaulting to OBJECT_KEY_REGEX
2026-07-15 10:22:46 -04:00
9b65de72dc feat(16-02): add AutotaskClient.getAttachmentContent()
- Fetches Tickets/{id}/Attachments/{attachmentId}, reads response.items?.[0]
- Confirmed live: per-attachment-ID GET is list-shaped, not {item:...}
2026-07-15 10:22:03 -04:00
8be10db6e0 test(16-02): add failing test for AutotaskClient.getAttachmentContent
- Asserts items[0] convention for per-attachment-ID GET
- Asserts {item:...}-shaped response yields null (guards against regression)
2026-07-15 10:22:01 -04:00
2fde1156da test(16-01): add failing tests for selectOriginalMessage (EVID-02)
RED: covers all three selection tiers plus ambiguous/no-eml/empty edge
cases. lib/services/eml-parser.ts does not exist yet — tests fail to
resolve the module import.
2026-07-15 10:21:58 -04:00
0f4dc1fc20 chore(16-01): install mailparser + linkify-it for EML parsing
- mailparser 3.9.14 (RFC822/MIME parsing, sha256 attachment checksums)
- linkify-it 6.0.0 (URL extraction for phishing evidence)
- @types/mailparser devDependency (mailparser ships no bundled types)
- Verified: only these deps added (git diff package.json clean check)
2026-07-15 10:21:16 -04:00
32fcf3b9f6 docs(16): create phase plan 2026-07-15 10:17:13 -04:00
d63b9adadf fix(16): revise plans based on checker feedback 2026-07-15 10:13:30 -04:00
00b8afc546 docs(16): create phase plan (3 plans, 2 waves) 2026-07-15 10:04:31 -04:00
94b53c11d0 docs(16): add pattern map 2026-07-15 09:55:39 -04:00
91bac62dd8 docs(phase-16): add validation strategy 2026-07-15 09:52:28 -04:00
dc273c002d docs(16): research phase domain 2026-07-15 09:50:36 -04:00
d3e24d968b docs(state): record phase 16 context session 2026-07-15 09:26:22 -04:00
4857a35629 docs(16): capture phase context 2026-07-15 09:26:15 -04:00
d7b8c6b72d docs(15): add pattern map 2026-07-15 08:23:34 -04:00
c33f52b435 docs(phase-15): evolve PROJECT.md after phase completion 2026-07-15 08:23:10 -04:00
664dfcb763 docs(phase-15): complete phase execution 2026-07-15 08:22:05 -04:00
c33b6615c9 test(15): persist verification report 2026-07-15 08:21:58 -04:00
502e0b95a7 docs(15): re-review after code-review fixes 2026-07-15 08:17:36 -04:00
9c4584d428 fix(15): exclude soft-deleted notes/time entries from phishing evidence
ticket_notes and time_entries both carry an is_deleted soft-delete flag
(per CLAUDE.md audit-column convention); gatherTicketEvidence was reading
both without filtering it, so retracted notes and reversed time entries
showed up as evidence for every phishing report. Found during code-review
re-verification of the Phase 15 CR-01/WR-01/WR-02 fixes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wWroM6FXkQJiH3JgYcony
2026-07-15 08:17:07 -04:00
cb9d5af784 docs(15): add code review fix report 2026-07-15 08:09:49 -04:00