Skip to content

Releases: OpenSource-For-Freedom/Legion_runner

v1.0.38

15 Jun 09:56
62ddd21

Choose a tag to compare

Added

  • Curated egress presets (allowed-presets): opt-in per-ecosystem allowlists
    (npm, yarn, pnpm, pip, pypi, cargo, rust, go, maven, gradle, nuget, apt, debian,
    docker) so block mode "just works" for common toolchains without hand-listing
    endpoints. e.g. allowed-presets: "cargo, apt". Unit-tested.
  • Download integrity verification: the action verifies the legionr-bpf /
    legionr-fim release binaries against a .sha256 sidecar before running them
    (the release now attaches the checksums), and fails closed — an
    unverified/corrupted/tampered download is rejected and the action degrades
    instead of executing it.
  • learned-baseline input (default true): in block mode, also allow
    destinations previously learned into the Actions cache. Set false to enforce
    ONLY the explicit allowlist (inline + policy-file + GitHub) with no cache
    read/write — used by the enforce self-test for deterministic deny.
  • File-integrity / tamper detection (Rust legionr-fim agent): snapshots
    high-value tamper targets at job start (credential/config files, .git
    config + hooks, and checked-out source) and diffs them at job end, surfacing
    anything overwritten, deleted, or chmod'd in the summary. Only sha256 hashes
    are stored — never contents. New inputs file-integrity (auto|off) and
    fim-extra-paths. file-integrity: auto downloads the agent from the latest
    release (plain stable Rust, no eBPF toolchain) and degrades to a silent skip
    if unavailable. Logic lives in legionr-core::fim (unit-tested); the binary
    is a release asset like legionr-bpf, built + attached by release.yml.
  • Package repositories roll-up (📦): the summary now classifies named
    outbound destinations into their ecosystem/registry (npm, PyPI, crates.io,
    apt, Docker, Go, NuGet, Maven, Gradle, RubyGems, Alpine, GitHub) and shows a
    Package repositories reached table — registry, ecosystem, connections, and
    the process that reached each. Supply-chain risk hides in which registries a
    build talks to, so we surface them directly instead of leaving you to read
    IPs. Bare IPs that never got a forward name get a coarse CDN/provider hint
    (Fastly/Cloudflare/GitHub) via CIDR match — honest about ambiguity (a shared
    CDN can't name a registry). Logic in action/repos.js, fully unit-tested.
  • Combined cross-job egress report (one summary for the whole run): GitHub
    has no run-level summary, so each job emits its captured egress as a JSON
    artifact (node action/report.js emit) and a final egress-report job merges
    them into a SINGLE table — which job + process reached what — with a package
    repositories roll-up and a per-job diagnostics block (render). Wired into CI;
    render is pure and unit-tested. Pairs with job-summary: false so the run
    shows one combined summary instead of one table per job.
  • job-summary input (default true): set false to keep monitoring and
    enforcement fully active but suppress the connections table in the job summary.
    Useful when many jobs in one workflow each run the action and you only want the
    table once (our own CI uses it so a run shows one table, not one per job).
  • Secure diagnostics line in the summary: reports which resolution path
    actually fired (forwarder on/off · captured DNS records N · getaddrinfo route … · named X/Y destinations) so a run that comes back as bare IPs is triagable.
    Secure by construction — only booleans, counts, and a fixed enum; never the
    upstream resolver IP, file paths, captured hostnames, or env values.

Fixed

  • Block mode no longer hangs the runner at teardown. applyEgressBlock
    installed a default-deny LEGION_EGRESS chain in OUTPUT and nothing ever
    removed it, so the runner's own completion call (to rotating GitHub-backend IPs
    not in the static seed) was dropped and the job spun until timeout. post()
    now tears the firewall down (removeEgressBlock).
  • Runner hang from leaked daemons. The post step left privileged background
    processes alive — eBPF agent, DNS forwarder — and the /proc monitor could
    wedge in a blocking ss subprocess. The monitor now reads /proc/net/tcp
    directly (no subprocess); daemons are reliably reaped.
  • No more spurious "could not resolve" annotations. Allowlist entries that
    are wildcard parents with no A record of their own (e.g. blob.core.windows.net,
    actions.githubusercontent.com) used to emit one CI warning annotation
    each on every run. They are benign: the action skips them, and their subdomains
    are still observed via PTR / DNS capture (and opened just-in-time in block
    mode). They are now collected into a single plain-text log line instead.
  • Docs/labels: the eBPF mechanism is a tracepoint on sys_enter_connect
    (not a "kprobe on tcp_connect"); the sampler is /proc-only (the "ss" fallback
    was removed). Corrected the runtime log line, summary label, and README.
  • Outbound connections showed as bare IPs when systemd-resolved owns
    getaddrinfo.
    The nsswitch reroute didn't always stick, so package-repo
    lookups bypassed the capture forwarder and were never named. The forwarder now
    targets the real upstream (systemd-resolved's actual servers, not the
    127.0.0.53 stub), and when the bypass is detected but the nsswitch reroute
    fails, Legion redirects systemd-resolved itself at the forwarder via a
    resolved.conf.d drop-in — verify-or-revert and restored on teardown, so
    it never breaks the job's DNS.
  • IPv4-mapped IPv6 destinations rendered as long expanded addresses
    (0000:0000:0000:0000:0000:ffff:HHHH:HHHH) in the summary. The /proc sampler
    emitted the expanded form while normalizeIp only collapsed the compressed
    ::ffff: form. Both now collapse to dotted IPv4 (and the v4/v6 tables dedupe).
    (Shipped in v1.0.35.)
  • Removed dead action/baseline.js; pinned release.yml checkout to v6.

Reliability

  • Tests for the paths that kept breaking: the firewall rule builders
    (egressBlockRules/egressUnblockRules — order, DNS-allow, DROP-last,
    OUTPUT-jump-removed-first), the checksum parser, the curated presets, and a
    full-stack PR gate that runs block + DNS-capture + eBPF and asserts the
    job finalizes (catches any teardown-hang regression), plus the package-repo
    classifier (host-suffix + CIDR matching). Action test count 19 → 37.

