Andromeda Security provides identity and access management; its audit trail records user and system events (logins, access requests, configuration changes, etc.). Helr pulls these events via the Andromeda GraphQL API: POST https://api.live.andromedasecurity.com/graphql. The API uses the operation AndromedaEventsList and returns events in data.AndromedaEvents.edges[].node. Pagination is offset-based (pageArgs: { skip, pageSize }).
Helr supports this API via hooks for cookie auth (PAT → cookie) and backfill by advancing skip each poll. See hooks.
- An Andromeda tenant (e.g.
app.live.andromedasecurity.com). - Authentication: PAT (Personal Access Token) — set
ANDROMEDA_PATandallow_network: true; the hook's getAuth uses fetch() to exchange the PAT for a session cookie and returns{ cookie }. Or set ANDROMEDA_COOKIE (full Cookie header from the browser) for short-lived runs; buildRequest reads it from ctx.env.
- Endpoint:
https://api.live.andromedasecurity.com/graphql - Method: POST
- Request:
Content-Type: application/json; body is JSON withoperationName,variables(includingpageArgs: { skip, pageSize }), andquery. - Response:
data.AndromedaEvents.edges[]; eachedge.nodehas:id,type,name,time,actor,level,subtype,eventPrimaryKey,__typename.
Events are returned newest first. The hook uses getNextPage(ctx, request, response): request is the request that was sent (url, body), so the hook can advance pageArgs.skip and return the next body — multiple pages per poll until a page returns fewer than pageSize events. commitState saves skip (so the next poll continues from the right offset) and watermark. After backfill, reset skip to 0 (or clear state) and use dedupe for continuous collection of new events.
- Enable hooks in
helr.yaml:
global:
hooks:
enabled: true
path: "./hooks"
timeout_secs: 5
allow_network: true # required for PAT → cookie via fetch() in getAuth- Credentials (one of):
- PAT (recommended): Create a Personal Access Token in Andromeda and set
ANDROMEDA_PAT. Enableallow_network: trueinglobal.hooks; the hook's getAuth uses fetch() to exchange the PAT for a session cookie and returns{ cookie }(same flow as Andromeda's official Python script). See hooks. - Cookie: From the browser (e.g. DevTools → Application → Cookies for
app.live.andromedasecurity.com), copy the fullCookieheader (e.g.DS=...; DSR=...) and setANDROMEDA_COOKIE. buildRequest reads it from ctx.env. Session cookies expire; use for testing or short-lived runs.
- PAT (recommended): Create a Personal Access Token in Andromeda and set
In helr.yaml, add the source with method: post, no body (the hook builds it), and the hook script. Set ANDROMEDA_PAT (or ANDROMEDA_COOKIE for manual cookie). The hook's getAuth handles PAT → cookie. The API expects browser-like headers (User-Agent, Referer, Origin) to avoid WAF/blocking; set them under the source's headers. The hook reuses them for the login fetch via ctx.headers.
andromeda-audit:
url: "https://api.live.andromedasecurity.com/graphql"
method: post
headers:
User-Agent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:147.0) Gecko/20100101 Firefox/147.0"
Referer: "https://app.live.andromedasecurity.com/"
Origin: "https://app.live.andromedasecurity.com"
Accept: "*/*"
Accept-Language: "en-US,en;q=0.9"
schedule:
interval_secs: 300
jitter_secs: 30
hooks:
script: "andromeda-audit.js"
dedupe:
id_path: "id"
capacity: 100000
resilience:
timeout_secs: 30
retries:
max_attempts: 5
initial_backoff_secs: 2
max_backoff_secs: 60
multiplier: 2.0
circuit_breaker:
enabled: true
failure_threshold: 5
success_threshold: 2
half_open_timeout_secs: 60hooks.script: Uses./hooks/andromeda-audit.js(underglobal.hooks.path). The script builds the GraphQL body withpageArgs.skipfrom state and advancesskipincommitState.- Backfill: Each poll requests one page at
state.skip(starts at 0), then savesskip += events.length. Runhelr run(orhelr test --source andromeda-auditrepeatedly) to walk through pages. When a page returns fewer thanpageSizeevents, you've reached the end. - Continuous (after backfill): Reset state so
skipis 0 (e.g. delete or clear the source's state keyskip), then keep running; dedupe byidavoids re-emitting old events.
The script in hooks/andromeda-audit.js:
- getAuth: When
ANDROMEDA_PATis set (andallow_network: true), uses fetch() to POST to the login URL with{ code: ctx.env.ANDROMEDA_PAT }, reusing ctx.headers (source-configured) so the login request sends the same User-Agent, Referer, Origin, etc. ReadsSet-Cookiefrom the response and returns{ cookie }. When onlyANDROMEDA_COOKIEis set, returnsnull; buildRequest reads the cookie from ctx.env. - buildRequest: Sets
Content-Type, then Cookie (from ctx.env.ANDROMEDA_COOKIE when set). Builds the GraphQL body withpageArgs: { skip: state.skip || 0, pageSize: 50 }. - parseResponse: Reads
data.AndromedaEvents.edges, maps eachnodeto{ ts: node.time, source, event: node, meta: { id: node.id } }. - getNextPage(ctx, request, response): Uses
request.body.variables.pageArgs.skipfrom the request that was sent. If the response has a full page (edges.length === PAGE_SIZE), returns the next body withskip: currentSkip + PAGE_SIZEso Helr fetches the next page in the same poll. Returnsnullwhen no more pages. - commitState: Returns
{ skip: state.skip + events.length, watermark: lastTs }(events here are all events from the poll, so skip advances correctly for the next poll).
You can change PAGE_SIZE at the top of the script if you want more or fewer events per request.
Build with the hooks feature, set credentials, then validate and run:
cargo build --features hooks
export ANDROMEDA_PAT="your-personal-access-token" # or ANDROMEDA_COOKIE (from browser)
helr validate
helr test --source andromeda-audit
helr runFor backfill, run until you see fewer than 50 events in a poll (or run a fixed number of times). For continuous collection after that, reset skip to 0 and rely on dedupe.
If you only need continuous collection of the latest page (no backfill), you can use the declarative config: response_events_path: "data.AndromedaEvents.edges", response_event_object_path: "node", static body with skip: 0, and cookie or other auth as needed.