docs(usage): state in the consent dialog that the connection only runs outwards
The dialog described what is sent and where it goes, but never said which way the connection runs. That is the part an operator is actually being asked to accept: opening an outbound path to someone else's service. PicPeak sends and never pulls. One place in the service reaches the network, it is a POST, and it requests exactly two paths — /api/envelopes, and /api/participant/lookup only when an operator asks for their own export. No scheduled job contacts the collector; the daily rollup is driven solely by an authenticated admin hitting /activity. There is no route the collector could call, and redirect: 'error' means it cannot even point a request somewhere else. From a reply only the acknowledgement for the packet just sent is read, with every field compared against that packet before it is accepted; the stored copy drops the session token and no read path hands it back to the UI. A requested export is streamed to the operator as a file and never interpreted. The consequence is why it belongs in the consent text and not only in the docs: this channel cannot deliver code, configuration or content into an installation, not even from a collector that has been taken over. It is a security property by design rather than by convention. usageOutboundOnly.test.js guards it by source inspection rather than behaviour, because a behavioural test only proves that today's calls behave. It fails the moment someone adds a second fetch, a poll for messages, a scheduled pull, or a public route touching the usage service — verified by injecting each of those.
This commit is contained in:
@@ -39,6 +39,30 @@ somewhere the operator did not choose.
|
||||
|
||||
Local development can use an HTTP loopback collector outside production. The
|
||||
collector URL is never writable through generic settings or request payloads.
|
||||
|
||||
### The connection only runs outwards
|
||||
|
||||
PicPeak sends; it never pulls. There is exactly one place in the service that
|
||||
reaches the network, it is a POST, and it makes requests to exactly two paths:
|
||||
`/api/envelopes` and — only when an operator asks for their own data export —
|
||||
`/api/participant/lookup`. There is no scheduled job that contacts the
|
||||
collector (the daily rollup is driven solely by an authenticated admin hitting
|
||||
`/activity`), no route the collector could call, and `redirect: 'error'` so the
|
||||
collector cannot even redirect a request elsewhere.
|
||||
|
||||
From a reply the service reads only the acknowledgement for the packet it just
|
||||
sent, and compares `packet_id`, `installation_id`, `packet_digest`, `action`,
|
||||
`sequence` and `status` against that packet before accepting it; a mismatch is
|
||||
an error and nothing else in the response is looked at. The stored copy drops
|
||||
the session token, and no read path hands it back to the UI. A requested data
|
||||
export is streamed to the operator as a file attachment and is never
|
||||
interpreted or executed.
|
||||
|
||||
The consequence is the point, and it is stated in the consent dialog: this
|
||||
channel cannot deliver code, configuration or content into an installation —
|
||||
not even from a collector that has been taken over. It is a one-way path by
|
||||
design, not by convention, and `__tests__/services/usageOutboundOnly.test.js`
|
||||
fails if that ever stops being true.
|
||||
Keep the encryption material stable and protected; losing it makes the old
|
||||
identity unable to sign deletion requests. Note that `USAGE_ENCRYPTION_KEY`
|
||||
defaults to `JWT_SECRET`, so rotating `JWT_SECRET` without setting a dedicated
|
||||
|
||||
Reference in New Issue
Block a user