Skip to content

feat: Zeek Intel export format + public campaigns API endpoint - #15

Merged
SkxOverKill merged 2 commits into
mainfrom
feat/zeek-export-and-campaigns-api
Aug 12, 2026
Merged

feat: Zeek Intel export format + public campaigns API endpoint#15
SkxOverKill merged 2 commits into
mainfrom
feat/zeek-export-and-campaigns-api

Conversation

@SkxOverKill

Copy link
Copy Markdown
Owner

Two self-contained, tested additions to the CTI surface. Both verified against the repo's gates: tsc --noEmit clean, eslint clean (no new warnings), full vitest suite 188 passed.

1. Zeek Intel framework export (feat(export))

Adds zeek as a sixth indicator export format — the defensive counterpart to the existing Snort/Suricata exporter, loadable directly by Zeek's Intel::read_files.

  • toZeekIntel() emits a #fields header + tab-separated rows. zeekIntelType() maps types to Intel::Type (IP→ADDR, domain→DOMAIN, URL→URL, email→EMAIL, MD5/SHA1/SHA256→FILE_HASH, filename→FILE_NAME); unmatchable types (CVE, ASN, registry key, mutex, BTC, user-agent) are omitted, same rule Snort follows.
  • URLs have their scheme stripped (Zeek matches host+URI); domains/hashes use the normalized form.
  • No trailing # comment for skipped items — Zeek's ASCII reader parses # lines after the header as data, so a comment would become a bogus indicator. Body stays strictly header+rows so the file loads.
  • Values containing a tab/newline (malformed feed) are dropped rather than shift columns.
  • Wired end-to-end for free: the export route validates against EXPORT_FORMATS and the menu renders from it.
  • 8 new tests.

2. Public GET /api/v1/campaigns (feat(api))

The programmatic equivalent of /campaigns, mirroring the /api/v1/actors route (bearer-key auth, per-key rate limiting, pagination).

  • New campaigns:read scope — auto-appears in the Settings key-creation UI, which renders from the same API_SCOPES array.
  • Supports page, pageSize (default 50, max 200), and a status filter validated by the existing parseCampaignStatus helper.
  • Each row carries actorCount / techniqueCount / indicatorCount via one Prisma _count, so consumers gauge scope without a second call.

Docs kept in sync

README endpoint list, docs/API.md (format table, scope table, new Campaigns section), and docs/openapi.yaml (zeek in the format enum, new Campaign schema + /api/v1/campaigns path) — OpenAPI document parses.

🤖 Generated with Claude Code

SkxOverKill and others added 2 commits August 12, 2026 20:14
Adds `zeek` as a sixth indicator export format alongside CSV, STIX 2.1,
MISP JSON, and Snort/Suricata — the defensive counterpart to the Snort
exporter, loadable directly by Zeek's `Intel::read_files`.

Details
-------
* `toZeekIntel()` emits a `#fields` header followed by tab-separated rows.
  `zeekIntelType()` maps indicator types to `Intel::Type`: IPs → ADDR,
  domains → DOMAIN, URLs → URL, emails → EMAIL, MD5/SHA1/SHA256 all fold
  into FILE_HASH, filenames → FILE_NAME. Types Zeek cannot match on (CVE,
  ASN, registry key, mutex, BTC address, user agent) are omitted, the same
  rule the Snort exporter follows.
* URL indicators have their scheme stripped (Zeek matches host+URI);
  domains and hashes use the normalized (lowercased, refanged) form.
* Unlike the Snort exporter, skipped indicators are NOT annotated in a
  trailing comment: Zeek's ASCII input reader parses `#` lines after the
  header as data, so a comment there would become a bogus indicator.
  Keeping the body strictly header+rows is what makes the file loadable.
* Any indicator whose value contains a tab or newline (only possible from
  a malformed feed) is dropped rather than shift the tab-delimited columns.
* Wired end to end automatically: the export route validates against
  `EXPORT_FORMATS` and the indicators export menu renders from it, so the
  new format needs no route or UI change.

Docs kept in sync: README endpoint list, docs/API.md format table, and the
OpenAPI `format` enum all gain `zeek`.

Tests: 8 new cases in formats.test.ts (type mapping incl. hash folding,
header + row shape, URL scheme stripping, non-matchable omission with no
stray `#` line, tab-injection drop, meta columns, default source) plus the
dispatch test. Full suite: 26 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the campaigns collection to the read-only public API, the
programmatic equivalent of the /campaigns UI. Mirrors the existing
/api/v1/actors route: bearer-key auth, per-key rate limiting, and cursor
pagination.

Details
-------
* New `campaigns:read` scope in `API_SCOPES`. Because the Settings UI
  renders its checkbox list from that same array, the scope appears for
  key creation automatically — one list, never two to keep in sync.
* `GET /api/v1/campaigns` supports `page`, `pageSize` (default 50, max
  200), and a `status` filter validated by the existing
  `parseCampaignStatus` helper (SUSPECTED / ACTIVE / DORMANT / CONCLUDED).
* Each row carries `actorCount`, `techniqueCount`, and `indicatorCount`
  (via a single Prisma `_count`) so a consumer can gauge a campaign's
  scope without a follow-up request. Ordered by start date, nulls last.

Docs kept in sync: README endpoint list, docs/API.md scope table + a new
Campaigns section, and the OpenAPI spec (new `Campaign` schema,
`CampaignPageSize` parameter, and `/api/v1/campaigns` path).

Verified: tsc --noEmit clean, eslint clean (no new warnings), full vitest
suite 188 passed, and the OpenAPI document parses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@SkxOverKill
SkxOverKill merged commit 577242a into main Aug 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant