Skip to content

Releases

blackice 1.0.7

Choose a tag to compare

@ValentinTorassa ValentinTorassa released this 11 Nov 18:02
Immutable release. Only release title and notes can be modified.

New:

  • blackice deny --all forgets every approval, with one Touch ID. What waits keeps waiting, remembered denials
    stay, and connections already let through go on. Since 1.0.6 approvals outlast a stop, an upgrade and a restart
    of the Mac, and blackice deny takes back one app and host at a time: nothing cleared them all. It works while
    blackice is on; off, it says so before asking for Touch ID, and the saved approvals wait for the next start.
  • An upgrade no longer starts the new filter beside the old one. On macOS 27.0.1 the kernel detaches a stopped
    filter's sockets one by one and can stay at one of them: at a UDP socket whose flow table had two flows and has
    one left, which it takes again and again until the socket closes or gets another flow, and at an ICMP socket
    bound to no address, until a packet reaches it. While it does, the filter stays registered. A filter started
    meanwhile was held with it: every new socket was attached to both, the one going gave no verdict, and no new
    connection got through, lookups included. On 10 and 11 November four of five upgrades were held that way, for
    16.3, 12.6, 4.1 and 25.8 s, the last on a socket of Tailscale's network extension. A filter going with none in
    its place holds nothing up: in a blackice stop held 10 s a lookup a second went on in 0.21 s or less. So when
    macOS runs an older build, start turns the filter off, waits until the kernel has let the old one go, and only
    then replaces the extension and turns the filter on. Connections are not checked meanwhile, and start says so
    once the wait has lasted a second, with the process whose socket macOS waits on: It waits on a UDP socket of mDNSResponder (pid 474): it is done when that socket closes or sends to another address. On 11 November an
    upgrade with a test socket kept open 10 s went that way: the old filter was held 10.2 s, the new one started 0.3
    s after the kernel let the old one go, and a lookup a second took 0.13 s or less throughout; a test socket of the
    same kind had held the upgrade before it for 10.3 s, with lookups waiting up to 9.8 s. While it waits, start
    sends an ICMP echo request to 127.0.0.1, 0.3 s in and every 2 s after. Three upgrades that day were held at an
    ICMP socket of macOS's own symptomsd, for 58.9, 47.3 and 116.0 s, longer than start then waited (30 s twice, 90
    s once): the kernel let go only 28.5, 17.0 and 25.8 s after the new filter had started and stopped every new
    connection. An echo reply goes to every ICMP socket bound to no address: in a blackice stop held 8 s at that
    socket one ping of 127.0.0.1 ended it within 0.01 s, and in the next upgrade, at that socket again, the kernel
    let go 0.4 s after the filter was turned off, the new filter started half a second later, start said nothing of
    it, and a lookup a second took 0.13 s or less. The request does not leave the Mac. stop sends it too, for 3 s
    at most, so that the kernel is not left at it: in a stop at that socket it let go in 0.4 s. A start after a
    stop waits as an upgrade does. A UDP socket is not ended this way: there start waits 90 s at most, then goes
    on anyway, and it and blackice status (a network line) say that no new connection gets through until macOS is
    done. blackice reads the kernel's lists of filters and of the sockets attached to them, which any process may
    read; where they are not laid out as on macOS 27.0.1, start upgrades as before and says nothing.

Fixed:

  • blackice stop says that macOS asks next. After Touch ID, macOS has an administrator confirm removing the
    network extension in a dialog of its own and does not tell the app that asked, so stop waited on that dialog
    having said nothing: 78 s on 11 November. It now says macOS now asks you to confirm removing the network extension: follow its dialog. before it asks macOS, unless macOS answers that it has no extension of blackice's
    enabled.
  • Every connection the filter drops is counted, and delayed once its app has had 64 dropped within 2 s, wherever
    it is dropped. 1.0.6 counted only a connection dropped as it started: one dropped once its TLS hello was read,
    after a lookup, or for a full table was dropped at once. On 10 November, on 1.0.6, node had 7,586 connections
    dropped as denied;remembered in 55 minutes after the agent denied it registry.npmjs.org, about 1,300 of them
    within one second three times, and none was delayed.
  • A host that is an alias of the name its address came under passes the address check. The filter reads the
    system resolver's answers and keeps, for each address, the name the answer was for. The resolver keeps each
    CNAME of a chain for its own TTL and, once the addresses at the end have expired, asks only for the name they
    belong to, so that answer names the end of the chain and not the host the app asked for. The filter then looked
    the host up itself and could get other addresses. On 10 November outlook.office365.com was three CNAMEs of 300,
    300 and 45 s and eight addresses of 15 s, with eight others at each query: a mail CLI's approved connections
    there were dropped as address_mismatch in 10 of 239 runs, 9 of them among the 34 runs in which the resolver
    sent three queries, and each time the CLI got through on a later address. The filter now keeps the CNAMEs it
    reads, each as long as the resolver keeps it, and follows them back from an address's name.
  • The names and CNAMEs the filter reads last as long as the resolver answers from them. mDNSResponder keeps a
    record for its TTL, a quarter more and 2 s, and for at least 15 s: asked for outlook.office365.com twice a
    second on 10 November, it asked the network again 77 s after a CNAME of 60 s, 39 s after one of 30 s and 15 s
    after addresses of 3 to 9 s. The filter kept a name for its TTL, so the name went here while the resolver
    still handed its addresses to apps, and the filter looked the host up itself. With the CNAMEs followed back
    and nothing else, the mail CLI's listing every 10 s still failed on address_mismatch in 2 of 180 runs. In
    the first the resolver followed a CNAME 322 s old whose TTL was 295 s, and gave the CLI the addresses under
    one name and the filter's lookup, 65 ms later, those under another. Names and CNAMEs now last that long and
    5 s more, for a TTL counted as 30 s at least, and at most 5 minutes for a name and a day for a CNAME. With
    both, the listing ran 180 times on 10 November with no connection dropped. On 11 November, with Tailscale's
    resolver, it ran 180 times and none failed, and in one run six connections to login.microsoftonline.com were
    dropped as address_mismatch within 6 ms, 5 ms after the resolver had that host's addresses anew; the run's
    next connection passed. Why the answer the filter read then did not cover the host is not known. An alias is
    dropped this way less often, not never.
  • A burst of DNS lookups no longer leaves the answers after it unread. The filter read at most 256 of the
    resolver's DNS flows at once, counted each for two more minutes once it had let it go, and did not read a new
    flow when the table was full. On 10 November a network came back after an outage with 267 resolver flows in
    two minutes, 156 of them in one second: the table stayed full for two minutes, the filter looked
    outlook.office365.com up itself five times in them, and the second of the two failed runs above was one. A
    test's 611 resolver flows in half a minute did the same to the next host looked up. The filter now reads 512 flows
    at once, a new flow takes the place of the one quiet the longest, and the flows let go are counted apart, at
    most 4,096. A flow let go to make room with a query still unanswered is counted, each minute, in control dns_flows_unread.
  • A restart of the filter no longer cuts a VPN whose client a host rule covers. A filter that has just started
    can be given a UDP socket that was open before it as a new flow, and it has read no DNS answer yet, so it looked
    the flow's host up itself with the flow paused. For a VPN client's own flow that lookup goes into the tunnel the
    paused flow carries, and neither can finish. On 10 November an upgrade did that to the AWS VPN Client, which a
    rule for *.amazonaws.com covers: the lookup of its server's name got no answer for 30 s, the flow was dropped
    as address_mismatch, and with the Mac's DNS server behind the dead tunnel no name resolved for most of the next
    8 minutes. The filter now saves the app, host, address and port of each UDP flow it lets through on a host's
    name, and for two minutes after a start a UDP flow of the same app to the same address and port is decided on
    that name with no lookup. Rules and approvals decide it as always. A pair is used for 30 days after the last
    time a lookup or a DNS answer gave the host that address; a flow that passes on a saved pair does not renew it.
    The first start of 1.0.7 has nothing saved: disconnect such a VPN before upgrading from 1.0.6.
  • A TCP connection to a link-local IPv6 address is asked about. It was dropped as no_host with no event, since
    the address could be any host on any link without its interface: on 10 November identityservicesd's
    connections to a peer on a utun interface all were. macOS gives the filter no interface for a connection's
    destination: one to fe80::dead:beef%en0 arrives as fe80::dead:beef (macOS 27.0.1). So the event names the
    address alone, and an approval covers it on every interface, as an address entry of a rule does. approve --host and deny --host take a link-local address with no interface; until now they took one only with an
    interface, and that matched no connection.
  • npm's folder is no longer its script. node writes npm's title over its own command line (npm run build where
    node, npm's path and its arguments were), so the argument the script was in reads as empty, and the filter
    joined that to ...
Read more

blackice 1.0.6

Choose a tag to compare

@ValentinTorassa ValentinTorassa released this 10 Nov 16:23
Immutable release. Only release title and notes can be modified.

Fixed:

  • An app that opens its next connection as soon as one is dropped no longer fills the log, and a loop of TCP
    connections is slowed. Once an app has had 64 TCP connections dropped within 2 s, its next ones that would be
    dropped wait 2 s first, at most 64 at a time, and after 30 s the app is counted afresh. UDP flows never wait:
    past the same count they get 8 log lines per 2 s. Drops with no log line are counted (drops_not_logged). On
    10 November, after a restart, identityservicesd tried a link-local peer about 430 times a second: the filter
    used half a core, other connections were slow to start, and the system log dropped most of the filter's lines,
    so blackice report counted 6,122 connections where there had been more than 200,000. 1.0.5 and earlier have
    the fault. A loop of UDP flows, or of more than 64 TCP connections at once, still costs the filter its work
    on each flow. Only connections dropped as they start are counted and delayed: one dropped once its TLS hello
    was read, or after a lookup, is dropped at once as before.

Changed:

  • Approvals outlast blackice stop, an upgrade and a restart of the Mac. The filter saves them to a file only root
    can read or write, and each comes back with what it had left, less the time gone by. Until now they lived only in
    the filter's memory, so each stop or upgrade cleared them and the agent was asked again about apps and hosts it
    had approved. Remembered denials are still forgotten at a stop.
  • One app takes at most 256 of the 1,024 places for connections waiting for blackice's lookup of their host
    (lookup_capacity_app). On 10 November one app's burst took them all while lookups were slow, and another app's
    connection that a rule allowed was dropped as lookup_capacity.

Upgrading from 1.0.5: brew upgrade --cask blackice, or the install script with --force, then blackice start.
This upgrade clears the approvals one last time, since 1.0.5 does not save them; from 1.0.6 on they come back.

Blackice.app 1.0.6, build 40, built from f432e06, signed with the Developer ID of team
M294LK997M, notarized and stapled. The tag is two commits later: they add this zip's SHA-256 to the install script,
the record of the test (evidence/P5-21), a check to the fault-test script and a limit to this changelog entry.
Blackice-1.0.6.zip SHA-256: 43dbe4277707c0d6ebe8f58ebed9419a49cda5eb7b8cf991d28654441c976a94

blackice 1.0.5

Choose a tag to compare

@ValentinTorassa ValentinTorassa released this 10 Nov 03:39
Immutable release. Only release title and notes can be modified.

Changed:

  • In ask mode a connection that a rule, the Apple services table or an approval allows by name, but whose address
    is not among the host's answers even after a fresh lookup, is dropped at once as address_mismatch, as in
    block-all. Until now it raised an event, and the agent's approval could not let it through: in 18 hours on one
    Mac, 100 such events, every one approved and its connection dropped anyway, each holding one of the 4 hook slots
    for about 3 s. An app and host that nothing allows yet are still asked about, since the approval counts from the
    app's next connection.

Upgrading from 1.0.4: brew upgrade --cask blackice, or the install script with --force, then blackice start.
Approvals live in the filter's memory, so the upgrade clears them and your agent is asked again about apps and hosts
it had approved.

Blackice.app 1.0.5, build 37, built from cdfc234, signed with the Developer ID of team
M294LK997M, notarized and stapled. The tag is one commit later: it adds this zip's SHA-256 to the install script and
the record of the test (evidence/P5-20).
Blackice-1.0.5.zip SHA-256: 7b73e48ea44902e362e677e36561ffffb8e08c2a76b966e3d7aabb8c51ac1fee

blackice 1.0.4

Choose a tag to compare

@ValentinTorassa ValentinTorassa released this 09 Nov 15:13
Immutable release. Only release title and notes can be modified.

Fixed:

  • A hook the Mac slept through is killed when the Mac wakes past its event's deadline. Until now its timeout counted
    only time awake, so it ran on for up to its whole timeout after its event had timed out, held a slot, and its answer
    was refused: on 8 November four of them held all 4 slots for 12 minutes after a sleep, and the next 4 events got no
    hook.
  • A host's answers 2 s old or older that miss the address a connection goes to are looked up again before they
    count as a mismatch. Until now answers were kept 30 s, and a CDN that rotates its addresses every few seconds
    missed: about 2 % of a mail CLI's approved connections to outlook.office365.com were dropped as
    approved;address_mismatch, each after an event the agent approved again.

Upgrading from 1.0.3: brew upgrade --cask blackice, or the install script with --force, then blackice start.

Blackice.app 1.0.4, build 36, built from 946cea0, signed with the Developer ID of team
M294LK997M, notarized and stapled. The tag is one commit later: it adds this zip's SHA-256 to the install script and
the record of the test (evidence/P5-19).
Blackice-1.0.4.zip SHA-256: 7e8678f2d79322bb27361e6f290dc0c62efd1f651c40f3c39587c99d76b95a23

blackice 1.0.3

Choose a tag to compare

@ValentinTorassa ValentinTorassa released this 07 Nov 22:48
Immutable release. Only release title and notes can be modified.

New:

  • An [[allow]] rule can name ad hoc signed code, such as Homebrew's gh, by cdhash = "…" in place of app and
    team. It covers that one build, so after an upgrade ask mode asks again. It never counts for an interpreter (python,
    node, the shells and others), whose connections are decided as without it (;interpreter). Until now ad hoc code
    could only be approved, for at most 24 hours.
  • blackice approve takes --host up to 16 times: one app's hosts under one Touch ID, whose dialog names them all.
    Until now each host took its own command and Touch ID, seven for a mail CLI that signs in to Google and Microsoft.
  • blackice approve --save also writes a lasting [[allow]] rule for the app and hosts in blackice.toml, which counts
    from the next start. Until now an approval was the only way to let ad hoc code through, for at most 24 hours.
  • For an interpreter (python, node, a shell…), the event, pending and the event log (app_script) show the script it
    runs, as app.script, so the agent and your notes can tell one tool from another. Until now every uv or npm CLI
    looked the same: its interpreter.
  • An interpreter's approvals and denials are bound to its script: the agent's answer covers the script its event
    named, each script waits on its own event, and pending says which script an approval covers. approve --script
    and deny --script name one script, and the menu's answers take the event's; without it your answer covers them
    all. Until now an approval of an interpreter let every script it runs reach that host: once the agent approved the
    AWS CLI's python to STS for a day, any script of that python reached STS for the day.

Changed:

  • blackice needs macOS 14.4 or later: the daemon starts program hooks with a launch requirement, so macOS runs one
    only when its signature names the hook's app and team, with a Developer ID (or, with no team, Apple's own
    code). Until now the daemon started it suspended and checked it before letting it run, and a program that stopped
    the hook and let it run first got past the check. With SIP off macOS does not check launch requirements, and a
    hook that exits before the daemon can read its code is refused.
  • A hook that names an agent's instructions gets no files: the instructions and the owner's notes come on its
    command line (Claude Code, Codex, fx) or in its environment (opencode), the event on stdin, and it runs in
    /var/empty. Claude Code runs with no tools at all. A hook from 1.0 runs the same way. Until now all three were
    files in a folder any program running as you could change while the agent read them.
  • The daemon looks in Codex's and fx's homes before it starts their hook and once it exits. A file there that they
    would load as instructions, such as AGENTS.md or a skill, keeps the hook from starting, or voids its answer
    (agent_home in the log and the menu), and the connection waits for you. Until now nothing looked. This only
    detects the file, and can miss one added and removed while the hook runs.

Fixed:

  • When nobody answers Touch ID within 2 minutes, the command says so ("No answer to Touch ID within 2 minutes"), and
    a finger that does not match says that. Until now each said "Touch ID was cancelled", as if Cancel had been pressed.
  • pending lists the apps and hosts denied a moment ago first, each app's hosts with one blackice approve line,
    and status counts them. Until now pending opened with "Nothing waiting" while a remembered denial dropped the
    app's connections without an event, so there seemed to be nothing to approve.
  • An approval that lands while the configuration changes no longer lets its connections through under the new one.
    Until now a switch to block-all at that moment missed them, and the old approval let them go.
  • One app holds at most 64 of the 256 connections waiting for their TLS hello, and a new one replaces the oldest
    that has sent nothing (evicted_quiet). Until now one app could fill them all, and other apps' TLS connections
    were dropped as hold_capacity.
  • In ask mode one app takes at most 64 of the 256 events (ask_capacity_app). Until now one app could fill the
    table, and other apps' connections were dropped with no event.
  • A second switch of who answers, from a file that names no one, keeps the first one's record. Until now the next
    start dropped both switches.
  • agent connect saves blackice.toml under a lock, and stops a sign-in check that gives no answer in 30 s. Until
    now two runs could overwrite each other, and a stalled check left the Mac in accept-all.
  • npx blackice install checks --force and the installed build again just before replacing the app. Until now
    another install meanwhile, even of a newer build, was replaced.
  • pending, report and the menu call code known only by its cdhash cdhash 8fbec897, not ad hoc: since 1.0.1
    that includes signed programs in ~/Documents or ~/Downloads.
  • report names the capacity drops in words.

Upgrading from 1.0.2: brew upgrade --cask blackice, or the install script with --force, then blackice start. blackice now needs macOS 14.4 or later.

Blackice.app 1.0.3, build 35, built from f54be2c, signed with the Developer ID of team
M294LK997M, notarized and stapled. The tag is one commit later: it adds this zip's SHA-256 to the install script and
the record of the test (evidence/P5-18).
Blackice-1.0.3.zip SHA-256: 4424c192708e74171424d0e874baf006383386daf022afbbb80240904e4ee012

blackice 1.0.2

Choose a tag to compare

@ValentinTorassa ValentinTorassa released this 06 Nov 21:17
Immutable release. Only release title and notes can be modified.

Fixed:

  • Approvals, remembered denials and connections waiting for an answer count the time the Mac spends asleep. Until now
    the filter's clock stopped during sleep, so an approval for 1 hour was still valid after 2 hours asleep.
  • A program whose path has a line break or another control character no longer keeps blackice pending and the menu
    from listing what waits: its event leaves the path out. Until now one such event made the whole list unreadable
    until it expired.
  • blackice report escapes the reasons in its estimate for ask mode, as it already did app names, so a program that
    writes to the filter's log cannot put terminal control sequences in the report.
  • The install script checks the signature of a Blackice.app already installed at the same version before it writes
    the blackice command, and stops when it is not signed with the Developer ID of team M294LK997M. Until now it
    trusted the version in the app's Info.plist.

