fix(notify): use https:// in the ntfy notify command - #218
Merged
Conversation
`meta skuid != <agent> accept` never matches kernel-generated packets -- ICMPv6 MLD/ND and DHCP renewal carry no owning socket -- so they fell through to the drop rule. Observed live: MLD listener reports (TYPE=131) from the box's own link-local address were dropped and alarmed on. Restructure to jump on a positive UID match, so only packets the agent actually owns are filtered. Also from the same run: drop link-local multicast WITHOUT the log prefix (mDNS/MLD is discovery noise, and an alarm that cries wolf gets ignored); add the v6 set and the lo/established rules the sketch omitted; and record that statsig.anthropic.com has no A record -- Claude Code's telemetry goes to api.statsig.com / statsigapi.net / events.statsigapi.net / featureassets.org. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Working files from a real provisioning run on bare-metal Ubuntu 26.04, so the next box does not rebuild them from the sketches in HARDENING.md: nftables egress fence, egress alarm, auditd rules, divergence tripwire, and the user-unit-safe systemd drop-in. The README records the reasoning, which is the part that cost time: - The fence restricts protocol/port, not destination. IP-set allowlisting was built, tested and abandoned -- GitHub and Google LBs rotate faster than a DNS refresh can track, and it dropped real work. HARDENING.md already lists GitHub as a sanctioned exfil channel, so an allowlist that must include GitHub could never close that path anyway. - Jump on a positive skuid match; `skuid != <uid> accept` never matches kernel-generated packets and silently filters the host's own stack. - The alarm runs as root, keys its cooldown on destination AND port, and deliberately avoids `set -e`/`pipefail` -- an address with no PTR made getent fail and killed the follower, losing events silently. - ufw is left alone; nftables.service stays disabled so it cannot flush it. - The systemd drop-in is the user-unit-safe subset; the full block crash-loops a --user unit with 218/CAPABILITIES. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cloudflare retired the per-application SSH CA the runbook depends on. On a current account, Access -> Service auth -> SSH -> Generate certificate is disabled and `cloudflared access ssh-gen` returns "Bad request, please create CA for application" -- the client never gets a certificate, so no server-side change can fix it. Verified with the correct account CA trusted, ruling out a CA mismatch first. Steps 1-3 of the runbook are unaffected and still correct: loopback-only hardened sshd, the dedicated outbound-only tunnel, the Access application. Documents the successor and its real cost: Access for Infrastructure requires the WARP client on every device in Traffic + DNS mode, uses a private network route rather than a public hostname ingress, and offers no browser-rendered SSH terminal. Also records the self-managed-keys alternative and why it is a worse trade -- it needs WARP too while reintroducing long-lived authorized_keys, reversing the "nothing long-lived to steal" property the design exists for. Two field notes worth keeping: - Verify the installed CA is the one Cloudflare signs with. On a live run the dashboard-pasted key did not match the account gateway CA (SHA256:UdAX7Pben vs SHA256:K3EDSsU0olUb), and a cert signed by an untrusted CA fails exactly like a client-side fault. - If sshd is loopback-only, register the target as 127.0.0.1 so cloudflared dials its own loopback and the property survives; the LAN-IP fallback needs a second ListenAddress and exposes sshd to the LAN. provision.md Phase 8 rewritten accordingly, plus: run cloudflared as a system unit (the admin account often has Linger=no, and remote access must not depend on a login session), watch for an orphaned DNS record on the chosen hostname, and audit the account's other tunnels as standing ingress paths. Phase 9 additions: trigger the notification rather than assuming it, check for non-loopback listeners, and the claude-rc trap -- arm-loop.sh passes --spawn but the server still prompts "Enable Remote Control? (y/n)" inside tmux, so the unit reports active while parked on a prompt. Also: state the unattended posture out loud before signing off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The self-adapter's notify command passed a scheme-less "ntfy.sh/$topic" to curl, which defaults a bare host to http://. On a box with a kernel egress fence permitting only DNS and TCP 443, every loop notification therefore hit port 80 and was dropped -- silently. The loop believed it had notified; the operator received nothing. Found only because the egress alarm reported the blocked attempt: "TCP -> 159.203.148.75:80". Without that alarm the loop would have run unattended with a dead feedback channel and no indication. notify.sh's own usage example carried the same scheme-less form, so anyone copying it inherits the bug on any egress-filtered host. Both fixed. Worth noting the general shape, which recurred repeatedly on this run: a layer that looks correct in its configuration and does nothing in practice. curl's http:// default is invisible until something blocks port 80. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Collaborator
|
fix(notify): use https:// in the ntfy notify command (not yet reviewed) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The self-adapter's notify command passed a scheme-less
ntfy.sh/$topictocurl, which defaults a bare host to http://. On a box with a kernel egress fence permitting only DNS and TCP 443, every loop notification hit port 80 and was dropped — silently. The loop believed it had notified; nothing arrived.Found only because the egress alarm reported the blocked attempt (
TCP -> 159.203.148.75:80). Without that alarm, the loop would have run unattended with a dead feedback channel and no indication anything was wrong — on a box whose operator was about to leave it alone.notify.sh's own usage example carried the same scheme-less form, so anyone copying it inherits the bug on any egress-filtered host. Both fixed.The general shape recurred throughout this provisioning run: a layer that looks correct in its configuration and does nothing in practice. Inert
Write()deny rules,is-activeon a crash-looping unit, an alarm dying on a missing PTR,claude-rcparked on a prompt while reportingactive— and nowcurl'shttp://default, invisible until something blocks port 80.🤖 Generated with Claude Code