Changed

  • Removed em-dashes from the job-summary output (headers, the unresolved-host
    note, the enforce hint, and empty-cell placeholders) for plainer rendering.
  • Name more destinations: route glibc getaddrinfo (curl/apt/cargo/git)
    through the DNS-capture forwarder via an nsswitch.conf reroute, so hosts
    resolved by systemd-resolved (which ignores resolv.conf) are now captured
    and named — not just resolv.conf/c-ares callers. Health-checked and restored
    on teardown. (A connection to a hard-coded IP with no PTR still shows the IP —
    there is no name to resolve.)
  • More accurate "unresolved destination" note in the summary (a name may have
    been resolved outside the capture path, vs. a genuine raw-IP connection).
  • We dogfood our own action. Every real-work job in this repo (CI, release,
    eBPF agent, FIM self-test) now runs Legion Runner as its first step (@v1,
    audit) — our CI is hardened by the product it ships.
  • Docs-only PRs skip the build/test matrix (and the release it gates) via
    paths-ignore; a docs-passthrough workflow reports the same check names
    green so required checks stay satisfied and README edits stay mergeable
    without burning CI minutes.
  • Our CI now captures names. The dogfooded harden steps run with
    dns-capture: true (still audit — monitor, don't firewall), so job summaries
    show real destination and package-repository names instead of bare IPs, and the
    capture path is exercised on every run. Safe now that @v1 (>= 1.0.35) carries
    the teardown + systemd-resolved fixes.

v1.0.37

13 Jun 20:16

Choose a tag to compare

Added

  • Curated egress presets (allowed-presets): opt-in per-ecosystem allowlists
    (npm, yarn, pnpm, pip, pypi, cargo, rust, go, maven, gradle, nuget, apt, debian,
    docker) so block mode "just works" for common toolchains without hand-listing
    endpoints. e.g. allowed-presets: "cargo, apt". Unit-tested.
  • Download integrity verification: the action verifies the legionr-bpf /
    legionr-fim release binaries against a .sha256 sidecar before running them
    (the release now attaches the checksums), and fails closed — an
    unverified/corrupted/tampered download is rejected and the action degrades
    instead of executing it.
  • learned-baseline input (default true): in block mode, also allow
    destinations previously learned into the Actions cache. Set false to enforce
    ONLY the explicit allowlist (inline + policy-file + GitHub) with no cache
    read/write — used by the enforce self-test for deterministic deny.
  • File-integrity / tamper detection (Rust legionr-fim agent): snapshots
    high-value tamper targets at job start (credential/config files, .git
    config + hooks, and checked-out source) and diffs them at job end, surfacing
    anything overwritten, deleted, or chmod'd in the summary. Only sha256 hashes
    are stored — never contents. New inputs file-integrity (auto|off) and
    fim-extra-paths. file-integrity: auto downloads the agent from the latest
    release (plain stable Rust, no eBPF toolchain) and degrades to a silent skip
    if unavailable. Logic lives in legionr-core::fim (unit-tested); the binary
    is a release asset like legionr-bpf, built + attached by release.yml.
  • Package repositories roll-up (📦): the summary now classifies named
    outbound destinations into their ecosystem/registry (npm, PyPI, crates.io,
    apt, Docker, Go, NuGet, Maven, Gradle, RubyGems, Alpine, GitHub) and shows a
    Package repositories reached table — registry, ecosystem, connections, and
    the process that reached each. Supply-chain risk hides in which registries a
    build talks to, so we surface them directly instead of leaving you to read
    IPs. Bare IPs that never got a forward name get a coarse CDN/provider hint
    (Fastly/Cloudflare/GitHub) via CIDR match — honest about ambiguity (a shared
    CDN can't name a registry). Logic in action/repos.js, fully unit-tested.
  • Combined cross-job egress report (one summary for the whole run): GitHub
    has no run-level summary, so each job emits its captured egress as a JSON
    artifact (node action/report.js emit) and a final egress-report job merges
    them into a SINGLE table — which job + process reached what — with a package
    repositories roll-up and a per-job diagnostics block (render). Wired into CI;
    render is pure and unit-tested. Pairs with job-summary: false so the run
    shows one combined summary instead of one table per job.
  • job-summary input (default true): set false to keep monitoring and
    enforcement fully active but suppress the connections table in the job summary.
    Useful when many jobs in one workflow each run the action and you only want the
    table once (our own CI uses it so a run shows one table, not one per job).
  • Secure diagnostics line in the summary: reports which resolution path
    actually fired (forwarder on/off · captured DNS records N · getaddrinfo route … · named X/Y destinations) so a run that comes back as bare IPs is triagable.
    Secure by construction — only booleans, counts, and a fixed enum; never the
    upstream resolver IP, file paths, captured hostnames, or env values.

Fixed

  • Block mode no longer hangs the runner at teardown. applyEgressBlock
    installed a default-deny LEGION_EGRESS chain in OUTPUT and nothing ever
    removed it, so the runner's own completion call (to rotating GitHub-backend IPs
    not in the static seed) was dropped and the job spun until timeout. post()
    now tears the firewall down (removeEgressBlock).
  • Runner hang from leaked daemons. The post step left privileged background
    processes alive — eBPF agent, DNS forwarder — and the /proc monitor could
    wedge in a blocking ss subprocess. The monitor now reads /proc/net/tcp
    directly (no subprocess); daemons are reliably reaped.
  • No more spurious "could not resolve" annotations. Allowlist entries that
    are wildcard parents with no A record of their own (e.g. blob.core.windows.net,
    actions.githubusercontent.com) used to emit one CI warning annotation
    each on every run. They are benign: the action skips them, and their subdomains
    are still observed via PTR / DNS capture (and opened just-in-time in block
    mode). They are now collected into a single plain-text log line instead.
  • Docs/labels: the eBPF mechanism is a tracepoint on sys_enter_connect
    (not a "kprobe on tcp_connect"); the sampler is /proc-only (the "ss" fallback
    was removed). Corrected the runtime log line, summary label, and README.
  • Outbound connections showed as bare IPs when systemd-resolved owns
    getaddrinfo.
    The nsswitch reroute didn't always stick, so package-repo
    lookups bypassed the capture forwarder and were never named. The forwarder now
    targets the real upstream (systemd-resolved's actual servers, not the
    127.0.0.53 stub), and when the bypass is detected but the nsswitch reroute
    fails, Legion redirects systemd-resolved itself at the forwarder via a
    resolved.conf.d drop-in — verify-or-revert and restored on teardown, so
    it never breaks the job's DNS.
  • IPv4-mapped IPv6 destinations rendered as long expanded addresses
    (0000:0000:0000:0000:0000:ffff:HHHH:HHHH) in the summary. The /proc sampler
    emitted the expanded form while normalizeIp only collapsed the compressed
    ::ffff: form. Both now collapse to dotted IPv4 (and the v4/v6 tables dedupe).
    (Shipped in v1.0.35.)
  • Removed dead action/baseline.js; pinned release.yml checkout to v6.

Reliability

  • Tests for the paths that kept breaking: the firewall rule builders
    (egressBlockRules/egressUnblockRules — order, DNS-allow, DROP-last,
    OUTPUT-jump-removed-first), the checksum parser, the curated presets, and a
    full-stack PR gate that runs block + DNS-capture + eBPF and asserts the
    job finalizes (catches any teardown-hang regression), plus the package-repo
    classifier (host-suffix + CIDR matching). Action test count 19 → 37.

Changed

  • Removed em-dashes from the job-summary output (headers, the unresolved-host
    note, the enforce hint, and empty-cell placeholders) for plainer rendering.
  • Name more destinations: route glibc getaddrinfo (curl/apt/cargo/git)
    through the DNS-capture forwarder via an nsswitch.conf reroute, so hosts
    resolved by systemd-resolved (which ignores resolv.conf) are now captured
    and named — not just resolv.conf/c-ares callers. Health-checked and restored
    on teardown. (A connection to a hard-coded IP with no PTR still shows the IP —
    there is no name to resolve.)
  • More accurate "unresolved destination" note in the summary (a name may have
    been resolved outside the capture path, vs. a genuine raw-IP connection).
  • We dogfood our own action. Every real-work job in this repo (CI, release,
    eBPF agent, FIM self-test) now runs Legion Runner as its first step (@v1,
    audit) — our CI is hardened by the product it ships.
  • Docs-only PRs skip the build/test matrix (and the release it gates) via
    paths-ignore; a docs-passthrough workflow reports the same check names
    green so required checks stay satisfied and README edits stay mergeable
    without burning CI minutes.
  • Our CI now captures names. The dogfooded harden steps run with
    dns-capture: true (still audit — monitor, don't firewall), so job summaries
    show real destination and package-repository names instead of bare IPs, and the
    capture path is exercised on every run. Safe now that @v1 (>= 1.0.35) carries
    the teardown + systemd-resolved fixes.

v1.0.36

13 Jun 19:41
8824693

Choose a tag to compare

Added

  • Curated egress presets (allowed-presets): opt-in per-ecosystem allowlists
    (npm, yarn, pnpm, pip, pypi, cargo, rust, go, maven, gradle, nuget, apt, debian,
    docker) so block mode "just works" for common toolchains without hand-listing
    endpoints. e.g. allowed-presets: "cargo, apt". Unit-tested.
  • Download integrity verification: the action verifies the legionr-bpf /
    legionr-fim release binaries against a .sha256 sidecar before running them
    (the release now attaches the checksums), and fails closed — an
    unverified/corrupted/tampered download is rejected and the action degrades
    instead of executing it.
  • learned-baseline input (default true): in block mode, also allow
    destinations previously learned into the Actions cache. Set false to enforce
    ONLY the explicit allowlist (inline + policy-file + GitHub) with no cache
    read/write — used by the enforce self-test for deterministic deny.
  • File-integrity / tamper detection (Rust legionr-fim agent): snapshots
    high-value tamper targets at job start (credential/config files, .git
    config + hooks, and checked-out source) and diffs them at job end, surfacing
    anything overwritten, deleted, or chmod'd in the summary. Only sha256 hashes
    are stored — never contents. New inputs file-integrity (auto|off) and
    fim-extra-paths. file-integrity: auto downloads the agent from the latest
    release (plain stable Rust, no eBPF toolchain) and degrades to a silent skip
    if unavailable. Logic lives in legionr-core::fim (unit-tested); the binary
    is a release asset like legionr-bpf, built + attached by release.yml.
  • Package repositories roll-up (📦): the summary now classifies named
    outbound destinations into their ecosystem/registry (npm, PyPI, crates.io,
    apt, Docker, Go, NuGet, Maven, Gradle, RubyGems, Alpine, GitHub) and shows a
    Package repositories reached table — registry, ecosystem, connections, and
    the process that reached each. Supply-chain risk hides in which registries a
    build talks to, so we surface them directly instead of leaving you to read
    IPs. Bare IPs that never got a forward name get a coarse CDN/provider hint
    (Fastly/Cloudflare/GitHub) via CIDR match — honest about ambiguity (a shared
    CDN can't name a registry). Logic in action/repos.js, fully unit-tested.
  • Secure diagnostics line in the summary: reports which resolution path
    actually fired (forwarder on/off · captured DNS records N · getaddrinfo route … · named X/Y destinations) so a run that comes back as bare IPs is triagable.
    Secure by construction — only booleans, counts, and a fixed enum; never the
    upstream resolver IP, file paths, captured hostnames, or env values.

Fixed

  • Block mode no longer hangs the runner at teardown. applyEgressBlock
    installed a default-deny LEGION_EGRESS chain in OUTPUT and nothing ever
    removed it, so the runner's own completion call (to rotating GitHub-backend IPs
    not in the static seed) was dropped and the job spun until timeout. post()
    now tears the firewall down (removeEgressBlock).
  • Runner hang from leaked daemons. The post step left privileged background
    processes alive — eBPF agent, DNS forwarder — and the /proc monitor could
    wedge in a blocking ss subprocess. The monitor now reads /proc/net/tcp
    directly (no subprocess); daemons are reliably reaped.
  • No more spurious "could not resolve" annotations. Allowlist entries that
    are wildcard parents with no A record of their own (e.g. blob.core.windows.net,
    actions.githubusercontent.com) used to emit one CI warning annotation
    each on every run. They are benign: the action skips them, and their subdomains
    are still observed via PTR / DNS capture (and opened just-in-time in block
    mode). They are now collected into a single plain-text log line instead.
  • Docs/labels: the eBPF mechanism is a tracepoint on sys_enter_connect
    (not a "kprobe on tcp_connect"); the sampler is /proc-only (the "ss" fallback
    was removed). Corrected the runtime log line, summary label, and README.
  • Outbound connections showed as bare IPs when systemd-resolved owns
    getaddrinfo.
    The nsswitch reroute didn't always stick, so package-repo
    lookups bypassed the capture forwarder and were never named. The forwarder now
    targets the real upstream (systemd-resolved's actual servers, not the
    127.0.0.53 stub), and when the bypass is detected but the nsswitch reroute
    fails, Legion redirects systemd-resolved itself at the forwarder via a
    resolved.conf.d drop-in — verify-or-revert and restored on teardown, so
    it never breaks the job's DNS.
  • IPv4-mapped IPv6 destinations rendered as long expanded addresses
    (0000:0000:0000:0000:0000:ffff:HHHH:HHHH) in the summary. The /proc sampler
    emitted the expanded form while normalizeIp only collapsed the compressed
    ::ffff: form. Both now collapse to dotted IPv4 (and the v4/v6 tables dedupe).
    (Shipped in v1.0.35.)
  • Removed dead action/baseline.js; pinned release.yml checkout to v6.

Reliability

  • Tests for the paths that kept breaking: the firewall rule builders
    (egressBlockRules/egressUnblockRules — order, DNS-allow, DROP-last,
    OUTPUT-jump-removed-first), the checksum parser, the curated presets, and a
    full-stack PR gate that runs block + DNS-capture + eBPF and asserts the
    job finalizes (catches any teardown-hang regression), plus the package-repo
    classifier (host-suffix + CIDR matching). Action test count 19 → 37.

Changed

  • Removed em-dashes from the job-summary output (headers, the unresolved-host
    note, the enforce hint, and empty-cell placeholders) for plainer rendering.
  • Name more destinations: route glibc getaddrinfo (curl/apt/cargo/git)
    through the DNS-capture forwarder via an nsswitch.conf reroute, so hosts
    resolved by systemd-resolved (which ignores resolv.conf) are now captured
    and named — not just resolv.conf/c-ares callers. Health-checked and restored
    on teardown. (A connection to a hard-coded IP with no PTR still shows the IP —
    there is no name to resolve.)
  • More accurate "unresolved destination" note in the summary (a name may have
    been resolved outside the capture path, vs. a genuine raw-IP connection).
  • We dogfood our own action. Every real-work job in this repo (CI, release,
    eBPF agent, FIM self-test) now runs Legion Runner as its first step (@v1,
    audit) — our CI is hardened by the product it ships.
  • Docs-only PRs skip the build/test matrix (and the release it gates) via
    paths-ignore; a docs-passthrough workflow reports the same check names
    green so required checks stay satisfied and README edits stay mergeable
    without burning CI minutes.
  • Our CI now captures names. The dogfooded harden steps run with
    dns-capture: true (still audit — monitor, don't firewall), so job summaries
    show real destination and package-repository names instead of bare IPs, and the
    capture path is exercised on every run. Safe now that @v1 (>= 1.0.35) carries
    the teardown + systemd-resolved fixes.

v1.0.35

13 Jun 19:33
6e55912

Choose a tag to compare

Added

  • Curated egress presets (allowed-presets): opt-in per-ecosystem allowlists
    (npm, yarn, pnpm, pip, pypi, cargo, rust, go, maven, gradle, nuget, apt, debian,
    docker) so block mode "just works" for common toolchains without hand-listing
    endpoints. e.g. allowed-presets: "cargo, apt". Unit-tested.
  • Download integrity verification: the action verifies the legionr-bpf /
    legionr-fim release binaries against a .sha256 sidecar before running them
    (the release now attaches the checksums), and fails closed — an
    unverified/corrupted/tampered download is rejected and the action degrades
    instead of executing it.
  • learned-baseline input (default true): in block mode, also allow
    destinations previously learned into the Actions cache. Set false to enforce
    ONLY the explicit allowlist (inline + policy-file + GitHub) with no cache
    read/write — used by the enforce self-test for deterministic deny.
  • File-integrity / tamper detection (Rust legionr-fim agent): snapshots
    high-value tamper targets at job start (credential/config files, .git
    config + hooks, and checked-out source) and diffs them at job end, surfacing
    anything overwritten, deleted, or chmod'd in the summary. Only sha256 hashes
    are stored — never contents. New inputs file-integrity (auto|off) and
    fim-extra-paths. file-integrity: auto downloads the agent from the latest
    release (plain stable Rust, no eBPF toolchain) and degrades to a silent skip
    if unavailable. Logic lives in legionr-core::fim (unit-tested); the binary
    is a release asset like legionr-bpf, built + attached by release.yml.
  • Package repositories roll-up (📦): the summary now classifies named
    outbound destinations into their ecosystem/registry (npm, PyPI, crates.io,
    apt, Docker, Go, NuGet, Maven, Gradle, RubyGems, Alpine, GitHub) and shows a
    Package repositories reached table — registry, ecosystem, connections, and
    the process that reached each. Supply-chain risk hides in which registries a
    build talks to, so we surface them directly instead of leaving you to read
    IPs. Bare IPs that never got a forward name get a coarse CDN/provider hint
    (Fastly/Cloudflare/GitHub) via CIDR match — honest about ambiguity (a shared
    CDN can't name a registry). Logic in action/repos.js, fully unit-tested.
  • Secure diagnostics line in the summary: reports which resolution path
    actually fired (forwarder on/off · captured DNS records N · getaddrinfo route … · named X/Y destinations) so a run that comes back as bare IPs is triagable.
    Secure by construction — only booleans, counts, and a fixed enum; never the
    upstream resolver IP, file paths, captured hostnames, or env values.

Fixed

  • Block mode no longer hangs the runner at teardown. applyEgressBlock
    installed a default-deny LEGION_EGRESS chain in OUTPUT and nothing ever
    removed it, so the runner's own completion call (to rotating GitHub-backend IPs
    not in the static seed) was dropped and the job spun until timeout. post()
    now tears the firewall down (removeEgressBlock).
  • Runner hang from leaked daemons. The post step left privileged background
    processes alive — eBPF agent, DNS forwarder — and the /proc monitor could
    wedge in a blocking ss subprocess. The monitor now reads /proc/net/tcp
    directly (no subprocess); daemons are reliably reaped.
  • No more spurious "could not resolve" annotations. Allowlist entries that
    are wildcard parents with no A record of their own (e.g. blob.core.windows.net,
    actions.githubusercontent.com) used to emit one CI warning annotation
    each on every run. They are benign: the action skips them, and their subdomains
    are still observed via PTR / DNS capture (and opened just-in-time in block
    mode). They are now collected into a single plain-text log line instead.
  • Docs/labels: the eBPF mechanism is a tracepoint on sys_enter_connect
    (not a "kprobe on tcp_connect"); the sampler is /proc-only (the "ss" fallback
    was removed). Corrected the runtime log line, summary label, and README.
  • Outbound connections showed as bare IPs when systemd-resolved owns
    getaddrinfo.
    The nsswitch reroute didn't always stick, so package-repo
    lookups bypassed the capture forwarder and were never named. The forwarder now
    targets the real upstream (systemd-resolved's actual servers, not the
    127.0.0.53 stub), and when the bypass is detected but the nsswitch reroute
    fails, Legion redirects systemd-resolved itself at the forwarder via a
    resolved.conf.d drop-in — verify-or-revert and restored on teardown, so
    it never breaks the job's DNS.
  • Removed dead action/baseline.js; pinned release.yml checkout to v6.

Reliability

  • Tests for the paths that kept breaking: the firewall rule builders
    (egressBlockRules/egressUnblockRules — order, DNS-allow, DROP-last,
    OUTPUT-jump-removed-first), the checksum parser, the curated presets, and a
    full-stack PR gate that runs block + DNS-capture + eBPF and asserts the
    job finalizes (catches any teardown-hang regression), plus the package-repo
    classifier (host-suffix + CIDR matching). Action test count 19 → 37.

Changed

  • Removed em-dashes from the job-summary output (headers, the unresolved-host
    note, the enforce hint, and empty-cell placeholders) for plainer rendering.
  • Name more destinations: route glibc getaddrinfo (curl/apt/cargo/git)
    through the DNS-capture forwarder via an nsswitch.conf reroute, so hosts
    resolved by systemd-resolved (which ignores resolv.conf) are now captured
    and named — not just resolv.conf/c-ares callers. Health-checked and restored
    on teardown. (A connection to a hard-coded IP with no PTR still shows the IP —
    there is no name to resolve.)
  • More accurate "unresolved destination" note in the summary (a name may have
    been resolved outside the capture path, vs. a genuine raw-IP connection).
  • We dogfood our own action. Every real-work job in this repo (CI, release,
    eBPF agent, FIM self-test) now runs Legion Runner as its first step (@v1,
    audit) — our CI is hardened by the product it ships.
  • Docs-only PRs skip the build/test matrix (and the release it gates) via
    paths-ignore; a docs-passthrough workflow reports the same check names
    green so required checks stay satisfied and README edits stay mergeable
    without burning CI minutes.

v1.0.34

13 Jun 19:13
04c1945

Choose a tag to compare

Added

  • Curated egress presets (allowed-presets): opt-in per-ecosystem allowlists
    (npm, yarn, pnpm, pip, pypi, cargo, rust, go, maven, gradle, nuget, apt, debian,
    docker) so block mode "just works" for common toolchains without hand-listing
    endpoints. e.g. allowed-presets: "cargo, apt". Unit-tested.
  • Download integrity verification: the action verifies the legionr-bpf /
    legionr-fim release binaries against a .sha256 sidecar before running them
    (the release now attaches the checksums), and fails closed — an
    unverified/corrupted/tampered download is rejected and the action degrades
    instead of executing it.
  • learned-baseline input (default true): in block mode, also allow
    destinations previously learned into the Actions cache. Set false to enforce
    ONLY the explicit allowlist (inline + policy-file + GitHub) with no cache
    read/write — used by the enforce self-test for deterministic deny.
  • File-integrity / tamper detection (Rust legionr-fim agent): snapshots
    high-value tamper targets at job start (credential/config files, .git
    config + hooks, and checked-out source) and diffs them at job end, surfacing
    anything overwritten, deleted, or chmod'd in the summary. Only sha256 hashes
    are stored — never contents. New inputs file-integrity (auto|off) and
    fim-extra-paths. file-integrity: auto downloads the agent from the latest
    release (plain stable Rust, no eBPF toolchain) and degrades to a silent skip
    if unavailable. Logic lives in legionr-core::fim (unit-tested); the binary
    is a release asset like legionr-bpf, built + attached by release.yml.

Fixed

  • Block mode no longer hangs the runner at teardown. applyEgressBlock
    installed a default-deny LEGION_EGRESS chain in OUTPUT and nothing ever
    removed it, so the runner's own completion call (to rotating GitHub-backend IPs
    not in the static seed) was dropped and the job spun until timeout. post()
    now tears the firewall down (removeEgressBlock).
  • Runner hang from leaked daemons. The post step left privileged background
    processes alive — eBPF agent, DNS forwarder — and the /proc monitor could
    wedge in a blocking ss subprocess. The monitor now reads /proc/net/tcp
    directly (no subprocess); daemons are reliably reaped.
  • No more spurious "could not resolve" annotations. Allowlist entries that
    are wildcard parents with no A record of their own (e.g. blob.core.windows.net,
    actions.githubusercontent.com) used to emit one CI warning annotation
    each on every run. They are benign: the action skips them, and their subdomains
    are still observed via PTR / DNS capture (and opened just-in-time in block
    mode). They are now collected into a single plain-text log line instead.
  • Docs/labels: the eBPF mechanism is a tracepoint on sys_enter_connect
    (not a "kprobe on tcp_connect"); the sampler is /proc-only (the "ss" fallback
    was removed). Corrected the runtime log line, summary label, and README.
  • Removed dead action/baseline.js; pinned release.yml checkout to v6.

Reliability

  • Tests for the paths that kept breaking: the firewall rule builders
    (egressBlockRules/egressUnblockRules — order, DNS-allow, DROP-last,
    OUTPUT-jump-removed-first), the checksum parser, the curated presets, and a
    full-stack PR gate that runs block + DNS-capture + eBPF and asserts the
    job finalizes (catches any teardown-hang regression). Action test count 19 → 30.

Changed

  • Removed em-dashes from the job-summary output (headers, the unresolved-host
    note, the enforce hint, and empty-cell placeholders) for plainer rendering.
  • Name more destinations: route glibc getaddrinfo (curl/apt/cargo/git)
    through the DNS-capture forwarder via an nsswitch.conf reroute, so hosts
    resolved by systemd-resolved (which ignores resolv.conf) are now captured
    and named — not just resolv.conf/c-ares callers. Health-checked and restored
    on teardown. (A connection to a hard-coded IP with no PTR still shows the IP —
    there is no name to resolve.)
  • More accurate "unresolved destination" note in the summary (a name may have
    been resolved outside the capture path, vs. a genuine raw-IP connection).

v1.0.33

10 Jun 16:50
1d78182

Choose a tag to compare

Added

  • Curated egress presets (allowed-presets): opt-in per-ecosystem allowlists
    (npm, yarn, pnpm, pip, pypi, cargo, rust, go, maven, gradle, nuget, apt, debian,
    docker) so block mode "just works" for common toolchains without hand-listing
    endpoints. e.g. allowed-presets: "cargo, apt". Unit-tested.
  • Download integrity verification: the action verifies the legionr-bpf /
    legionr-fim release binaries against a .sha256 sidecar before running them
    (the release now attaches the checksums), and fails closed — an
    unverified/corrupted/tampered download is rejected and the action degrades
    instead of executing it.
  • learned-baseline input (default true): in block mode, also allow
    destinations previously learned into the Actions cache. Set false to enforce
    ONLY the explicit allowlist (inline + policy-file + GitHub) with no cache
    read/write — used by the enforce self-test for deterministic deny.
  • File-integrity / tamper detection (Rust legionr-fim agent): snapshots
    high-value tamper targets at job start (credential/config files, .git
    config + hooks, and checked-out source) and diffs them at job end, surfacing
    anything overwritten, deleted, or chmod'd in the summary. Only sha256 hashes
    are stored — never contents. New inputs file-integrity (auto|off) and
    fim-extra-paths. file-integrity: auto downloads the agent from the latest
    release (plain stable Rust, no eBPF toolchain) and degrades to a silent skip
    if unavailable. Logic lives in legionr-core::fim (unit-tested); the binary
    is a release asset like legionr-bpf, built + attached by release.yml.

Fixed

  • Block mode no longer hangs the runner at teardown. applyEgressBlock
    installed a default-deny LEGION_EGRESS chain in OUTPUT and nothing ever
    removed it, so the runner's own completion call (to rotating GitHub-backend IPs
    not in the static seed) was dropped and the job spun until timeout. post()
    now tears the firewall down (removeEgressBlock).
  • Runner hang from leaked daemons. The post step left privileged background
    processes alive — eBPF agent, DNS forwarder — and the /proc monitor could
    wedge in a blocking ss subprocess. The monitor now reads /proc/net/tcp
    directly (no subprocess); daemons are reliably reaped.
  • No more spurious "could not resolve" annotations. Allowlist entries that
    are wildcard parents with no A record of their own (e.g. blob.core.windows.net,
    actions.githubusercontent.com) used to emit one CI warning annotation
    each on every run. They are benign: the action skips them, and their subdomains
    are still observed via PTR / DNS capture (and opened just-in-time in block
    mode). They are now collected into a single plain-text log line instead.
  • Docs/labels: the eBPF mechanism is a tracepoint on sys_enter_connect
    (not a "kprobe on tcp_connect"); the sampler is /proc-only (the "ss" fallback
    was removed). Corrected the runtime log line, summary label, and README.
  • Removed dead action/baseline.js; pinned release.yml checkout to v6.

Reliability

  • Tests for the paths that kept breaking: the firewall rule builders
    (egressBlockRules/egressUnblockRules — order, DNS-allow, DROP-last,
    OUTPUT-jump-removed-first), the checksum parser, the curated presets, and a
    full-stack PR gate that runs block + DNS-capture + eBPF and asserts the
    job finalizes (catches any teardown-hang regression). Action test count 19 → 30.

Changed

  • Removed em-dashes from the job-summary output (headers, the unresolved-host
    note, the enforce hint, and empty-cell placeholders) for plainer rendering.
  • Name more destinations: route glibc getaddrinfo (curl/apt/cargo/git)
    through the DNS-capture forwarder via an nsswitch.conf reroute, so hosts
    resolved by systemd-resolved (which ignores resolv.conf) are now captured
    and named — not just resolv.conf/c-ares callers. Health-checked and restored
    on teardown. (A connection to a hard-coded IP with no PTR still shows the IP —
    there is no name to resolve.)
  • More accurate "unresolved destination" note in the summary (a name may have
    been resolved outside the capture path, vs. a genuine raw-IP connection).

v1.0.32

10 Jun 12:46
ac565c1

Choose a tag to compare

Added

  • Curated egress presets (allowed-presets): opt-in per-ecosystem allowlists
    (npm, yarn, pnpm, pip, pypi, cargo, rust, go, maven, gradle, nuget, apt, debian,
    docker) so block mode "just works" for common toolchains without hand-listing
    endpoints. e.g. allowed-presets: "cargo, apt". Unit-tested.
  • Download integrity verification: the action verifies the legionr-bpf /
    legionr-fim release binaries against a .sha256 sidecar before running them
    (the release now attaches the checksums), and fails closed — an
    unverified/corrupted/tampered download is rejected and the action degrades
    instead of executing it.
  • learned-baseline input (default true): in block mode, also allow
    destinations previously learned into the Actions cache. Set false to enforce
    ONLY the explicit allowlist (inline + policy-file + GitHub) with no cache
    read/write — used by the enforce self-test for deterministic deny.
  • File-integrity / tamper detection (Rust legionr-fim agent): snapshots
    high-value tamper targets at job start (credential/config files, .git
    config + hooks, and checked-out source) and diffs them at job end, surfacing
    anything overwritten, deleted, or chmod'd in the summary. Only sha256 hashes
    are stored — never contents. New inputs file-integrity (auto|off) and
    fim-extra-paths. file-integrity: auto downloads the agent from the latest
    release (plain stable Rust, no eBPF toolchain) and degrades to a silent skip
    if unavailable. Logic lives in legionr-core::fim (unit-tested); the binary
    is a release asset like legionr-bpf, built + attached by release.yml.

Fixed

  • Block mode no longer hangs the runner at teardown. applyEgressBlock
    installed a default-deny LEGION_EGRESS chain in OUTPUT and nothing ever
    removed it, so the runner's own completion call (to rotating GitHub-backend IPs
    not in the static seed) was dropped and the job spun until timeout. post()
    now tears the firewall down (removeEgressBlock).
  • Runner hang from leaked daemons. The post step left privileged background
    processes alive — eBPF agent, DNS forwarder — and the /proc monitor could
    wedge in a blocking ss subprocess. The monitor now reads /proc/net/tcp
    directly (no subprocess); daemons are reliably reaped.
  • No more spurious "could not resolve" annotations. Allowlist entries that
    are wildcard parents with no A record of their own (e.g. blob.core.windows.net,
    actions.githubusercontent.com) used to emit one CI warning annotation
    each on every run. They are benign: the action skips them, and their subdomains
    are still observed via PTR / DNS capture (and opened just-in-time in block
    mode). They are now collected into a single plain-text log line instead.
  • Docs/labels: the eBPF mechanism is a tracepoint on sys_enter_connect
    (not a "kprobe on tcp_connect"); the sampler is /proc-only (the "ss" fallback
    was removed). Corrected the runtime log line, summary label, and README.
  • Removed dead action/baseline.js; pinned release.yml checkout to v6.