Upgrading from 1.0.1: brew upgrade --cask blackice, or the install script with --force, then blackice start.

Blackice.app 1.0.2, build 32, built from 414b229, signed with the Developer ID of team
M294LK997M, notarized and stapled. The tag is two commits later: they add this zip's SHA-256 to the install script and
the record of the test (evidence/P5-17).
Blackice-1.0.2.zip SHA-256: 7386b3d7e462baaa2e47178237004f9e6685783aa58843868e3973a941995576

blackice 1.0.1

Choose a tag to compare

@ValentinTorassa ValentinTorassa released this 06 Nov 20:09
Immutable release. Only release title and notes can be modified.

Fixed:

  • A program that runs from a folder macOS keeps the filter from reading (~/Documents, ~/Desktop, ~/Downloads,
    iCloud Drive, removable and network volumes) is named by the cdhash of the code the kernel loaded, as ad hoc code is,
    so ask mode asks about it. Until now the filter could not check its signature and dropped its connections as
    unknown_identity, with no question: a tool a project keeps in node_modules, such as the Codex that deepsec runs,
    could not connect in ask mode. A rule still cannot name such a program, and block-all still drops it.

Upgrading from 1.0.0: brew upgrade --cask blackice, or the install script with --force, then blackice start.

Blackice.app 1.0.1, build 31, built from 8cf9127, signed with the Developer ID of team
M294LK997M, notarized and stapled. The tag is two commits later: they add this zip's SHA-256 to the install script and
the record of the test (evidence/P5-16).
Blackice-1.0.1.zip SHA-256: 91b81cfb2ebb0e2748aad6b70d7a5291f868105ed27833c64a01e8a2e6bcb2ce

