Repository navigation
feat(cloud): security notices for every owner's phone, naming the client only while the copy carries events - #1661
Conversation
…'s name only while the copy carries events The box's notice may name the client on agent.added while the readable copy is on and its grant has events. cloudwire.ReadNotice refuses a body with anything beyond the header, kind and client, and ReadAlert reads a signed notice in an alert as an event of its kind. The notice kinds no longer go to FTW Cloud as events, so an owner gets one alert and a viewer none. Push vectors gain six notice cases for the app to replay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ec5db08b31
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if slices.Contains(cloudwire.NoticeKinds, e.Kind) { | ||
| return false |
There was a problem hiding this comment.
Avoid recording delivered cloud notices as failures
When FTW Cloud is the only effective notification transport—for example, no ntfy provider and no web-push subscriptions—agentPorts.push successfully queues the separate notice and then calls NotifyAgents; the registered CloudPublisher reaches this branch, returns ErrNothingToSend, and notifications.Service.deliver consequently increments Failed and writes a failed history entry because every publisher appeared absent. Thus successfully delivered security notices are reported as failures; exclude these kinds before dispatching to the cloud event publisher or account for the notice delivery as a successful transport.
AGENTS.md reference: AGENTS.md:L22-L23
Useful? React with 👍 / 👎.
…nsent page cuts it, and count what the box drops Review of #1661: - ReadNotice checks the header (a 16-byte boot) and takes a client name of at most 65 characters, the consent page's 64 and its "…", with no control or format character (NoticeClientOK), which the box applies too. - A notice states a lasting fact: the phone shows it for a day (NoticeMaxAgeMs). ReadAlert reads a notice kind sent as an event as the generic line, since that path reached viewers. - The box counts and logs a notice its full queue drops (noticesDropped). - ADR 0008 and the docs say Apple may see an agent's client name, and that FTW Cloud sees it in the roster whatever the copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # go/internal/cloudlink/link.go
Copied from srcfl/ftw-webapp (claude/agent-added-neutral, 8d80b8ea) and regenerated. The box renders it with no name of its own; the phone names the client from the notice when it has one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What
Security notices reach every owner's phone through FTW Cloud, with the readable copy on or off. This is the box and protocol half; the service half is srcfl/ftw-cloud#23, and the phone half is srcfl/ftw-app#77.
#1646 already made the box send
notice {kind}whenever the link is up. This PR checks that and closes three gaps:noticemay carryclientonagent.added, and only while the readable copy is on and its grant hasftw.events.read, as an event's arguments would. With the copy off the notice holds the kind alone. The name must passcloudwire.NoticeClientOK: at most 65 characters (the consent page keeps 64 and adds "…"), no control or format character. FTW Cloud knows the name from the roster's agent row whatever the copy; the copy decides only whether it also reaches Apple. ADR 0008 and the docs now say so.event(QueueEventrefuses them). With the copy on, an owner got the event and the notice; a viewer with events got the event.cloudwire.ReadNoticedecodes a notice strictly: any field beyond the header,kindandclientrefuses it, as do a boot that is not 16 bytes, a kind outsideNoticeKinds, or a name on another kind or thatNoticeClientOKrefuses. FTW Cloud uses it before it sends anything to Apple.ReadAlertaccepts one up toNoticeMaxAgeMs(24 h) old, and FTW Cloud gives Apple the same expiry. An event of a notice's kind reads as the generic line.noticesDropped(GET /api/cloud).ReadAlert, the reference for the phone's notification extension, now reads a signed notice in an alert as an event of its kind, withclientas its one argument. The alert'sapsstays the same for every kind.Docs:
docs/protocol-cloud.md(notice table, Push, byte budget, what Apple sees) anddocs/agents.md(Push kinds).Pairs
go/protocolto this branch (c41e6e9, master with QUIC merged in). It moves to the merge commit once this lands.Catalogue
agent.addednow reads "An assistant or Sourceful was given access to your box. See who has access in the app." (review S6), edited in srcfl/ftw-webapp#94 first and copied here withpush_gen.goregenerated. The box renders it with no name of its own; the phone names the client from the notice.Contract-pair: srcfl/ftw-webapp@8d80b8e
Deploy order
Cloud first (srcfl/ftw-cloud#23), then boxes (this), then phones (srcfl/ftw-app#77). A box that sends notices to a service that drops them loses nothing but those pushes; a service ready first drops the old event copies at once.
Tests
cloudwire: ten new push vectors (notice: agent added,with the client's name,owner added, a kind that is no notice, a name on another kind, a notice that carries a reading, a notice kind sent as an event, a notice twenty hours old, one over a day old, one with a short boot). The earlier cases keep their bytes.cloudwire.TestReadNoticeTakesOnlyANotice.cloudlink:TestANoticeGoesWithTheCopyOffnow also checks that no name goes with the copy off;TestANoticeNamesTheClientOnlyWhenTheGrantCarriesEvents(with a cut name and a format character);TestANoticeKindIsNoEvent;TestAFullNoticeQueueCountsWhatItDrops;TestANoticeGoesPastTheBudget.cmd/ftw:TestAnAgentAddedAtTheBoxPageNamesItsClient(add and widen).GOFLAGS=-p=2 make verify: clean (vet, tests, build).🤖 Generated with Claude Code