Reliability

  • Tests for the paths that kept breaking: the firewall rule builders
    (egressBlockRules/egressUnblockRules — order, DNS-allow, DROP-last,
    OUTPUT-jump-removed-first), the checksum parser, the curated presets, and a
    full-stack PR gate that runs block + DNS-capture + eBPF and asserts the
    job finalizes (catches any teardown-hang regression). Action test count 19 → 30.

Changed

  • Removed em-dashes from the job-summary output (headers, the unresolved-host
    note, the enforce hint, and empty-cell placeholders) for plainer rendering.
  • Name more destinations: route glibc getaddrinfo (curl/apt/cargo/git)
    through the DNS-capture forwarder via an nsswitch.conf reroute, so hosts
    resolved by systemd-resolved (which ignores resolv.conf) are now captured
    and named — not just resolv.conf/c-ares callers. Health-checked and restored
    on teardown. (A connection to a hard-coded IP with no PTR still shows the IP —
    there is no name to resolve.)
  • More accurate "unresolved destination" note in the summary (a name may have
    been resolved outside the capture path, vs. a genuine raw-IP connection).

v1.0.31

10 Jun 12:44
cfbac88

Choose a tag to compare

Added

  • Curated egress presets (allowed-presets): opt-in per-ecosystem allowlists
    (npm, yarn, pnpm, pip, pypi, cargo, rust, go, maven, gradle, nuget, apt, debian,
    docker) so block mode "just works" for common toolchains without hand-listing
    endpoints. e.g. allowed-presets: "cargo, apt". Unit-tested.
  • Download integrity verification: the action verifies the legionr-bpf /
    legionr-fim release binaries against a .sha256 sidecar before running them
    (the release now attaches the checksums), and fails closed — an
    unverified/corrupted/tampered download is rejected and the action degrades
    instead of executing it.
  • learned-baseline input (default true): in block mode, also allow
    destinations previously learned into the Actions cache. Set false to enforce
    ONLY the explicit allowlist (inline + policy-file + GitHub) with no cache
    read/write — used by the enforce self-test for deterministic deny.
  • File-integrity / tamper detection (Rust legionr-fim agent): snapshots
    high-value tamper targets at job start (credential/config files, .git
    config + hooks, and checked-out source) and diffs them at job end, surfacing
    anything overwritten, deleted, or chmod'd in the summary. Only sha256 hashes
    are stored — never contents. New inputs file-integrity (auto|off) and
    fim-extra-paths. file-integrity: auto downloads the agent from the latest
    release (plain stable Rust, no eBPF toolchain) and degrades to a silent skip
    if unavailable. Logic lives in legionr-core::fim (unit-tested); the binary
    is a release asset like legionr-bpf, built + attached by release.yml.