blackice 1.0.0

Choose a tag to compare

@ValentinTorassa ValentinTorassa released this 06 Nov 18:59
Immutable release. Only release title and notes can be modified.

The first stable release: a blackice.toml that works in 1.0 keeps working in every 1.x. It has run on one Mac,
with macOS 27.0.1; macOS 14 and 15 are not tested yet.

Changed:

  • The first blackice start ticks the VPN tunnels macOS has activated, even when they are off, and the common apps
    running (browsers, chat and video-call apps), matched by signing identifier and Team ID. Proxy extensions, such as
    a VPN's split tunneling, and the other apps are listed unticked.
  • The agents read the owner's notes as additions: only a note that says to deny something narrows what their
    instructions approve.
  • Toolchain folders (~/.local/bin, uv's Pythons, ~/.cargo/bin, nvm, pyenv and others) count as ordinary places to
    run from, and GitHub Actions' log storage joins the source hosts developer tools may reach.
  • The install script pins the zip's SHA-256 and checks the Developer ID requirement of team M294LK997M. The install
    guide says how to read the script first, or run the tag's copy from GitHub.

Fixed:

  • A picked app's rule covers its helpers signed by the same team. Until now Chrome's and Slack's connections, which
    come from their helpers, still waited in ask mode.
  • blackice report says when macOS dropped the filter's log lines, instead of printing low counts.

Upgrading from 0.4.7: brew upgrade --cask blackice, or the install script with --force, then blackice start.

Blackice.app 1.0.0, build 30, built from b37de79, signed with the Developer ID of team
M294LK997M, notarized and stapled. The tag is two commits later: they add this zip's SHA-256 to the install script and the record of the release tests (evidence/P5-15).
Blackice-1.0.0.zip SHA-256: 911b5fab9dd2f8e7f41a996a4dd041e3df28cf7c646fb47780802d7c7fec11d1

blackice 0.4.7

Choose a tag to compare

@ValentinTorassa ValentinTorassa released this 06 Nov 15:01

blackice start loads the file's mode again after a test file, and the agents let developer tools reach developer
infrastructure.

Changed:

  • The agents' instructions say when to approve a developer tool. node, python3, curl, git and the
    package managers and builds they run are approved, for 1 to 8 hours, when they reach a package registry, a source
    host or a public asset CDN such as Google Fonts, and still denied anywhere else. Until now Claude Code and fx
    denied curl to the npm registry. Agents already connected use the new text after the upgrade, with nothing to
    run: the daemon writes the instructions for each event.

Fixed:

  • blackice start keeps a mode or answerer switched with blackice mode or the menu only while the file is the one
    started then, unchanged. Until now it compared only the file's mode line, so starting another file, or the same
    file with a rule more, in the same mode kept the switch: after a test file was switched to accept-all, the owner's
    own ask-mode file started in accept-all.

Upgrading from 0.4.6: brew upgrade --cask blackice or the install script with --force, then blackice start.
Nothing else changes.

blackice 0.4.6

Choose a tag to compare

@ValentinTorassa ValentinTorassa released this 06 Nov 14:31

blackice start opens blackice in the menu bar, and the site and docs are a third as long.

Changed:

  • Each start that turns blackice on opens the menu bar app when it is not running, as it already did for a menu
    from before an upgrade. The first start asks only whether it also opens at each login.
  • The site keeps four pages: Introduction (with How it works), Install, Configuration and CLI. The changelog page
    points here. The install script prints five lines.

Upgrading from 0.4.5: brew upgrade --cask blackice or the install script with --force, then blackice start.
Nothing else changes.