Commit graph

636 commits

Author SHA1 Message Date
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
7c63c5f76d fix(15): WR-02 refresh evidence snapshot even when content_hash unchanged 2026-07-15 08:08:20 -04:00
c875081275 fix(15): WR-01 use shared getAutotaskClient factory in phishing-detector 2026-07-15 08:07:45 -04:00
ecc34b4bad fix(15): CR-01 fix webhook-triggered phishing detection reading from unpopulated payload.entity 2026-07-15 08:05:30 -04:00
e94482bd0b docs(15): add code review report 2026-07-15 07:58:33 -04:00
35339a62b8 docs(phase-15): update tracking after wave 3 2026-07-15 07:51:46 -04:00
86d6c63207 chore: merge executor worktree (worktree-agent-aec0d43efe5a155a3) 2026-07-15 07:50:30 -04:00
315a804fc3 docs(15-03): record self-check results in summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wWroM6FXkQJiH3JgYcony
2026-07-15 07:50:09 -04:00
63b4aabc5e docs(15-03): add plan execution summary
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012wWroM6FXkQJiH3JgYcony
2026-07-15 07:49:51 -04:00
b199d9991c feat(15-03): register phishing-sweep schedule + migration 098
- extend sync_type union with 'phishing-sweep'
- add defaultSchedules entry (disabled by default, daily 5am cron)
- dispatch branch dynamically imports and calls sweepPhishingTickets
- migrations/098_phishing_sweep_schedule.sql seeds the row for existing installs
2026-07-15 07:49:01 -04:00
194b58b196 feat(15-03): fire-and-forget phishing detection on ticket.created webhook
- triggerPhishingDetection() mirrors triggerWorkflowEngine's payload.entity-first shape
- reads createdByContactID (Autotask field) into created_by_contact_id, per entity-mapper.ts:211
- called alongside the existing workflow-engine trigger, not awaited in the request path
2026-07-15 07:48:17 -04:00
dbd2ebe63c feat(15-03): add bounded phishing sweep service
- sweepPhishingTickets() queries recently-modified tickets (7d window, LIMIT 500)
- delegates each ticket to shared detectPhishingTicket (no duplicated match/hash logic)
- per-row try/catch increments errors without aborting the loop
2026-07-15 07:47:08 -04:00