Integrations
CI
Driving Malspark from a pipeline with an API key.
A pipeline can submit a sample to a running Malspark deployment, wait for the analysis and act on what it publishes, through the REST API and an API key. This page is a recipe built from those endpoints; Malspark ships no CI plug-in of its own. The checks this repository runs on itself are described in Development.
Keep samples off shared runners
A CI job that submits malware carries the sample through the runner. Use a runner you control, on a network you are authorised to use for this, and keep the deployment off the public internet.
An API key for the pipeline
A key acts as the account that minted it, so mint it signed in as the account the pipeline should act as:
curl -s -X POST http://maljan.internal:8000/api/v1/audit/api-keys \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name": "ci", "expires_in_days": 90}'The answer's raw_key is shown once; store it as a CI secret. Keys are stored
only as a SHA-256 hash, can carry an expiry, and an unknown, revoked or expired
key is refused with the same 401. last_used_at is stamped on every accepted
call. See Security.
Submit, wait, act
Upload the sample.
SAMPLE=$(curl -s -X POST "$MALJAN/api/v1/samples/upload" \
-H "X-API-Key: $MALJAN_API_KEY" -F "file=@$SAMPLE_PATH" | jq -r .id)Start the job, naming the team if it is not the deployment's active one.
JOB=$(curl -s -X POST "$MALJAN/api/v1/jobs" \
-H "X-API-Key: $MALJAN_API_KEY" -H 'Content-Type: application/json' \
-d "{\"sample_id\":\"$SAMPLE\",\"config\":{\"profile\":\"default\"}}" | jq -r .id)A 422 here is a refusal to start a job that could only fail — a model no
probe has reached, a static provider that is not ready, an unknown team —
and its detail says which.
Wait for a terminal status — completed, failed or cancelled.
while :; do
STATUS=$(curl -s "$MALJAN/api/v1/jobs/$JOB" -H "X-API-Key: $MALJAN_API_KEY" | jq -r .status)
case "$STATUS" in completed|failed|cancelled) break ;; esac
sleep 30
doneRead the verdict and fetch what to act on.
REPORT=$(curl -s "$MALJAN/api/v1/reports/job/$JOB" -H "X-API-Key: $MALJAN_API_KEY")
echo "$REPORT" | jq '{verdict, verdict_reading, overall_confidence}'
REPORT_ID=$(echo "$REPORT" | jq -r .id)
curl -s "$MALJAN/api/v1/reports/$REPORT_ID/iocs" -H "X-API-Key: $MALJAN_API_KEY" > iocs.json
curl -s "$MALJAN/api/v1/reports/$REPORT_ID/stix" -H "X-API-Key: $MALJAN_API_KEY" > bundle.jsonWhat to gate on
verdictisMalware,SuspiciousorBenign. Gate on it together withverdict_reading:statedmeans the judge's structured answer stated it,fallbackthat it was read from an answer that was not a bundle, andunrecognisedandunstatedthat the judge's conclusion could not be read, so the inconclusiveSuspiciouswas published.- The IOC feed's default is only what the publish rule publishes, each row
with
publish_answersaying why. That is the set to push to a blocklist; see The IOC feed. - A failed job can still have a report, with
incomplete_reasonsaying where the run failed. Treat it as incomplete rather than as a verdict.
Runs take minutes to hours depending on the model and the team, so give the waiting step a timeout that suits the deployment. Requests are rate-limited per client address and path; see Operations.