Makes the TUI usable on small terminals and hardens the supply chain. Adds an 80x24 minimum with frame clamping and a resize prompt, alternate-screen rendering, scroll-to-focus in the rule editor and scrolling in the rule view, and a compact chain header; fixes quitting flushing the live ruleset and the rule list rendering every rule twice; and lands a security/CI wave — OpenSSF Scorecard, CodeQL, Codecov, Go fuzz targets, SHA-pinned actions, and hardened workflow token permissions.
Added
- Code coverage reporting via Codecov. The CI
build-and-testjob now writes a unit coverage profile (go test -race -covermode=atomic -coverprofile) and uploads it to Codecov (flagunit); theintegration-testjob uploads its live-netlink coverage profile too (flagintegration). Uploads use theCODECOV_TOKENrepo secret and are non-fatal (fail_ci_if_error: false), so a Codecov hiccup never blocks the build gate. A coverage badge was added to the README header. - OpenSSF Scorecard supply-chain security analysis (
.github/workflows/scorecard.yml). Theossf/scorecard-actionscores the repo against security best-practice checks (pinned dependencies, signed releases, token-permission hygiene, dangerous-workflow patterns, SAST, branch protection, …), uploads a SARIF report to the GitHub code-scanning (Security) tab, and publishes the result to the OpenSSF API (publish_results: true) that backs the new README Scorecard badge. Runs on pushes tomain, a weekly schedule, and branch-protection-rule changes. - CodeQL static analysis (SAST) for the Go code (
.github/workflows/codeql.yml). GitHub's CodeQL engine analyzes nftui's own source (autobuild mode, Go toolchain fromgo.mod) and reports findings to the code-scanning (Security) tab — complementing thegovulncheckjob, which covers known-vulnerable dependency calls rather than first-party bug/security patterns. Runs on push / PR tomainanddevelopplus a weekly schedule; workflow-level permissions are read-only, withsecurity-events: writegranted only on the analysis job. Closes the OpenSSF Scorecard SAST finding. - Go native fuzz targets for the package's least-trusted string parsers (
nft/fuzz_test.go):FuzzValidateIdentifierasserts the injection-safety invariant (any accepted name is non-empty, within the length bound, letter-led, and free of nft-script metacharacters) andFuzzParseSetElementKeydrives the set-element key parser across every key type, asserting it never panics and only ever returns correctly-sized element bytes (coveringparseIP4/parseIP6/parseInetService/parseUintBE/cidrToRange/dashRangeToBytes). The seed corpus runs under the normalgo testgate; extended runs usego test -run='^$' -fuzz=… ./nft/. Continues the E-series "don't crash on untrusted input" hardening and closes the OpenSSF Scorecard Fuzzing finding. examples/example-host-firewall.conf— a hardened, good-practice single-host firewall (not a router): oneinettable covering IPv4+IPv6, stateful filtering with invalid-drop, loopback anti-spoofing, ICMPv6 NDP allowed, rate-limited ping and SSH, exposed services (22/80/443) with default-deny inbound, unrestricted outbound, and forwarding denied. Verified withnft -c -f. Mentioned in the README "Example ruleset" section.
Fixed
- Views no longer scroll their top off-screen on short terminals. nftui uses no alternate screen, so a frame taller than the terminal made it scroll and pushed the title/header/tabs off the top (a chain view measured 27 lines at a 24-row terminal).
MainWindow.Viewnow clamps every frame to the terminal size (MaxWidth/MaxHeight), so the top always stays visible, and below the supported minimum of 80x24 a centered "Terminal too small — resize" prompt replaces the broken layout. The global quit-confirmation dialog is now rendered as its own terminal-sized frame (quitConfirmView) so the clamp can't hide it — previously it was stacked below the base view in a two-screen-tall frame, which the clamp would have truncated away. A new## Requirementsnote documents the minimum. Pinned byTestMainWindow_TooSmallGuard,TestMainWindow_FrameFitsTerminal, andTestMainWindow_QuitConfirmVisible. - The chain view fits the terminal; its footer is no longer clipped at the minimum size. The chain's metadata header was 14 fixed lines (Chain / Table / Type / Hook / Priority / Default policy on their own lines + a multi-line "Rules by type" block), which at 80x24 left room for only one rule and overflowed the frame by ~3 lines. The header is now compact — name on one line, all metadata (table·family·type·hook·priority·policy) on a second, and the rule-type counts on a third — and the content box is budgeted so the frame fits exactly (3 rules visible at 80x24 instead of 1).
headerLines()andmaxVisibleRules()were updated to match. Pinned byTestChainView_FitsTerminal(plus updatedTestChainView_MaxVisibleRules/TestChainView_HeaderLines). - The TUI now runs on the alternate screen.
tea.WithAltScreen()makes nftui a proper full-screen app that restores the terminal and the user's scrollback on exit instead of leaving its last frame behind. Frames are clamped to the terminal size, so nothing overflows the alternate buffer. - The rule editor's tall tabs now scroll to keep the focused field visible. On a short terminal the General (and other) tabs render more fields than fit, and the field area didn't scroll — only the top was visible and
Tabcould move focus to a field below the fold that you couldn't see. The editor now keeps the title and tab bar fixed and scrolls the field area so theTab/Shift+Tab-focused field always stays fully on screen (the focused field is located via an invisible sentinel injected by a newfviewhelper; the body is windowed to the box height). Pinned byTestRuleEdit_ScrollToFocus. - The read-only rule view scrolls for rules taller than the screen. It has no focus to follow, so it gained explicit
↑/kand↓/jscroll keys (shown in the footer): the title and tab bar stay fixed while the detail body scrolls, with the offset clamped at both ends and reset on tab switch, so a large rule no longer hides its lower half. Pinned byTestRuleView_Scroll. - Quitting no longer flushes the live ruleset (data loss). Confirming the "Are you sure you want to quit?" dialog called
nft.FlushRules()before exiting, wiping the entire kernel ruleset on every quit — a direct violation of nftui's contract to never mutate state the user didn't explicitly change. Exit now simply quits; no kernel call. Regression-pinned byTestMainWindow_QuitDoesNotFlush(bug B-2 in ROADMAP). - Chain view no longer renders every rule twice. The rule list printed each rule on two lines — once via
nft.RuleToHumanReadableand once vianftserializer.SerializeRule. Only the canonicalRuleToHumanReadableform (the one the rule view and the filter already use, with set resolution) is kept;ruleEntryLinesshrank 4 → 3 so the visible-window math stays correct. Pinned byTestChainView_RuleRenderedOnce(bug B-1 in ROADMAP).
Security
- Hardened GitHub Actions token permissions in the release workflow: the workflow level now defaults to read-only (
contents: read) and the write scopes (contents/id-token/attestations) are granted only on thegoreleaserjob that needs them, shrinking the token blast radius. No behavior change — the single release job still gets exactly the scopes it requires (closes the OpenSSF Scorecard Token-Permissions finding). - Pinned all build dependencies by digest. Every GitHub Action across
ci.yml/release.yml/scorecard.yml/codeql.ymlis now referenced by full commit SHA with a# vNcomment (so a moving tag can no longer change what runs), the Dockerfile base images are pinned by digest (golang:1.25.8-alpine@sha256:…,alpine:3.22@sha256:…), and the CIgovulncheckinstall is pinned to@v1.4.0instead of@latest. Dependabot gains adockerecosystem so the digest pins still receive update PRs (it already bumps the SHA-pinned actions via thegithub-actionsecosystem). Closes the OpenSSF Scorecard Pinned-Dependencies finding.sigstore/cosign-installeris held at v3 (cosign 2.x) and excluded from Dependabot major bumps — cosign 4.x'ssign-blobdefaults break Goreleaser'ssigns:block — until that block is made cosign-4 compatible. - Expanded
SECURITY.mdwith a concrete response and disclosure policy: a direct "Report a vulnerability" link, response SLAs (acknowledgement within 3 business days, triage within 7 days), a 90-day coordinated-disclosure window, a published-advisory link, and a supported-versions table. Strengthens the OpenSSF Scorecard Security-Policy signal (it scores a policy higher when it contains links and concrete disclosure timelines).