@ddtcorex/dsh-maestro-guard 0.1.0 → 0.2.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 (39) hide show
  1. package/cordis.patch.yml +1 -0
  2. package/lib/approval-store.d.ts.map +1 -1
  3. package/lib/approval-store.js.map +1 -1
  4. package/lib/approve-tool.d.ts +25 -0
  5. package/lib/approve-tool.d.ts.map +1 -0
  6. package/lib/approve-tool.js +54 -0
  7. package/lib/approve-tool.js.map +1 -0
  8. package/lib/full-scan-tool.d.ts +7 -0
  9. package/lib/full-scan-tool.d.ts.map +1 -0
  10. package/lib/full-scan-tool.js +79 -0
  11. package/lib/full-scan-tool.js.map +1 -0
  12. package/lib/index.d.ts +2 -1
  13. package/lib/index.d.ts.map +1 -1
  14. package/lib/index.js +144 -2
  15. package/lib/index.js.map +1 -1
  16. package/lib/pending.d.ts +44 -0
  17. package/lib/pending.d.ts.map +1 -0
  18. package/lib/pending.js +133 -0
  19. package/lib/pending.js.map +1 -0
  20. package/lib/permission-policy.d.ts.map +1 -1
  21. package/lib/permission-policy.js.map +1 -1
  22. package/lib/sandbox.d.ts +95 -0
  23. package/lib/sandbox.d.ts.map +1 -0
  24. package/lib/sandbox.js +317 -0
  25. package/lib/sandbox.js.map +1 -0
  26. package/lib/secret-redactor.d.ts.map +1 -1
  27. package/lib/secret-redactor.js.map +1 -1
  28. package/package.json +6 -1
  29. package/src/host/approve-tool.ts +61 -0
  30. package/src/host/full-scan-tool.ts +71 -0
  31. package/src/host/index.ts +184 -0
  32. package/src/host/pending.ts +144 -0
  33. package/src/host/sandbox.ts +318 -0
  34. package/src/sandbox.ts +5 -0
  35. package/src/index.ts +0 -38
  36. /package/src/{approval-store.ts → host/approval-store.ts} +0 -0
  37. /package/src/{augment.d.ts → host/augment.d.ts} +0 -0
  38. /package/src/{permission-policy.ts → host/permission-policy.ts} +0 -0
  39. /package/src/{secret-redactor.ts → host/secret-redactor.ts} +0 -0
