Skip to content

A threat model, and a site check that keeps it honest - #11

Merged
blessdyb merged 4 commits into
mainfrom
feature/threat-model
Sep 27, 2026
Merged

blessdyb merged 4 commits into
mainfrom
feature/threat-model

Conversation

@blessdyb

Copy link
Copy Markdown
Contributor

Documentation and website only. No app release — the Swift is untouched, so there is nothing to download and nothing to tag.

Three claims that were not true

Writing down what Flowlight cannot do turned up three pages claiming more than it does.

  • Reverse DNS leaves the Mac by default and has no off switch. The threat model said nothing leaves except two requests, both switchable off. NettopTrafficSource.swift:145 does reverse DNS unconditionally, the sampler is every user's state until the extension is approved, and there is no setting for it. The docs FAQ — the answer to "Does Flowlight send anything about me anywhere?" — put it more strongly still, that each of the three has a switch. All three pages now name all three requests and say which one has no switch, and why.
  • The privacy table implied a switch by omission, staying silent in the one default-on row where every other names its setting.
  • README ticked 0.9.2 as shipped when the newest release was 0.9.1, and called the inspection budget 0.9.2 in one place and 0.9.3 in another.

A check, so it doesn't happen again

scripts/check_site.py — twelve rules, each one a bug that shipped. Three read the app rather than the site, so adding a screen is what asks for the documentation. They found real breakage on the first run:

  • the views table gave Inspect ⌘5 long after it became ⌘7, and Capture ⌘6 where it is ⌘0
  • Rules, Ask, Devices and Coverage were never listed there at all
  • every screenshot declared a height wrong by 8–60% with height: auto in the CSS, so pages jumped on load
  • nine translated home pages had a one-item roadmap under a heading promising "the rest, in this order"
  • live.png was served to nobody for two releases

Screenshot dimensions are now stamped from the PNG header at build time rather than checked, so the numbers can't rot. Every rule was fault-injected first — a validator that silently passes is worse than none.

Runs on every PR, before every Pages deploy, and in build-site.sh. CI also checks README's Shipped: list against published releases now.

CodeQL

Autobuild failed with no-project-found because XcodeGen writes the Xcode project from project.yml. Swift had been dropped from the scanned languages in response, leaving the Network Extension, inspection proxy and updater unanalysed while the site generator still got scanned. Replaced with advanced setup, build-mode: manual, security-extended. Default setup is off — the two cannot both be active.

Also

SECURITY.md and a reporting contact; fonts served from this domain rather than Google, on the page that promises Flowlight sends nothing anywhere; the Coverage screen documented in all ten languages; the right-click-to-inspect paragraph moved out of Capture, where every sentence of it was about Inspect; Live given a screenshot for the first time.

Testing

check_site.py: 52 pages, 0 problems. Swift suite: 483 pass. Two HelpTests hang locally and that is environmental, not a defect — they read a file from the checkout, macOS gates reads under ~/Desktop, and the same two pass in 0.01s from a checkout elsewhere. Documented in docs/DEVELOPMENT.md; CI has never seen it.

🤖 Generated with Claude Code

blessdyb and others added 4 commits September 27, 2026 14:58
The site is generated, translated by hand into nine languages and rebuilt on every release, so its failures are
never syntax. A placeholder that didn't render, a link to a section that exists in English only, a font that
came back from Google, a roadmap still promising what you can already download — all of it is valid HTML, and
the build publishes it without a murmur.

check_site.py is twelve rules, each one a bug that shipped. Three of them read the app rather than the site:
every screen's helpAnchor has to have a docs section, and the views table has to agree with the sidebar's own
order, so adding a screen is itself what asks for the documentation. Those two caught real breakage on their
first run — the table still gave Inspect ⌘5 long after it had become ⌘7, and Rules, Ask, Devices and Coverage
had never reached it at all.

Screenshot dimensions were going to be a thirteenth rule. They are better fixed than checked: build_site.py now
stamps width and height from the PNG's own header, so the numbers cannot rot. They already had — every
screenshot declared a height that was wrong by between 8 and 60 percent, with `height: auto` in the CSS, which
made the page jump on load in exactly the way those attributes exist to prevent.

It runs on every pull request, before every Pages deploy, and in build-site.sh so it is the same check locally.
Each rule was fault-injected first, because a validator that silently passes is worse than none.

CI also checks README's Shipped list against the published releases now. It ticked 0.9.2 as done when the
newest release was 0.9.1; the site had been guarded against that claim for a while, but a ticked checkbox reads
less like a promise than a download button does, so nobody had thought to look there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CodeQL's autobuild looked for an Xcode project, found none because XcodeGen writes it from project.yml, and
failed with no-project-found. The reaction had been to drop Swift from the languages being scanned, which left
the Network Extension, the inspection proxy and the updater — the code that reads other people's traffic and
installs software — with no static analysis, while the site generator kept getting some.

Advanced setup with build-mode: manual, building the project the way CI already does. security-extended rather
than the default suite: this app holds decrypted request bodies and a certificate authority key, so the
extra precision-medium queries earn the false positives they bring.

Default setup has been turned off for the repository, because the two configurations cannot both be active.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Writing down what Flowlight cannot do turned up three places where it claimed more than it does, each of them
in the answer a careful reader reaches for first.

The threat model said nothing leaves the Mac by default except two requests, both switchable off. There is a
third: reverse DNS. NettopTrafficSource does it unconditionally, the sampler is every user's state until the
extension is approved, and it has no off switch. The docs FAQ — "Does Flowlight send anything about me
anywhere?" — made the same claim in stronger terms, that each of the three has a switch. Both now name all
three and say which one does not, and why: without it the sampler could not put a name to most connections.
The privacy table had implied a switch by staying silent where every other default-on row names one.

Also here: SECURITY.md and a reporting contact, fonts served from this domain rather than from Google on the
page that promises Flowlight sends nothing anywhere, the Coverage screen documented in all ten languages, and
the right-click-to-inspect paragraph moved out of Capture, where every sentence in it was about Inspect.

The views table now lists all ten screens with the shortcuts the sidebar actually assigns. Live has a
screenshot for the first time, having been the screen the app opens on and the one picture nobody was shown.

No app release: the Swift is untouched, so there is nothing to download. README's 0.9.2 line says so rather
than ticking a version that will never be tagged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Swift analysis failed on `brew install xcodegen` with "Cannot install under Rosetta 2 in ARM default
prefix". CodeQL traces a manual build by preloading an x86_64 libtrace.dylib through DYLD_INSERT_LIBRARIES, so
every process in that step — brew included — runs under Rosetta on an arm64 runner, and Homebrew will not put
an x86_64 build into /opt/homebrew.

Selecting Xcode, installing XcodeGen and writing the project now happen before init. None of it needs tracing;
only the compiler does. The traced step is the xcodebuild invocation alone, which is also the cheaper shape.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

@blessdyb
blessdyb merged commit 0194fc8 into main Sep 27, 2026
8 checks passed
@blessdyb
blessdyb deleted the feature/threat-model branch September 27, 2026 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants