shennian 0.3.86 → 0.4.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.
Files changed (98) hide show
  1. package/README.md +3 -2
  2. package/dist/bin/shennian.js +1 -1
  3. package/dist/publish-build-manifest.json +32 -27
  4. package/dist/src/agents/adapter.js +1 -19
  5. package/dist/src/agents/claude-managed-permissions.js +1 -248
  6. package/dist/src/agents/claude.js +8 -393
  7. package/dist/src/agents/codex-managed-permissions.js +1 -294
  8. package/dist/src/agents/codex-managed-room-tools.js +1 -249
  9. package/dist/src/agents/codex-utils.js +7 -200
  10. package/dist/src/agents/codex.d.ts +7 -1
  11. package/dist/src/agents/codex.js +17 -1352
  12. package/dist/src/agents/config-status.js +1 -226
  13. package/dist/src/agents/cursor-managed-permissions.js +2 -299
  14. package/dist/src/agents/cursor.js +1 -314
  15. package/dist/src/agents/custom.js +4 -418
  16. package/dist/src/agents/external-channel-instructions.js +11 -94
  17. package/dist/src/agents/gemini.js +1 -180
  18. package/dist/src/agents/manager.js +18 -250
  19. package/dist/src/agents/model-registry/cache.js +1 -37
  20. package/dist/src/agents/model-registry/discovery.js +2 -188
  21. package/dist/src/agents/model-registry/parsers.js +4 -521
  22. package/dist/src/agents/model-registry/runner.js +1 -30
  23. package/dist/src/agents/model-registry/service.js +1 -115
  24. package/dist/src/agents/model-registry/types.js +1 -8
  25. package/dist/src/agents/model-registry.js +1 -19
  26. package/dist/src/agents/native-capabilities.js +1 -237
  27. package/dist/src/agents/openclaw.js +2 -293
  28. package/dist/src/agents/opencode-managed-permissions.js +2 -187
  29. package/dist/src/agents/opencode.js +1 -298
  30. package/dist/src/agents/platform-instructions.js +17 -46
  31. package/dist/src/agents/runtime-compatibility.js +1 -44
  32. package/dist/src/agents/workbuddy-managed-permissions.js +2 -329
  33. package/dist/src/agents/workbuddy.js +3 -444
  34. package/dist/src/commands/logout.d.ts +3 -0
  35. package/dist/src/commands/logout.js +1 -0
  36. package/dist/src/daemon-ipc/paths.js +1 -1
  37. package/dist/src/fs/boundary.js +1 -185
  38. package/dist/src/fs/handler.js +1 -130
  39. package/dist/src/fs/security.js +1 -32
  40. package/dist/src/fs/text-decoder.js +1 -110
  41. package/dist/src/index.js +3 -587
  42. package/dist/src/manager/prompt.js +29 -34
  43. package/dist/src/manager/registry.js +1 -317
  44. package/dist/src/manager/retired-external-channel-compat.js +1 -40
  45. package/dist/src/manager/runtime.js +23 -1514
  46. package/dist/src/native-fusion/codex-parser.d.ts +3 -1
  47. package/dist/src/native-fusion/codex-parser.js +4 -660
  48. package/dist/src/native-fusion/codex-user-input.js +5 -102
  49. package/dist/src/native-fusion/config.js +1 -5
  50. package/dist/src/native-fusion/file-watcher.js +1 -191
  51. package/dist/src/native-fusion/local-repository.js +2 -569
  52. package/dist/src/native-fusion/opencode-parser.js +3 -130
  53. package/dist/src/native-fusion/parser-common.d.ts +6 -1
  54. package/dist/src/native-fusion/parser-common.js +4 -4
  55. package/dist/src/native-fusion/parsers.d.ts +4 -2
  56. package/dist/src/native-fusion/parsers.js +3 -140
  57. package/dist/src/native-fusion/service.js +2 -739
  58. package/dist/src/native-fusion/session-header.js +2 -101
  59. package/dist/src/native-fusion/state.js +2 -104
  60. package/dist/src/native-fusion/types.js +1 -1
  61. package/dist/src/personal-sync/machine-authority.js +1 -34
  62. package/dist/src/room/activation-audit.js +2 -45
  63. package/dist/src/room/activation-policy.js +1 -30
  64. package/dist/src/room/activation-service.js +8 -408
  65. package/dist/src/room/invite-bootstrap.js +1 -156
  66. package/dist/src/room/managed-prompt-composer.d.ts +1 -1
  67. package/dist/src/room/managed-prompt-composer.js +17 -16
  68. package/dist/src/room/managed-room-binding.js +1 -167
  69. package/dist/src/room/managed-room-ipc.d.ts +4 -0
  70. package/dist/src/room/managed-room-ipc.js +2 -2
  71. package/dist/src/room/mirror-sync-service.d.ts +1 -0
  72. package/dist/src/room/mirror-sync-service.js +1 -183
  73. package/dist/src/room/room-binding-client.js +1 -151
  74. package/dist/src/session/archive-zip.js +1 -220
  75. package/dist/src/session/deletion-sync-state.js +2 -136
  76. package/dist/src/session/handlers/agent-capabilities.js +1 -14
  77. package/dist/src/session/handlers/agent-config.js +1 -150
  78. package/dist/src/session/handlers/agents.js +1 -55
  79. package/dist/src/session/handlers/chat.js +2 -1030
  80. package/dist/src/session/handlers/control.js +1 -61
  81. package/dist/src/session/handlers/fs.js +1 -594
  82. package/dist/src/session/handlers/message-status.js +1 -15
  83. package/dist/src/session/handlers/remote-access.js +1 -43
  84. package/dist/src/session/handlers/room-managed.js +1 -101
  85. package/dist/src/session/handlers/session-refresh.js +1 -35
  86. package/dist/src/session/handlers/title.js +1 -79
  87. package/dist/src/session/handlers/tool-detail.js +1 -149
  88. package/dist/src/session/handlers/voice-window.js +1 -62
  89. package/dist/src/session/history-subscriptions.js +1 -470
  90. package/dist/src/session/manager.d.ts +1 -0
  91. package/dist/src/session/manager.js +1 -658
  92. package/dist/src/session/native-cleanup-outbox.js +2 -287
  93. package/dist/src/session/native-session-lifecycle.js +6 -327
  94. package/dist/src/session/queue.d.ts +8 -0
  95. package/dist/src/session/queue.js +6 -6
  96. package/dist/src/upgrade/scheduler.js +1 -126
  97. package/node_modules/@shennian/wire/dist/agent-workspace.js +2 -2
  98. package/package.json +1 -1
@@ -1,226 +1 @@
1
- // @arch docs/features/agent-provider-config.md
2
- // @test src/__tests__/agent-config-status.test.ts
3
- import fs from 'node:fs';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import { readLatestUserEnv } from '../agent-env.js';
7
- import { loadConfig, resolveShennianPath } from '../config/index.js';
8
- const MANAGED_CONFIG_PATH = resolveShennianPath('agent-provider-config.json');
9
- const TOKEN_SUFFIX_LENGTH = 4;
10
- function isConfigurableAgent(agent) {
11
- return agent === 'codex' || agent === 'claude' || agent === 'pi';
12
- }
13
- const AGENT_ENV_KEYS = {
14
- codex: {
15
- baseUrl: ['OPENAI_BASE_URL', 'OPENAI_API_BASE', 'OPENAI_API_URL'],
16
- token: ['OPENAI_API_KEY', 'OPENAI_TOKEN'],
17
- },
18
- claude: {
19
- baseUrl: ['ANTHROPIC_BASE_URL', 'ANTHROPIC_API_URL'],
20
- token: ['ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN'],
21
- },
22
- pi: {
23
- baseUrl: ['DASHSCOPE_BASE_URL', 'DASHSCOPE_API_BASE', 'DASHSCOPE_API_URL'],
24
- token: ['DASHSCOPE_API_KEY', 'DASHSCOPE_TOKEN'],
25
- },
26
- };
27
- export function getManagedAgentProviderConfig(agent) {
28
- return loadManagedConfig().configs[agent];
29
- }
30
- export function upsertManagedAgentProviderConfig(input) {
31
- const file = loadManagedConfig();
32
- const existing = file.configs[input.agent];
33
- const record = {
34
- agent: input.agent,
35
- baseUrl: input.baseUrl?.trim() || existing?.baseUrl,
36
- token: input.token?.trim() || existing?.token,
37
- updatedAt: new Date().toISOString(),
38
- };
39
- file.configs[input.agent] = record;
40
- saveManagedConfig(file);
41
- return record;
42
- }
43
- export function deleteManagedAgentProviderConfig(agent) {
44
- const file = loadManagedConfig();
45
- delete file.configs[agent];
46
- saveManagedConfig(file);
47
- }
48
- export function buildManagedAgentEnv(agent) {
49
- if (!isConfigurableAgent(agent))
50
- return {};
51
- const config = getManagedAgentProviderConfig(agent);
52
- if (!config)
53
- return {};
54
- if (agent === 'codex') {
55
- return compactEnv({
56
- OPENAI_BASE_URL: config.baseUrl,
57
- OPENAI_API_KEY: config.token,
58
- });
59
- }
60
- if (agent === 'claude') {
61
- return compactEnv({
62
- ANTHROPIC_BASE_URL: config.baseUrl,
63
- ANTHROPIC_API_KEY: config.token,
64
- });
65
- }
66
- return compactEnv({
67
- DASHSCOPE_BASE_URL: config.baseUrl,
68
- DASHSCOPE_API_KEY: config.token,
69
- });
70
- }
71
- export function getAgentConfigSummary(agent, env = readLatestUserEnv()) {
72
- if (!isConfigurableAgent(agent))
73
- return undefined;
74
- const managed = getManagedAgentProviderConfig(agent);
75
- if (managed?.token || managed?.baseUrl) {
76
- return toSummary({
77
- agent,
78
- source: 'shennian',
79
- status: managed.token ? 'managed' : 'missing',
80
- baseUrl: managed.baseUrl,
81
- token: managed.token,
82
- tokenPresent: !!managed.token,
83
- });
84
- }
85
- const envConfig = detectFromEnv(agent, env);
86
- if (envConfig.tokenPresent || envConfig.baseUrl) {
87
- return toSummary({
88
- agent,
89
- source: 'env',
90
- status: envConfig.tokenPresent ? 'detected' : 'missing',
91
- baseUrl: envConfig.baseUrl,
92
- token: envConfig.token,
93
- tokenPresent: envConfig.tokenPresent,
94
- });
95
- }
96
- const fileConfig = detectFromKnownConfigFiles(agent);
97
- if (fileConfig.tokenPresent || fileConfig.baseUrl) {
98
- return toSummary({
99
- agent,
100
- source: 'config-file',
101
- status: fileConfig.tokenPresent ? 'detected' : 'missing',
102
- baseUrl: fileConfig.baseUrl,
103
- token: fileConfig.token,
104
- tokenPresent: fileConfig.tokenPresent,
105
- });
106
- }
107
- return toSummary({
108
- agent,
109
- source: 'unknown',
110
- status: 'missing',
111
- tokenPresent: false,
112
- });
113
- }
114
- function loadManagedConfig() {
115
- try {
116
- const parsed = JSON.parse(fs.readFileSync(MANAGED_CONFIG_PATH, 'utf-8'));
117
- return { configs: parsed.configs ?? {} };
118
- }
119
- catch {
120
- return { configs: {} };
121
- }
122
- }
123
- function saveManagedConfig(file) {
124
- fs.mkdirSync(path.dirname(MANAGED_CONFIG_PATH), { recursive: true });
125
- fs.writeFileSync(MANAGED_CONFIG_PATH, JSON.stringify(file, null, 2), { mode: 0o600 });
126
- try {
127
- fs.chmodSync(MANAGED_CONFIG_PATH, 0o600);
128
- }
129
- catch {
130
- // Best effort on filesystems without POSIX modes.
131
- }
132
- }
133
- function compactEnv(env) {
134
- const compacted = {};
135
- for (const [key, value] of Object.entries(env)) {
136
- if (value)
137
- compacted[key] = value;
138
- }
139
- return compacted;
140
- }
141
- function detectFromEnv(agent, env) {
142
- const keys = AGENT_ENV_KEYS[agent];
143
- const baseUrl = firstEnv(env, keys.baseUrl);
144
- const token = firstEnv(env, keys.token);
145
- return { baseUrl, token, tokenPresent: !!token };
146
- }
147
- function firstEnv(env, keys) {
148
- for (const key of keys) {
149
- const value = env[key]?.trim();
150
- if (value)
151
- return value;
152
- }
153
- return undefined;
154
- }
155
- function detectFromKnownConfigFiles(agent) {
156
- const home = os.homedir();
157
- if (agent === 'pi') {
158
- const config = loadConfig();
159
- const token = config.apiKeys?.dashscope?.trim();
160
- if (token)
161
- return { token, tokenPresent: true };
162
- }
163
- const candidates = agent === 'codex'
164
- ? [path.join(home, '.codex', 'config.toml'), path.join(home, '.codex', 'auth.json')]
165
- : agent === 'claude'
166
- ? [path.join(home, '.claude', 'settings.json'), path.join(home, '.claude.json')]
167
- : [path.join(home, '.dashscope', 'config.json')];
168
- for (const file of candidates) {
169
- const text = readSmallTextFile(file);
170
- if (!text)
171
- continue;
172
- const token = extractTokenLikeValue(text, agent);
173
- const baseUrl = extractBaseUrl(text);
174
- if (token || baseUrl)
175
- return { token, baseUrl, tokenPresent: !!token };
176
- }
177
- return { tokenPresent: false };
178
- }
179
- function readSmallTextFile(file) {
180
- try {
181
- const stat = fs.statSync(file);
182
- if (!stat.isFile() || stat.size > 1024 * 1024)
183
- return null;
184
- return fs.readFileSync(file, 'utf-8');
185
- }
186
- catch {
187
- return null;
188
- }
189
- }
190
- function extractBaseUrl(text) {
191
- const match = text.match(/(?:base_url|baseURL|api_url|apiUrl|ANTHROPIC_BASE_URL|OPENAI_BASE_URL|DASHSCOPE_BASE_URL)["'\s:=]+([^"'\s,}]+)/i);
192
- return match?.[1]?.trim();
193
- }
194
- function extractTokenLikeValue(text, agent) {
195
- const prefix = agent === 'claude' ? 'sk-ant-' : agent === 'pi' ? 'sk-' : 'sk-';
196
- const direct = text.match(new RegExp(`${prefix.replace(/-/g, '\\-')}[A-Za-z0-9_\\-]{8,}`));
197
- if (direct?.[0])
198
- return direct[0];
199
- const keyMatch = text.match(/(?:api[_-]?key|auth[_-]?token|token|dashscope)["'\s:=]+([A-Za-z0-9_\-.]{12,})/i);
200
- return keyMatch?.[1]?.trim();
201
- }
202
- function toSummary(input) {
203
- return {
204
- status: input.status,
205
- source: input.source,
206
- tokenPresent: input.tokenPresent,
207
- ...(input.baseUrl ? { baseUrlHost: formatBaseUrlHost(input.baseUrl) } : {}),
208
- ...(input.token ? { tokenHint: maskToken(input.token) } : {}),
209
- updatedAt: new Date().toISOString(),
210
- };
211
- }
212
- export function maskToken(token) {
213
- const trimmed = token.trim();
214
- if (!trimmed)
215
- return '';
216
- const suffix = trimmed.slice(-TOKEN_SUFFIX_LENGTH);
217
- return `••••${suffix}`;
218
- }
219
- function formatBaseUrlHost(baseUrl) {
220
- try {
221
- return new URL(baseUrl).host || baseUrl;
222
- }
223
- catch {
224
- return baseUrl.replace(/^https?:\/\//i, '').replace(/\/.*$/, '') || baseUrl;
225
- }
226
- }
1
+ import s from"node:fs";import m from"node:os";import i from"node:path";import{readLatestUserEnv as E}from"../agent-env.js";import{loadConfig as S,resolveShennianPath as g}from"../config/index.js";const c=g("agent-provider-config.json"),U=4;function A(e){return e==="codex"||e==="claude"||e==="pi"}const O={codex:{baseUrl:["OPENAI_BASE_URL","OPENAI_API_BASE","OPENAI_API_URL"],token:["OPENAI_API_KEY","OPENAI_TOKEN"]},claude:{baseUrl:["ANTHROPIC_BASE_URL","ANTHROPIC_API_URL"],token:["ANTHROPIC_API_KEY","ANTHROPIC_AUTH_TOKEN"]},pi:{baseUrl:["DASHSCOPE_BASE_URL","DASHSCOPE_API_BASE","DASHSCOPE_API_URL"],token:["DASHSCOPE_API_KEY","DASHSCOPE_TOKEN"]}};function k(e){return u().configs[e]}function v(e){const n=u(),o=n.configs[e.agent],t={agent:e.agent,baseUrl:e.baseUrl?.trim()||o?.baseUrl,token:e.token?.trim()||o?.token,updatedAt:new Date().toISOString()};return n.configs[e.agent]=t,_(n),t}function B(e){const n=u();delete n.configs[e],_(n)}function K(e){if(!A(e))return{};const n=k(e);return n?l(e==="codex"?{OPENAI_BASE_URL:n.baseUrl,OPENAI_API_KEY:n.token}:e==="claude"?{ANTHROPIC_BASE_URL:n.baseUrl,ANTHROPIC_API_KEY:n.token}:{DASHSCOPE_BASE_URL:n.baseUrl,DASHSCOPE_API_KEY:n.token}):{}}function j(e,n=E()){if(!A(e))return;const o=k(e);if(o?.token||o?.baseUrl)return a({agent:e,source:"shennian",status:o.token?"managed":"missing",baseUrl:o.baseUrl,token:o.token,tokenPresent:!!o.token});const t=I(e,n);if(t.tokenPresent||t.baseUrl)return a({agent:e,source:"env",status:t.tokenPresent?"detected":"missing",baseUrl:t.baseUrl,token:t.token,tokenPresent:t.tokenPresent});const r=p(e);return r.tokenPresent||r.baseUrl?a({agent:e,source:"config-file",status:r.tokenPresent?"detected":"missing",baseUrl:r.baseUrl,token:r.token,tokenPresent:r.tokenPresent}):a({agent:e,source:"unknown",status:"missing",tokenPresent:!1})}function u(){try{return{configs:JSON.parse(s.readFileSync(c,"utf-8")).configs??{}}}catch{return{configs:{}}}}function _(e){s.mkdirSync(i.dirname(c),{recursive:!0}),s.writeFileSync(c,JSON.stringify(e,null,2),{mode:384});try{s.chmodSync(c,384)}catch{}}function l(e){const n={};for(const[o,t]of Object.entries(e))t&&(n[o]=t);return n}function I(e,n){const o=O[e],t=P(n,o.baseUrl),r=P(n,o.token);return{baseUrl:t,token:r,tokenPresent:!!r}}function P(e,n){for(const o of n){const t=e[o]?.trim();if(t)return t}}function p(e){const n=m.homedir();if(e==="pi"){const r=S().apiKeys?.dashscope?.trim();if(r)return{token:r,tokenPresent:!0}}const o=e==="codex"?[i.join(n,".codex","config.toml"),i.join(n,".codex","auth.json")]:e==="claude"?[i.join(n,".claude","settings.json"),i.join(n,".claude.json")]:[i.join(n,".dashscope","config.json")];for(const t of o){const r=C(t);if(!r)continue;const f=N(r,e),d=b(r);if(f||d)return{token:f,baseUrl:d,tokenPresent:!!f}}return{tokenPresent:!1}}function C(e){try{const n=s.statSync(e);return!n.isFile()||n.size>1024*1024?null:s.readFileSync(e,"utf-8")}catch{return null}}function b(e){return e.match(/(?:base_url|baseURL|api_url|apiUrl|ANTHROPIC_BASE_URL|OPENAI_BASE_URL|DASHSCOPE_BASE_URL)["'\s:=]+([^"'\s,}]+)/i)?.[1]?.trim()}function N(e,n){const o=n==="claude"?"sk-ant-":"sk-",t=e.match(new RegExp(`${o.replace(/-/g,"\\-")}[A-Za-z0-9_\\-]{8,}`));return t?.[0]?t[0]:e.match(/(?:api[_-]?key|auth[_-]?token|token|dashscope)["'\s:=]+([A-Za-z0-9_\-.]{12,})/i)?.[1]?.trim()}function a(e){return{status:e.status,source:e.source,tokenPresent:e.tokenPresent,...e.baseUrl?{baseUrlHost:R(e.baseUrl)}:{},...e.token?{tokenHint:h(e.token)}:{},updatedAt:new Date().toISOString()}}function h(e){const n=e.trim();return n?`\u2022\u2022\u2022\u2022${n.slice(-U)}`:""}function R(e){try{return new URL(e).host||e}catch{return e.replace(/^https?:\/\//i,"").replace(/\/.*$/,"")||e}}export{K as buildManagedAgentEnv,B as deleteManagedAgentProviderConfig,j as getAgentConfigSummary,k as getManagedAgentProviderConfig,h as maskToken,v as upsertManagedAgentProviderConfig};
@@ -1,299 +1,2 @@
1
- // @arch docs/features/managed-agent-restricted-runtime.md
2
- // @test src/__tests__/cursor-managed-permissions.test.ts
3
- import fs from 'node:fs';
4
- import fsPromises from 'node:fs/promises';
5
- import os from 'node:os';
6
- import path from 'node:path';
7
- import { resolveProtectedShennianDirectories } from '../security/managed-agent-credential-paths.js';
8
- const CURSOR_MANAGED_CONFIG_PREFIX = 'shennian-cursor-managed-';
9
- const MAX_PROJECT_SANDBOX_CONFIG_BYTES = 1024 * 1024;
10
- const BASE_SENSITIVE_READ_RULES = [
11
- 'Read(.env)',
12
- 'Read(.env.*)',
13
- 'Read(**/.env)',
14
- 'Read(**/.env.*)',
15
- 'Read(*.key)',
16
- 'Read(**/*.key)',
17
- 'Read(*.pem)',
18
- 'Read(**/*.pem)',
19
- 'Read(~/.shennian/**)',
20
- 'Read($HOME/.shennian/**)',
21
- ];
22
- const supportedExternalCapabilities = {
23
- // Cursor's shell can request a one-off network permission and retry a
24
- // denied command in non-interactive mode. Managed mode therefore keeps the
25
- // entire Shell tool disabled; it cannot safely offer command networking.
26
- commandNetwork: { allow: false, deny: true },
27
- webSearch: { allow: true, deny: true },
28
- // Cursor CLI does not expose non-interactive Browser or computer-use
29
- // permission switches. Keeping them unavailable is safer than pretending
30
- // that MCP/WebFetch rules control the native tools.
31
- browser: { allow: false, deny: true },
32
- computerUse: { allow: false, deny: true },
33
- // Project MCP discovery happens outside the invocation-only config and can
34
- // start before a tool permission is evaluated.
35
- mcp: { allow: false, deny: false },
36
- };
37
- /**
38
- * Evidence baseline: Cursor Agent 2026.08.31 exposes --sandbox, --mode=ask,
39
- * CURSOR_CONFIG_DIR, sandbox.json network policy, and CLI permission tokens.
40
- *
41
- * The profile is deliberately partial. Real 2026.08.31 probes showed that
42
- * Read can cross the workspace boundary, and that an allowed Shell can read
43
- * outside files, write temporary files, and request network access despite a
44
- * deny policy. Project MCP discovery also lacks a strict invocation-scoped
45
- * disable switch. Shell is therefore disabled in managed mode.
46
- */
47
- export const CURSOR_MANAGED_RUNTIME_SECURITY = {
48
- minimumRuntimeVersion: '2026.08.31',
49
- maximumRuntimeVersionExclusive: '2026.09.01',
50
- platforms: {
51
- darwin: {
52
- level: 'partial',
53
- fileAccess: { 'workspace-read': false, 'workspace-write': false, 'host-full': true },
54
- externalCapabilities: supportedExternalCapabilities,
55
- knownGaps: [
56
- 'fileAccess.workspaceBoundaryUnenforced',
57
- 'fileAccess.outsideWorkspaceRead',
58
- 'sandbox.shellDisabled',
59
- 'externalCapabilities.webSearchToolGranularity',
60
- 'externalCapabilities.projectMcpStartup',
61
- ],
62
- },
63
- linux: {
64
- level: 'partial',
65
- fileAccess: { 'workspace-read': false, 'workspace-write': false, 'host-full': true },
66
- externalCapabilities: supportedExternalCapabilities,
67
- knownGaps: [
68
- 'fileAccess.workspaceBoundaryUnenforced',
69
- 'fileAccess.outsideWorkspaceRead',
70
- 'sandbox.shellDisabled',
71
- 'externalCapabilities.webSearchToolGranularity',
72
- 'externalCapabilities.projectMcpStartup',
73
- ],
74
- },
75
- wsl: {
76
- level: 'partial',
77
- fileAccess: { 'workspace-read': false, 'workspace-write': false, 'host-full': true },
78
- externalCapabilities: supportedExternalCapabilities,
79
- knownGaps: [
80
- 'fileAccess.workspaceBoundaryUnenforced',
81
- 'fileAccess.outsideWorkspaceRead',
82
- 'sandbox.shellDisabled',
83
- 'externalCapabilities.webSearchToolGranularity',
84
- 'externalCapabilities.projectMcpStartup',
85
- ],
86
- },
87
- win32: {
88
- level: 'partial',
89
- fileAccess: { 'workspace-read': false, 'workspace-write': false, 'host-full': true },
90
- externalCapabilities: {
91
- commandNetwork: { allow: true, deny: false },
92
- webSearch: { allow: true, deny: true },
93
- browser: { allow: false, deny: true },
94
- computerUse: { allow: false, deny: true },
95
- mcp: { allow: true, deny: true },
96
- },
97
- knownGaps: [
98
- 'sandbox.nativeWindowsUnverified',
99
- 'sandbox.shellDisabled',
100
- 'externalCapabilities.projectMcpStartup',
101
- ],
102
- },
103
- },
104
- };
105
- /**
106
- * Compiles one Cursor invocation policy. The returned JSON is written only to
107
- * an isolated CURSOR_CONFIG_DIR created for the child process; neither the
108
- * user's ~/.cursor directory nor the workspace is modified.
109
- */
110
- export function compileCursorManagedPermissions(enforcement, resolvedWorkDir) {
111
- const workDir = validateResolvedWorkDir(resolvedWorkDir);
112
- const policy = enforcement.effectivePolicy;
113
- const hostFull = policy.fileAccess === 'host-full';
114
- const workspaceReadOnly = policy.fileAccess === 'workspace-read';
115
- const sensitiveReadRules = resolveSensitiveReadRules();
116
- const sensitiveWriteRules = sensitiveReadRules.map((rule) => rule.replace(/^Read\(/, 'Write('));
117
- if (policy.externalCapabilities.commandNetwork) {
118
- throw new Error('cursor_managed_command_network_unavailable');
119
- }
120
- if (policy.externalCapabilities.mcp) {
121
- throw new Error('cursor_managed_mcp_isolation_unavailable');
122
- }
123
- if (policy.externalCapabilities.browser || policy.externalCapabilities.computerUse) {
124
- throw new Error('cursor_managed_native_interactive_tool_control_unavailable');
125
- }
126
- if (enforcement.platform === 'win32' && !hostFull) {
127
- throw new Error('cursor_managed_native_windows_sandbox_unavailable');
128
- }
129
- if (!hostFull)
130
- assertProjectSandboxCannotBroadenPolicy(workDir, workspaceReadOnly);
131
- const allow = ['Read(**)'];
132
- if (!workspaceReadOnly)
133
- allow.push('Write(**)');
134
- if (policy.externalCapabilities.webSearch)
135
- allow.push('WebFetch(*)');
136
- if (policy.externalCapabilities.mcp)
137
- allow.push('Mcp(*:*)');
138
- const deny = [
139
- ...sensitiveReadRules,
140
- ...sensitiveWriteRules,
141
- 'Shell(*)',
142
- ...(workspaceReadOnly ? ['Write(**)'] : []),
143
- ...(!policy.externalCapabilities.webSearch ? ['WebFetch(*)'] : []),
144
- ...(!policy.externalCapabilities.mcp ? ['Mcp(*:*)'] : []),
145
- ];
146
- const networkAllowed = policy.externalCapabilities.commandNetwork;
147
- const cliConfig = {
148
- version: 1,
149
- editor: { vimMode: false },
150
- approvalMode: hostFull ? 'unrestricted' : 'allowlist',
151
- sandbox: {
152
- mode: hostFull ? 'disabled' : 'enabled',
153
- networkAccess: networkAllowed ? 'allow_all' : 'user_config_only',
154
- readBoundary: hostFull ? 'system' : 'workspace',
155
- },
156
- permissions: {
157
- allow: [...new Set(allow)].sort(),
158
- deny: [...new Set(deny)].sort(),
159
- },
160
- notifications: false,
161
- hints: false,
162
- suggestNextPrompt: false,
163
- attribution: {
164
- attributeCommitsToAgent: false,
165
- attributePRsToAgent: false,
166
- },
167
- };
168
- const sandboxConfig = {
169
- type: hostFull
170
- ? 'insecure_none'
171
- : workspaceReadOnly
172
- ? 'workspace_readonly'
173
- : 'workspace_readwrite',
174
- additionalReadwritePaths: [],
175
- additionalReadonlyPaths: [],
176
- disableTmpWrite: true,
177
- enableSharedBuildCache: false,
178
- networkPolicy: networkAllowed
179
- ? { default: 'allow', allow: [], deny: [] }
180
- : { default: 'deny', allow: [], deny: ['*'] },
181
- networkPolicyStrict: !networkAllowed,
182
- readBoundary: hostFull ? 'system' : 'workspace',
183
- };
184
- return {
185
- args: [
186
- '--trust',
187
- '--workspace',
188
- workDir,
189
- '--sandbox',
190
- hostFull ? 'disabled' : 'enabled',
191
- ...(workspaceReadOnly ? ['--mode', 'ask'] : []),
192
- ...(policy.externalCapabilities.mcp ? ['--approve-mcps'] : []),
193
- ...(hostFull ? ['--force'] : []),
194
- ],
195
- cliConfig,
196
- sandboxConfig,
197
- };
198
- }
199
- export async function createCursorManagedConfigDirectory(config) {
200
- const configDir = await fsPromises.mkdtemp(path.join(os.tmpdir(), CURSOR_MANAGED_CONFIG_PREFIX));
201
- try {
202
- await fsPromises.chmod(configDir, 0o700);
203
- await Promise.all([
204
- writePrivateJson(path.join(configDir, 'cli-config.json'), config.cliConfig),
205
- writePrivateJson(path.join(configDir, 'sandbox.json'), config.sandboxConfig),
206
- ]);
207
- return configDir;
208
- }
209
- catch (error) {
210
- await disposeCursorManagedConfigDirectory(configDir).catch(() => { });
211
- throw new Error('cursor_managed_config_initialization_failed', { cause: error });
212
- }
213
- }
214
- export async function disposeCursorManagedConfigDirectory(configDir) {
215
- const resolved = path.resolve(configDir);
216
- const tempRoot = path.resolve(os.tmpdir());
217
- if (path.dirname(resolved) !== tempRoot ||
218
- !path.basename(resolved).startsWith(CURSOR_MANAGED_CONFIG_PREFIX)) {
219
- throw new Error('cursor_managed_config_cleanup_refused');
220
- }
221
- await fsPromises.rm(resolved, { recursive: true, force: true });
222
- }
223
- function assertProjectSandboxCannotBroadenPolicy(workDir, workspaceReadOnly) {
224
- const projectConfigPath = path.join(workDir, '.cursor', 'sandbox.json');
225
- if (!fs.existsSync(projectConfigPath))
226
- return;
227
- let raw;
228
- try {
229
- const stat = fs.statSync(projectConfigPath);
230
- if (!stat.isFile() || stat.size > MAX_PROJECT_SANDBOX_CONFIG_BYTES) {
231
- throw new Error('invalid_project_sandbox');
232
- }
233
- raw = fs.readFileSync(projectConfigPath, 'utf8');
234
- }
235
- catch (error) {
236
- throw new Error('cursor_managed_project_sandbox_unverifiable', { cause: error });
237
- }
238
- let parsed;
239
- try {
240
- const value = JSON.parse(raw);
241
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
242
- throw new Error('invalid_project_sandbox');
243
- }
244
- parsed = value;
245
- }
246
- catch (error) {
247
- throw new Error('cursor_managed_project_sandbox_unverifiable', { cause: error });
248
- }
249
- if (parsed.type === 'insecure_none') {
250
- throw new Error('cursor_managed_project_sandbox_broadens_access');
251
- }
252
- if (workspaceReadOnly && parsed.type === 'workspace_readwrite') {
253
- throw new Error('cursor_managed_project_sandbox_broadens_access');
254
- }
255
- if (hasConfiguredPaths(parsed.additionalReadwritePaths) || hasConfiguredPaths(parsed.additionalReadonlyPaths)) {
256
- throw new Error('cursor_managed_project_sandbox_broadens_access');
257
- }
258
- }
259
- function hasConfiguredPaths(value) {
260
- if (value == null)
261
- return false;
262
- return !Array.isArray(value) || value.length > 0;
263
- }
264
- function normalizeForCursor(value) {
265
- return value.replaceAll('\\', '/');
266
- }
267
- function resolveSensitiveReadRules() {
268
- const shennianDirectories = resolveProtectedShennianDirectories()
269
- .map((entry) => normalizeForCursor(path.normalize(entry)))
270
- .filter((entry) => path.isAbsolute(entry));
271
- return [...new Set([
272
- ...BASE_SENSITIVE_READ_RULES,
273
- ...shennianDirectories.map((entry) => `Read(${entry}/**)`),
274
- ])];
275
- }
276
- function validateResolvedWorkDir(value) {
277
- const normalized = path.normalize(value.trim());
278
- if (!normalized || !path.isAbsolute(normalized)) {
279
- throw new Error('cursor_managed_workdir_invalid');
280
- }
281
- let realPath;
282
- try {
283
- realPath = fs.realpathSync(normalized);
284
- }
285
- catch {
286
- throw new Error('cursor_managed_workdir_unavailable');
287
- }
288
- if (path.normalize(realPath) !== normalized) {
289
- throw new Error('cursor_managed_workdir_not_realpath');
290
- }
291
- return normalized;
292
- }
293
- async function writePrivateJson(filePath, value) {
294
- await fsPromises.writeFile(filePath, `${JSON.stringify(value)}\n`, {
295
- encoding: 'utf8',
296
- mode: 0o600,
297
- flag: 'wx',
298
- });
299
- }
1
+ import l from"node:fs";import c from"node:fs/promises";import u from"node:os";import s from"node:path";import{resolveProtectedShennianDirectories as C}from"../security/managed-agent-credential-paths.js";const f="shennian-cursor-managed-",R=1024*1024,v=["Read(.env)","Read(.env.*)","Read(**/.env)","Read(**/.env.*)","Read(*.key)","Read(**/*.key)","Read(*.pem)","Read(**/*.pem)","Read(~/.shennian/**)","Read($HOME/.shennian/**)"],p={commandNetwork:{allow:!1,deny:!0},webSearch:{allow:!0,deny:!0},browser:{allow:!1,deny:!0},computerUse:{allow:!1,deny:!0},mcp:{allow:!1,deny:!1}},U={minimumRuntimeVersion:"2026.08.31",maximumRuntimeVersionExclusive:"2026.09.01",platforms:{darwin:{level:"partial",fileAccess:{"workspace-read":!1,"workspace-write":!1,"host-full":!0},externalCapabilities:p,knownGaps:["fileAccess.workspaceBoundaryUnenforced","fileAccess.outsideWorkspaceRead","sandbox.shellDisabled","externalCapabilities.webSearchToolGranularity","externalCapabilities.projectMcpStartup"]},linux:{level:"partial",fileAccess:{"workspace-read":!1,"workspace-write":!1,"host-full":!0},externalCapabilities:p,knownGaps:["fileAccess.workspaceBoundaryUnenforced","fileAccess.outsideWorkspaceRead","sandbox.shellDisabled","externalCapabilities.webSearchToolGranularity","externalCapabilities.projectMcpStartup"]},wsl:{level:"partial",fileAccess:{"workspace-read":!1,"workspace-write":!1,"host-full":!0},externalCapabilities:p,knownGaps:["fileAccess.workspaceBoundaryUnenforced","fileAccess.outsideWorkspaceRead","sandbox.shellDisabled","externalCapabilities.webSearchToolGranularity","externalCapabilities.projectMcpStartup"]},win32:{level:"partial",fileAccess:{"workspace-read":!1,"workspace-write":!1,"host-full":!0},externalCapabilities:{commandNetwork:{allow:!0,deny:!1},webSearch:{allow:!0,deny:!0},browser:{allow:!1,deny:!0},computerUse:{allow:!1,deny:!0},mcp:{allow:!0,deny:!0}},knownGaps:["sandbox.nativeWindowsUnverified","sandbox.shellDisabled","externalCapabilities.projectMcpStartup"]}}};function G(a,e){const n=j(e),o=a.effectivePolicy,t=o.fileAccess==="host-full",r=o.fileAccess==="workspace-read",w=E(),b=w.map(k=>k.replace(/^Read\(/,"Write("));if(o.externalCapabilities.commandNetwork)throw new Error("cursor_managed_command_network_unavailable");if(o.externalCapabilities.mcp)throw new Error("cursor_managed_mcp_isolation_unavailable");if(o.externalCapabilities.browser||o.externalCapabilities.computerUse)throw new Error("cursor_managed_native_interactive_tool_control_unavailable");if(a.platform==="win32"&&!t)throw new Error("cursor_managed_native_windows_sandbox_unavailable");t||S(n,r);const i=["Read(**)"];r||i.push("Write(**)"),o.externalCapabilities.webSearch&&i.push("WebFetch(*)"),o.externalCapabilities.mcp&&i.push("Mcp(*:*)");const h=[...w,...b,"Shell(*)",...r?["Write(**)"]:[],...o.externalCapabilities.webSearch?[]:["WebFetch(*)"],...o.externalCapabilities.mcp?[]:["Mcp(*:*)"]],d=o.externalCapabilities.commandNetwork,y={version:1,editor:{vimMode:!1},approvalMode:t?"unrestricted":"allowlist",sandbox:{mode:t?"disabled":"enabled",networkAccess:d?"allow_all":"user_config_only",readBoundary:t?"system":"workspace"},permissions:{allow:[...new Set(i)].sort(),deny:[...new Set(h)].sort()},notifications:!1,hints:!1,suggestNextPrompt:!1,attribution:{attributeCommitsToAgent:!1,attributePRsToAgent:!1}},x={type:t?"insecure_none":r?"workspace_readonly":"workspace_readwrite",additionalReadwritePaths:[],additionalReadonlyPaths:[],disableTmpWrite:!0,enableSharedBuildCache:!1,networkPolicy:d?{default:"allow",allow:[],deny:[]}:{default:"deny",allow:[],deny:["*"]},networkPolicyStrict:!d,readBoundary:t?"system":"workspace"};return{args:["--trust","--workspace",n,"--sandbox",t?"disabled":"enabled",...r?["--mode","ask"]:[],...o.externalCapabilities.mcp?["--approve-mcps"]:[],...t?["--force"]:[]],cliConfig:y,sandboxConfig:x}}async function T(a){const e=await c.mkdtemp(s.join(u.tmpdir(),f));try{return await c.chmod(e,448),await Promise.all([_(s.join(e,"cli-config.json"),a.cliConfig),_(s.join(e,"sandbox.json"),a.sandboxConfig)]),e}catch(n){throw await g(e).catch(()=>{}),new Error("cursor_managed_config_initialization_failed",{cause:n})}}async function g(a){const e=s.resolve(a),n=s.resolve(u.tmpdir());if(s.dirname(e)!==n||!s.basename(e).startsWith(f))throw new Error("cursor_managed_config_cleanup_refused");await c.rm(e,{recursive:!0,force:!0})}function S(a,e){const n=s.join(a,".cursor","sandbox.json");if(!l.existsSync(n))return;let o;try{const r=l.statSync(n);if(!r.isFile()||r.size>R)throw new Error("invalid_project_sandbox");o=l.readFileSync(n,"utf8")}catch(r){throw new Error("cursor_managed_project_sandbox_unverifiable",{cause:r})}let t;try{const r=JSON.parse(o);if(!r||typeof r!="object"||Array.isArray(r))throw new Error("invalid_project_sandbox");t=r}catch(r){throw new Error("cursor_managed_project_sandbox_unverifiable",{cause:r})}if(t.type==="insecure_none")throw new Error("cursor_managed_project_sandbox_broadens_access");if(e&&t.type==="workspace_readwrite")throw new Error("cursor_managed_project_sandbox_broadens_access");if(m(t.additionalReadwritePaths)||m(t.additionalReadonlyPaths))throw new Error("cursor_managed_project_sandbox_broadens_access")}function m(a){return a==null?!1:!Array.isArray(a)||a.length>0}function A(a){return a.replaceAll("\\","/")}function E(){const a=C().map(e=>A(s.normalize(e))).filter(e=>s.isAbsolute(e));return[...new Set([...v,...a.map(e=>`Read(${e}/**)`)])]}function j(a){const e=s.normalize(a.trim());if(!e||!s.isAbsolute(e))throw new Error("cursor_managed_workdir_invalid");let n;try{n=l.realpathSync(e)}catch{throw new Error("cursor_managed_workdir_unavailable")}if(s.normalize(n)!==e)throw new Error("cursor_managed_workdir_not_realpath");return e}async function _(a,e){await c.writeFile(a,`${JSON.stringify(e)}
2
+ `,{encoding:"utf8",mode:384,flag:"wx"})}export{U as CURSOR_MANAGED_RUNTIME_SECURITY,G as compileCursorManagedPermissions,T as createCursorManagedConfigDirectory,g as disposeCursorManagedConfigDirectory};