@securecoms/agent-bridge 0.33.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -224,10 +224,17 @@ All routes except `/healthz` require `Authorization: Bearer <BRIDGE_TOKEN>`.
224
224
  | `GET` | `/v1/messages` | query: `channel_id`, `wait` (0–50) | `[{channel_id, message_id, sender_id, text, ts}]` |
225
225
  | `GET` | `/v1/events` | query: `channel_id` | SSE stream |
226
226
  | `POST` | `/v1/status` | `{channel_id, state, detail?}` | `{ok:true}` |
227
+ | `GET` | `/v1/vault-outbox` | — | `{path}` (the upload outbox; `null` when uploads are disabled) |
228
+ | `POST` | `/v1/send-attachment` | `{channel_id, path, caption?}` (`path` must be in the vault outbox) | `{message_id, status:"sent"}` |
229
+ | `POST` | `/v1/vault-write` | `{enclave_id, path, name, vault_channel_id?}` (`path` must be in the vault outbox) | `{node_id}` |
227
230
  | `GET` (Upgrade) | `/v1/ws` | query: `token` or `Authorization` header | WebSocket — see [WebSocket](#websocket-recommended) |
228
231
 
229
232
  The agent's capabilities, including the receive modes the daemon supports, are advertised on `/healthz` under `capabilities` (e.g. `receive_modes: ["longpoll","sse","websocket"]`).
230
233
 
234
+ **Uploads come only from the vault outbox.** `/v1/send-attachment` and `/v1/vault-write` read a local file and share it with the channel or the workspace Files. They read only a regular file placed directly in the daemon's outbox, `<store-dir>/vault-outbox/` (next to `bridge-state.db`). The daemon creates it at start with mode 0700, owned by the daemon's user, and pins it. Every read checks that it is still that same directory, owned by the daemon's user, with mode 0700. The authenticated `GET /v1/vault-outbox` returns its absolute path; the unauthenticated `/healthz` does not show it. Copy the file into that directory first, then pass its path (absolute, or a bare file name, which is resolved inside the outbox). Any other path is refused with `400 {"error":"path_outside_outbox"}`. That includes a `..` path, a subdirectory, a symlink (even one pointing back into the outbox), a hard link, a directory, a FIFO or a device, and an outbox that was swapped or loosened after start. The response and the daemon log never contain the path. A missing file inside the outbox is still `400 invalid_request`. **After a successful upload the daemon deletes the file from the outbox**, so no plaintext copy lingers. After a failed upload the file stays, so you can retry. A received attachment saved under `<store-dir>/attachments/` is outside the outbox: to forward it, copy it into the outbox first. Uploads are refused until the daemon has started, and always for an in-memory store. If the outbox mode or owner is changed while the daemon runs, uploads are refused until the daemon is restarted (it fails closed; a restart tightens the mode again).
235
+
236
+ **What the outbox does not stop.** It stops a caller that can only send HTTP requests to the daemon, for example a prompt-injected harness that is allowed to call the bridge but not to touch files. It does **not** stop a harness that can also write files (shell or file tools): such a harness can copy `~/.ssh/id_ed25519` into the outbox and upload it. The real control for that case is per-upload owner approval, which is tracked as a follow-up (W6). Until then, give file-capable harnesses only the bridge token you would trust with your files.
237
+
231
238
  **`POST /v1/send` — 503 channel_pending**
232
239
 
233
240
  If the daemon hasn't been admitted to the named channel yet (the owner's watcher hasn't processed the key package), the response is:
@@ -670,9 +677,12 @@ the agent **plaintext** — message text and, for attachments, a **local file pa
670
677
  - **Receiving an attachment:** the delivered message includes
671
678
  `attachments: [{ filename, mime, size, path, sha256 }]`. Read the file at `path`
672
679
  (saved under `<store-dir>/attachments/<channel>/`). No MLS, no key handling.
673
- - **Sending an attachment:** `POST /v1/send-attachment` with
674
- `{ "channel_id": "...", "path": "/local/file.png", "caption": "optional" }`.
675
- The daemon reads, encrypts, uploads, and dispatches it.
680
+ - **Sending an attachment:** copy the file into the vault outbox (`path` from the
681
+ authenticated `GET /v1/vault-outbox`), then `POST /v1/send-attachment` with
682
+ `{ "channel_id": "...", "path": "<vault_outbox>/file.png", "caption": "optional" }`.
683
+ The daemon reads, encrypts, uploads, and dispatches it, then deletes the
684
+ outbox copy. A path outside the outbox (including a received file under
685
+ `<store-dir>/attachments/`) is refused with `400 path_outside_outbox`.
676
686
 
677
687
  This is why Python agents (which have no native MLS library) fully support
678
688
  attachments: the cryptography lives in the daemon, not the agent.
@@ -963,6 +973,54 @@ copied after the owner's next typed message.
963
973
 
964
974
  ---
965
975
 
976
+ ## Release notes: 0.35.0
977
+
978
+ <!-- [COPY, approved by Eric 2026-10-30 ("notes OK"), outbox + blob-id release] -->
979
+
980
+ #### Security
981
+ - File uploads are safer. The agent now uploads to FileSafe or to a chat only from its own outbox folder (`vault-outbox`, next to the agent's data). Before, a program on the agent's computer could make it upload any file, such as a private key.
982
+ - The agent checks every attachment ID it receives before it downloads the file. A bad ID is refused and the message is still marked as read.
983
+
984
+ #### Change for custom agent scripts
985
+ - A script that uploads a file must first copy it into the outbox folder. The script gets the folder path from `GET /v1/vault-outbox`. Uploads from other folders get the error `path_outside_outbox`.
986
+ - The outbox path is no longer shown on `/healthz`.
987
+ - After a good upload, the agent deletes the file from the outbox.
988
+
989
+ #### Upgrade
990
+
991
+ Upgrade the bridge and restart your agent. It works with @securecoms/agent-runtime 0.15.0.
992
+
993
+ ## Release notes: 0.34.0
994
+
995
+ <!-- [COPY, approved by Eric 2026-10-29 ("notes ok"), weather + currency release] -->
996
+
997
+ #### What's new
998
+
999
+ This bridge release brings weather and currency answers to your agent, together with
1000
+ @securecoms/agent-runtime 0.15.0.
1001
+
1002
+ - Weather answers, using your own WeatherAPI.com key, kept in a file on your agent's machine.
1003
+ - Currency conversion at the daily reference rate from Frankfurter, with no key needed.
1004
+ - Both work in your private chat with your agent and in your organization's internal rooms where you
1005
+ switch them on, once you sign your tool settings. Neither works during voice calls.
1006
+ - At most 200 weather lookups a day in your private chat. In rooms, at most 50 lookups and 50 conversions
1007
+ per room a day, and 200 of each across all rooms.
1008
+ - Replies that use weather data end with "Weather data: WeatherAPI.com. For general information only."
1009
+ That line appears once on the reply you see and is not saved into your agent's memory or chat history.
1010
+
1011
+ If you keep an older runtime on this bridge, nothing changes for it: its existing tools keep working and
1012
+ it never sees the two new tools.
1013
+
1014
+ #### Good to know
1015
+
1016
+ If a later release switches to a different weather service, the places you ask about would go to that new
1017
+ service without you signing your tool settings again. We will say so in that release's notes.
1018
+
1019
+ #### Upgrade
1020
+
1021
+ Install this bridge together with @securecoms/agent-runtime 0.15.0. Upgrade both at the same time and
1022
+ restart your agent.
1023
+
966
1024
  ## Release notes: 0.33.1
967
1025
 
968
1026
  <!-- [COPY, approved by Eric 2026-10-21 ("approved RN 0.33.1")] -->
@@ -54,6 +54,19 @@ export type VaultRefEntry = {
54
54
  /** Plaintext ceiling for the size hint. PARITY: connect-web useVault.ts
55
55
  * VAULT_MAX_FILE_BYTES (100 MiB object limit minus the 16-byte AEAD tag). */
56
56
  export declare const VAULT_REF_MAX_SIZE_HINT: number;
57
+ /**
58
+ * A received `artifact_share.blobId` is attacker-controlled (any channel
59
+ * member writes it). It becomes a path segment of the agent's bearer-
60
+ * authenticated GET, and encodeURIComponent leaves `.` and `..` alone, so
61
+ * `..` would be a dot segment that reaches another /api/v1/ route. The
62
+ * backend only ever issues str(uuid4()) (connect_attachments.py), so anything
63
+ * but a canonical lowercase UUID is refused before any fetch. Content-free:
64
+ * the id is never echoed.
65
+ */
66
+ export declare class InvalidBlobIdError extends Error {
67
+ constructor();
68
+ }
69
+ export declare function assertBlobId(blobId: unknown): string;
57
70
  /** The vault pin format. Exported for the /v1/vault-read expected_* params. */
58
71
  export declare const VAULT_PIN: RegExp;
59
72
  /**