{"id":13398251,"url":"https://github.com/stamparm/maltrail","last_synced_at":"2026-10-18T22:27:41.302Z","repository":{"id":24170696,"uuid":"27561102","full_name":"stamparm/maltrail","owner":"stamparm","description":"Malicious traffic detection system","archived":false,"fork":false,"pushed_at":"2026-10-14T15:26:22.000Z","size":7884,"stargazers_count":8622,"open_issues_count":37,"forks_count":1268,"subscribers_count":231,"default_branch":"master","last_synced_at":"2026-10-14T15:42:53.955Z","etag":null,"topics":["attack-detection","intrusion-detection","malware","network-monitoring","python","rust","security","sensor"],"latest_commit_sha":null,"homepage":"https://www.sekuripy.hr/labs/maltrail/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stamparm.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":".github/CODEOWNERS","security":"SECURITY.md","accessibility":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"claude":null,"gemini":null,"cursor":null,"copilot":null,"dco":null,"cla":null,"disclosure":null}},"created_at":"2014-12-04T21:33:46.000Z","updated_at":"2026-10-14T15:26:34.000Z","dependencies_parsed_at":"2026-10-14T13:25:24.092Z","dependency_job_id":null,"html_url":"https://github.com/stamparm/maltrail","commit_stats":{"total_commits":78172,"total_committers":46,"mean_commits":1699.391304347826,"dds":0.04615463337256309,"last_synced_commit":"7020b0bbfbaa4eb0c938ab78d8bb64b66f0290c1"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/stamparm/maltrail","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stamparm%2Fmaltrail","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stamparm%2Fmaltrail/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stamparm%2Fmaltrail/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stamparm%2Fmaltrail/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stamparm","download_url":"https://codeload.github.com/stamparm/maltrail/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stamparm%2Fmaltrail/sbom","scorecard":{"id":571847,"data":{"date":"2025-08-11","repo":{"name":"github.com/stamparm/maltrail","commit":"bf5b36fddf759fcbd300f50106d314ae207b7423"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 15 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/docker-release.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/docker-release.yml:9"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/docker-release.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/stamparm/maltrail/docker-release.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/docker-release.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/stamparm/maltrail/docker-release.yml/master?enable=pin","Warn: containerImage not pinned by hash: docker/Dockerfile:1: pin your Docker image by updating python:3 to python:3@sha256:3b2f1b9c9948e9dc96e1a2f4668ba9870ff43ab834f91155697476142b3bc299","Warn: pipCommand not pinned by hash: docker/Dockerfile:20","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned","Info:   0 out of   1 pipCommand dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}}]},"last_synced_at":"2025-08-20T16:33:09.633Z","repository_id":24170696,"created_at":"2025-08-20T16:33:09.633Z","updated_at":"2025-08-20T16:33:09.633Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":344872684,"owners_count":38342757,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-18T02:00:06.448Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["attack-detection","intrusion-detection","malware","network-monitoring","python","rust","security","sensor"],"created_at":"2024-07-30T19:00:21.032Z","updated_at":"2026-10-18T22:27:41.294Z","avatar_url":"https://github.com/stamparm.png","language":"Python","funding_links":[],"categories":["Tools","Python","Security","\u003ca id=\"tag-dev\" href=\"#tag-dev\"\u003eDev\u003c/a\u003e","Network","\u003ca id=\"79499aeece9a2a9f64af6f61ee18cbea\"\u003e\u003c/a\u003e浏览嗅探\u0026\u0026流量拦截\u0026\u0026流量分析\u0026\u0026中间人","Python (1887)","\u003ca id=\"7bf0f5839fb2827fdc1b93ae6ac7f53d\"\u003e\u003c/a\u003e工具","Links","Other Lists","Others","security","安全监控","Repos","🚀 DevOps \u0026 Infrastructure","Rust","Security monitoring"],"sub_categories":["Traffic","Security Tools","\u003ca id=\"tag-dev.security\" href=\"#tag-dev.security\"\u003eSecurity\u003c/a\u003e","Other Resources","\u003ca id=\"99398a5a8aaf99228829dadff48fb6a7\"\u003e\u003c/a\u003e未分类-Network","\u003ca id=\"471c124b012dbde8ea3288f35667efc8\"\u003e\u003c/a\u003e流量检测","Incident Response","LAB","🧪 LAB","网络安全监控（NSM）","IDS / IPS / Host IDS / Host IPS","Network Security Monitoring (NSM)"],"readme":"![Maltrail](https://i.imgur.com/3xjInOD.png)\n\n[![License](https://img.shields.io/badge/license-MIT-red.svg)](#license)\n[![Sensor](https://img.shields.io/badge/sensor-Rust%201.74%2B-orange.svg)](sensor/)\n[![Server](https://img.shields.io/badge/server-Python%203.6%2B-blue.svg)](server.py)\n[![Trails](https://img.shields.io/badge/trails-%3E1.5M-brightgreen.svg)](#trails)\n[![X](https://img.shields.io/badge/X-%40maltrail-black.svg)](https://x.com/maltrail)\n\n# Maltrail\n\nMaltrail is a network traffic detection system that identifies communication with known malicious\ninfrastructure and reports selected traffic anomalies. It matches domains, URLs, IP addresses,\n`IP:port` pairs, and User-Agent values observed on the network against a set of indicators called\n_trails_.\n\nA detection is recorded as a single event containing the source, destination, protocol, matched\ntrail, classification, and trail source:\n\n```text\n\"2026-08-07 09:14:22.117034\" gw 10.13.13.2 57809 1.1.1.1 53 UDP DNS malware.bakewithdavid.com \"asyncrat (malware)\" (static)\n```\n\nMaltrail is designed for indicator-based network monitoring. Its heuristic detections supplement\ntrail matching, but it is not a replacement for endpoint telemetry or a general-purpose intrusion\nprevention system.\n\n## Features\n\n- A full trail build combining the static trail set (over 3,000 curated files, fetched from\n  [stamparm/trails](https://github.com/stamparm/trails)), 39 public-feed integrations, and optional\n  operator-supplied trails.\n- A multithreaded Rust sensor using libpcap, with optional Linux `PACKET_FANOUT` capture workers.\n- A Python server providing the reporting interface, event intake, and HTTP API.\n- Plain-text custom trails and whitelists that can be reviewed and version-controlled.\n- Heuristics for scanning, DNS exhaustion, DGA-like lookups, suspicious downloads, proxy probes,\n  suspicious User-Agent values, and related network activity.\n- Local event logging, remote Maltrail logging, CEF over syslog, and Logstash JSON output.\n- Deployment validation with `maltrail-sensor -T` and optional Prometheus metrics.\n\n## Contents\n\n- [Architecture](#architecture)\n- [Reporting interface](#reporting-interface)\n- [Performance](#performance)\n- [Installation](#installation)\n  - [Installer](#installer)\n  - [Building from source](#building-from-source)\n  - [Systemd](#systemd)\n  - [Docker](#docker)\n- [Configuration](#configuration)\n- [Trails](#trails)\n- [Events and API](#events-and-api)\n- [Operations](#operations)\n  - [Monitoring](#monitoring)\n  - [Event retention](#event-retention)\n- [Documentation](#documentation)\n- [Contributing](#contributing)\n- [Project](#project)\n  - [License](#license)\n  - [Maintainers](#maintainers)\n  - [Sponsors](#sponsors)\n  - [Presentations and publications](#presentations-and-publications)\n  - [Derived blacklist](#derived-blacklist)\n  - [Third-party integrations](#third-party-integrations)\n  - [Acknowledgements](#acknowledgements)\n\n## Architecture\n\nMaltrail has two independently deployable processes that can run on the same host or on separate\nhosts: the **sensor** detects suspicious traffic, while the **server** stores and exposes events for\nreporting and API access.\n\n```mermaid\nflowchart LR\n    Traffic((\"\u003cb\u003eNetwork traffic\u003c/b\u003e\"))\n\n    subgraph Maltrail [\"Maltrail System\"]\n        direction LR\n        Sensor[\"\u003cb\u003eSensor\u003c/b\u003e\u003cbr/\u003eRust / libpcap\u003cbr/\u003eCapture \u0026 detection\u003cbr/\u003eTrail matching \u0026 heuristics\"]\n        Logs[(\"\u003cb\u003eEvent logs\u003c/b\u003e\u003cbr/\u003eLOG_DIR\")]\n        Server[\"\u003cb\u003eServer\u003c/b\u003e\u003cbr/\u003ePython\u003cbr/\u003eEvent intake \u0026 access\u003cbr/\u003eReporting UI \u0026 API\"]\n    end\n\n    Browser((\"\u003cb\u003eBrowser\u003c/b\u003e\u003cbr/\u003eReporting interface\"))\n    Syslog[\"\u003cb\u003eSyslog / SIEM\u003c/b\u003e\u003cbr/\u003eCEF\"]\n    Logstash[\"\u003cb\u003eLogstash\u003c/b\u003e\u003cbr/\u003eJSON\"]\n\n    Traffic --\u003e Sensor\n    Sensor --\u003e|local events| Logs\n    Sensor --\u003e|remote events / LOG_SERVER| Server\n    Server --\u003e|stores remote events| Logs\n    Logs --\u003e|reads daily logs| Server\n    Sensor --\u003e|SYSLOG_SERVER| Syslog\n    Sensor --\u003e|LOGSTASH_SERVER| Logstash\n    Server \u003c--\u003e|HTTP / SSE| Browser\n```\n\nThe sensor captures network traffic, performs trail matching and heuristic analysis, and produces\nevents. It can write events locally to `LOG_DIR`, send them to a remote Maltrail server through\n`LOG_SERVER`, or do both. It can also emit CEF over syslog with `SYSLOG_SERVER` and JSON to\nLogstash with `LOGSTASH_SERVER`.\n\nThe server accepts remote events, stores them in the event logs, reads locally available daily logs,\nand provides the HTTP API and browser-based reporting interface. The sensor does not depend on the\nreporting interface and can be deployed independently when events are forwarded to another system.\n\n## Reporting interface\n\nMaltrail includes a browser-based reporting interface for exploring detected traffic, with live\nupdates, field-aware search, retro hunting, geographic views, triage, sensor status, saved views and\nexport.\n\n![Maltrail reporting interface](https://i.imgur.com/bqCErCK.png)\n\nThe interface is served by `server.py` at `HTTP_ADDRESS:HTTP_PORT`. It is plain JavaScript with a\nsingle third-party runtime dependency (PapaParse, for CSV parsing) and no build step. One day is\nviewed at a time, selected with a date picker that also acts as an event-density grid over the\navailable daily logs. Events are streamed from `/events` and aggregated in the browser into\n*threats* — one row per distinct `(source, trail)` — shown in a sortable grid with a detail panel.\n\n| Feature | Notes |\n| --- | --- |\n| Live mode | Appended events arrive over Server-Sent Events (`/live`) and are merged into the current view. When SSE is unavailable, or the stream cannot serve the session, the interface falls back to polling byte ranges of the daily log. New high-severity threats can raise desktop and audible alerts; both can be muted |\n| Search | Field-aware queries support `src:`, `dst:`, `port:`, `proto:`, `type:`, `trail:`, `info:`, `family:`, `tag:`, `uid:`, `sev:`, `dir:` and `status:` selectors, plus exclusions, wildcards, CIDR matching and numeric comparisons. Active filters appear as removable chips |\n| Retro hunt | `/hunt` searches all retained daily logs for one indicator rather than only the selected day. Searches are bounded by configurable limits; an optional per-day sidecar index accelerates the sweep and makes `/counts` exact |\n| World map | `/geo` shows per-country event density for the selected day using the external endpoint of each event. Unattributable events remain unmapped rather than being guessed. `HOME_LAT` / `HOME_LON` can be set to draw origin arcs |\n| Triage | Per-threat status (new / investigating / resolved / false positive), free-text notes, tags and hiding. Whitelist rules and OSINT pivots are available from the row context menu |\n| Sensor status | `/sensors` shows which sensors are reporting, the version each runs and how long ago it last checked in |\n| Saved views | Named filter presets |\n| Export | The current filtered view as CSV, JSON, or defanged indicators |\n| Appearance | Dark and light themes, with discrete text-size steps |\n\nSearch terms separated by spaces are combined as **AND**. Prefix a term with `-` to exclude it, use\n`*` for wildcards, use CIDR notation such as `src:10.0.0.0/8`, and use numeric ranges or comparisons\nsuch as `port:\u003e1024` and `count:\u003e=100`. `family:interlock` also includes `interlock-1` and\n`interlock-2`, the shards in which that feed dump arrives.\n\nRetro hunts are bounded by a day limit, a wall-clock budget and a sample cap. If the time budget cuts\na day short, that day is reported separately rather than counted as a completed day. With\n`USE_EVENT_INDEX` enabled, the per-day sidecar index under `LOG_DIR/index/` lets the hunt skip\nnon-matching lines and provides exact `/counts` results.\n\nSensor liveness is tracked separately from event activity: a failed sensor and a healthy sensor on a\nquiet link can both produce no events. Sensors therefore announce themselves over the `LOG_SERVER`\nchannel every `HEARTBEAT_PERIOD` seconds. When `LOG_SERVER_SECRET` is configured, heartbeats are\nsigned in the same way as events.\n\nTriage state, saved views, tags and appearance settings are stored in the **browser**\n(`localStorage`), not on the server. They are therefore per-browser and per-origin and are not shared\nbetween analysts.\n\nSessions restricted with a network filter see only events from their own networks. The same\nrestriction also applies to the counts, map and blacklist endpoints, not only to the event list.\n\nCountry and ASN enrichment for individual addresses is looked up at `stat.ripe.net` by the\n**server**, cached there, and exposed to the interface through Maltrail's own `/ripe` endpoint. The\nbrowser talks to nothing but Maltrail. Set `DISABLE_RIPE_LOOKUPS` to disable outbound lookups\nentirely. Without RIPE lookups — including on hosts without internet access — flags fall back to the\nlocal RIR table and the rest of the interface continues to work offline.\n\n## Performance\n\nPerformance depends on processor, traffic composition, trail-set size, capture driver, and network\ninterface. The figures below measure the sensor's packet-processing path in isolation; they are not\nend-to-end live-capture measurements.\n\nRepresentative measurements on an AMD Ryzen 7 PRO 4750U with heuristics enabled and a 1.5\nmillion-row trail set:\n\n| Traffic | Time per packet |\n| --- | ---: |\n| ICMP echo, 58 bytes | 101 ns |\n| TCP SYN, 70 bytes | 302 ns |\n| Bulk TLS, 1,473 bytes | 402 ns |\n| DNS query with a warm cache, 93 bytes | 452 ns |\n| Mixed traffic, 866-byte average | 552 ns |\n| HTTP request, 169 bytes | 602 ns |\n| DNS query with a unique name, 93 bytes | 1,102 ns |\n\nOffline comparison runs using the same generated capture, configuration, and trail set measured a\n14–37× lower steady-state per-packet cost than the retired Python sensor across the tested systems.\nThose figures separate whole-process time from steady state, because trail loading dominates a\nshort replay. Detection itself is asserted separately, by the 42-case corpus in\n`sensor/tests/replay.rs`.\n\nMeasure it on the target system with:\n\n```bash\ncargo bench --manifest-path sensor/Cargo.toml --bench hotpath\n```\n\nOne capture worker is used by default. Additional workers increase capture capacity, and with more\nthan one the sensor distributes packets by source address (`CAPTURE_FANOUT_MODE source`), so each\nscanner stays on one worker and the scan heuristics keep all their alerts. Plain flow hashing\n(`hash`) would split a scanner across workers: in the documented test it kept 91% of single-worker\nheuristic alerts at two workers, 87% at four and 66% at eight. Exact trail matching is unaffected\neither way. Add workers when capture-drop metrics show that one is not keeping up.\n\nBenchmark methodology, hardware results, profiler output, memory measurements, and live fanout\nchecks are documented in [`sensor/docs/REPORT.md`](sensor/docs/REPORT.md).\n\n## Installation\n\n### Installer\n\nThe installer is verified on twelve Linux distributions, counting releases (Debian 12 and 13,\nUbuntu 22.04 and 24.04, Fedora 41 and 44, Rocky, AlmaLinux, Arch, openSUSE Leap and Tumbleweed,\nand Alpine), plus FreeBSD, NetBSD, OpenBSD and macOS. The matrix is re-run weekly and whenever the installer changes,\nwith the full result recorded in [`docs/compat`](docs/compat). Raspberry Pi OS and other\n64-bit ARM systems use the `aarch64` build. 32-bit ARM, down to the Raspberry Pi 1 and Zero, uses a\nstatic ARMv6 build (`arm-unknown-linux-musleabihf`), and the sensor fits in a 256 MB Pi.\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/stamparm/maltrail/master/install.sh | sudo sh\n```\n\nIt installs dependencies, creates a managed checkout under `/opt/maltrail`, verifies the checksum\nof the prebuilt sensor, creates an unprivileged `maltrail` account, installs systemd units, prepares\nthe log and state directories, and starts the sensor and server. Re-running the installer upgrades\nthe managed checkout.\n\nReview the script before running it with elevated privileges. From an existing checkout, the dry\nrun shows the commands without changing the system:\n\n```bash\nsh install.sh --dry-run\n```\n\nCommon installer options:\n\n```bash\nsh install.sh --role sensor      # Install only the sensor\nsh install.sh --ref 3.5          # Install a release tag instead of master\nsh install.sh --no-service       # Install without changing systemd\nsh install.sh --dry-run          # Print commands without applying them\nsh install.sh --uninstall        # Remove the managed installation; keep logs and state\n```\n\nThe dashboard is available at \u003chttp://127.0.0.1:8338\u003e after installation. Note that the shipped\n`HTTP_ADDRESS` is `0.0.0.0`, so it is reachable on **every** interface, not only loopback — and\nthe default credentials are `admin` / `changeme!`. Change `USERS`, and set `HTTP_ADDRESS` to\n`127.0.0.1` (or put the server behind a reverse proxy with TLS), before the host is on an\nuntrusted network.\n\nThe initial trail build can take several minutes. The sensor does not detect trail matches until a\nvalid trail set is available. The systemd unit runs the sensor's `-T` validation before startup so\nthat missing privileges, an unwritable log directory, or an invalid trail set causes startup to\nfail visibly.\n\nThe installer test harness covers twelve distributions, and \"it installed\" is not the assertion: in\neach one the server is started and asked for `/ping`, the sensor is asked to validate itself with\n`-T`, the units are checked for paths that resolve, the installer is re-run to prove an upgrade\nkeeps operator configuration, and `--uninstall` is run. Every result is recorded per platform in\n[`docs/compat`](docs/compat), and the page there is generated from those rows rather than written\nby hand.\n\nAlpine and other musl systems get a `-musl` sensor build. They used to be told the prebuilt binary\nwas glibc-linked and to compile their own; the sensor builds and runs on musl natively, so that was\na missing artifact rather than a platform limit.\n\n### Building from source\n\nThe sensor requires Rust 1.74 or newer, libpcap development headers, and the system's capability\ntools. The server and trail updater require Python 3.6 or newer.\n\nInstall the distribution packages:\n\n```bash\n# Debian / Ubuntu / Raspberry Pi OS\nsudo apt-get install cargo libpcap-dev libcap2-bin python3\n\n# RHEL / Fedora\nsudo dnf install cargo libpcap-devel libcap python3\n\n# openSUSE / SLES\nsudo zypper install cargo rust libpcap-devel libcap-progs python311\n```\n\nThen build and validate the sensor:\n\n```bash\ngit clone --depth 1 https://github.com/stamparm/maltrail.git\ncd maltrail\n\ncargo build --release --manifest-path sensor/Cargo.toml\n\nsudo setcap cap_net_raw,cap_net_admin=eip \\\n  sensor/target/release/maltrail-sensor\n\nsudo install -d -o \"$USER\" -g \"$(id -gn)\" -m 750 /var/log/maltrail\n\nsensor/target/release/maltrail-sensor -T\nsensor/target/release/maltrail-sensor\n```\n\nStart the server in another terminal or on another host:\n\n```bash\npython3 server.py\n```\n\nPrebuilt sensor binaries are attached to current releases with SHA-256 checksums: Linux `x86_64`\nand `aarch64` against both glibc and musl, 32-bit ARM (`armv6`/`armv7`, static musl), macOS on Apple\nsilicon and Intel, FreeBSD `amd64`, and Windows `x86_64`.\n\nA binary is not a complete sensor on its own. Install with `install.sh` (or `install.ps1` on\nWindows), which downloads the right binary for you.\n\nThe glibc builds link libpcap statically and target glibc 2.28, so the C library is the only thing\nthey need — nothing to install, on RHEL 8+, Debian 10+, Ubuntu 20.04+ and Leap 15.6 alike. The musl\nbuilds are fully static, so Alpine needs nothing at all. The Windows build is 64-bit and needs Windows 10 or later plus\n[Npcap](https://npcap.com) installed before it will start — `wpcap.dll` is a load-time dependency,\nso without it the loader refuses the executable rather than failing at capture. The archive says so\ntoo. From a checkout, `install.ps1` does the rest:\n\n```powershell\npowershell -ExecutionPolicy Bypass -File install.ps1\n```\n\nIt refuses to install without Npcap, puts the binary, configuration and support tree under\n`%ProgramFiles%\\Maltrail`, keeps logs and the trail set in `%ProgramData%\\Maltrail`, seeds the trail\nset from the release, runs the sensor's own `-T` preflight, and starts it at boot as SYSTEM via a\nscheduled task. `-Uninstall` removes the task and the program directory and keeps everything under\n`%ProgramData%`.\n\nBinaries from **3.1.1 and earlier** did not: they linked libpcap dynamically, and asked for it by\nthe name their AlmaLinux build host uses. Debian and Ubuntu ship the identical library under the\nolder name `libpcap.so.0.8`, so those binaries stop before they start —\n\n```\n./maltrail-sensor: error while loading shared libraries: libpcap.so.1: cannot open shared object file\n```\n\n— on a machine that has libpcap installed. `install.sh` links the missing name for you. By hand:\n\n```bash\n# adjust the directory for your architecture: aarch64-linux-gnu, or /usr/lib64 on RPM distributions\nsudo ln -sf /usr/lib/x86_64-linux-gnu/libpcap.so.0.8 /usr/lib/x86_64-linux-gnu/libpcap.so.1\nsudo ldconfig\n```\n\n### Systemd\n\nThe supplied `packaging/systemd/` units run both processes as the\nunprivileged `maltrail` user. Systemd creates `/var/log/maltrail` and `/var/lib/maltrail`, restricts\nfilesystem access, and grants the sensor `CAP_NET_RAW` and `CAP_NET_ADMIN`.\n\nThe installer configures these units automatically. For an existing source installation, follow\nthe manual service procedure in [`sensor/docs/INSTALL.md`](sensor/docs/INSTALL.md).\n\nCheck service state and logs with:\n\n```bash\nsystemctl status maltrail-sensor maltrail-server\njournalctl -u maltrail-sensor -f\n```\n\n### Docker\n\nStart the supplied Compose deployment with:\n\n```bash\ndocker compose -f docker/docker-compose.yml up -d\n```\n\nContainer configuration, storage, privileges, and health checks are documented in\n[`docker/README.md`](docker/README.md).\n\n## Configuration\n\nMaltrail reads `maltrail.conf`, which contains separate `[Sensor]` and `[Server]` settings. The\ninstaller places the managed configuration at `/etc/maltrail.conf`.\n\nFrequently used sensor options include:\n\n| Option | Purpose |\n| --- | --- |\n| `MONITOR_INTERFACE` | Capture interface or interfaces; `any` selects all supported interfaces |\n| `CAPTURE_FILTER` | BPF capture filter |\n| `CAPTURE_FANOUT` | Number of Linux capture sockets; defaults to one |\n| `CAPTURE_WORKERS` | Capture workers, one socket each; defaults to `CAPTURE_FANOUT`, so one unless either is set |\n| `LOG_DIR` | Local event-log directory |\n| `TRAILS_FILE` | Generated trail database |\n| `LOG_SERVER` | Remote Maltrail event server |\n| `SYSLOG_SERVER` | CEF syslog destination or destinations |\n| `LOGSTASH_SERVER` | Logstash JSON destination or destinations |\n| `STATS_ADDRESS` | Prometheus metrics listener; disabled unless configured |\n| `UPDATE_PERIOD` | Trail refresh interval |\n| `STATIC_TRAILS_URL` | Where the assembled static trail set is fetched from; pin it to a dated release to control when new content lands |\n| `USER_WHITELIST` | Operator-managed indicators that should not alert |\n| `CUSTOM_TRAILS_DIR` | Operator-managed trail directory |\n| `STATIC_TRAILS_DIR` | Optional checkout of the trails repository; only used to show a trail's source citation in the UI |\n\n`PROCESS_COUNT` applies to the retired Python sensor and to the legacy event-log throttle; it does\n**not** set the Rust sensor's worker count. Configure capture workers with `CAPTURE_FANOUT` or\n`CAPTURE_WORKERS` instead.\n\nRun the deployment check after changing configuration:\n\n```bash\nsensor/target/release/maltrail-sensor -T\n```\n\nThe check validates configuration, trails, whitelist entries, capture filter, privileges, log\nstorage, update support, and worker settings. A successful check includes positive trail and\nwhitelist counts rather than only confirming that files exist.\n\n## Trails\n\nA trail is one indicator — a domain, URL, IP address, `IP:port` pair, User-Agent, JA3/JA4\nfingerprint or certificate hash — together with what it means and where it came from. The updater\nmerges five sources into `TRAILS_FILE`, in this order:\n\n| source | where it comes from |\n| --- | --- |\n| Feeds | `feeds/*.py`, fetched directly by your deployment from each publisher, e.g. [IPnoise](https://ipnoise.sekuripy.hr/) |\n| Custom | `CUSTOM_TRAILS_DIR`, your own indicators |\n| Static | the assembled set from [stamparm/trails](https://github.com/stamparm/trails), fetched from `STATIC_TRAILS_URL`; [separately licensed](https://github.com/stamparm/trails/blob/main/LICENSE.md) |\n| Engine lists | `data/mass_scanner.txt`, shipped here because it changes rarely |\n| Remote custom | `CUSTOM_TRAILS_URL`, merged last; it does not override custom or static entries |\n\nFeeds are fetched by your own deployment rather than bundled here, so each is as current as its\npublisher keeps it. [IPnoise](https://ipnoise.sekuripy.hr/), for example, publishes the addresses\ncaught attacking a distributed network of honeypot sensors — hosts running no legitimate service,\nso every connection reaching them is hostile by construction — and `feeds/ipnoise.py` pulls its\n7-day list.\n\nThe static trails live in their own repository. Detection content changes tens of times a\nday; the engine does not, and keeping them together meant updating detection required pulling code\nand made this repository's history unusable. `STATIC_TRAILS_URL` points at the newest published\nset:\n\n```text\nSTATIC_TRAILS_URL https://github.com/stamparm/trails/releases/latest/download/trails.csv.gz\n```\n\nPoint it at a specific `content-YYYYMMDD-HHMM` release instead to pin a version, so a bad publish\nis not immediately global. The set is cached next to `TRAILS_FILE`, which is what makes an offline\nor air-gapped rebuild work; the published `sha256` is checked before downloading, so a deployment\nthat updates more often than the content changes transfers 65 bytes rather than 11 MB, and a\npayload that does not match its digest is refused in favor of the cache.\n\n`update_trails()` publishes a new `TRAILS_FILE` atomically and only after a successful build. Feeds\nthat return nothing are reported by name, so a deployment does not silently depend on a source that\nhas quietly retired.\n\nAdd your own indicators under `CUSTOM_TRAILS_DIR`, and anything that must never raise an event to\n`USER_WHITELIST`. Keep both outside the installation directory so an upgrade cannot overwrite them.\n\nStatic trail contributions go to [stamparm/trails](https://github.com/stamparm/trails); new feeds\ngo here. Either way an indicator needs a classification and a source somebody can check — see\n[Contributing](#contributing).\n\n## Events and API\n\nMaltrail records one whitespace-separated event per detection, using CSV quoting where a value\ncontains spaces:\n\n```text\n\"\u003ctime\u003e\" \u003csensor\u003e \u003csrc_ip\u003e \u003csrc_port\u003e \u003cdst_ip\u003e \u003cdst_port\u003e \u003cproto\u003e \u003ctype\u003e \u003ctrail\u003e \"\u003cinfo\u003e\" \u003creference\u003e\n```\n\nThe `type` field identifies what matched, including `DNS`, `IP`, `IPORT`, `URL`, `PATH`, `HTTP`,\n`UA`, `PORT`, `CERT`, `JA3`, and `JA4`. The `info` field contains the trail classification, and\n`reference` identifies the static list, feed, custom source, or heuristic that produced it. The\n`JA3`/`JA4` types fire on TLS *client* fingerprints: an implant's TLS stack survives every\naddress and domain rotation, so its hello hash keeps matching after everything else has burned\n(published by the abuse.ch SSLBL JA3 feed).\n\n### Indicator lookup\n\nUse `/check` to query one domain, IP address, or URL:\n\n```bash\ncurl 'http://127.0.0.1:8338/check?q=www.sub.evil.example'\n```\n\n```json\n{\n  \"query\": \"www.sub.evil.example\",\n  \"found\": true,\n  \"trail\": \"evil.example\",\n  \"info\": \"asyncrat (malware)\",\n  \"reference\": \"(static)\",\n  \"confidence\": 100\n}\n```\n\nThe `confidence` field (0-100, or `null` when unavailable) says how strongly the sources back the\nlisting: 40 for a single feed, +15 per additional feed agreeing independently up to 100, and full\nmarks for the operator's own custom and static entries. It is computed at trail-update time from\nfeed agreement into a `trails.confidence` sidecar next to `trails.csv`; a server pulling trails\nfrom an `UPDATE_SERVER` has no provenance to score and reports `null`. Use it to prioritize\ntriage - a single-feed listing at 40 deserves a second look before it earns a firewall rule.\n\nA subdomain lookup can match its listed parent. URL lookups check `host/path` before checking the\nhost alone. The server reads the memory-mapped trail database and observes trail updates without a\nrestart.\n\nPublic static and feed trails are available without authentication, consistent with the `/trails`\nendpoint used by remote sensors. Custom trails require an authorized session; an unauthorized\ncustom-only lookup is reported as a miss. Event data remains authenticated.\n\n## Operations\n\n### Monitoring\n\nUse `maltrail-sensor -T` as a deployment and configuration gate. The supplied systemd unit runs it\nas `ExecStartPre`.\n\nTo confirm that detection itself works — not just that the processes start — run:\n\n```bash\npython3 server.py --detect-test\n```\n\nIt replays a crafted pcap of emulated malicious traffic (trail hits on a DNS query, an IP, an\n`IP:port`, a URL path and a `Host` header, plus the SQL-injection, traversal, RCE, XSS, proxy-probe,\nsinkhole, missing-`Host` and port/web/infection-scanning heuristics) through the installed sensor\nand asserts that every expected detection fires. It needs no root, no interface and no trail set of\nits own. A healthy install prints `27/27 detection(s) fired`.\n\nWhen `STATS_ADDRESS` is configured, monitor at least these Prometheus metrics:\n\n| Metric | Operational meaning |\n| --- | --- |\n| `maltrail_up == 0` | No capture worker is running |\n| Increasing `maltrail_capture_dropped_total` | The capture ring is dropping packets |\n| Increasing `maltrail_local_log_errors_total` | Events were produced but could not be written locally |\n| Increasing `maltrail_remote_log_errors_total` | Events could not be delivered to a remote sink; with `DISABLE_LOCAL_LOG_STORAGE` they are lost |\n| `maltrail_trail_generation` not advancing | The active trail set is not being refreshed |\n| `maltrail_log_dir_free_bytes` | Remaining capacity for local event storage |\n| Increasing `maltrail_state_saturations_total` | A heuristic state limit was reached |\n| Increasing `maltrail_throttle_evictions_total` | The event-throttle table is at its cap, so events are aggregated earlier than configured |\n\nState saturation affects the corresponding heuristic; exact trail matching remains active.\n\nSend `SIGHUP` or use `systemctl reload maltrail-sensor` to request a trail reload. Trail files\nupdated by another process are detected automatically and published to workers without restarting\nthe sensor.\n\nThe condensed observable store (`USE_CONDENSED_STORAGE`, `meta.sqlite`) supports the server's\nnovelty and retro-hunt views. The per-day event-log sidecar index (`USE_EVENT_INDEX`,\n`LOG_DIR/index/*.sqlite`, roughly twice the log size on disk) is what makes `/counts` exact and\n`/hunt` fast; it is maintained incrementally from the logs themselves and can be rebuilt with\n`server.py --rebuild-index`. Compatibility with the retired sensor is documented in\n[`sensor/docs/COMPATIBILITY.md`](sensor/docs/COMPATIBILITY.md).\n\n### Event retention\n\nMaltrail does not rotate or delete event logs. Operators are responsible for defining retention,\narchival, and deletion according to storage requirements and organizational policy.\n\nRecommended practices:\n\n- Send the durable event copy to a remote Maltrail server or SIEM with `LOG_SERVER`,\n  `SYSLOG_SERVER`, or `LOGSTASH_SERVER`.\n- Alert on `maltrail_log_dir_free_bytes` with enough headroom for the expected event rate.\n- Rotate, archive, or remove local daily logs using external tooling.\n- Keep files needed by the reporting interface uncompressed in `LOG_DIR`; archive compressed files\n  elsewhere.\n\nWhen the log filesystem is full, the sensor cannot append events. Event logs may also contain IP\naddresses and domains that are regulated as personal data in some jurisdictions; retention policy\nshould account for the applicable requirements.\n\n### Synthetic traffic\n\nTo check that detection and the dashboard both still work, without waiting for real traffic:\n\n```bash\npython3 server.py --detect-test                      # assert every detection fires, then exit\npython3 server.py --detect-test --keep DIR --serve   # ...and keep the events, serving them on :8338\n```\n\n`--keep` also replays `sensor/tests/corpus/` into the same log and prints which of the shapes the\ndashboard renders differently have an event behind them, so a missing icon, color or glyph is\nvisible rather than assumed. Timestamps are shifted so the newest day is today. A sensor binary is\nrequired (`cargo build --release --manifest-path sensor/Cargo.toml`).\n\nThe public demo's data is regenerated from such a run:\n\n```bash\npython3 sensor/tools/gen_demo_js.py --from DIR/logs   # tops up html/js/demo.js\n```\n\n## Documentation\n\n| Document | Contents |\n| --- | --- |\n| [`sensor/docs/INSTALL.md`](sensor/docs/INSTALL.md) | Installation, privileges, configuration, and troubleshooting |\n| [`sensor/docs/ARCHITECTURE.md`](sensor/docs/ARCHITECTURE.md) | Sensor internals and data flow |\n| [`sensor/docs/COMPATIBILITY.md`](sensor/docs/COMPATIBILITY.md) | Deliberate differences from the retired Python sensor |\n| [`sensor/docs/REPORT.md`](sensor/docs/REPORT.md) | Measurements, profiles, and test results |\n| [`sensor/docs/ROADMAP.md`](sensor/docs/ROADMAP.md) | Open sensor work |\n| [`SekuriPy Labs`](https://www.sekuripy.hr/labs/maltrail/) | Engineering notes, benchmarks and write-ups |\n\n## Contributing\n\nTrail additions, feed maintenance, bug reports, documentation, and sensor improvements are welcome.\nTrail submissions should include a reliable source and should use the narrowest appropriate\nclassification.\n\nRun the relevant checks before submitting code. The complete sensor gate is:\n\n```bash\nbash sensor/tools/check.sh\n```\n\nIt runs formatting, Clippy with warnings denied, and the debug and release test suites. Run the\nPython server suite with:\n\n```bash\nbash tests/run.sh python3\n```\n\nThe Windows build can be exercised from Linux, which is where its bugs have been found:\n\n```bash\nsh sensor/tools/check_windows.sh\n```\n\nIt cross-compiles the sensor with mingw-w64, extracts Npcap's userspace library from its installer\n(an NSIS archive, so nothing is installed), and runs the result under Wine — the whole unit suite,\n`-T` against the shipped configuration, the pcap corpus compared byte for byte against the native\nbinary, and the server answering `/ping` under a Windows Python. Live capture is the one thing it\ncannot cover; that needs Npcap's kernel driver and a real Windows machine. Prerequisites are\n`gcc-mingw-w64-x86-64`, `wine`, and `p7zip-full`.\n\n## Project\n\n### License\n\n**TL;DR:** Maltrail is MIT-licensed, but the Maltrail Trails dataset has separate terms. Independent IOC lookup/reference is fine; systematic use of Trails as an intelligence source in a commercial product or service requires permission/licensing.\n\nMaltrail is distributed under the MIT License. See [`LICENSE`](LICENSE).\n\nThat is the engine. The static trail set is separate work under separate terms: free for internal\ndefensive use, research and teaching, but a commercial product, service, MSSP or MDR offering, or a\nredistributed feed needs a license. An MIT engine does not make the content free to sell — see\n[`LICENSE.md`](https://github.com/stamparm/trails/blob/main/LICENSE.md) in\n[stamparm/trails](https://github.com/stamparm/trails) before shipping it in something you charge\nfor.\n\n### Maintainers\n\n- Miroslav Stampar ([@stamparm](https://github.com/stamparm))\n- Mikhail Kasimov ([@MikhailKasimov](https://github.com/MikhailKasimov))\n\n### Sponsors\n\n- [Sansec](https://sansec.io/) (2024–2025)\n- [Sansec](https://sansec.io/) (2020–2021)\n\n### Presentations and publications\n\n- 47th TF-CSIRT Meeting, Prague, 2016\n  ([slides](https://web.archive.org/web/20161109135211/https://www.terena.org/activities/tf-csirt/meeting47/M.Stampar-Maltrail.pdf))\n- _Detect attacks on your network with Maltrail_, Linux Magazine, 2022\n  ([article](https://www.linux-magazine.com/Issues/2022/258/Maltrail))\n- _Best Cyber Threat Intelligence Feeds_, Silent Push, 2022\n  ([review](https://www.silentpush.com/blog/best-cyber-threat-intelligence-feeds))\n- _Research on Network Malicious Traffic Detection System Based on Maltrail_, Nanotechnology\n  Perceptions, 2024\n  ([paper](https://nano-ntp.com/index.php/nano/article/view/1915/1497))\n\n### Third-party integrations\n\n- [FreeBSD Port](https://www.freshports.org/security/maltrail)\n- [OPNsense Gateway Plugin](https://github.com/opnsense/plugins/pull/1257)\n- [D4 Project](https://www.d4-project.org/2019/09/25/maltrail-integration.html)\n- [BlackArch Linux](https://github.com/BlackArch/blackarch/blob/master/packages/maltrail/PKGBUILD)\n- [Validin](https://x.com/ValidinLLC/status/1719666086390517762)\n- [Maltrail Add-on for Splunk](https://splunkbase.splunk.com/app/7211)\n- [Maltrail decoder and rules for Wazuh](https://github.com/MikhailKasimov/maltrail-wazuh-decoder-and-rules)\n- [GScan](https://github.com/grayddq/GScan) (trails only)\n- [MalwareWorld](https://www.malwareworld.com/) (trails only)\n- [oisd domain blocklist](https://oisd.nl/?p=inc) (trails only)\n- [NextDNS](https://github.com/nextdns/metadata/blob/e0c9c7e908f5d10823b517ad230df214a7251b13/security/threat-intelligence-feeds.json) (trails only)\n- [NoTracking](https://github.com/notracking/hosts-blocklists/blob/master/SOURCES.md) (trails only)\n- [OWASP Mobile Audit](https://github.com/mpast/mobileAudit/pull/182/changes/1e9faad9a3dfd8ce9b656e05bfc955a7d6f20bef) (trails only)\n- [Mobile Security Framework MobSF](https://github.com/MobSF/Mobile-Security-Framework-MobSF/commit/822123826b4bc800554f0f7b4be49967db506b03) (trails only)\n- [pfBlockerNG-devel](https://github.com/pfBlockerNG/pfBlockerNG/commit/6765a139c6470cbeaf89cb5d2c03c8598544b31f) (trails only)\n- [Sansec eComscan](https://sansec.io/kb/about-ecomscan/ecomscan-license) (trails only)\n- [Palo Alto Networks Cortex XSOAR](https://xsoar.pan.dev/docs/reference/integrations/github-maltrail-feed) (trail connector)\n\n### Acknowledgements\n\n- Thomas Kristner\n- Eduardo Arcusa Les\n- James Lay\n- Ladislav Baco (@laciKE)\n- John Kristoff (@jtkdpu)\n- Michael M\u0026uuml;nz (@mimugmail)\n- David Brush\n- @Godwottery\n- Chris Wild (@briskets)\n- Keith Irwin (@ki9us)\n- Simon Szustkowski (@simonszu)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstamparm%2Fmaltrail","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstamparm%2Fmaltrail","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstamparm%2Fmaltrail/lists"}