Repository navigation
Releases
v0.7.0 — The Public Suffix List whole, and a wide wildcard reported rather than refused
Breaking, in the relaxing direction: a wide wildcard is now reported, not refused
Since 0.5.0, allow: ['*.s3.amazonaws.com'] failed the boot. It now compiles, with an advisory
on every start, and every decision it clears is recorded as one a wide entry cleared.
The reasoning: an operator who writes that entry has still narrowed egress enormously against no
allow list at all, and it is a simple thing to write. That call is theirs. What the advisory owes
them is the part the entry does not say — which namespace it opens, what else that admits, which
revision decided it, and the one line that records the decision as made:
dsh-netguard: allow entry '*.s3.amazonaws.com' is a wildcard over "s3.amazonaws.com", a namespace
anyone can register a name under. It is permitted and every decision it clears is recorded as one
a wide entry cleared.
Why: <which table carries it, in that table's own words>.
From: Public Suffix List VERSION 2026-09-03_19-51-30_UTC, retrieved …
Narrower: '*.<your-name>.s3.amazonaws.com', or '<your-name>.s3.amazonaws.com' for that one host.
To record this namespace as reviewed and stop this message, add it to the dsh-netguard row:
- id: dsh-netguard
config:
allowWideWildcards: ['s3.amazonaws.com']
To fail the load on an entry like this instead, set wideWildcards: 'refuse' on the same row.
wideWildcards: 'refuse' restores the 0.5.0 behaviour exactly. allowWideWildcards keeps one
meaning under both postures — this namespace has been reviewed and accepted — and never changes
the grant.
The Public Suffix List, carried whole
VERSION 2026-09-03_19-51-30_UTC, commit b952f046: 5,493 ICANN + 3,372 private suffixes of two
labels or more, plus 287 wildcard-rule parents and 8 exception rules. One-label rules are dropped
because the grammar refuses a wildcard over a TLD before this data is read.
Nothing fetches — not at build, not at load, not per request. scripts/regenerate-suffixes.mjs
takes a local .dat path. The module exports the revision and the advisory quotes it.
Two things the previous selection could not do: exception rules are honoured
(*.city.kawasaki.jp compiles), and the gap measured in 0.6.0 closes exactly — 1,899 private-section
rules and 4,160 ICANN bases compiled silently then; zero do now.
What to expect
6,162 more bases are wide than in 0.6.0 (3,124 → 9,286). 401 of those are wide only as an
ancestor, and that set includes domains a single organisation owns — *.salesforce.com,
*.linode.com, *.render.com, *.fastly.net, *.fedoraproject.org. Under the shipped warn each
is one advisory line. Under refuse each is a boot failure, so setting refuse with this table
is a stricter posture than refuse was in 0.6.0.
Tenant-scoped entries are unaffected: 12 of 12 measured shapes still compile, as do 7 of 7 ordinary
corporate names. Two patterns printed in our own docs moved — *.oslo.no and *.ny.us.
Still open, verified against this release: *.sharepoint.com, *.googleusercontent.com,
*.gitpod.io, *.statuspage.io, *.zoom.us compile with nothing said about them. The list is
populated by request, and a namespace whose owner never asked is absent from it.
v0.6.0 — Command-text egress guarded, twenty more wildcards refused
Breaking: twenty more namespaces refused as wildcards, and bash egress is now decided
*.slack.com and nineteen others stop loading
The Public Suffix List is populated by request. A namespace whose owner never filed is absent
from it no matter how freely it hands out hostnames — so *.slack.com (every workspace on the
internet), *.atlassian.net (every Jira tenant) and *.zendesk.com (every help centre) all
compiled onto an allow list, and no selection from the PSL would ever have refused them.
src/observed-namespaces.ts carries twenty names admitted under a rule you can re-run yourself:
an unclaimed label resolves under the name, and the operator answers that hostname with a
per-hostname vacancy. Each entry records the probe date, the HTTP status, and the vacancy text.
Full list: app.github.dev, atlassian.net, freshdesk.com, glitch.me, itch.io, koyeb.app,
lhr.life, livejournal.com, modal.run, neocities.org, slack.com, sourceforge.io,
squarespace.com, substack.com, surge.sh, tumblr.com, tunnelto.dev, weebly.com,
wordpress.com, zendesk.com.
Tenant-scoped entries are unaffected. *.mycompany.atlassian.net, myteam.slack.com and
acme.freshdesk.com all still compile — your own label sits between the wildcard and the
namespace. Deny entries are unaffected in every case: a too-wide deny refuses more.
If this breaks your boot
Narrow the pattern to your own tenant, or open the namespace deliberately:
- id: dsh-netguard
config:
allow: ['*.myteam.slack.com'] # your tenant
# or, to accept what it means:
allowWideWildcards: ['slack.com']bash and run_code destinations are now decided
ctx.tools.guard() is handed every tool execution, and this plugin was answering for web_fetch
and web_search only. curl inside a bash command reached the network with no decision and no
record — including to 127.0.0.1, which the address floor would have refused had it been asked.
Destinations written with a scheme are now read out of the command text before the spawn and
decided against the same host policy, one OCSF record each. This is the upgrade that can start
refusing work: a build script that fetches from a host outside your allow list now fails.
Names are never resolved — a DNS lookup inside a synchronous guard is I/O the agent loop waits on,
and is itself egress for a command that has not run. Bare and site: spellings stay behind
shell.readTextHosts, off by default on measured false positives: hostsNamedIn reads
main.cc, Makefile.in and socket.io as hostnames.
shell.enabled: false restores the previous behaviour exactly.
Fixed
docs/enforcement.md recommended **.githubusercontent.com as a starting allow entry while the
loader has refused it since 0.5.0. Replaced with raw.githubusercontent.com and
objects.githubusercontent.com, both verified reachable.
Still open
1,827 of the PSL's private-section rules and 4,168 of its multi-label ICANN bases still compile.
Closing that means carrying both sections whole, which is a trusted-computing-base decision
recorded in ADR §29 and not taken here.
v0.5.0 — wildcards over self-service namespaces refused
Breaking: a wildcard over a self-service namespace is refused at load
allow: ['*.s3.amazonaws.com'] reads like a grant to one vendor. It is a grant to every bucket
anyone can register — an attacker creates a name in that namespace in about ninety seconds and the
allowlist admits it. The same holds for *.pages.dev, *.workers.dev, *.vercel.app,
*.blob.core.windows.net, *.githubusercontent.com, and for country-registry namespaces like
*.co.ke and *.com.pk.
The grammar has always refused a pattern that could be read two ways — *.com and *.co.uk were
already load-time errors. The table simply stopped short. It now covers the namespaces the Public
Suffix List's own owners declare as handed out to unrelated parties, sourced from the list at
version 2026-09-02_06-03-53_UTC.
Deny entries are unaffected. A too-wide deny refuses more, which is the safe direction.
If this breaks your boot
The profile will not start until the entry is fixed, and the error names both remedies with
pasteable YAML. Either narrow the pattern:
- id: dsh-netguard
config:
allow:
- '*.mybucket.s3.amazonaws.com' # or 'mybucket.s3.amazonaws.com' for that one host…or open the namespace deliberately, by name:
- id: dsh-netguard
config:
allow: ['*.s3.amazonaws.com']
allowWideWildcards: ['s3.amazonaws.com']allowWideWildcards is deployment configuration only — a repo-local policyFile may tighten a
policy, never open one. Naming one namespace does not open any other.
Full upgrade notes: https://charlotten7.github.io/dsh-netguard/upgrading.html
Also in this release
- The tool-call join now survives redirects. It was failing on every hop, so a redirected
request recorded nocorrelation_uidand the cross-package join withdsh-ocsf-forwarder
answered nothing for it. - Host-memory persistence was rewriting the whole sidecar on every decision — 2.137 ms per
decision on the agent's event loop, now 0.001 ms, with the same file on disk. - An existing spool left world-writable by a wider umask is now corrected rather than kept.
- The harness peer range matches the rest of the suite, so all four plugins install together.