@@ -0,0 +1,95 @@
1
+ /**
2
+ * True if path points to a blocked credential / secret location.
3
+ * Covers: ~/.dsh/.credentials.yaml (any expansion), ~/.cloudflared, NPM_TOKEN, .credentials.yaml substring
4
+ * credentialPaths: additional custom paths from config (guard.credentialPaths) — merged with always-blocked defaults.
5
+ * The 3 always-blocked substrings stay blocked even when credentialPaths is empty.
6
+ */
7
+ export declare function isBlockedPath(input: string, credentialPaths?: string[]): boolean;
8
+ /**
9
+ * True if command string is a publish command (pnpm|npm publish)
10
+ */
11
+ /**
12
+ * Resolve the working directory a command actually executes in, when it names
13
+ * one explicitly (cd <dir> / git -C <dir>). Falls back to the passed cwd when
14
+ * the command has no explicit target — preserving the historical session-cwd
15
+ * semantics for commands that run in place.
16
+ */
17
+ export declare function getCommandWorkingDir(command: string | undefined, cwd: string | undefined): string | undefined;
18
+ /**
19
+ * Choose the branch used for protected-branch detection: the branch of the repo
20
+ * the command targets (via `getCommandWorkingDir`), falling back to the session
21
+ * cwd when the command runs in place. If the command cd's into a directory that
22
+ * is not a git repo, no protected branch applies (the push would fail there
23
+ * anyway) — do NOT fall back to the session cwd, that reintroduces the
24
+ * false-positive where a feature-branch push inside a sub-repo is blocked
25
+ * because the session cwd repo happens to sit on master.
26
+ */
27
+ export declare function resolveCurrentBranch(command: string | undefined, sessionCwd: string | undefined, branchOf: (dir: string) => string | undefined): string | undefined;
28
+ /**
29
+ * The executed command surface of a tool call. Shell-style tools carry their
30
+ * script in `args.command` (bash/exec/shell/govard_shell); bare string args are
31
+ * the command itself. Tools with no command field (read/write/memory/...) have
32
+ * no execution surface, so protected-op detection must not apply to their
33
+ * content — that was the source of the analysis-tool false positives.
34
+ */
35
+ export declare function extractCommandText(args: unknown): string | undefined;
36
+ export declare function isBlockedCommand(cmd: string): boolean;
37
+ /**
38
+ * True when the command executes a protected git operation. Detection is
39
+ * per-command-segment (split on && / ; / | / newline) so a `gh pr create
40
+ * --base master` mention after a feature push — or quoted text anywhere — does
41
+ * not turn a safe push into a blocked one. Hard rules (gh pr merge, gh release,
42
+ * protection deletion, protected branch words in the push segment, being
43
+ * checked out on a protected branch) keep their unconditional coverage.
44
+ */
45
+ export declare function isBlockedGitCommand(cmd: string, currentBranch?: string, branches?: string[]): boolean;
46
+ export declare function isPublishBlocked(cmd: string, approved: boolean): boolean;
47
+ /**
48
+ * True if target path is outside cwd (strict containment).
49
+ * Uses resolve for absolute comparison; expanded ~/ handled.
50
+ */
51
+ export declare function isOutsideCwd(target: string, cwd: string): boolean;
52
+ export interface SandboxCheckResult {
53
+ blocked: boolean;
54
+ reason?: string;
55
+ }
56
+ export interface CheckSandboxOpts {
57
+ cwd?: string;
58
+ currentBranch?: string;
59
+ approved?: boolean;
60
+ credentialPaths?: string[];
61
+ gitProtection?: {
62
+ enabled: boolean;
63
+ branches: string[];
64
+ };
65
+ publishBlocked?: boolean;
66
+ cwdContainment?: boolean;
67
+ }
68
+ /**
69
+ * Central sandbox check. Combines credential-path, git-protection, publish, and cwd containment.
70
+ * @param tool tool name (e.g. maestro_read_file, exec, bash)
71
+ * @param args tool arguments (object, string, or unknown)
72
+ * @param opts.cwd session cwd (exec.agent.session.header.cwd)
73
+ * @param opts.currentBranch git current branch (from getCurrentBranch)
74
+ * @param opts.approved whether publish/git is APPROVED (via ApprovalStore)
75
+ * @param opts.credentialPaths additional blocked credential paths from guard config
76
+ * @param opts.gitProtection git protection toggle + branches from guard config
77
+ * @param opts.publishBlocked whether publish is blocked (default true)
78
+ * @param opts.cwdContainment whether cwd containment is enforced (default true)
79
+ */
80
+ export declare function checkSandbox(tool: string, args: unknown, opts?: CheckSandboxOpts): SandboxCheckResult;
81
+ /**
82
+ * Simple guard per task 5 snippet: guard(p) => false if blocked, true if allowed.
83
+ * For publish, second arg approved indicates APPROVED.
84
+ */
85
+ export declare function guard(p: string, approved?: boolean): boolean;
86
+ export declare const sandbox: {
87
+ isBlockedPath: typeof isBlockedPath;
88
+ isBlockedCommand: typeof isBlockedCommand;
89
+ isBlockedGitCommand: typeof isBlockedGitCommand;
90
+ isPublishBlocked: typeof isPublishBlocked;
91
+ isOutsideCwd: typeof isOutsideCwd;
92
+ checkSandbox: typeof checkSandbox;
93
+ guard: typeof guard;
94
+ };
95
+ //# sourceMappingURL=sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/host/sandbox.ts"],"names":[],"mappings":"AAoBA;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAoChF;AAED;;GAEG;AACH;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAkB7G;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAC5C,MAAM,GAAG,SAAS,CAIpB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOpE;AAOD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIrD;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAkCrG;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAGxE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CASjE;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;IACxD,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE,gBAAgB,GACtB,kBAAkB,CAsEpB;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,OAAO,CAK5D;AAED,eAAO,MAAM,OAAO;;;;;;;;CAAgH,CAAA"}
package/lib/sandbox.js ADDED
@@ -0,0 +1,317 @@
1
+ import { homedir } from 'node:os';
2
+ import { join, resolve, normalize } from 'node:path';
3
+ function expandHome(p) {
4
+ if (!p)
5
+ return p;
6
+ if (p === '~')
7
+ return homedir();
8
+ if (p.startsWith('~/'))
9
+ return join(homedir(), p.slice(2));
10
+ if (p.startsWith('$HOME/'))
11
+ return join(homedir(), p.slice(6));
12
+ if (p.startsWith('${HOME}/'))
13
+ return join(homedir(), p.slice(8));
14
+ return p;
15
+ }
16
+ function normalizePath(p) {
17
+ try {
18
+ return normalize(expandHome(p));
19
+ }
20
+ catch {
21
+ return expandHome(p);
22
+ }
23
+ }
24
+ /**
25
+ * True if path points to a blocked credential / secret location.
26
+ * Covers: ~/.dsh/.credentials.yaml (any expansion), ~/.cloudflared, NPM_TOKEN, .credentials.yaml substring
27
+ * credentialPaths: additional custom paths from config (guard.credentialPaths) — merged with always-blocked defaults.
28
+ * The 3 always-blocked substrings stay blocked even when credentialPaths is empty.
29
+ */
30
+ export function isBlockedPath(input, credentialPaths) {
31
+ if (!input || typeof input !== 'string')
32
+ return false;
33
+ const trimmed = input.trim();
34
+ if (!trimmed)
35
+ return false;
36
+ // Direct substring checks (covers JSON-stringified args, env leakage, etc.) — always blocked
37
+ if (trimmed.includes('.credentials.yaml'))
38
+ return true;
39
+ if (trimmed.includes('.cloudflared'))
40
+ return true;
41
+ if (trimmed.includes('NPM_TOKEN'))
42
+ return true;
43
+ if (trimmed.includes('.dsh') && trimmed.includes('credentials'))
44
+ return true;
45
+ // Normalized expanded check for defaults
46
+ const norm = normalizePath(trimmed);
47
+ if (norm.includes('.credentials.yaml'))
48
+ return true;
49
+ if (norm.includes('.cloudflared'))
50
+ return true;
51
+ // Check absolute homedir variant
52
+ const absCred = join(homedir(), '.dsh', '.credentials.yaml');
53
+ if (norm === absCred || norm.startsWith(absCred))
54
+ return true;
55
+ const absCf = join(homedir(), '.cloudflared');
56
+ if (norm === absCf || norm.startsWith(absCf + '/') || norm.includes('.cloudflared'))
57
+ return true;
58
+ // Injected credentialPaths from config (additional) — substring + normalized + prefix
59
+ if (credentialPaths && credentialPaths.length > 0) {
60
+ for (const p of credentialPaths) {
61
+ if (!p || typeof p !== 'string')
62
+ continue;
63
+ const t = p.trim();
64
+ if (!t)
65
+ continue;
66
+ if (trimmed.includes(t))
67
+ return true;
68
+ const normP = normalizePath(t);
69
+ if (norm.includes(normP))
70
+ return true;
71
+ if (norm === normP)
72
+ return true;
73
+ if (norm.startsWith(normP + '/'))
74
+ return true;
75
+ }
76
+ }
77
+ return false;
78
+ }
79
+ /**
80
+ * True if command string is a publish command (pnpm|npm publish)
81
+ */
82
+ /**
83
+ * Resolve the working directory a command actually executes in, when it names
84
+ * one explicitly (cd <dir> / git -C <dir>). Falls back to the passed cwd when
85
+ * the command has no explicit target — preserving the historical session-cwd
86
+ * semantics for commands that run in place.
87
+ */
88
+ export function getCommandWorkingDir(command, cwd) {
89
+ if (!command || !cwd)
90
+ return cwd ?? undefined;
91
+ const hasCdVerb = /\bcd\b/.test(command) || /\bgit\s+-C\b/.test(command);
92
+ const cd = /\bcd\s+([^\s;&|"'`${}]+)(?:\s*(?:[;&|]|$))/.exec(command);
93
+ const c = /\bgit\s+-C\s+([^\s;&|"'`${}]+)/.exec(command);
94
+ const dir = cd?.[1] ?? c?.[1];
95
+ if (!dir) {
96
+ // A cd/-C verb is present but its target cannot be parsed (quoted, $VAR,
97
+ // wildcard, bare `cd`): do NOT assume the session cwd — that reintroduces
98
+ // the false positive when the session cwd repo sits on a protected branch.
99
+ // Unknown target means no protected-branch assumption (segment word checks
100
+ // still apply); commands with no cd verb keep the session-cwd semantics.
101
+ if (hasCdVerb)
102
+ return undefined;
103
+ return cwd;
104
+ }
105
+ if (dir === '~')
106
+ return homedir();
107
+ if (dir.startsWith('~/'))
108
+ return join(homedir(), dir.slice(2));
109
+ return resolve(cwd, dir);
110
+ }
111
+ /**
112
+ * Choose the branch used for protected-branch detection: the branch of the repo
113
+ * the command targets (via `getCommandWorkingDir`), falling back to the session
114
+ * cwd when the command runs in place. If the command cd's into a directory that
115
+ * is not a git repo, no protected branch applies (the push would fail there
116
+ * anyway) — do NOT fall back to the session cwd, that reintroduces the
117
+ * false-positive where a feature-branch push inside a sub-repo is blocked
118
+ * because the session cwd repo happens to sit on master.
119
+ */
120
+ export function resolveCurrentBranch(command, sessionCwd, branchOf) {
121
+ const dir = getCommandWorkingDir(command, sessionCwd);
122
+ if (!dir)
123
+ return undefined;
124
+ return branchOf(dir);
125
+ }
126
+ /**
127
+ * The executed command surface of a tool call. Shell-style tools carry their
128
+ * script in `args.command` (bash/exec/shell/govard_shell); bare string args are
129
+ * the command itself. Tools with no command field (read/write/memory/...) have
130
+ * no execution surface, so protected-op detection must not apply to their
131
+ * content — that was the source of the analysis-tool false positives.
132
+ */
133
+ export function extractCommandText(args) {
134
+ if (args == null)
135
+ return undefined;
136
+ if (typeof args === 'string')
137
+ return args;
138
+ if (typeof args === 'object' && typeof args.command === 'string') {
139
+ return args.command;
140
+ }
141
+ return undefined;
142
+ }
143
+ /** Collapse quoted spans — text inside quotes is data (echo/printf/script bodies), not argv. */
144
+ function stripQuoted(cmd) {
145
+ return cmd.replace(/"[^"]*"/g, ' ').replace(/'[^']*'/g, ' ');
146
+ }
147
+ export function isBlockedCommand(cmd) {
148
+ if (!cmd || typeof cmd !== 'string')
149
+ return false;
150
+ // package-manager publish verbs as a whole-word sequence
151
+ return /\b(pnpm|npm)\s+publish\b/.test(stripQuoted(cmd));
152
+ }
153
+ /**
154
+ * True when the command executes a protected git operation. Detection is
155
+ * per-command-segment (split on && / ; / | / newline) so a `gh pr create
156
+ * --base master` mention after a feature push — or quoted text anywhere — does
157
+ * not turn a safe push into a blocked one. Hard rules (gh pr merge, gh release,
158
+ * protection deletion, protected branch words in the push segment, being
159
+ * checked out on a protected branch) keep their unconditional coverage.
160
+ */
161
+ export function isBlockedGitCommand(cmd, currentBranch, branches) {
162
+ if (!cmd || typeof cmd !== 'string')
163
+ return false;
164
+ const effectiveBranches = branches && branches.length > 0 ? branches : ['master', 'main'];
165
+ const lowerBranches = effectiveBranches.map((b) => b.toLowerCase());
166
+ const escBranch = (b) => b.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
167
+ const segments = cmd.split(/\s*(?:&&|\|\||;|\||\r?\n)+\s*/);
168
+ for (const seg of segments) {
169
+ const lower = stripQuoted(seg).toLowerCase();
170
+ // hard rules are unconditional per segment
171
+ if (/\bgh\s+pr\s+merge\b/.test(lower))
172
+ return true;
173
+ if (/\bgh\s+release\s+(create|publish)\b/.test(lower))
174
+ return true;
175
+ for (const b of lowerBranches) {
176
+ const re = new RegExp(`gh\\s+api\\b.*delete.*\\/branches\\/${escBranch(b)}\\/protection`);
177
+ if (re.test(lower))
178
+ return true;
179
+ }
180
+ // git push: protected branch word in THIS segment, checked out on one, or
181
+ // pushing a release tag (version tags trigger the CI publish workflow and
182
+ // must follow the same human-approval rule as gh release create)
183
+ if (/\bgit\s+push\b/.test(lower)) {
184
+ for (const b of lowerBranches) {
185
+ if (new RegExp(`\\b${escBranch(b)}\\b`).test(lower))
186
+ return true;
187
+ }
188
+ if (/refs\/tags\//.test(lower))
189
+ return true;
190
+ // semver-like tag as a push refspec: standalone token (space/start
191
+ // preceded), so a branch name like feat/1.2.3 is not a false positive
192
+ if (/(?:^|\s)v?\d+\.\d+\.\d+(?:[-+][0-9a-z.]+)?\b/.test(lower))
193
+ return true;
194
+ if (currentBranch) {
195
+ const curLower = currentBranch.toLowerCase();
196
+ if (lowerBranches.includes(curLower))
197
+ return true;
198
+ }
199
+ }
200
+ }
201
+ // git tag push that includes protected branch (rare) — already covered by push regex
202
+ return false;
203
+ }
204
+ export function isPublishBlocked(cmd, approved) {
205
+ if (!isBlockedCommand(cmd))
206
+ return false;
207
+ return !approved;
208
+ }
209
+ /**
210
+ * True if target path is outside cwd (strict containment).
211
+ * Uses resolve for absolute comparison; expanded ~/ handled.
212
+ */
213
+ export function isOutsideCwd(target, cwd) {
214
+ if (!target || !cwd)
215
+ return false;
216
+ const expTarget = expandHome(target);
217
+ const expCwd = expandHome(cwd);
218
+ const resolvedTarget = resolve(expTarget);
219
+ const resolvedCwd = resolve(expCwd);
220
+ if (resolvedTarget === resolvedCwd)
221
+ return false;
222
+ // Ensure cwd prefix with separator to avoid /tmp/proj matching /tmp/proj2
223
+ return !resolvedTarget.startsWith(resolvedCwd + '/');
224
+ }
225
+ /**
226
+ * Central sandbox check. Combines credential-path, git-protection, publish, and cwd containment.
227
+ * @param tool tool name (e.g. maestro_read_file, exec, bash)
228
+ * @param args tool arguments (object, string, or unknown)
229
+ * @param opts.cwd session cwd (exec.agent.session.header.cwd)
230
+ * @param opts.currentBranch git current branch (from getCurrentBranch)
231
+ * @param opts.approved whether publish/git is APPROVED (via ApprovalStore)
232
+ * @param opts.credentialPaths additional blocked credential paths from guard config
233
+ * @param opts.gitProtection git protection toggle + branches from guard config
234
+ * @param opts.publishBlocked whether publish is blocked (default true)
235
+ * @param opts.cwdContainment whether cwd containment is enforced (default true)
236
+ */
237
+ export function checkSandbox(tool, args, opts) {
238
+ const approved = !!opts?.approved;
239
+ const cwd = opts?.cwd;
240
+ const currentBranch = opts?.currentBranch;
241
+ const credentialPaths = opts?.credentialPaths;
242
+ const gitProtection = opts?.gitProtection;
243
+ const publishBlocked = opts?.publishBlocked ?? true;
244
+ const cwdContainment = opts?.cwdContainment ?? true;
245
+ // Serialize args for generic substring checks
246
+ const asText = args != null ? (typeof args === 'string' ? args : JSON.stringify(args)) : '';
247
+ const combined = `${tool ?? ''} ${asText}`;
248
+ // Protected-op detection (git/publish) runs on the executed command surface
249
+ // only — non-shell tools (write/read/memory/...) have no command and must not
250
+ // be flagged for text that merely mentions a protected phrase.
251
+ const execText = extractCommandText(args);
252
+ // 1) Block credential paths anywhere in tool+args (always, with injected list)
253
+ if (isBlockedPath(tool, credentialPaths) || isBlockedPath(asText, credentialPaths) || isBlockedPath(combined, credentialPaths)) {
254
+ return { blocked: true, reason: 'credential path blocked: ~/.dsh/.credentials.yaml or ~/.cloudflared or NPM_TOKEN' };
255
+ }
256
+ // 2) Block git push to protected branches without APPROVED (branch-aware, toggle-aware)
257
+ const gitEnabled = gitProtection?.enabled ?? true;
258
+ const branches = gitProtection?.branches ?? ['master', 'main'];
259
+ if (gitEnabled && execText && isBlockedGitCommand(execText, currentBranch, branches) && !approved) {
260
+ return { blocked: true, reason: 'git push to master/main blocked without APPROVED: ' + execText.slice(0, 300) };
261
+ }
262
+ // 3) Block publish without APPROVED (toggle-aware)
263
+ if (publishBlocked && execText && isBlockedCommand(execText) && !approved) {
264
+ return { blocked: true, reason: 'publish blocked without APPROVED: pnpm publish requires approval' };
265
+ }
266
+ // 4) Block maestro file tools outside cwd (toggle-aware)
267
+ const fileTools = new Set(['maestro_read_file', 'maestro_write_file', 'fs_read', 'fs_write', 'read_file', 'write_file']);
268
+ if (cwdContainment && cwd && fileTools.has(tool)) {
269
+ let pathVal;
270
+ if (typeof args === 'object' && args !== null) {
271
+ const a = args;
272
+ // common keys
273
+ pathVal = a.path ?? a.file ?? a.file_path ?? a.filePath;
274
+ // array-like args: {0: 'path'}
275
+ if (!pathVal && typeof a[0] === 'string')
276
+ pathVal = a[0];
277
+ // if args itself has nested command with path
278
+ if (!pathVal && typeof a.command === 'string') {
279
+ // command may contain path; fallback to blocked path check already done, but also cwd check if command contains path
280
+ // no explicit path to check
281
+ }
282
+ }
283
+ else if (typeof args === 'string') {
284
+ pathVal = args;
285
+ }
286
+ if (pathVal) {
287
+ if (isBlockedPath(pathVal, credentialPaths)) {
288
+ return { blocked: true, reason: `credential path blocked: ${pathVal}` };
289
+ }
290
+ if (isOutsideCwd(pathVal, cwd)) {
291
+ return { blocked: true, reason: `path outside cwd: ${pathVal} not in ${cwd}` };
292
+ }
293
+ }
294
+ }
295
+ // Also check generic exec/bash tool with cwd containment if it looks like a file path arg
296
+ // (optional: not strictly required for credential/publish but supports broader sandbox)
297
+ if (cwd && (tool === 'exec' || tool === 'bash' || tool === 'shell')) {
298
+ // already covered publish; for credential path we already blocked above via substring
299
+ // No additional cwd check for shell commands unless they are obvious file paths
300
+ }
301
+ return { blocked: false };
302
+ }
303
+ /**
304
+ * Simple guard per task 5 snippet: guard(p) => false if blocked, true if allowed.
305
+ * For publish, second arg approved indicates APPROVED.
306
+ */
307
+ export function guard(p, approved) {
308
+ if (!p || typeof p !== 'string')
309
+ return true;
310
+ if (isBlockedPath(p))
311
+ return false;
312
+ if (isBlockedCommand(p) && !approved)
313
+ return false;
314
+ return true;
315
+ }
316
+ export const sandbox = { isBlockedPath, isBlockedCommand, isBlockedGitCommand, isPublishBlocked, isOutsideCwd, checkSandbox, guard };
317
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/host/sandbox.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"secret-redactor.d.ts","sourceRoot":"","sources":["../src/secret-redactor.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,EAAE,MAAM,EAKpC,CAAA;AACD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AACD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI3C"}
1
+ {"version":3,"file":"secret-redactor.d.ts","sourceRoot":"","sources":["../src/host/secret-redactor.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,EAAE,MAAM,EAKpC,CAAA;AACD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AACD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI3C"}
@@ -1 +1 @@
1
- {"version":3,"file":"secret-redactor.js","sourceRoot":"","sources":["../src/secret-redactor.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAa;IACxC,2BAA2B;IAC3B,sBAAsB;IACtB,uBAAuB;IACvB,2BAA2B;CAC5B,CAAA;AACD,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,SAAS,GAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;AAC3E,CAAC;AACD,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,IAAI,GAAG,GAAG,IAAI,CAAA;IACd,KAAK,MAAM,CAAC,IAAI,gBAAgB,EAAE,CAAC;QAAC,CAAC,CAAC,SAAS,GAAC,CAAC,CAAC;QAAC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,YAAY,CAAC,CAAA;IAAC,CAAC;IACvF,OAAO,GAAG,CAAA;AACZ,CAAC"}
1
+ {"version":3,"file":"secret-redactor.js","sourceRoot":"","sources":["../src/host/secret-redactor.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAa;IACxC,2BAA2B;IAC3B,sBAAsB;IACtB,uBAAuB;IACvB,2BAA2B;CAC5B,CAAA;AACD,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,SAAS,GAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;AAC3E,CAAC;AACD,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,IAAI,GAAG,GAAG,IAAI,CAAA;IACd,KAAK,MAAM,CAAC,IAAI,gBAAgB,EAAE,CAAC;QAAC,CAAC,CAAC,SAAS,GAAC,CAAC,CAAC;QAAC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,YAAY,CAAC,CAAA;IAAC,CAAC;IACvF,OAAO,GAAG,CAAA;AACZ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@ddtcorex/dsh-maestro-guard",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
+ "private": false,
4
5
  "description": "Maestro Guard — waterfall tools/pre-execute approval + blocklist stub (Phase A scaffold)",
5
6
  "type": "module",
6
7
  "main": "./lib/index.js",
@@ -20,11 +21,15 @@
20
21
  "patch": "./cordis.patch.yml"
21
22
  }
22
23
  },
24
+ "dependencies": {
25
+ "@ddtcorex/dsh-maestro-config-lib": "^0.1.2"
26
+ },
23
27
  "peerDependencies": {
24
28
  "@deepseek-ai/cordis": "^4.0.1"
25
29
  },
26
30
  "devDependencies": {
27
31
  "@deepseek-ai/cordis": "^4.0.1",
32
+ "@deepseek-ai/dsh-tools": "0.1.2-alpha.2",
28
33
  "typescript": "^5.8.0",
29
34
  "vitest": "^2.0.0",
30
35
  "@types/node": "^22.0.0"
@@ -0,0 +1,61 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import { defineTool } from '@deepseek-ai/dsh-tools'
3
+ import type { PendingRequest, PendingStore } from './pending.js'
4
+
5
+ export const name = 'maestro-guard-approve-tools'
6
+ export const inject = ['tools'] as const
7
+
8
+ export interface ApproveResult { ok: boolean; requestId: string; scope: string; status: string; message?: string }
9
+ export interface ListResult { ok: true; requests: PendingRequest[] }
10
+
11
+ export function createApproveTools(deps: { pending: PendingStore }) {
12
+ return {
13
+ async approve(requestId: string): Promise<ApproveResult> {
14
+ if (typeof requestId !== 'string' || requestId.length === 0) {
15
+ return { ok: false, requestId: '', scope: '', status: 'error', message: 'requestId (string) is required' }
16
+ }
17
+ const req = await deps.pending.approve(requestId)
18
+ if (!req) {
19
+ return { ok: false, requestId, scope: '', status: 'error', message: `no pending request with id ${requestId}` }
20
+ }
21
+ return { ok: true, requestId: req.id, scope: req.scope, status: req.status }
22
+ },
23
+ async list(): Promise<ListResult> {
24
+ return { ok: true, requests: await deps.pending.list() }
25
+ },
26
+ }
27
+ }
28
+
29
+ export function applyApproveTools(ctx: Context, deps: { pending: PendingStore }): void {
30
+ const tools = createApproveTools(deps)
31
+ ctx.effect(() => ctx.tools.register(defineTool({
32
+ name: 'maestro_guard_approve',
33
+ description: 'Approve one recorded guard request so its exact blocked operation may run once. Call after the human approves the operation in the conversation.',
34
+ parameters: { requestId: { type: 'string', description: 'the request id from the guard block message or maestro_guard_list' } },
35
+ output: {
36
+ schema: {
37
+ type: 'object', additionalProperties: true,
38
+ properties: { ok: { type: 'boolean', required: true }, requestId: { type: 'string', required: true }, scope: { type: 'string', required: true }, status: { type: 'string', required: true }, message: { type: 'string' } },
39
+ },
40
+ render: (_a: unknown, v: any) => [{ type: 'text', text: v.ok ? `approved request ${v.requestId} (scope ${v.scope})` : `approve failed: ${v.message ?? v.status}` }],
41
+ },
42
+ async execute(args: any) {
43
+ return tools.approve((args as any)?.requestId) as any
44
+ },
45
+ })))
46
+ ctx.effect(() => ctx.tools.register(defineTool({
47
+ name: 'maestro_guard_list',
48
+ description: 'List guard tickets (pending/approved/consumed) with redacted commands. Read-only.',
49
+ parameters: {},
50
+ output: {
51
+ schema: {
52
+ type: 'object', additionalProperties: true,
53
+ properties: { ok: { type: 'boolean', required: true }, requests: { type: 'array', required: true, items: { type: 'object', additionalProperties: true } } },
54
+ },
55
+ render: (_a: unknown, v: any) => [{ type: 'text', text: (v.requests ?? []).map((r: any) => `${r.status} ${r.id} ${r.scope} @${r.requestedAt} ${String(r.command ?? '').slice(0, 80)}`).join('\n') || 'no guard requests' }],
56
+ },
57
+ async execute() {
58
+ return tools.list() as any
59
+ },
60
+ })))
61
+ }
@@ -0,0 +1,71 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { existsSync } from 'node:fs'
3
+ import { resolve, join } from 'node:path'
4
+ import type { Context } from '@deepseek-ai/cordis'
5
+ import { defineTool } from '@deepseek-ai/dsh-tools'
6
+ export const name = 'maestro-full-scan-tool'
7
+ export const inject = ['tools'] as const
8
+ function workspaceRootFor(c:string|undefined, e:unknown): string {
9
+ if (c) return c
10
+ const cwd=(e as any)?.agent?.session?.header?.cwd
11
+ return typeof cwd==='string'&&cwd?cwd:process.cwd()
12
+ }
13
+ function resolveScript(root:string): string {
14
+ const direct = join(root, 'scripts/enforce-rules.mjs')
15
+ if (existsSync(direct)) return direct
16
+ // fallback: walk up from root and from process.cwd() to find workspace root containing scripts/enforce-rules.mjs
17
+ const candidates: string[] = []
18
+ // try ancestors of root
19
+ let cur = resolve(root)
20
+ for (let i=0;i<8;i++) {
21
+ const cand = join(cur, 'scripts/enforce-rules.mjs')
22
+ if (existsSync(cand)) return cand
23
+ candidates.push(cand)
24
+ const parent = resolve(cur, '..')
25
+ if (parent===cur) break
26
+ cur = parent
27
+ }
28
+ // try ancestors of process.cwd()
29
+ cur = process.cwd()
30
+ for (let i=0;i<8;i++) {
31
+ const cand = join(cur, 'scripts/enforce-rules.mjs')
32
+ if (existsSync(cand)) return cand
33
+ const parent = resolve(cur, '..')
34
+ if (parent===cur) break
35
+ cur = parent
36
+ }
37
+ // try maestro-harness workspace root resolved via __dirname ancestor search for packages/dsh-maestro-guard
38
+ // last resort: return direct (will error but report will contain command)
39
+ return direct
40
+ }
41
+ export function apply(ctx: Context, config:{rootPath?:string}={}) {
42
+ ctx.effect(()=> ctx.tools.register(defineTool({
43
+ name:'maestro_full_scan',
44
+ description:'Run full enforce-rules scan (blacklist + protection + publish gate) and return report. No approval needed; read-only.',
45
+ parameters:{ rootPath:{type:'string', description:'workspace root override'} },
46
+ output:{ schema:{type:'object', additionalProperties:true, properties:{ok:{type:'boolean', required:true}, report:{type:'string', required:true}, code:{type:'number', required:true}}}, render:(_a:any,v:any)=>[{type:'text', text: v.report.slice(0,4000)}] },
47
+ async execute(args:any, exec:unknown) {
48
+ const root = workspaceRootFor((args as any)?.rootPath ?? config.rootPath, exec)
49
+ const script = resolveScript(root)
50
+ if (!existsSync(script)) {
51
+ return {ok: true, report: `enforce-rules: no script found at ${script} (standalone repo, skipping)`, code: 0}
52
+ }
53
+ const run = (extra:string[])=>{
54
+ const r=spawnSync('node', [script, ...extra], {encoding:'utf-8', timeout: 30000})
55
+ return {code: r.status??0, out: (r.stdout??'')+(r.stderr??'')}
56
+ }
57
+ let combined=''
58
+ let ok=true
59
+ const a=run([])
60
+ combined+=`$ node scripts/enforce-rules.mjs\n${a.out}\n`
61
+ if (a.code!==0) ok=false
62
+ const b=run(['--check-protection'])
63
+ combined+=`$ node scripts/enforce-rules.mjs --check-protection\n${b.out}\n`
64
+ if (b.code!==0) combined+=`[warn] --check-protection non-zero (may need gh auth)\n`
65
+ const c=run(['--check-publish'])
66
+ combined+=`$ node scripts/enforce-rules.mjs --check-publish\n${c.out}\n`
67
+ // do not fail on --check-publish when no tag
68
+ return {ok, report: combined.slice(0,12000), code: ok?0:1}
69
+ }
70
+ })))
71
+ }