@securecoms/agent-bridge 0.34.0 → 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 +30 -3
- package/dist/attachments.d.ts +13 -0
- package/dist/bin.js +386 -249
- package/dist/bridge-agent.d.ts +16 -1
- package/dist/errors.d.ts +1 -1
- package/dist/index.js +325 -188
- package/dist/outbox.d.ts +42 -0
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
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:**
|
|
674
|
-
`
|
|
675
|
-
|
|
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,23 @@ 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
|
+
|
|
966
993
|
## Release notes: 0.34.0
|
|
967
994
|
|
|
968
995
|
<!-- [COPY, approved by Eric 2026-10-29 ("notes ok"), weather + currency release] -->
|
package/dist/attachments.d.ts
CHANGED
|
@@ -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
|
/**
|