Fixed

  • Block mode no longer hangs the runner at teardown. applyEgressBlock
    installed a default-deny LEGION_EGRESS chain in OUTPUT and nothing ever
    removed it, so the runner's own completion call (to rotating GitHub-backend IPs
    not in the static seed) was dropped and the job spun until timeout. post()
    now tears the firewall down (removeEgressBlock).
  • Runner hang from leaked daemons. The post step left privileged background
    processes alive — eBPF agent, DNS forwarder — and the /proc monitor could
    wedge in a blocking ss subprocess. The monitor now reads /proc/net/tcp
    directly (no subprocess); daemons are reliably reaped.
  • No more spurious "could not resolve" annotations. Allowlist entries that
    are wildcard parents with no A record of their own (e.g. blob.core.windows.net,
    actions.githubusercontent.com) used to emit one CI warning annotation
    each on every run. They are benign: the action skips them, and their subdomains
    are still observed via PTR / DNS capture (and opened just-in-time in block
    mode). They are now collected into a single plain-text log line instead.
  • Docs/labels: the eBPF mechanism is a tracepoint on sys_enter_connect
    (not a "kprobe on tcp_connect"); the sampler is /proc-only (the "ss" fallback
    was removed). Corrected the runtime log line, summary label, and README.
  • Removed dead action/baseline.js; pinned release.yml checkout to v6.

Reliability

  • Tests for the paths that kept breaking: the firewall rule builders
    (egressBlockRules/egressUnblockRules — order, DNS-allow, DROP-last,
    OUTPUT-jump-removed-first), the checksum parser, the curated presets, and a
    full-stack PR gate that runs block + DNS-capture + eBPF and asserts the
    job finalizes (catches any teardown-hang regression). Action test count 19 → 30.

Changed

  • Removed em-dashes from the job-summary output (headers, the unresolved-host
    note, the enforce hint, and empty-cell placeholders) for plainer rendering.
  • Name more destinations: route glibc getaddrinfo (curl/apt/cargo/git)
    through the DNS-capture forwarder via an nsswitch.conf reroute, so hosts
    resolved by systemd-resolved (which ignores resolv.conf) are now captured
    and named — not just resolv.conf/c-ares callers. Health-checked and restored
    on teardown. (A connection to a hard-coded IP with no PTR still shows the IP —
    there is no name to resolve.)
  • More accurate "unresolved destination" note in the summary (a name may have
    been resolved outside the capture path, vs. a genuine raw-IP connection).

v1.0.30

10 Jun 12:42
5ff572c

Choose a tag to compare

Added

  • Curated egress presets (allowed-presets): opt-in per-ecosystem allowlists
    (npm, yarn, pnpm, pip, pypi, cargo, rust, go, maven, gradle, nuget, apt, debian,
    docker) so block mode "just works" for common toolchains without hand-listing
    endpoints. e.g. allowed-presets: "cargo, apt". Unit-tested.
  • Download integrity verification: the action verifies the legionr-bpf /
    legionr-fim release binaries against a .sha256 sidecar before running them
    (the release now attaches the checksums), and fails closed — an
    unverified/corrupted/tampered download is rejected and the action degrades
    instead of executing it.
  • learned-baseline input (default true): in block mode, also allow
    destinations previously learned into the Actions cache. Set false to enforce
    ONLY the explicit allowlist (inline + policy-file + GitHub) with no cache
    read/write — used by the enforce self-test for deterministic deny.
  • File-integrity / tamper detection (Rust legionr-fim agent): snapshots
    high-value tamper targets at job start (credential/config files, .git
    config + hooks, and checked-out source) and diffs them at job end, surfacing
    anything overwritten, deleted, or chmod'd in the summary. Only sha256 hashes
    are stored — never contents. New inputs file-integrity (auto|off) and
    fim-extra-paths. file-integrity: auto downloads the agent from the latest
    release (plain stable Rust, no eBPF toolchain) and degrades to a silent skip
    if unavailable. Logic lives in legionr-core::fim (unit-tested); the binary
    is a release asset like legionr-bpf, built + attached by release.yml.

Fixed

  • Block mode no longer hangs the runner at teardown. applyEgressBlock
    installed a default-deny LEGION_EGRESS chain in OUTPUT and nothing ever
    removed it, so the runner's own completion call (to rotating GitHub-backend IPs
    not in the static seed) was dropped and the job spun until timeout. post()
    now tears the firewall down (removeEgressBlock).
  • Runner hang from leaked daemons. The post step left privileged background
    processes alive — eBPF agent, DNS forwarder — and the /proc monitor could
    wedge in a blocking ss subprocess. The monitor now reads /proc/net/tcp
    directly (no subprocess); daemons are reliably reaped.
  • No more spurious "could not resolve" annotations. Allowlist entries that
    are wildcard parents with no A record of their own (e.g. blob.core.windows.net,
    actions.githubusercontent.com) used to emit one CI warning annotation
    each on every run. They are benign: the action skips them, and their subdomains
    are still observed via PTR / DNS capture (and opened just-in-time in block
    mode). They are now collected into a single plain-text log line instead.
  • Docs/labels: the eBPF mechanism is a tracepoint on sys_enter_connect
    (not a "kprobe on tcp_connect"); the sampler is /proc-only (the "ss" fallback
    was removed). Corrected the runtime log line, summary label, and README.
  • Removed dead action/baseline.js; pinned release.yml checkout to v6.

Reliability

  • Tests for the paths that kept breaking: the firewall rule builders
    (egressBlockRules/egressUnblockRules — order, DNS-allow, DROP-last,
    OUTPUT-jump-removed-first), the checksum parser, the curated presets, and a
    full-stack PR gate that runs block + DNS-capture + eBPF and asserts the
    job finalizes (catches any teardown-hang regression). Action test count 19 → 30.

Changed

  • Removed em-dashes from the job-summary output (headers, the unresolved-host
    note, the enforce hint, and empty-cell placeholders) for plainer rendering.
  • Name more destinations: route glibc getaddrinfo (curl/apt/cargo/git)
    through the DNS-capture forwarder via an nsswitch.conf reroute, so hosts
    resolved by systemd-resolved (which ignores resolv.conf) are now captured
    and named — not just resolv.conf/c-ares callers. Health-checked and restored
    on teardown. (A connection to a hard-coded IP with no PTR still shows the IP —
    there is no name to resolve.)
  • More accurate "unresolved destination" note in the summary (a name may have
    been resolved outside the capture path, vs. a genuine raw-IP connection).

v1.0.29

10 Jun 03:58
0cf847c

Choose a tag to compare

Added

  • Curated egress presets (allowed-presets): opt-in per-ecosystem allowlists
    (npm, yarn, pnpm, pip, pypi, cargo, rust, go, maven, gradle, nuget, apt, debian,
    docker) so block mode "just works" for common toolchains without hand-listing
    endpoints. e.g. allowed-presets: "cargo, apt". Unit-tested.
  • Download integrity verification: the action verifies the legionr-bpf /
    legionr-fim release binaries against a .sha256 sidecar before running them
    (the release now attaches the checksums), and fails closed — an
    unverified/corrupted/tampered download is rejected and the action degrades
    instead of executing it.
  • learned-baseline input (default true): in block mode, also allow
    destinations previously learned into the Actions cache. Set false to enforce
    ONLY the explicit allowlist (inline + policy-file + GitHub) with no cache
    read/write — used by the enforce self-test for deterministic deny.
  • File-integrity / tamper detection (Rust legionr-fim agent): snapshots
    high-value tamper targets at job start (credential/config files, .git
    config + hooks, and checked-out source) and diffs them at job end, surfacing
    anything overwritten, deleted, or chmod'd in the summary. Only sha256 hashes
    are stored — never contents. New inputs file-integrity (auto|off) and
    fim-extra-paths. file-integrity: auto downloads the agent from the latest
    release (plain stable Rust, no eBPF toolchain) and degrades to a silent skip
    if unavailable. Logic lives in legionr-core::fim (unit-tested); the binary
    is a release asset like legionr-bpf, built + attached by release.yml.

