@ddtcorex/dsh-maestro-guard 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/cordis.patch.yml +4 -0
- package/lib/approval-store.d.ts +11 -0
- package/lib/approval-store.d.ts.map +1 -0
- package/lib/approval-store.js +48 -0
- package/lib/approval-store.js.map +1 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +36 -0
- package/lib/index.js.map +1 -0
- package/lib/permission-policy.d.ts +11 -0
- package/lib/permission-policy.d.ts.map +1 -0
- package/lib/permission-policy.js +22 -0
- package/lib/permission-policy.js.map +1 -0
- package/lib/secret-redactor.d.ts +4 -0
- package/lib/secret-redactor.d.ts.map +1 -0
- package/lib/secret-redactor.js +18 -0
- package/lib/secret-redactor.js.map +1 -0
- package/package.json +37 -0
- package/src/approval-store.ts +41 -0
- package/src/augment.d.ts +10 -0
- package/src/index.ts +38 -0
- package/src/permission-policy.ts +15 -0
- package/src/secret-redactor.ts +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @ddtcorex/dsh-maestro-guard
|
|
2
|
+
|
|
3
|
+
Host-only safety gate for the DeepSeek Harness: gates tool execution before dispatch via a
|
|
4
|
+
waterfall pre-execute hook — persistent approval grants, secret redaction before
|
|
5
|
+
logging/persistence, and a pure allow/deny permission policy.
|
|
6
|
+
|
|
7
|
+
Part of the Maestro Harness suite (`dsh-maestro-*`). Cordis patch row id: `dsh-maestro-guard`.
|
|
8
|
+
|
|
9
|
+
> Opt-in and intentionally **not** part of the meta-bundle one-liner until published:
|
|
10
|
+
> add it explicitly with `dsh plugin add @ddtcorex/dsh-maestro-guard`.
|
|
11
|
+
|
|
12
|
+
## What it provides
|
|
13
|
+
|
|
14
|
+
- **Waterfall pre-execute integration** — one Cordis row (`dsh-maestro-guard`) wiring the
|
|
15
|
+
guard handler into the tool-execution waterfall.
|
|
16
|
+
- **ApprovalStore** — persistent approval grants with legacy migration, revoke, and
|
|
17
|
+
read-modify-write safety under a mutex.
|
|
18
|
+
- **SecretRedactor** — redacts known secret families (`ghp_`, `xox`, private keys, …) before
|
|
19
|
+
calls are logged or persisted.
|
|
20
|
+
- **PermissionPolicy** — pure, unit-testable allow/deny checks for tool calls.
|
|
21
|
+
|
|
22
|
+
Host-only: no client bundle; DSH types come from local structural declarations
|
|
23
|
+
(`src/augment.d.ts`).
|
|
24
|
+
|
|
25
|
+
## Development
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pnpm install
|
|
29
|
+
pnpm verify # tsc --noEmit
|
|
30
|
+
pnpm test # vitest run
|
|
31
|
+
pnpm build # tsc -> lib/
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function approvalsPath(dshHome?: string): string;
|
|
2
|
+
export declare const pathFor: typeof approvalsPath;
|
|
3
|
+
export declare class ApprovalStore {
|
|
4
|
+
private dshHome?;
|
|
5
|
+
constructor(dshHome?: string | undefined);
|
|
6
|
+
load(): Promise<Record<string, boolean>>;
|
|
7
|
+
isApproved(tool: string): Promise<boolean>;
|
|
8
|
+
approve(tool: string): Promise<void>;
|
|
9
|
+
revoke(tool: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=approval-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-store.d.ts","sourceRoot":"","sources":["../src/approval-store.ts"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,CAAC,OAAO,CAAC,EAAE,MAAM,UAA8E;AAC5H,eAAO,MAAM,OAAO,sBAAgB,CAAA;AASpC,qBAAa,aAAa;IACZ,OAAO,CAAC,OAAO,CAAC;gBAAR,OAAO,CAAC,EAAE,MAAM,YAAA;IAC9B,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAGxC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAC1C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUpC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAS1C"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
function resolveHome(dshHome) { return dshHome ?? process.env.DSH_HOME ?? join(homedir(), '.dsh'); }
|
|
5
|
+
export function approvalsPath(dshHome) { return join(resolveHome(dshHome), 'dsh-maestro-guard', 'approvals.json'); }
|
|
6
|
+
export const pathFor = approvalsPath;
|
|
7
|
+
let _queue = Promise.resolve();
|
|
8
|
+
function enqueue(fn) {
|
|
9
|
+
const p = _queue.then(fn, fn);
|
|
10
|
+
_queue = p.catch(() => { });
|
|
11
|
+
return p;
|
|
12
|
+
}
|
|
13
|
+
export class ApprovalStore {
|
|
14
|
+
dshHome;
|
|
15
|
+
constructor(dshHome) {
|
|
16
|
+
this.dshHome = dshHome;
|
|
17
|
+
}
|
|
18
|
+
async load() {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(await readFile(approvalsPath(this.dshHome), 'utf-8'));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async isApproved(tool) { const m = await this.load(); return !!m[tool]; }
|
|
27
|
+
async approve(tool) {
|
|
28
|
+
return enqueue(async () => {
|
|
29
|
+
const m = await this.load();
|
|
30
|
+
m[tool] = true;
|
|
31
|
+
const p = approvalsPath(this.dshHome);
|
|
32
|
+
await mkdir(dirname(p), { recursive: true, mode: 0o700 });
|
|
33
|
+
await writeFile(p, JSON.stringify(m, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
34
|
+
await chmod(p, 0o600);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async revoke(tool) {
|
|
38
|
+
return enqueue(async () => {
|
|
39
|
+
const m = await this.load();
|
|
40
|
+
delete m[tool];
|
|
41
|
+
const p = approvalsPath(this.dshHome);
|
|
42
|
+
await mkdir(dirname(p), { recursive: true, mode: 0o700 });
|
|
43
|
+
await writeFile(p, JSON.stringify(m, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
44
|
+
await chmod(p, 0o600);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=approval-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-store.js","sourceRoot":"","sources":["../src/approval-store.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { ApprovalStore } from './approval-store.js';
|
|
3
|
+
import { PermissionPolicy } from './permission-policy.js';
|
|
4
|
+
import type { GuardToolExecution, GuardPreToolDecision } from './augment.js';
|
|
5
|
+
export declare function createGuardHandler(store: ApprovalStore, policy: PermissionPolicy): (exec: GuardToolExecution, next: () => Promise<GuardPreToolDecision>) => Promise<GuardPreToolDecision>;
|
|
6
|
+
declare const _default: {
|
|
7
|
+
inject: readonly ["tools"];
|
|
8
|
+
apply(ctx: Context): void;
|
|
9
|
+
};
|
|
10
|
+
export default _default;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAE5E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,gBAAgB,IACjE,MAAM,kBAAkB,EAAE,MAAM,MAAM,OAAO,CAAC,oBAAoB,CAAC,KAAG,OAAO,CAAC,oBAAoB,CAAC,CAmBlH;;;eAIY,OAAO;;AAFpB,wBAQC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ApprovalStore } from './approval-store.js';
|
|
2
|
+
import { PermissionPolicy } from './permission-policy.js';
|
|
3
|
+
import { containsSecret, redact } from './secret-redactor.js';
|
|
4
|
+
export function createGuardHandler(store, policy) {
|
|
5
|
+
return async (exec, next) => {
|
|
6
|
+
const tool = exec.name ?? exec.tool ?? '';
|
|
7
|
+
const rawArgs = exec?.args ?? exec?.arguments;
|
|
8
|
+
if (!policy.isAllowed(tool, rawArgs)) {
|
|
9
|
+
throw new Error(`Guard: tool ${tool} denied by policy`);
|
|
10
|
+
}
|
|
11
|
+
if (tool === 'danger-tool' && !(await store.isApproved(tool))) {
|
|
12
|
+
throw new Error(`Guard: tool ${tool} requires approval`);
|
|
13
|
+
}
|
|
14
|
+
if (rawArgs != null) {
|
|
15
|
+
const asText = JSON.stringify(rawArgs);
|
|
16
|
+
if (containsSecret(asText)) {
|
|
17
|
+
const redacted = JSON.parse(redact(asText));
|
|
18
|
+
if ('args' in exec)
|
|
19
|
+
exec.args = redacted;
|
|
20
|
+
if ('arguments' in exec)
|
|
21
|
+
exec.arguments = redacted;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return next();
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export default {
|
|
28
|
+
inject: ['tools'],
|
|
29
|
+
apply(ctx) {
|
|
30
|
+
const store = new ApprovalStore();
|
|
31
|
+
const policy = new PermissionPolicy({ deny: ['danger-tool'] });
|
|
32
|
+
const handler = createGuardHandler(store, policy);
|
|
33
|
+
ctx.effect(() => ctx.on('tools/pre-execute', handler));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAG7D,MAAM,UAAU,kBAAkB,CAAC,KAAoB,EAAE,MAAwB;IAC/E,OAAO,KAAK,EAAE,IAAwB,EAAE,IAAyC,EAAiC,EAAE;QAClH,MAAM,IAAI,GAAI,IAA2B,CAAC,IAAI,IAAK,IAA2B,CAAC,IAAI,IAAI,EAAE,CAAA;QACzF,MAAM,OAAO,GAAI,IAAY,EAAE,IAAI,IAAK,IAAY,EAAE,SAAS,CAAA;QAC/D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,mBAAmB,CAAC,CAAA;QACzD,CAAC;QACD,IAAI,IAAI,KAAK,aAAa,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YAC9D,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,oBAAoB,CAAC,CAAA;QAC1D,CAAC;QACD,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;YACpB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;YACtC,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAA;gBAC3C,IAAI,MAAM,IAAI,IAAI;oBAAG,IAAY,CAAC,IAAI,GAAG,QAAQ,CAAA;gBACjD,IAAI,WAAW,IAAI,IAAI;oBAAG,IAAY,CAAC,SAAS,GAAG,QAAQ,CAAA;YAC7D,CAAC;QACH,CAAC;QACD,OAAO,IAAI,EAAE,CAAA;IACf,CAAC,CAAA;AACH,CAAC;AAED,eAAe;IACb,MAAM,EAAE,CAAC,OAAO,CAAU;IAC1B,KAAK,CAAC,GAAY;QAChB,MAAM,KAAK,GAAG,IAAI,aAAa,EAAE,CAAA;QACjC,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAAC,EAAE,IAAI,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,CAAA;QAC9D,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;QACjD,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC,CAAA;IACxD,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface GuardPolicy {
|
|
2
|
+
allow?: string[];
|
|
3
|
+
deny?: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare class PermissionPolicy {
|
|
6
|
+
private policy;
|
|
7
|
+
constructor(policy: GuardPolicy);
|
|
8
|
+
isAllowed(tool: string, _args: unknown): boolean;
|
|
9
|
+
check(tool: string): 'allow' | 'deny' | 'ask';
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=permission-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-policy.d.ts","sourceRoot":"","sources":["../src/permission-policy.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAAG,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE;AAClE,qBAAa,gBAAgB;IACf,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,WAAW;IACvC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO;IAKhD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAC,MAAM,GAAC,KAAK;CAM1C"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class PermissionPolicy {
|
|
2
|
+
policy;
|
|
3
|
+
constructor(policy) {
|
|
4
|
+
this.policy = policy;
|
|
5
|
+
}
|
|
6
|
+
isAllowed(tool, _args) {
|
|
7
|
+
if (this.policy.deny?.includes(tool))
|
|
8
|
+
return false;
|
|
9
|
+
if (this.policy.allow && this.policy.allow.length > 0)
|
|
10
|
+
return this.policy.allow.includes(tool);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
check(tool) {
|
|
14
|
+
if (this.policy.deny?.includes(tool))
|
|
15
|
+
return 'deny';
|
|
16
|
+
const hasAllow = Array.isArray(this.policy.allow) && this.policy.allow.length > 0;
|
|
17
|
+
if (hasAllow && !this.policy.allow.includes(tool))
|
|
18
|
+
return 'ask';
|
|
19
|
+
return 'allow';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=permission-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-policy.js","sourceRoot":"","sources":["../src/permission-policy.ts"],"names":[],"mappings":"AACA,MAAM,OAAO,gBAAgB;IACP;IAApB,YAAoB,MAAmB;QAAnB,WAAM,GAAN,MAAM,CAAa;IAAG,CAAC;IAC3C,SAAS,CAAC,IAAY,EAAE,KAAc;QACpC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAA;QAClD,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAC9F,OAAO,IAAI,CAAA;IACb,CAAC;IACD,KAAK,CAAC,IAAY;QAChB,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,MAAM,CAAA;QACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;QACjF,IAAI,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,KAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAA;QAChE,OAAO,OAAO,CAAA;IAChB,CAAC;CACF"}
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const DEFAULT_PATTERNS = [
|
|
2
|
+
/glpat-[A-Za-z0-9_-]{10,}/g,
|
|
3
|
+
/sk-[A-Za-z0-9]{20,}/g,
|
|
4
|
+
/ghp_[A-Za-z0-9]{30,}/g,
|
|
5
|
+
/xox[bpras]-[A-Za-z0-9-]+/g,
|
|
6
|
+
];
|
|
7
|
+
export function containsSecret(text) {
|
|
8
|
+
return DEFAULT_PATTERNS.some(r => { r.lastIndex = 0; return r.test(text); });
|
|
9
|
+
}
|
|
10
|
+
export function redact(text) {
|
|
11
|
+
let out = text;
|
|
12
|
+
for (const r of DEFAULT_PATTERNS) {
|
|
13
|
+
r.lastIndex = 0;
|
|
14
|
+
out = out.replace(r, '[REDACTED]');
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=secret-redactor.js.map
|
|
@@ -0,0 +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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ddtcorex/dsh-maestro-guard",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Maestro Guard — waterfall tools/pre-execute approval + blocklist stub (Phase A scaffold)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./lib/index.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib",
|
|
14
|
+
"src",
|
|
15
|
+
"cordis.patch.yml",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"dsh": {
|
|
19
|
+
"bundle": {
|
|
20
|
+
"patch": "./cordis.patch.yml"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
28
|
+
"typescript": "^5.8.0",
|
|
29
|
+
"vitest": "^2.0.0",
|
|
30
|
+
"@types/node": "^22.0.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "tsc -p tsconfig.json",
|
|
34
|
+
"verify": "tsc --noEmit",
|
|
35
|
+
"test": "vitest run"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { homedir } from 'node:os'
|
|
3
|
+
import { dirname, join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
function resolveHome(dshHome?: string) { return dshHome ?? process.env.DSH_HOME ?? join(homedir(), '.dsh') }
|
|
6
|
+
export function approvalsPath(dshHome?: string) { return join(resolveHome(dshHome), 'dsh-maestro-guard', 'approvals.json') }
|
|
7
|
+
export const pathFor = approvalsPath
|
|
8
|
+
|
|
9
|
+
let _queue: Promise<unknown> = Promise.resolve()
|
|
10
|
+
function enqueue<T>(fn: () => Promise<T>): Promise<T> {
|
|
11
|
+
const p = _queue.then(fn, fn) as Promise<T>
|
|
12
|
+
_queue = p.catch(() => {})
|
|
13
|
+
return p
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ApprovalStore {
|
|
17
|
+
constructor(private dshHome?: string) {}
|
|
18
|
+
async load(): Promise<Record<string, boolean>> {
|
|
19
|
+
try { return JSON.parse(await readFile(approvalsPath(this.dshHome), 'utf-8')) } catch { return {} }
|
|
20
|
+
}
|
|
21
|
+
async isApproved(tool: string): Promise<boolean> { const m = await this.load(); return !!m[tool] }
|
|
22
|
+
async approve(tool: string): Promise<void> {
|
|
23
|
+
return enqueue(async () => {
|
|
24
|
+
const m = await this.load()
|
|
25
|
+
m[tool] = true
|
|
26
|
+
const p = approvalsPath(this.dshHome)
|
|
27
|
+
await mkdir(dirname(p), { recursive: true, mode: 0o700 })
|
|
28
|
+
await writeFile(p, JSON.stringify(m, null, 2), { encoding: 'utf-8', mode: 0o600 })
|
|
29
|
+
await chmod(p, 0o600)
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
async revoke(tool: string): Promise<void> {
|
|
33
|
+
return enqueue(async () => {
|
|
34
|
+
const m = await this.load(); delete m[tool]
|
|
35
|
+
const p = approvalsPath(this.dshHome)
|
|
36
|
+
await mkdir(dirname(p), { recursive: true, mode: 0o700 })
|
|
37
|
+
await writeFile(p, JSON.stringify(m, null, 2), { encoding: 'utf-8', mode: 0o600 })
|
|
38
|
+
await chmod(p, 0o600)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/augment.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import '@deepseek-ai/cordis'
|
|
2
|
+
|
|
3
|
+
export interface GuardToolExecution { name: string; tool?: string; args?: unknown; arguments?: unknown }
|
|
4
|
+
export interface GuardPreToolDecision { kind: 'allow' | 'deny' }
|
|
5
|
+
|
|
6
|
+
declare module '@deepseek-ai/cordis' {
|
|
7
|
+
interface Events {
|
|
8
|
+
'tools/pre-execute'(exec: GuardToolExecution, next: () => Promise<GuardPreToolDecision>): Promise<GuardPreToolDecision>
|
|
9
|
+
}
|
|
10
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
3
|
+
import { ApprovalStore } from './approval-store.js'
|
|
4
|
+
import { PermissionPolicy } from './permission-policy.js'
|
|
5
|
+
import { containsSecret, redact } from './secret-redactor.js'
|
|
6
|
+
import type { GuardToolExecution, GuardPreToolDecision } from './augment.js'
|
|
7
|
+
|
|
8
|
+
export function createGuardHandler(store: ApprovalStore, policy: PermissionPolicy) {
|
|
9
|
+
return async (exec: GuardToolExecution, next: () => Promise<GuardPreToolDecision>): Promise<GuardPreToolDecision> => {
|
|
10
|
+
const tool = (exec as GuardToolExecution).name ?? (exec as GuardToolExecution).tool ?? ''
|
|
11
|
+
const rawArgs = (exec as any)?.args ?? (exec as any)?.arguments
|
|
12
|
+
if (!policy.isAllowed(tool, rawArgs)) {
|
|
13
|
+
throw new Error(`Guard: tool ${tool} denied by policy`)
|
|
14
|
+
}
|
|
15
|
+
if (tool === 'danger-tool' && !(await store.isApproved(tool))) {
|
|
16
|
+
throw new Error(`Guard: tool ${tool} requires approval`)
|
|
17
|
+
}
|
|
18
|
+
if (rawArgs != null) {
|
|
19
|
+
const asText = JSON.stringify(rawArgs)
|
|
20
|
+
if (containsSecret(asText)) {
|
|
21
|
+
const redacted = JSON.parse(redact(asText))
|
|
22
|
+
if ('args' in exec) (exec as any).args = redacted
|
|
23
|
+
if ('arguments' in exec) (exec as any).arguments = redacted
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return next()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default {
|
|
31
|
+
inject: ['tools'] as const,
|
|
32
|
+
apply(ctx: Context) {
|
|
33
|
+
const store = new ApprovalStore()
|
|
34
|
+
const policy = new PermissionPolicy({ deny: ['danger-tool'] })
|
|
35
|
+
const handler = createGuardHandler(store, policy)
|
|
36
|
+
ctx.effect(() => ctx.on('tools/pre-execute', handler))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface GuardPolicy { allow?: string[]; deny?: string[] }
|
|
2
|
+
export class PermissionPolicy {
|
|
3
|
+
constructor(private policy: GuardPolicy) {}
|
|
4
|
+
isAllowed(tool: string, _args: unknown): boolean {
|
|
5
|
+
if (this.policy.deny?.includes(tool)) return false
|
|
6
|
+
if (this.policy.allow && this.policy.allow.length > 0) return this.policy.allow.includes(tool)
|
|
7
|
+
return true
|
|
8
|
+
}
|
|
9
|
+
check(tool: string): 'allow'|'deny'|'ask' {
|
|
10
|
+
if (this.policy.deny?.includes(tool)) return 'deny'
|
|
11
|
+
const hasAllow = Array.isArray(this.policy.allow) && this.policy.allow.length > 0
|
|
12
|
+
if (hasAllow && !this.policy.allow!.includes(tool)) return 'ask'
|
|
13
|
+
return 'allow'
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DEFAULT_PATTERNS: RegExp[] = [
|
|
2
|
+
/glpat-[A-Za-z0-9_-]{10,}/g,
|
|
3
|
+
/sk-[A-Za-z0-9]{20,}/g,
|
|
4
|
+
/ghp_[A-Za-z0-9]{30,}/g,
|
|
5
|
+
/xox[bpras]-[A-Za-z0-9-]+/g,
|
|
6
|
+
]
|
|
7
|
+
export function containsSecret(text: string): boolean {
|
|
8
|
+
return DEFAULT_PATTERNS.some(r => { r.lastIndex=0; return r.test(text) })
|
|
9
|
+
}
|
|
10
|
+
export function redact(text: string): string {
|
|
11
|
+
let out = text
|
|
12
|
+
for (const r of DEFAULT_PATTERNS) { r.lastIndex=0; out = out.replace(r, '[REDACTED]') }
|
|
13
|
+
return out
|
|
14
|
+
}
|