@retasc/cli 1.39.0 → 1.39.1
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/CHANGELOG.md +19 -0
- package/dist/lib/keystore.js +38 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ release commits and the issues they reference.
|
|
|
6
6
|
|
|
7
7
|
Dates are the npm publish date. Each entry names the RTSC issue behind it.
|
|
8
8
|
|
|
9
|
+
## 1.39.1 (2026-09-02)
|
|
10
|
+
|
|
11
|
+
- **RTSC-800** — (security) a keystore-resolved key now travels only to the keystore's own
|
|
12
|
+
url, whether the competing url arrives in the `.mcp.json` entry or in the environment.
|
|
13
|
+
1.6.0 made this promise for the entry, which covered `claim`/`tidy`/`done` but not the
|
|
14
|
+
proxy: the proxy is the one caller that ever reads a committed marker, and it passes no
|
|
15
|
+
entry at all. The harness copies the marker's `env` block into the spawned process, so a
|
|
16
|
+
`RETASC_MCP_URL` planted next to `RETASC_WORKSPACE` in a repo's committed `.mcp.json`
|
|
17
|
+
arrived as an environment url, outranked the keystore, and every teammate's proxy sent its
|
|
18
|
+
real workspace key to that host on the next start, during session-key minting, with no
|
|
19
|
+
prompt. An environment url is now honored only alongside an explicit `RETASC_MCP_KEY`,
|
|
20
|
+
where the key and the url share a trust domain.
|
|
21
|
+
Nothing legitimate loses a path: `bind` already stores the deployment's url into the
|
|
22
|
+
binding, so a self-hosted url is in the keystore before the proxy asks for it.
|
|
23
|
+
This closes the url, and only the url. A committed marker also chooses the `command` that
|
|
24
|
+
runs and can set any other variable, so a marker you do not trust is code execution by the
|
|
25
|
+
MCP config format's own design, and the harness's project-trust prompt is the boundary for
|
|
26
|
+
that. What this removes is the one variant that reads as ordinary configuration in review.
|
|
27
|
+
|
|
9
28
|
## 1.39.0 (2026-08-30)
|
|
10
29
|
|
|
11
30
|
- **RTSC-793** — `retasc setup` now wires Cursor, OpenCode and Gemini CLI too, bringing it
|
package/dist/lib/keystore.js
CHANGED
|
@@ -136,18 +136,41 @@ export function findBindingByPath(dir) {
|
|
|
136
136
|
* workspace `.mcp.json`'s `mcpServers.retasc` entry, or undefined when only the
|
|
137
137
|
* environment carries the binding (the spawned proxy).
|
|
138
138
|
*
|
|
139
|
-
* URL trust pairs with key source (RTSC-262 review): a
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
139
|
+
* URL trust pairs with key source (RTSC-262 review, tightened by RTSC-800): a url
|
|
140
|
+
* is honored only when it arrived WITH the key actually used. A keystore-resolved
|
|
141
|
+
* key therefore travels only to the keystore's url — never to one from the entry,
|
|
142
|
+
* and never to one from the environment. Co-located pairs keep their own url,
|
|
143
|
+
* because there the key and url share a trust domain: a legacy inline entry, and
|
|
144
|
+
* an explicit `RETASC_MCP_KEY` + `RETASC_MCP_URL` in the environment.
|
|
145
|
+
*
|
|
146
|
+
* RTSC-800 is why the env url is PAIRED rather than absolute. RTSC-262 made this
|
|
147
|
+
* promise but implemented it on the `mcpEntry` branch only, and the proxy — the
|
|
148
|
+
* one caller that ever reads a committed marker — passes no entry. It hands us
|
|
149
|
+
* `process.env`, which the harness populates from the marker's own `env` block
|
|
150
|
+
* (`mcpMarkerEntry` / `autoMarkerEntry`). So a `RETASC_MCP_URL` planted next to
|
|
151
|
+
* `RETASC_WORKSPACE` in a committed `.mcp.json` arrived as an env url, won, and
|
|
152
|
+
* every teammate's proxy posted its real keystore key to that host on the next
|
|
153
|
+
* harness start. Nothing legitimate loses a path: `bind` stores the deployment's
|
|
154
|
+
* url INTO the binding, so a self-hosted url is already in the keystore by the
|
|
155
|
+
* time the proxy asks for it.
|
|
156
|
+
*
|
|
157
|
+
* This closes the url, and only the url. A hostile committed marker can set any
|
|
158
|
+
* variable for the spawned proxy (`RETASC_DIR` relocates the keystore outright —
|
|
159
|
+
* see `keystoreDir` above — and `NODE_OPTIONS` is arbitrary code) and it chooses
|
|
160
|
+
* the `command` that runs, so a marker you do not trust is
|
|
161
|
+
* code execution by the MCP config format's own design — the harness's project-
|
|
162
|
+
* trust prompt is the boundary for that, not this function. What this removes is
|
|
163
|
+
* the one variant that survives review, because a url string in a config file
|
|
164
|
+
* looks like configuration.
|
|
145
165
|
*/
|
|
146
166
|
export function resolveConn(opts) {
|
|
147
167
|
const env = opts.env ?? process.env;
|
|
148
168
|
const entry = opts.mcpEntry;
|
|
149
169
|
let key = env.RETASC_MCP_KEY || "";
|
|
150
|
-
|
|
170
|
+
// Paired with the env KEY, not read unconditionally (RTSC-800): without a key
|
|
171
|
+
// beside it, an env url is just a string the committed marker chose, and the key
|
|
172
|
+
// it would redirect belongs to the keystore.
|
|
173
|
+
let url = key ? env.RETASC_MCP_URL || "" : "";
|
|
151
174
|
// Entry-supplied key/url are read together — the url is honored only when
|
|
152
175
|
// the key it's paired with is the one actually used.
|
|
153
176
|
let entryKey = "";
|
|
@@ -171,7 +194,8 @@ export function resolveConn(opts) {
|
|
|
171
194
|
url = entryUrl;
|
|
172
195
|
}
|
|
173
196
|
// Canonical (RTSC-92): secret-free marker → workspace id → home keystore.
|
|
174
|
-
//
|
|
197
|
+
// Neither the entry's url nor the environment's is consulted on this branch —
|
|
198
|
+
// the key came from the keystore, so the url does too (RTSC-800).
|
|
175
199
|
//
|
|
176
200
|
// RTSC-780 adds one more step in front of the id: `auto` names no workspace, so
|
|
177
201
|
// the folder we were spawned in picks it. Same trust rule either way — the key
|
|
@@ -187,8 +211,12 @@ export function resolveConn(opts) {
|
|
|
187
211
|
: undefined;
|
|
188
212
|
if (b) {
|
|
189
213
|
key = b.key;
|
|
190
|
-
if (!url)
|
|
191
|
-
|
|
214
|
+
// Unconditional, where this used to be `if (!url)`. Equivalent today — reaching
|
|
215
|
+
// here means `key` was empty, and both writes to `url` above are key-gated, so it
|
|
216
|
+
// is empty too — and that is the point: the rule "a keystore key rides the keystore
|
|
217
|
+
// url" is stated here outright rather than left resting on that invariant, so
|
|
218
|
+
// restoring an unpaired url above cannot quietly resurrect the RTSC-800 redirect.
|
|
219
|
+
url = b.url;
|
|
192
220
|
}
|
|
193
221
|
}
|
|
194
222
|
return { key, url: url || opts.defaultUrl || "" };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retasc/cli",
|
|
3
|
-
"version": "1.39.
|
|
3
|
+
"version": "1.39.1",
|
|
4
4
|
"description": "Retasc CLI — the issue tracker AI agents pull work from. Sign in with GitHub or Google, create projects, mint agent API keys, and wire your agent to the Retasc MCP server in one command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|