Fixed

  • Block mode no longer hangs the runner at teardown. applyEgressBlock
    installed a default-deny LEGION_EGRESS chain in OUTPUT and nothing ever
    removed it, so the runner's own completion call (to rotating GitHub-backend IPs
    not in the static seed) was dropped and the job spun until timeout. post()
    now tears the firewall down (removeEgressBlock).
  • Runner hang from leaked daemons. The post step left privileged background
    processes alive — eBPF agent, DNS forwarder — and the /proc monitor could
    wedge in a blocking ss subprocess. The monitor now reads /proc/net/tcp
    directly (no subprocess); daemons are reliably reaped.
  • No more spurious "could not resolve" annotations. Allowlist entries that
    are wildcard parents with no A record of their own (e.g. blob.core.windows.net,
    actions.githubusercontent.com) used to emit one CI warning annotation
    each on every run. They are benign: the action skips them, and their subdomains
    are still observed via PTR / DNS capture (and opened just-in-time in block
    mode). They are now collected into a single plain-text log line instead.
  • Docs/labels: the eBPF mechanism is a tracepoint on sys_enter_connect
    (not a "kprobe on tcp_connect"); the sampler is /proc-only (the "ss" fallback
    was removed). Corrected the runtime log line, summary label, and README.
  • Removed dead action/baseline.js; pinned release.yml checkout to v6.

Reliability

  • Tests for the paths that kept breaking: the firewall rule builders
    (egressBlockRules/egressUnblockRules — order, DNS-allow, DROP-last,
    OUTPUT-jump-removed-first), the checksum parser, the curated presets, and a
    full-stack PR gate that runs block + DNS-capture + eBPF and asserts the
    job finalizes (catches any teardown-hang regression). Action test count 19 → 30.

Changed

  • Removed em-dashes from the job-summary output (headers, the unresolved-host
    note, the enforce hint, and empty-cell placeholders) for plainer rendering.
  • Name more destinations: route glibc getaddrinfo (curl/apt/cargo/git)
    through the DNS-capture forwarder via an nsswitch.conf reroute, so hosts
    resolved by systemd-resolved (which ignores resolv.conf) are now captured
    and named — not just resolv.conf/c-ares callers. Health-checked and restored
    on teardown. (A connection to a hard-coded IP with no PTR still shows the IP —
    there is no name to resolve.)
  • More accurate "unresolved destination" note in the summary (a name may have
    been resolved outside the capture path, vs. a genuine raw-IP connection).