xiaotime 0.2.0 → 0.3.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/dist/__tests__/approval_menu.test.d.ts +15 -0
- package/dist/__tests__/approval_menu.test.d.ts.map +1 -0
- package/dist/__tests__/approval_menu.test.js +107 -0
- package/dist/__tests__/approval_menu.test.js.map +1 -0
- package/dist/__tests__/server_tool_render.test.d.ts +17 -0
- package/dist/__tests__/server_tool_render.test.d.ts.map +1 -0
- package/dist/__tests__/server_tool_render.test.js +74 -0
- package/dist/__tests__/server_tool_render.test.js.map +1 -0
- package/dist/__tests__/settings.test.d.ts +16 -0
- package/dist/__tests__/settings.test.d.ts.map +1 -0
- package/dist/__tests__/settings.test.js +289 -0
- package/dist/__tests__/settings.test.js.map +1 -0
- package/dist/__tests__/tools.test.d.ts +17 -0
- package/dist/__tests__/tools.test.d.ts.map +1 -0
- package/dist/__tests__/tools.test.js +381 -0
- package/dist/__tests__/tools.test.js.map +1 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +6 -2
- package/dist/auth.js.map +1 -1
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +24 -1
- package/dist/config.js.map +1 -1
- package/dist/display.d.ts +19 -0
- package/dist/display.d.ts.map +1 -1
- package/dist/display.js +23 -0
- package/dist/display.js.map +1 -1
- package/dist/settings.d.ts +109 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +332 -0
- package/dist/settings.js.map +1 -0
- package/dist/tools.d.ts +11 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +96 -33
- package/dist/tools.js.map +1 -1
- package/dist/ui_state.d.ts +134 -0
- package/dist/ui_state.d.ts.map +1 -0
- package/dist/ui_state.js +460 -0
- package/dist/ui_state.js.map +1 -0
- package/dist/ws.d.ts +5 -0
- package/dist/ws.d.ts.map +1 -1
- package/dist/ws.js +99 -66
- package/dist/ws.js.map +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-user CLI settings + approval allowlist.
|
|
3
|
+
*
|
|
4
|
+
* SPRINT-CLI-APPROVAL-MENU-1 (#6509) PR2.
|
|
5
|
+
*
|
|
6
|
+
* Backs the option-2 "Run and don't ask again" path of
|
|
7
|
+
* `promptApprovalMenu`. Allowlist is persisted to
|
|
8
|
+
* `~/.xiaotime/settings.json` (mode 0600) and consulted before every
|
|
9
|
+
* `executeTool` invocation of the three prompting client-side tools
|
|
10
|
+
* (`shell_command` / `write_file` / `git_commit`). A match short-
|
|
11
|
+
* circuits the menu entirely; the inline log line gets an
|
|
12
|
+
* ` (auto-approved)` suffix so the user can still see what ran.
|
|
13
|
+
*
|
|
14
|
+
* Schema is `version: 1`; missing/malformed/unknown-version files
|
|
15
|
+
* fall back to an empty allowlist (with a stderr warning for
|
|
16
|
+
* malformed/unknown cases) — never crash the CLI on corrupted local
|
|
17
|
+
* config (ISC-6509-007).
|
|
18
|
+
*
|
|
19
|
+
* Pattern derivation differs per tool (see D2 in the sprint spec):
|
|
20
|
+
* - shell_command: argv[0] + leading flags + first subcommand
|
|
21
|
+
* token. `npm install --no-save lodash` → "npm install".
|
|
22
|
+
* Multi-level subcommand grammars (gh issue list) collapse to
|
|
23
|
+
* the first 2 tokens; users can hand-edit settings.json for
|
|
24
|
+
* finer-grained patterns.
|
|
25
|
+
* - write_file: `dirname(path) + "/**"`. Glob-matched against
|
|
26
|
+
* subsequent write_file invocations so allowlisting a write to
|
|
27
|
+
* `/repo/foo.ts` covers `/repo/bar.ts`.
|
|
28
|
+
* - git_commit: literal `"*"` — allowlisting one commit
|
|
29
|
+
* allowlists all future commits (the commit message + staged
|
|
30
|
+
* files vary by definition; there's no meaningful pattern
|
|
31
|
+
* parameter to narrow on).
|
|
32
|
+
*
|
|
33
|
+
* Distinct from `terminal_orchestrator/task_manager.py:57`'s
|
|
34
|
+
* server-side allowlist (EXACT-NAME on argv[0] basename, binary `gh`
|
|
35
|
+
* only). That's a security perimeter for `task_create`'s long-running
|
|
36
|
+
* managed-task envelope; this is a UX gate for client-side tool
|
|
37
|
+
* approvals. Two allowlists, two audiences, two update cadences.
|
|
38
|
+
*/
|
|
39
|
+
export type AllowlistTool = "shell_command" | "write_file" | "git_commit";
|
|
40
|
+
export interface AllowlistEntry {
|
|
41
|
+
tool: AllowlistTool;
|
|
42
|
+
pattern: string;
|
|
43
|
+
added_at: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Settings-file location. Resolved lazily per call so the
|
|
47
|
+
* `XIAOTIME_SETTINGS_PATH` env-var override can redirect (used by
|
|
48
|
+
* tests to avoid touching the real `~/.xiaotime/settings.json`).
|
|
49
|
+
* Default: `~/.xiaotime/settings.json`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function _settingsPath(): string;
|
|
52
|
+
/**
|
|
53
|
+
* Read the allowlist from disk. Returns [] for any of:
|
|
54
|
+
* - file missing (first run; not an error)
|
|
55
|
+
* - file unreadable (permissions; logged + treated as empty)
|
|
56
|
+
* - malformed JSON (logged + treated as empty)
|
|
57
|
+
* - unknown schema version (logged + treated as empty)
|
|
58
|
+
* - shape doesn't match SettingsV1 (silently treated as empty)
|
|
59
|
+
*
|
|
60
|
+
* Never throws. The CLI must remain usable when the user has a
|
|
61
|
+
* corrupted local config (ISC-6509-007).
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadAllowlist(): AllowlistEntry[];
|
|
64
|
+
/**
|
|
65
|
+
* Append a new entry to the allowlist + persist. Creates
|
|
66
|
+
* ~/.xiaotime/ with mode 0700 if it doesn't exist; writes
|
|
67
|
+
* settings.json with mode 0600. Idempotent on duplicate
|
|
68
|
+
* (tool, pattern) — duplicate appends are silently dropped.
|
|
69
|
+
*/
|
|
70
|
+
export declare function appendAllowlist(entry: {
|
|
71
|
+
tool: AllowlistTool;
|
|
72
|
+
pattern: string;
|
|
73
|
+
}): void;
|
|
74
|
+
/**
|
|
75
|
+
* Derive the allowlist pattern for a tool invocation. Returns null
|
|
76
|
+
* if the input shape is unusable (empty command, missing path). The
|
|
77
|
+
* pattern is used both at write time (option-2 selection persists
|
|
78
|
+
* `derivePattern(tool, input)`) and at read time (the next call's
|
|
79
|
+
* derived pattern feeds `matchAllowlist`).
|
|
80
|
+
*
|
|
81
|
+
* Per D2 in the sprint spec, derivation is coarse on purpose. For
|
|
82
|
+
* shell_command, the v1 rule captures argv[0] + leading flags + at
|
|
83
|
+
* most one subcommand token. Multi-level grammars (gh issue list,
|
|
84
|
+
* git remote add) collapse to 2-token prefixes; users who want finer
|
|
85
|
+
* patterns can hand-edit settings.json. For write_file, the directory
|
|
86
|
+
* the file lives in is allowlisted (`/**` suffix); writes deeper in
|
|
87
|
+
* the tree match. For git_commit, the literal `"*"` allowlists all
|
|
88
|
+
* future commits.
|
|
89
|
+
*/
|
|
90
|
+
export declare function derivePattern(tool: AllowlistTool, input: Record<string, unknown>): string | null;
|
|
91
|
+
/**
|
|
92
|
+
* Check whether a tool invocation matches any entry in the
|
|
93
|
+
* allowlist. Matching semantics differ per tool:
|
|
94
|
+
* - shell_command: exact equality between derived pattern and
|
|
95
|
+
* stored pattern (`npm install` matches only `npm install`,
|
|
96
|
+
* not `npm install lodash` directly — but the derived pattern
|
|
97
|
+
* for `npm install lodash` IS `npm install`, so it matches.)
|
|
98
|
+
* - write_file: glob-match the incoming path against each
|
|
99
|
+
* `write_file` entry's stored glob.
|
|
100
|
+
* - git_commit: any `git_commit` entry matches (stored pattern
|
|
101
|
+
* is `"*"`).
|
|
102
|
+
*
|
|
103
|
+
* The cross-tool isolation invariant (PR3 ISC-spec): an entry with
|
|
104
|
+
* `tool === "shell_command"` can never authorize a `write_file` call
|
|
105
|
+
* even if patterns coincidentally match — the tool field is checked
|
|
106
|
+
* first.
|
|
107
|
+
*/
|
|
108
|
+
export declare function matchAllowlist(tool: AllowlistTool, input: Record<string, unknown>, allowlist: AllowlistEntry[]): AllowlistEntry | null;
|
|
109
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAMH,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,YAAY,GAAG,YAAY,CAAC;AAQ1E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AASD;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAKtC;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,IAAI,cAAc,EAAE,CAoChD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC9C,IAAI,CAwCN;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,MAAM,GAAG,IAAI,CA+Cf;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,aAAa,EACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,SAAS,EAAE,cAAc,EAAE,GAC1B,cAAc,GAAG,IAAI,CAsBvB"}
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Per-user CLI settings + approval allowlist.
|
|
4
|
+
*
|
|
5
|
+
* SPRINT-CLI-APPROVAL-MENU-1 (#6509) PR2.
|
|
6
|
+
*
|
|
7
|
+
* Backs the option-2 "Run and don't ask again" path of
|
|
8
|
+
* `promptApprovalMenu`. Allowlist is persisted to
|
|
9
|
+
* `~/.xiaotime/settings.json` (mode 0600) and consulted before every
|
|
10
|
+
* `executeTool` invocation of the three prompting client-side tools
|
|
11
|
+
* (`shell_command` / `write_file` / `git_commit`). A match short-
|
|
12
|
+
* circuits the menu entirely; the inline log line gets an
|
|
13
|
+
* ` (auto-approved)` suffix so the user can still see what ran.
|
|
14
|
+
*
|
|
15
|
+
* Schema is `version: 1`; missing/malformed/unknown-version files
|
|
16
|
+
* fall back to an empty allowlist (with a stderr warning for
|
|
17
|
+
* malformed/unknown cases) — never crash the CLI on corrupted local
|
|
18
|
+
* config (ISC-6509-007).
|
|
19
|
+
*
|
|
20
|
+
* Pattern derivation differs per tool (see D2 in the sprint spec):
|
|
21
|
+
* - shell_command: argv[0] + leading flags + first subcommand
|
|
22
|
+
* token. `npm install --no-save lodash` → "npm install".
|
|
23
|
+
* Multi-level subcommand grammars (gh issue list) collapse to
|
|
24
|
+
* the first 2 tokens; users can hand-edit settings.json for
|
|
25
|
+
* finer-grained patterns.
|
|
26
|
+
* - write_file: `dirname(path) + "/**"`. Glob-matched against
|
|
27
|
+
* subsequent write_file invocations so allowlisting a write to
|
|
28
|
+
* `/repo/foo.ts` covers `/repo/bar.ts`.
|
|
29
|
+
* - git_commit: literal `"*"` — allowlisting one commit
|
|
30
|
+
* allowlists all future commits (the commit message + staged
|
|
31
|
+
* files vary by definition; there's no meaningful pattern
|
|
32
|
+
* parameter to narrow on).
|
|
33
|
+
*
|
|
34
|
+
* Distinct from `terminal_orchestrator/task_manager.py:57`'s
|
|
35
|
+
* server-side allowlist (EXACT-NAME on argv[0] basename, binary `gh`
|
|
36
|
+
* only). That's a security perimeter for `task_create`'s long-running
|
|
37
|
+
* managed-task envelope; this is a UX gate for client-side tool
|
|
38
|
+
* approvals. Two allowlists, two audiences, two update cadences.
|
|
39
|
+
*/
|
|
40
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
41
|
+
if (k2 === undefined) k2 = k;
|
|
42
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
43
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
44
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
45
|
+
}
|
|
46
|
+
Object.defineProperty(o, k2, desc);
|
|
47
|
+
}) : (function(o, m, k, k2) {
|
|
48
|
+
if (k2 === undefined) k2 = k;
|
|
49
|
+
o[k2] = m[k];
|
|
50
|
+
}));
|
|
51
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
52
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
53
|
+
}) : function(o, v) {
|
|
54
|
+
o["default"] = v;
|
|
55
|
+
});
|
|
56
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
57
|
+
var ownKeys = function(o) {
|
|
58
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
59
|
+
var ar = [];
|
|
60
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
61
|
+
return ar;
|
|
62
|
+
};
|
|
63
|
+
return ownKeys(o);
|
|
64
|
+
};
|
|
65
|
+
return function (mod) {
|
|
66
|
+
if (mod && mod.__esModule) return mod;
|
|
67
|
+
var result = {};
|
|
68
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
69
|
+
__setModuleDefault(result, mod);
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
})();
|
|
73
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
74
|
+
exports._settingsPath = _settingsPath;
|
|
75
|
+
exports.loadAllowlist = loadAllowlist;
|
|
76
|
+
exports.appendAllowlist = appendAllowlist;
|
|
77
|
+
exports.derivePattern = derivePattern;
|
|
78
|
+
exports.matchAllowlist = matchAllowlist;
|
|
79
|
+
const fs = __importStar(require("fs"));
|
|
80
|
+
const path = __importStar(require("path"));
|
|
81
|
+
const os = __importStar(require("os"));
|
|
82
|
+
const ALLOWLIST_TOOLS = new Set([
|
|
83
|
+
"shell_command",
|
|
84
|
+
"write_file",
|
|
85
|
+
"git_commit",
|
|
86
|
+
]);
|
|
87
|
+
/**
|
|
88
|
+
* Settings-file location. Resolved lazily per call so the
|
|
89
|
+
* `XIAOTIME_SETTINGS_PATH` env-var override can redirect (used by
|
|
90
|
+
* tests to avoid touching the real `~/.xiaotime/settings.json`).
|
|
91
|
+
* Default: `~/.xiaotime/settings.json`.
|
|
92
|
+
*/
|
|
93
|
+
function _settingsPath() {
|
|
94
|
+
return (process.env.XIAOTIME_SETTINGS_PATH
|
|
95
|
+
?? path.join(os.homedir(), ".xiaotime", "settings.json"));
|
|
96
|
+
}
|
|
97
|
+
function _settingsDir() {
|
|
98
|
+
return path.dirname(_settingsPath());
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Read the allowlist from disk. Returns [] for any of:
|
|
102
|
+
* - file missing (first run; not an error)
|
|
103
|
+
* - file unreadable (permissions; logged + treated as empty)
|
|
104
|
+
* - malformed JSON (logged + treated as empty)
|
|
105
|
+
* - unknown schema version (logged + treated as empty)
|
|
106
|
+
* - shape doesn't match SettingsV1 (silently treated as empty)
|
|
107
|
+
*
|
|
108
|
+
* Never throws. The CLI must remain usable when the user has a
|
|
109
|
+
* corrupted local config (ISC-6509-007).
|
|
110
|
+
*/
|
|
111
|
+
function loadAllowlist() {
|
|
112
|
+
if (!fs.existsSync(_settingsPath()))
|
|
113
|
+
return [];
|
|
114
|
+
let raw;
|
|
115
|
+
try {
|
|
116
|
+
raw = fs.readFileSync(_settingsPath(), "utf-8");
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
process.stderr.write(`[xiaotime] ~/.xiaotime/settings.json unreadable: ${err.message}\n`);
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
let parsed;
|
|
123
|
+
try {
|
|
124
|
+
parsed = JSON.parse(raw);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
process.stderr.write(`[xiaotime] ~/.xiaotime/settings.json malformed JSON, treating as empty: ${err.message}\n`);
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const obj = parsed;
|
|
131
|
+
if (!obj || typeof obj !== "object")
|
|
132
|
+
return [];
|
|
133
|
+
if (obj.version !== 1) {
|
|
134
|
+
process.stderr.write(`[xiaotime] ~/.xiaotime/settings.json: unrecognized schema version ${JSON.stringify(obj.version)}, treating as empty\n`);
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
const entries = obj.permissions?.allow;
|
|
138
|
+
if (!Array.isArray(entries))
|
|
139
|
+
return [];
|
|
140
|
+
return entries.filter(_isValidEntry);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Append a new entry to the allowlist + persist. Creates
|
|
144
|
+
* ~/.xiaotime/ with mode 0700 if it doesn't exist; writes
|
|
145
|
+
* settings.json with mode 0600. Idempotent on duplicate
|
|
146
|
+
* (tool, pattern) — duplicate appends are silently dropped.
|
|
147
|
+
*/
|
|
148
|
+
function appendAllowlist(entry) {
|
|
149
|
+
const existing = loadAllowlist();
|
|
150
|
+
const isDup = existing.some((e) => e.tool === entry.tool && e.pattern === entry.pattern);
|
|
151
|
+
if (isDup)
|
|
152
|
+
return;
|
|
153
|
+
const newEntries = [
|
|
154
|
+
...existing,
|
|
155
|
+
{
|
|
156
|
+
tool: entry.tool,
|
|
157
|
+
pattern: entry.pattern,
|
|
158
|
+
added_at: new Date().toISOString(),
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
const settings = {
|
|
162
|
+
version: 1,
|
|
163
|
+
permissions: { allow: newEntries },
|
|
164
|
+
};
|
|
165
|
+
// Directory: mode 0700 (user-only). The settings file's mode is the
|
|
166
|
+
// load-bearing one but the parent permission tightens the surface
|
|
167
|
+
// against a hostile-co-tenant scenario on shared boxes.
|
|
168
|
+
fs.mkdirSync(_settingsDir(), { recursive: true, mode: 0o700 });
|
|
169
|
+
// openSync + writeSync + closeSync (with O_TRUNC + O_CREAT + 0o600
|
|
170
|
+
// mode) so the file is created with 0600 from the first byte. Using
|
|
171
|
+
// writeFileSync's `mode` option only sets mode on CREATE — but on
|
|
172
|
+
// overwrite of an existing file Node skips chmod entirely on some
|
|
173
|
+
// platforms. Explicit fchmod closes that gap.
|
|
174
|
+
const fd = fs.openSync(_settingsPath(), fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_TRUNC, 0o600);
|
|
175
|
+
try {
|
|
176
|
+
fs.writeSync(fd, JSON.stringify(settings, null, 2) + "\n");
|
|
177
|
+
fs.fchmodSync(fd, 0o600);
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
fs.closeSync(fd);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Derive the allowlist pattern for a tool invocation. Returns null
|
|
185
|
+
* if the input shape is unusable (empty command, missing path). The
|
|
186
|
+
* pattern is used both at write time (option-2 selection persists
|
|
187
|
+
* `derivePattern(tool, input)`) and at read time (the next call's
|
|
188
|
+
* derived pattern feeds `matchAllowlist`).
|
|
189
|
+
*
|
|
190
|
+
* Per D2 in the sprint spec, derivation is coarse on purpose. For
|
|
191
|
+
* shell_command, the v1 rule captures argv[0] + leading flags + at
|
|
192
|
+
* most one subcommand token. Multi-level grammars (gh issue list,
|
|
193
|
+
* git remote add) collapse to 2-token prefixes; users who want finer
|
|
194
|
+
* patterns can hand-edit settings.json. For write_file, the directory
|
|
195
|
+
* the file lives in is allowlisted (`/**` suffix); writes deeper in
|
|
196
|
+
* the tree match. For git_commit, the literal `"*"` allowlists all
|
|
197
|
+
* future commits.
|
|
198
|
+
*/
|
|
199
|
+
function derivePattern(tool, input) {
|
|
200
|
+
if (tool === "shell_command") {
|
|
201
|
+
const cmd = String(input.command ?? "").trim();
|
|
202
|
+
if (!cmd)
|
|
203
|
+
return null;
|
|
204
|
+
const tokens = cmd.split(/\s+/);
|
|
205
|
+
// #6538 CR-r1: guard flag-only input. `"-la -h"` would
|
|
206
|
+
// otherwise produce pattern `"-la -h"`, which is nonsensical
|
|
207
|
+
// (no argv[0]) and would persist as a broken allowlist entry.
|
|
208
|
+
if (tokens[0].startsWith("-"))
|
|
209
|
+
return null;
|
|
210
|
+
const result = [tokens[0]];
|
|
211
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
212
|
+
const tok = tokens[i];
|
|
213
|
+
if (tok.startsWith("-")) {
|
|
214
|
+
result.push(tok);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
// First non-flag arg. Treat as a subcommand iff it's at
|
|
218
|
+
// position 1 (immediately after argv[0]). Otherwise stop —
|
|
219
|
+
// it's a data argument (path / value / package name) we
|
|
220
|
+
// don't want to bake into the pattern.
|
|
221
|
+
if (i === 1)
|
|
222
|
+
result.push(tok);
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
return result.join(" ");
|
|
226
|
+
}
|
|
227
|
+
if (tool === "write_file") {
|
|
228
|
+
const p = String(input.path ?? "");
|
|
229
|
+
if (!p)
|
|
230
|
+
return null;
|
|
231
|
+
// #6538 CR-r1: normalize BEFORE deriving the pattern. Without
|
|
232
|
+
// this, a malicious or careless `input.path` like
|
|
233
|
+
// `"../../etc/passwd"` would persist as allowlist pattern
|
|
234
|
+
// `"../../etc/**"`, opening every future write to that
|
|
235
|
+
// resolved location to bypass the prompt. `path.resolve`
|
|
236
|
+
// anchors against the process cwd; tools.ts calls
|
|
237
|
+
// `resolvePath` (which uses the session cwd) before the file
|
|
238
|
+
// write itself, but derivePattern was operating on raw input
|
|
239
|
+
// — closing the gap here.
|
|
240
|
+
const normalized = path.resolve(p);
|
|
241
|
+
return path.dirname(normalized) + path.sep + "**";
|
|
242
|
+
}
|
|
243
|
+
if (tool === "git_commit") {
|
|
244
|
+
return "*";
|
|
245
|
+
}
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Check whether a tool invocation matches any entry in the
|
|
250
|
+
* allowlist. Matching semantics differ per tool:
|
|
251
|
+
* - shell_command: exact equality between derived pattern and
|
|
252
|
+
* stored pattern (`npm install` matches only `npm install`,
|
|
253
|
+
* not `npm install lodash` directly — but the derived pattern
|
|
254
|
+
* for `npm install lodash` IS `npm install`, so it matches.)
|
|
255
|
+
* - write_file: glob-match the incoming path against each
|
|
256
|
+
* `write_file` entry's stored glob.
|
|
257
|
+
* - git_commit: any `git_commit` entry matches (stored pattern
|
|
258
|
+
* is `"*"`).
|
|
259
|
+
*
|
|
260
|
+
* The cross-tool isolation invariant (PR3 ISC-spec): an entry with
|
|
261
|
+
* `tool === "shell_command"` can never authorize a `write_file` call
|
|
262
|
+
* even if patterns coincidentally match — the tool field is checked
|
|
263
|
+
* first.
|
|
264
|
+
*/
|
|
265
|
+
function matchAllowlist(tool, input, allowlist) {
|
|
266
|
+
for (const entry of allowlist) {
|
|
267
|
+
if (entry.tool !== tool)
|
|
268
|
+
continue;
|
|
269
|
+
if (tool === "git_commit")
|
|
270
|
+
return entry;
|
|
271
|
+
if (tool === "shell_command") {
|
|
272
|
+
const derived = derivePattern(tool, input);
|
|
273
|
+
if (derived !== null && derived === entry.pattern)
|
|
274
|
+
return entry;
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (tool === "write_file") {
|
|
278
|
+
const raw = String(input.path ?? "");
|
|
279
|
+
if (!raw)
|
|
280
|
+
continue;
|
|
281
|
+
// #6538 CR-r1: normalize the input path before matching for
|
|
282
|
+
// the same reason derivePattern does — so `../foo.txt`
|
|
283
|
+
// resolves to its absolute canonical form before being
|
|
284
|
+
// compared against stored absolute patterns.
|
|
285
|
+
const p = path.resolve(raw);
|
|
286
|
+
if (_globMatch(p, entry.pattern))
|
|
287
|
+
return entry;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
// ── Internals ────────────────────────────────────────────────────
|
|
294
|
+
function _isValidEntry(e) {
|
|
295
|
+
if (!e || typeof e !== "object")
|
|
296
|
+
return false;
|
|
297
|
+
const entry = e;
|
|
298
|
+
return (typeof entry.tool === "string" &&
|
|
299
|
+
ALLOWLIST_TOOLS.has(entry.tool) &&
|
|
300
|
+
typeof entry.pattern === "string" &&
|
|
301
|
+
typeof entry.added_at === "string");
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Tiny glob matcher. Supports `*` (matches everything) and
|
|
305
|
+
* `<prefix><sep>**` (matches `<prefix>` and any path under it),
|
|
306
|
+
* where `<sep>` is the platform path separator (`/` on Unix, `\`
|
|
307
|
+
* on Windows). Plain patterns require exact equality. Sufficient
|
|
308
|
+
* for write_file's directory-scope allowlist; not a general-
|
|
309
|
+
* purpose glob engine.
|
|
310
|
+
*
|
|
311
|
+
* #6538 CR-r1: was hardcoded to `/` — broken on Windows where
|
|
312
|
+
* `path.dirname` returns backslash-separated paths. Now uses
|
|
313
|
+
* `path.sep` so the comparison matches the form derivePattern
|
|
314
|
+
* stores (also using `path.sep`).
|
|
315
|
+
*/
|
|
316
|
+
function _globMatch(p, pattern) {
|
|
317
|
+
if (pattern === "*")
|
|
318
|
+
return true;
|
|
319
|
+
const sepGlob = path.sep + "**";
|
|
320
|
+
if (pattern.endsWith(sepGlob)) {
|
|
321
|
+
const prefix = pattern.slice(0, -sepGlob.length);
|
|
322
|
+
return p === prefix || p.startsWith(prefix + path.sep);
|
|
323
|
+
}
|
|
324
|
+
// Tolerant of legacy entries that stored `/**` on Unix (no-op
|
|
325
|
+
// on Linux/macOS; harmless on Windows since path.sep is `\`).
|
|
326
|
+
if (pattern.endsWith("/**")) {
|
|
327
|
+
const prefix = pattern.slice(0, -3);
|
|
328
|
+
return p === prefix || p.startsWith(prefix + "/");
|
|
329
|
+
}
|
|
330
|
+
return p === pattern;
|
|
331
|
+
}
|
|
332
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCH,sCAKC;AAiBD,sCAoCC;AAQD,0CA0CC;AAkBD,sCAkDC;AAmBD,wCA0BC;AA5PD,uCAAyB;AACzB,2CAA6B;AAC7B,uCAAyB;AAIzB,MAAM,eAAe,GAA+B,IAAI,GAAG,CAAC;IAC1D,eAAe;IACf,YAAY;IACZ,YAAY;CACb,CAAC,CAAC;AAeH;;;;;GAKG;AACH,SAAgB,aAAa;IAC3B,OAAO,CACL,OAAO,CAAC,GAAG,CAAC,sBAAsB;WAC/B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,eAAe,CAAC,CACzD,CAAC;AACJ,CAAC;AAED,SAAS,YAAY;IACnB,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,aAAa;IAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;QAAE,OAAO,EAAE,CAAC;IAE/C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oDAAqD,GAAa,CAAC,OAAO,IAAI,CAC/E,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2EAA4E,GAAa,CAAC,OAAO,IAAI,CACtG,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,GAAG,GAAG,MAAoC,CAAC;IACjD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IAC/C,IAAI,GAAG,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qEAAqE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,uBAAuB,CACxH,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC;IACvC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAEvC,OAAO,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;AACvC,CAAC;AAED;;;;;GAKG;AACH,SAAgB,eAAe,CAC7B,KAA+C;IAE/C,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CACzB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,CAC5D,CAAC;IACF,IAAI,KAAK;QAAE,OAAO;IAElB,MAAM,UAAU,GAAqB;QACnC,GAAG,QAAQ;QACX;YACE,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACnC;KACF,CAAC;IACF,MAAM,QAAQ,GAAe;QAC3B,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE;KACnC,CAAC;IAEF,oEAAoE;IACpE,kEAAkE;IAClE,wDAAwD;IACxD,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/D,mEAAmE;IACnE,oEAAoE;IACpE,kEAAkE;IAClE,kEAAkE;IAClE,8CAA8C;IAC9C,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CACpB,aAAa,EAAE,EACf,EAAE,CAAC,SAAS,CAAC,QAAQ,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,EACnE,KAAK,CACN,CAAC;IACF,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC3D,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC3B,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,SAAgB,aAAa,CAC3B,IAAmB,EACnB,KAA8B;IAE9B,IAAI,IAAI,KAAK,eAAe,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC/C,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAChC,uDAAuD;QACvD,6DAA6D;QAC7D,8DAA8D;QAC9D,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,MAAM,MAAM,GAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACtB,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACjB,SAAS;YACX,CAAC;YACD,wDAAwD;YACxD,2DAA2D;YAC3D,wDAAwD;YACxD,uCAAuC;YACvC,IAAI,CAAC,KAAK,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC9B,MAAM;QACR,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpB,8DAA8D;QAC9D,kDAAkD;QAClD,0DAA0D;QAC1D,uDAAuD;QACvD,yDAAyD;QACzD,kDAAkD;QAClD,6DAA6D;QAC7D,6DAA6D;QAC7D,0BAA0B;QAC1B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;IACpD,CAAC;IAED,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;QAC1B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAgB,cAAc,CAC5B,IAAmB,EACnB,KAA8B,EAC9B,SAA2B;IAE3B,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI;YAAE,SAAS;QAClC,IAAI,IAAI,KAAK,YAAY;YAAE,OAAO,KAAK,CAAC;QACxC,IAAI,IAAI,KAAK,eAAe,EAAE,CAAC;YAC7B,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3C,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,CAAC,OAAO;gBAAE,OAAO,KAAK,CAAC;YAChE,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,YAAY,EAAE,CAAC;YAC1B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YACrC,IAAI,CAAC,GAAG;gBAAE,SAAS;YACnB,4DAA4D;YAC5D,uDAAuD;YACvD,uDAAuD;YACvD,6CAA6C;YAC7C,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC/C,SAAS;QACX,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,oEAAoE;AAEpE,SAAS,aAAa,CAAC,CAAU;IAC/B,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9C,MAAM,KAAK,GAAG,CAA4B,CAAC;IAC3C,OAAO,CACL,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAC9B,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC,IAAqB,CAAC;QAChD,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;QACjC,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CACnC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,UAAU,CAAC,CAAS,EAAE,OAAe;IAC5C,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;IAChC,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACjD,OAAO,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACzD,CAAC;IACD,8DAA8D;IAC9D,8DAA8D;IAC9D,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACpC,OAAO,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IACpD,CAAC;IACD,OAAO,CAAC,KAAK,OAAO,CAAC;AACvB,CAAC"}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local tool execution + approval gate.
|
|
3
3
|
*
|
|
4
|
-
* All tools execute on Harvey's machine. write_file
|
|
5
|
-
*
|
|
4
|
+
* All tools execute on Harvey's machine. write_file, shell_command,
|
|
5
|
+
* and git_commit pass through `_gateApproval` which checks the
|
|
6
|
+
* persisted allowlist (`~/.xiaotime/settings.json`) before prompting.
|
|
7
|
+
* Matching allowlist entries short-circuit the menu; the post-execute
|
|
8
|
+
* status line is suffixed with ` (auto-approved)` so the user can
|
|
9
|
+
* see which calls bypassed the prompt (ISC-6509-004).
|
|
10
|
+
*
|
|
11
|
+
* Read-only tools (`read_file`, `list_directory`, `git_status`,
|
|
12
|
+
* `git_diff`) never gate (ISC-6509-006).
|
|
6
13
|
*/
|
|
14
|
+
import type { SessionUI } from "./ui_state.js";
|
|
7
15
|
export interface ToolCall {
|
|
8
16
|
tool_use_id: string;
|
|
9
17
|
name: string;
|
|
@@ -15,5 +23,5 @@ export interface ToolResult {
|
|
|
15
23
|
is_error: boolean;
|
|
16
24
|
}
|
|
17
25
|
export declare function setSessionCwd(cwd: string): void;
|
|
18
|
-
export declare function executeTool(call: ToolCall): Promise<ToolResult>;
|
|
26
|
+
export declare function executeTool(call: ToolCall, ui: SessionUI): Promise<ToolResult>;
|
|
19
27
|
//# sourceMappingURL=tools.d.ts.map
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAOH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAS/C,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAID,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,QAExC;AA2ED,wBAAsB,WAAW,CAC/B,IAAI,EAAE,QAAQ,EACd,EAAE,EAAE,SAAS,GACZ,OAAO,CAAC,UAAU,CAAC,CA2KrB"}
|