@securecoms/agent-bridge 0.34.0 → 0.36.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,41 @@ copied after the owner's next typed message.
963
973
 
964
974
  ---
965
975
 
976
+ ## Release notes: 0.36.0
977
+
978
+ <!-- [COPY, approved by Eric 2026-10-31 ("notes OK, ship now"), tool status + connected apps groundwork] -->
979
+
980
+ #### What's new
981
+ - **Tool status.** Your agent now tells SecureComs, in a short summary, how it handled your most recent message in your private chat with it. The summary says which tools it offered, which tools your settings turned off, and, if tools were off, why. For example, the agent could not confirm that the message came from you. The summary never includes your messages, what the agent did with its tools, or data from your connected apps. Only you can see it.
982
+ - The summary gives the time only to the 15-minute period, and the agent sends it only after that period ends. So SecureComs never learns the exact time of a message in your private chat.
983
+
984
+ #### Coming later: connected apps
985
+ - This release has the bridge part of connected apps. Connected apps let your agent read from apps that you sign in to. Linear is the first one. You cannot turn connected apps on yet. That comes in a later SecureComs update.
986
+ - Until then, nothing changes for your agent's tools.
987
+
988
+ #### Upgrade
989
+
990
+ Upgrade the bridge and restart your agent. It works with @securecoms/agent-runtime 0.16.0 and 0.15.0. We recommend that you upgrade both.
991
+
992
+ If you go back to 0.35.0, your agent keeps working.
993
+
994
+ ## Release notes: 0.35.0
995
+
996
+ <!-- [COPY, approved by Eric 2026-10-30 ("notes OK"), outbox + blob-id release] -->
997
+
998
+ #### Security
999
+ - 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.
1000
+ - 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.
1001
+
1002
+ #### Change for custom agent scripts
1003
+ - 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`.
1004
+ - The outbox path is no longer shown on `/healthz`.
1005
+ - After a good upload, the agent deletes the file from the outbox.
1006
+
1007
+ #### Upgrade
1008
+
1009
+ Upgrade the bridge and restart your agent. It works with @securecoms/agent-runtime 0.15.0.
1010
+
966
1011
  ## Release notes: 0.34.0
967
1012
 
968
1013
  <!-- [COPY, approved by Eric 2026-10-29 ("notes ok"), weather + currency release] -->
@@ -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
  /**