@swarmdo/cli 1.8.0 → 1.10.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 +3 -1
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +9 -3
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/pack.d.ts +18 -0
- package/dist/src/commands/pack.d.ts.map +1 -0
- package/dist/src/commands/pack.js +184 -0
- package/dist/src/commands/pack.js.map +1 -0
- package/dist/src/commands/redact.d.ts +18 -0
- package/dist/src/commands/redact.d.ts.map +1 -0
- package/dist/src/commands/redact.js +128 -0
- package/dist/src/commands/redact.js.map +1 -0
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +2 -0
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-tools/index.d.ts +1 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +1 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/mcp-tools/profiles.d.ts.map +1 -1
- package/dist/src/mcp-tools/profiles.js +1 -0
- package/dist/src/mcp-tools/profiles.js.map +1 -1
- package/dist/src/mcp-tools/redact-tools.d.ts +11 -0
- package/dist/src/mcp-tools/redact-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/redact-tools.js +69 -0
- package/dist/src/mcp-tools/redact-tools.js.map +1 -0
- package/dist/src/pack/pack.d.ts +59 -0
- package/dist/src/pack/pack.d.ts.map +1 -0
- package/dist/src/pack/pack.js +178 -0
- package/dist/src/pack/pack.js.map +1 -0
- package/dist/src/redact/redact.d.ts +68 -0
- package/dist/src/redact/redact.d.ts.map +1 -0
- package/dist/src/redact/redact.js +153 -0
- package/dist/src/redact/redact.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EAAE,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -15,6 +15,7 @@ export { workflowTools } from './workflow-tools.js';
|
|
|
15
15
|
export { coverageRouterTools } from '../swarmvector/coverage-tools.js';
|
|
16
16
|
export { analyzeTools } from './analyze-tools.js';
|
|
17
17
|
export { codegraphTools } from './codegraph-tools.js';
|
|
18
|
+
export { redactTools } from './redact-tools.js';
|
|
18
19
|
export { progressTools } from './progress-tools.js';
|
|
19
20
|
export { transferTools } from './transfer-tools.js';
|
|
20
21
|
export { securityTools } from './security-tools.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mcp-tools/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,yFAAyF;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,iEAAiE;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/mcp-tools/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,yFAAyF;AACzF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,iEAAiE;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;AA0B5D,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAI9E,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,IAAI,gBAAgB,CAEnE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,MAAM,EAAE,GAAG,KAAK,CAIxF"}
|
|
@@ -23,6 +23,7 @@ const LEAN_GROUPS = [
|
|
|
23
23
|
'agentdb', // agentdb pattern store/search
|
|
24
24
|
'embeddings', // embeddings_generate / search / compare
|
|
25
25
|
'codegraph', // codegraph_query / file / index — "where is X defined?"
|
|
26
|
+
'redact', // redact_text / redact_scan — secret guard on the data path
|
|
26
27
|
];
|
|
27
28
|
/** Balanced = lean + orchestration/session/system the average user reaches for. */
|
|
28
29
|
const BALANCED_EXTRA = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../../src/mcp-tools/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,8EAA8E;AAC9E,MAAM,WAAW,GAAG;IAClB,OAAO,EAAO,2DAA2D;IACzE,OAAO,EAAO,gCAAgC;IAC9C,QAAQ,EAAM,0CAA0C;IACxD,OAAO,EAAO,mDAAmD;IACjE,SAAS,EAAK,+BAA+B;IAC7C,YAAY,EAAE,yCAAyC;IACvD,WAAW,EAAG,yDAAyD;
|
|
1
|
+
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../../src/mcp-tools/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,8EAA8E;AAC9E,MAAM,WAAW,GAAG;IAClB,OAAO,EAAO,2DAA2D;IACzE,OAAO,EAAO,gCAAgC;IAC9C,QAAQ,EAAM,0CAA0C;IACxD,OAAO,EAAO,mDAAmD;IACjE,SAAS,EAAK,+BAA+B;IAC7C,YAAY,EAAE,yCAAyC;IACvD,WAAW,EAAG,yDAAyD;IACvE,QAAQ,EAAM,4DAA4D;CAClE,CAAC;AAEX,mFAAmF;AACnF,MAAM,cAAc,GAAG;IACrB,UAAU;IACV,MAAM;IACN,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,cAAc;IACd,QAAQ;IACR,aAAa;CACL,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAwD;IACjF,IAAI,EAAE,WAAW;IACjB,QAAQ,EAAE,CAAC,GAAG,WAAW,EAAE,GAAG,cAAc,CAAC;IAC7C,IAAI,EAAE,KAAK;CACZ,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,CAAS;IAC1C,OAAO,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,UAAU,IAAI,CAAC,KAAK,MAAM,CAAC;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAwB;IAC3D,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,kBAAkB,CAAC,IAAI,CAAC;QAAE,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC;IAC1D,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redact MCP Tools
|
|
3
|
+
*
|
|
4
|
+
* Give agents a secret guard on the data path: before writing tool output or
|
|
5
|
+
* context into memory / a message / a file, an agent can mask secrets
|
|
6
|
+
* (redact_text) or check for their presence (redact_scan). Deterministic —
|
|
7
|
+
* shares the pure engine in ../redact/redact.ts with the CLI command.
|
|
8
|
+
*/
|
|
9
|
+
import type { MCPTool } from './types.js';
|
|
10
|
+
export declare const redactTools: MCPTool[];
|
|
11
|
+
//# sourceMappingURL=redact-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact-tools.d.ts","sourceRoot":"","sources":["../../../src/mcp-tools/redact-tools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AA4D1C,eAAO,MAAM,WAAW,EAAE,OAAO,EAAqC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redact MCP Tools
|
|
3
|
+
*
|
|
4
|
+
* Give agents a secret guard on the data path: before writing tool output or
|
|
5
|
+
* context into memory / a message / a file, an agent can mask secrets
|
|
6
|
+
* (redact_text) or check for their presence (redact_scan). Deterministic —
|
|
7
|
+
* shares the pure engine in ../redact/redact.ts with the CLI command.
|
|
8
|
+
*/
|
|
9
|
+
import { redactText, scanText } from '../redact/redact.js';
|
|
10
|
+
function optsOf(params) {
|
|
11
|
+
const opts = { entropy: params.entropy !== false };
|
|
12
|
+
if (typeof params.keepPrefix === 'number')
|
|
13
|
+
opts.keepPrefix = params.keepPrefix;
|
|
14
|
+
if (typeof params.token === 'string')
|
|
15
|
+
opts.token = params.token;
|
|
16
|
+
if (typeof params.entropyThreshold === 'number')
|
|
17
|
+
opts.entropyThreshold = params.entropyThreshold;
|
|
18
|
+
if (Array.isArray(params.allowlist))
|
|
19
|
+
opts.allowlist = params.allowlist.filter((a) => typeof a === 'string');
|
|
20
|
+
return opts;
|
|
21
|
+
}
|
|
22
|
+
const redactTextTool = {
|
|
23
|
+
name: 'redact_text',
|
|
24
|
+
description: 'Mask secrets (API keys, tokens, private keys) in a string before you store it in memory, put it in a message, or write it to a file. Returns the redacted text plus what was found. Deterministic; run this on any untrusted or command-derived content you are about to persist or forward.',
|
|
25
|
+
category: 'redact',
|
|
26
|
+
tags: ['security', 'secrets', 'redaction', 'safety'],
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
text: { type: 'string', description: 'The content to redact' },
|
|
31
|
+
keepPrefix: { type: 'number', description: 'Keep this many leading chars of each secret (default 3; 0 = full mask)' },
|
|
32
|
+
token: { type: 'string', description: 'Replacement token after the kept prefix (default [REDACTED])' },
|
|
33
|
+
entropy: { type: 'boolean', description: 'Enable the high-entropy keyword=value fallback (default true)' },
|
|
34
|
+
entropyThreshold: { type: 'number', description: 'Entropy threshold in bits/char for the fallback (default 3.5)' },
|
|
35
|
+
allowlist: { type: 'array', items: { type: 'string' }, description: 'Substrings; matching secrets are left untouched' },
|
|
36
|
+
},
|
|
37
|
+
required: ['text'],
|
|
38
|
+
},
|
|
39
|
+
handler: async (params) => {
|
|
40
|
+
if (typeof params.text !== 'string')
|
|
41
|
+
return { error: true, message: 'text is required' };
|
|
42
|
+
const { output, findings } = redactText(params.text, optsOf(params));
|
|
43
|
+
return { redacted: output, count: findings.length, findings };
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
const redactScanTool = {
|
|
47
|
+
name: 'redact_scan',
|
|
48
|
+
description: 'Check a string for secrets WITHOUT rewriting it — returns the list of findings (rule, line, column, description). Use to decide whether content is safe to store/forward, or to gate on it. Pair with redact_text when you actually need the masked output.',
|
|
49
|
+
category: 'redact',
|
|
50
|
+
tags: ['security', 'secrets', 'scan', 'safety'],
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
text: { type: 'string', description: 'The content to scan' },
|
|
55
|
+
entropy: { type: 'boolean', description: 'Enable the high-entropy keyword=value fallback (default true)' },
|
|
56
|
+
entropyThreshold: { type: 'number', description: 'Entropy threshold in bits/char for the fallback (default 3.5)' },
|
|
57
|
+
allowlist: { type: 'array', items: { type: 'string' }, description: 'Substrings to ignore' },
|
|
58
|
+
},
|
|
59
|
+
required: ['text'],
|
|
60
|
+
},
|
|
61
|
+
handler: async (params) => {
|
|
62
|
+
if (typeof params.text !== 'string')
|
|
63
|
+
return { error: true, message: 'text is required' };
|
|
64
|
+
const findings = scanText(params.text, optsOf(params));
|
|
65
|
+
return { clean: findings.length === 0, count: findings.length, findings };
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
export const redactTools = [redactTextTool, redactScanTool];
|
|
69
|
+
//# sourceMappingURL=redact-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact-tools.js","sourceRoot":"","sources":["../../../src/mcp-tools/redact-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pack.ts — bundle a repo (or a subset) into one AI-friendly context blob.
|
|
3
|
+
*
|
|
4
|
+
* swarmdo has no context-assembly primitive: `codegraph` indexes symbols for
|
|
5
|
+
* *querying* and `compact` de-noises a *stream*, but neither emits promptable
|
|
6
|
+
* source for *feeding a model*. `pack` walks the tree (respecting default skips
|
|
7
|
+
* + a minimal .gitignore + glob include/exclude), and writes a bundle in
|
|
8
|
+
* markdown / xml / json / plain with a directory tree and per-file + total
|
|
9
|
+
* token estimates. Deterministic — same inputs, byte-identical output.
|
|
10
|
+
*
|
|
11
|
+
* Pure engine: the fs walk is injected as a file list + reader by the caller
|
|
12
|
+
* (../commands/pack.ts), so this module has no fs dependency and is trivially
|
|
13
|
+
* testable. Optional secret redaction reuses ../redact/redact.ts.
|
|
14
|
+
*/
|
|
15
|
+
export type PackFormat = 'md' | 'xml' | 'json' | 'plain';
|
|
16
|
+
export interface PackFile {
|
|
17
|
+
/** repo-relative path, POSIX separators */
|
|
18
|
+
path: string;
|
|
19
|
+
content: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PackOptions {
|
|
22
|
+
format?: PackFormat;
|
|
23
|
+
/** include a directory tree at the top (default true) */
|
|
24
|
+
tree?: boolean;
|
|
25
|
+
/** optional per-file transform (e.g. secret redaction) applied before bundling */
|
|
26
|
+
transform?: (content: string, path: string) => string;
|
|
27
|
+
}
|
|
28
|
+
export interface PackFileStat {
|
|
29
|
+
path: string;
|
|
30
|
+
bytes: number;
|
|
31
|
+
tokens: number;
|
|
32
|
+
}
|
|
33
|
+
export interface PackResult {
|
|
34
|
+
output: string;
|
|
35
|
+
stats: {
|
|
36
|
+
files: number;
|
|
37
|
+
bytes: number;
|
|
38
|
+
tokens: number;
|
|
39
|
+
perFile: PackFileStat[];
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Cheap, deterministic token estimate (~4 chars/token, the widely-used rule of
|
|
44
|
+
* thumb for English + code). Not a real BPE tokenizer — good enough for budget
|
|
45
|
+
* warnings, and dependency-free.
|
|
46
|
+
*/
|
|
47
|
+
export declare function estimateTokens(text: string): number;
|
|
48
|
+
/** Build an ASCII directory tree from a sorted file list. */
|
|
49
|
+
export declare function buildTree(paths: string[]): string;
|
|
50
|
+
/** Bundle files into a single context blob per `format`. */
|
|
51
|
+
export declare function packFiles(input: PackFile[], opts?: PackOptions): PackResult;
|
|
52
|
+
/**
|
|
53
|
+
* Minimal .gitignore-style matcher. Handles the common cases — plain names,
|
|
54
|
+
* `dir/`, `*.ext` globs, leading `/` anchors, and `!` negation — not the full
|
|
55
|
+
* spec (no `**` depth semantics beyond substring, no nested ignore files).
|
|
56
|
+
* Good enough to keep obvious noise out; full semantics is a follow-up.
|
|
57
|
+
*/
|
|
58
|
+
export declare function makeIgnoreMatcher(patterns: string[]): (relPath: string) => boolean;
|
|
59
|
+
//# sourceMappingURL=pack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../../../src/pack/pack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAEzD,MAAM,WAAW,QAAQ;IACvB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kFAAkF;IAClF,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACvD;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,YAAY,EAAE,CAAC;KACzB,CAAC;CACH;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CA8BjD;AAuDD,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,GAAE,WAAgB,GAAG,UAAU,CA4B/E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CA8BlF"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pack.ts — bundle a repo (or a subset) into one AI-friendly context blob.
|
|
3
|
+
*
|
|
4
|
+
* swarmdo has no context-assembly primitive: `codegraph` indexes symbols for
|
|
5
|
+
* *querying* and `compact` de-noises a *stream*, but neither emits promptable
|
|
6
|
+
* source for *feeding a model*. `pack` walks the tree (respecting default skips
|
|
7
|
+
* + a minimal .gitignore + glob include/exclude), and writes a bundle in
|
|
8
|
+
* markdown / xml / json / plain with a directory tree and per-file + total
|
|
9
|
+
* token estimates. Deterministic — same inputs, byte-identical output.
|
|
10
|
+
*
|
|
11
|
+
* Pure engine: the fs walk is injected as a file list + reader by the caller
|
|
12
|
+
* (../commands/pack.ts), so this module has no fs dependency and is trivially
|
|
13
|
+
* testable. Optional secret redaction reuses ../redact/redact.ts.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Cheap, deterministic token estimate (~4 chars/token, the widely-used rule of
|
|
17
|
+
* thumb for English + code). Not a real BPE tokenizer — good enough for budget
|
|
18
|
+
* warnings, and dependency-free.
|
|
19
|
+
*/
|
|
20
|
+
export function estimateTokens(text) {
|
|
21
|
+
if (!text)
|
|
22
|
+
return 0;
|
|
23
|
+
return Math.ceil(text.length / 4);
|
|
24
|
+
}
|
|
25
|
+
/** Build an ASCII directory tree from a sorted file list. */
|
|
26
|
+
export function buildTree(paths) {
|
|
27
|
+
const root = {};
|
|
28
|
+
for (const p of [...paths].sort()) {
|
|
29
|
+
const parts = p.split('/');
|
|
30
|
+
let node = root;
|
|
31
|
+
for (let i = 0; i < parts.length; i++) {
|
|
32
|
+
const part = parts[i];
|
|
33
|
+
const isFile = i === parts.length - 1;
|
|
34
|
+
if (isFile) {
|
|
35
|
+
if (!node['\0files'])
|
|
36
|
+
node['\0files'] = [];
|
|
37
|
+
node['\0files'].push(part);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
node[part] = node[part] ?? {};
|
|
41
|
+
node = node[part];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const lines = [];
|
|
46
|
+
const walk = (node, prefix) => {
|
|
47
|
+
const dirs = Object.keys(node).filter((k) => k !== '\0files').sort();
|
|
48
|
+
const files = (node['\0files'] ?? []).sort();
|
|
49
|
+
const entries = [...dirs.map((d) => ['dir', d]), ...files.map((f) => ['file', f])];
|
|
50
|
+
entries.forEach(([kind, name], i) => {
|
|
51
|
+
const last = i === entries.length - 1;
|
|
52
|
+
lines.push(`${prefix}${last ? '└── ' : '├── '}${name}${kind === 'dir' ? '/' : ''}`);
|
|
53
|
+
if (kind === 'dir')
|
|
54
|
+
walk(node[name], prefix + (last ? ' ' : '│ '));
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
walk(root, '');
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
/** Pick a fenced-code language hint from a file extension. */
|
|
61
|
+
function langOf(path) {
|
|
62
|
+
const ext = path.slice(path.lastIndexOf('.') + 1).toLowerCase();
|
|
63
|
+
const map = {
|
|
64
|
+
ts: 'typescript', tsx: 'tsx', js: 'javascript', jsx: 'jsx', mjs: 'javascript', cjs: 'javascript',
|
|
65
|
+
json: 'json', md: 'markdown', py: 'python', rs: 'rust', go: 'go', sh: 'bash', yml: 'yaml', yaml: 'yaml',
|
|
66
|
+
html: 'html', css: 'css', sql: 'sql', toml: 'toml',
|
|
67
|
+
};
|
|
68
|
+
return map[ext] ?? '';
|
|
69
|
+
}
|
|
70
|
+
function renderMarkdown(files, opts) {
|
|
71
|
+
const parts = ['# Repository context', ''];
|
|
72
|
+
if (opts.tree !== false) {
|
|
73
|
+
parts.push('## Files', '', '```', buildTree(files.map((f) => f.path)), '```', '');
|
|
74
|
+
}
|
|
75
|
+
parts.push('## Contents', '');
|
|
76
|
+
for (const f of files) {
|
|
77
|
+
const lang = langOf(f.path);
|
|
78
|
+
parts.push(`### ${f.path}`, '', '```' + lang, f.content.replace(/\n$/, ''), '```', '');
|
|
79
|
+
}
|
|
80
|
+
return parts.join('\n');
|
|
81
|
+
}
|
|
82
|
+
function escapeXml(s) {
|
|
83
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
84
|
+
}
|
|
85
|
+
function renderXml(files, opts) {
|
|
86
|
+
const parts = ['<repository>'];
|
|
87
|
+
if (opts.tree !== false) {
|
|
88
|
+
parts.push(' <tree>', escapeXml(buildTree(files.map((f) => f.path))), ' </tree>');
|
|
89
|
+
}
|
|
90
|
+
parts.push(' <files>');
|
|
91
|
+
for (const f of files) {
|
|
92
|
+
parts.push(` <file path="${escapeXml(f.path)}">`, escapeXml(f.content.replace(/\n$/, '')), ' </file>');
|
|
93
|
+
}
|
|
94
|
+
parts.push(' </files>', '</repository>');
|
|
95
|
+
return parts.join('\n');
|
|
96
|
+
}
|
|
97
|
+
function renderJson(files) {
|
|
98
|
+
return JSON.stringify({ files: files.map((f) => ({ path: f.path, content: f.content })) }, null, 2);
|
|
99
|
+
}
|
|
100
|
+
function renderPlain(files) {
|
|
101
|
+
const parts = [];
|
|
102
|
+
for (const f of files) {
|
|
103
|
+
parts.push('='.repeat(8) + ' ' + f.path + ' ' + '='.repeat(8), f.content.replace(/\n$/, ''), '');
|
|
104
|
+
}
|
|
105
|
+
return parts.join('\n');
|
|
106
|
+
}
|
|
107
|
+
/** Bundle files into a single context blob per `format`. */
|
|
108
|
+
export function packFiles(input, opts = {}) {
|
|
109
|
+
const files = input
|
|
110
|
+
.map((f) => ({ path: f.path, content: opts.transform ? opts.transform(f.content, f.path) : f.content }))
|
|
111
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
112
|
+
const format = opts.format ?? 'md';
|
|
113
|
+
let output;
|
|
114
|
+
switch (format) {
|
|
115
|
+
case 'xml':
|
|
116
|
+
output = renderXml(files, opts);
|
|
117
|
+
break;
|
|
118
|
+
case 'json':
|
|
119
|
+
output = renderJson(files);
|
|
120
|
+
break;
|
|
121
|
+
case 'plain':
|
|
122
|
+
output = renderPlain(files);
|
|
123
|
+
break;
|
|
124
|
+
default: output = renderMarkdown(files, opts);
|
|
125
|
+
}
|
|
126
|
+
const perFile = files.map((f) => ({
|
|
127
|
+
path: f.path,
|
|
128
|
+
bytes: Buffer.byteLength(f.content, 'utf8'),
|
|
129
|
+
tokens: estimateTokens(f.content),
|
|
130
|
+
}));
|
|
131
|
+
return {
|
|
132
|
+
output,
|
|
133
|
+
stats: {
|
|
134
|
+
files: files.length,
|
|
135
|
+
bytes: perFile.reduce((n, f) => n + f.bytes, 0),
|
|
136
|
+
tokens: estimateTokens(output),
|
|
137
|
+
perFile,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Minimal .gitignore-style matcher. Handles the common cases — plain names,
|
|
143
|
+
* `dir/`, `*.ext` globs, leading `/` anchors, and `!` negation — not the full
|
|
144
|
+
* spec (no `**` depth semantics beyond substring, no nested ignore files).
|
|
145
|
+
* Good enough to keep obvious noise out; full semantics is a follow-up.
|
|
146
|
+
*/
|
|
147
|
+
export function makeIgnoreMatcher(patterns) {
|
|
148
|
+
const rules = patterns
|
|
149
|
+
.map((p) => p.trim())
|
|
150
|
+
.filter((p) => p && !p.startsWith('#'))
|
|
151
|
+
.map((p) => {
|
|
152
|
+
const negate = p.startsWith('!');
|
|
153
|
+
let body = negate ? p.slice(1) : p;
|
|
154
|
+
const dirOnly = body.endsWith('/');
|
|
155
|
+
if (dirOnly)
|
|
156
|
+
body = body.slice(0, -1);
|
|
157
|
+
const anchored = body.startsWith('/');
|
|
158
|
+
if (anchored)
|
|
159
|
+
body = body.slice(1);
|
|
160
|
+
const re = new RegExp('^' +
|
|
161
|
+
body
|
|
162
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
163
|
+
.replace(/\*/g, '[^/]*')
|
|
164
|
+
.replace(/\?/g, '[^/]') +
|
|
165
|
+
(dirOnly ? '(/|$)' : '($|/)'));
|
|
166
|
+
return { negate, anchored, re };
|
|
167
|
+
});
|
|
168
|
+
return (relPath) => {
|
|
169
|
+
let ignored = false;
|
|
170
|
+
for (const r of rules) {
|
|
171
|
+
const candidates = r.anchored ? [relPath] : [relPath, ...relPath.split('/').map((_, i, a) => a.slice(i).join('/'))];
|
|
172
|
+
if (candidates.some((c) => r.re.test(c)))
|
|
173
|
+
ignored = !r.negate;
|
|
174
|
+
}
|
|
175
|
+
return ignored;
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=pack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pack.js","sourceRoot":"","sources":["../../../src/pack/pack.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* redact.ts — deterministic secret detection + redaction for the agent data
|
|
3
|
+
* path. swarmdo pipes tool output, memory, and context into LLMs constantly;
|
|
4
|
+
* `compact` de-noises that stream but never redacts, and `security scan` is
|
|
5
|
+
* CVE-oriented, not a content filter. This engine is the missing guard: it
|
|
6
|
+
* catches API keys / tokens / private keys via a high-confidence rule catalog
|
|
7
|
+
* plus a Shannon-entropy fallback for keyword-prefixed assignments, and masks
|
|
8
|
+
* them before content reaches a model, a log, or memory.
|
|
9
|
+
*
|
|
10
|
+
* Pure + LLM-free by design — known-secret fixtures in, deterministic masked
|
|
11
|
+
* bytes out. The CLI wrapper (../commands/redact.ts) and an MCP tool share it.
|
|
12
|
+
*/
|
|
13
|
+
export interface RedactRule {
|
|
14
|
+
/** stable slug, e.g. 'aws-access-key' */
|
|
15
|
+
id: string;
|
|
16
|
+
/** human description for --scan output */
|
|
17
|
+
description: string;
|
|
18
|
+
/** global regex; the whole match (or capture group `group`) is the secret */
|
|
19
|
+
regex: RegExp;
|
|
20
|
+
/** if set, redact this capture group rather than the whole match */
|
|
21
|
+
group?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface Finding {
|
|
24
|
+
ruleId: string;
|
|
25
|
+
description: string;
|
|
26
|
+
/** 1-based line number */
|
|
27
|
+
line: number;
|
|
28
|
+
/** 1-based column of the secret within the line */
|
|
29
|
+
column: number;
|
|
30
|
+
/** the raw secret text that matched */
|
|
31
|
+
match: string;
|
|
32
|
+
/** what it was replaced with */
|
|
33
|
+
redacted: string;
|
|
34
|
+
}
|
|
35
|
+
export interface RedactOptions {
|
|
36
|
+
/** keep this many leading chars of a secret, mask the rest (default 3) */
|
|
37
|
+
keepPrefix?: number;
|
|
38
|
+
/** the replacement token appended after the kept prefix (default '[REDACTED]') */
|
|
39
|
+
token?: string;
|
|
40
|
+
/** enable the entropy fallback for keyword=value assignments (default true) */
|
|
41
|
+
entropy?: boolean;
|
|
42
|
+
/** Shannon-entropy threshold (bits/char) above which a keyworded value is a secret (default 3.5) */
|
|
43
|
+
entropyThreshold?: number;
|
|
44
|
+
/** substrings/regexes; any secret whose match includes one is left untouched */
|
|
45
|
+
allowlist?: Array<string | RegExp>;
|
|
46
|
+
}
|
|
47
|
+
export interface RedactResult {
|
|
48
|
+
output: string;
|
|
49
|
+
findings: Finding[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* High-confidence catalog. Ordered — more specific rules (e.g. Anthropic
|
|
53
|
+
* `sk-ant-`) come before broader ones (`sk-` OpenAI) so the specific rule wins
|
|
54
|
+
* and we don't double-count. Patterns are conservative to keep false positives
|
|
55
|
+
* low; the entropy fallback covers the long tail of custom secrets.
|
|
56
|
+
*/
|
|
57
|
+
export declare const RULES: RedactRule[];
|
|
58
|
+
/** Shannon entropy in bits/char. Pure; used by the assignment fallback. */
|
|
59
|
+
export declare function shannonEntropy(s: string): number;
|
|
60
|
+
/** Mask a secret: keep `keepPrefix` leading chars, then the token. */
|
|
61
|
+
export declare function maskSecret(secret: string, opts?: RedactOptions): string;
|
|
62
|
+
/** Scan without rewriting — returns findings only (for --scan / CI gating). */
|
|
63
|
+
export declare function scanText(text: string, opts?: RedactOptions): Finding[];
|
|
64
|
+
/** Redact secrets in `text`, returning the rewritten output + findings. */
|
|
65
|
+
export declare function redactText(text: string, opts?: RedactOptions): RedactResult;
|
|
66
|
+
/** One-line human summary for stderr. */
|
|
67
|
+
export declare function formatFindingsSummary(findings: Finding[]): string;
|
|
68
|
+
//# sourceMappingURL=redact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../../src/redact/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,UAAU;IACzB,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,0CAA0C;IAC1C,WAAW,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oGAAoG;IACpG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,SAAS,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,UAAU,EAiB7B,CAAC;AAMF,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAUhD;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,MAAM,CAI3E;AAmED,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,OAAO,EAAE,CAK1E;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,YAAY,CAc/E;AAED,yCAAyC;AACzC,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAMjE"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* redact.ts — deterministic secret detection + redaction for the agent data
|
|
3
|
+
* path. swarmdo pipes tool output, memory, and context into LLMs constantly;
|
|
4
|
+
* `compact` de-noises that stream but never redacts, and `security scan` is
|
|
5
|
+
* CVE-oriented, not a content filter. This engine is the missing guard: it
|
|
6
|
+
* catches API keys / tokens / private keys via a high-confidence rule catalog
|
|
7
|
+
* plus a Shannon-entropy fallback for keyword-prefixed assignments, and masks
|
|
8
|
+
* them before content reaches a model, a log, or memory.
|
|
9
|
+
*
|
|
10
|
+
* Pure + LLM-free by design — known-secret fixtures in, deterministic masked
|
|
11
|
+
* bytes out. The CLI wrapper (../commands/redact.ts) and an MCP tool share it.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* High-confidence catalog. Ordered — more specific rules (e.g. Anthropic
|
|
15
|
+
* `sk-ant-`) come before broader ones (`sk-` OpenAI) so the specific rule wins
|
|
16
|
+
* and we don't double-count. Patterns are conservative to keep false positives
|
|
17
|
+
* low; the entropy fallback covers the long tail of custom secrets.
|
|
18
|
+
*/
|
|
19
|
+
export const RULES = [
|
|
20
|
+
{ id: 'private-key', description: 'Private key block header', regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/g },
|
|
21
|
+
{ id: 'aws-access-key', description: 'AWS access key ID', regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASCA)[0-9A-Z]{16}\b/g },
|
|
22
|
+
{ id: 'github-pat', description: 'GitHub personal access token', regex: /\bghp_[0-9A-Za-z]{36}\b/g },
|
|
23
|
+
{ id: 'github-oauth', description: 'GitHub OAuth/app token', regex: /\b(?:gho|ghu|ghs|ghr)_[0-9A-Za-z]{36}\b/g },
|
|
24
|
+
{ id: 'github-fine-pat', description: 'GitHub fine-grained PAT', regex: /\bgithub_pat_[0-9A-Za-z_]{82}\b/g },
|
|
25
|
+
{ id: 'gitlab-pat', description: 'GitLab personal access token', regex: /\bglpat-[0-9A-Za-z_-]{20}\b/g },
|
|
26
|
+
{ id: 'anthropic-key', description: 'Anthropic API key', regex: /\bsk-ant-[0-9A-Za-z-]{16,}\b/g },
|
|
27
|
+
{ id: 'openai-key', description: 'OpenAI API key', regex: /\bsk-(?:proj-)?[0-9A-Za-z]{20,}\b/g },
|
|
28
|
+
{ id: 'google-api-key', description: 'Google API key', regex: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
29
|
+
{ id: 'slack-token', description: 'Slack token', regex: /\bxox[baprs]-[0-9A-Za-z-]{10,}\b/g },
|
|
30
|
+
{ id: 'slack-webhook', description: 'Slack incoming webhook', regex: /https:\/\/hooks\.slack\.com\/services\/T[0-9A-Za-z_]+\/B[0-9A-Za-z_]+\/[0-9A-Za-z_]+/g },
|
|
31
|
+
{ id: 'stripe-key', description: 'Stripe secret/restricted key', regex: /\b(?:sk|rk)_live_[0-9A-Za-z]{24,}\b/g },
|
|
32
|
+
{ id: 'sendgrid-key', description: 'SendGrid API key', regex: /\bSG\.[0-9A-Za-z_-]{22}\.[0-9A-Za-z_-]{43}\b/g },
|
|
33
|
+
{ id: 'twilio-key', description: 'Twilio API key SID', regex: /\bSK[0-9a-fA-F]{32}\b/g },
|
|
34
|
+
{ id: 'npm-token', description: 'npm access token', regex: /\bnpm_[0-9A-Za-z]{36}\b/g },
|
|
35
|
+
{ id: 'jwt', description: 'JSON Web Token', regex: /\beyJ[0-9A-Za-z_-]{10,}\.eyJ[0-9A-Za-z_-]{10,}\.[0-9A-Za-z_-]{10,}\b/g },
|
|
36
|
+
];
|
|
37
|
+
/** Keyword-prefixed assignment, e.g. `api_key = "..."` — the value is group 1. */
|
|
38
|
+
const ASSIGNMENT_RE = /(?:pass(?:word|wd)?|pwd|secret|token|api[_-]?key|apikey|access[_-]?key|auth[_-]?token|client[_-]?secret)["']?\s*[:=]\s*["']?([^\s"'`,;]{8,})/gi;
|
|
39
|
+
/** Shannon entropy in bits/char. Pure; used by the assignment fallback. */
|
|
40
|
+
export function shannonEntropy(s) {
|
|
41
|
+
if (!s)
|
|
42
|
+
return 0;
|
|
43
|
+
const freq = new Map();
|
|
44
|
+
for (const ch of s)
|
|
45
|
+
freq.set(ch, (freq.get(ch) ?? 0) + 1);
|
|
46
|
+
let bits = 0;
|
|
47
|
+
for (const n of freq.values()) {
|
|
48
|
+
const p = n / s.length;
|
|
49
|
+
bits -= p * Math.log2(p);
|
|
50
|
+
}
|
|
51
|
+
return bits;
|
|
52
|
+
}
|
|
53
|
+
/** Mask a secret: keep `keepPrefix` leading chars, then the token. */
|
|
54
|
+
export function maskSecret(secret, opts = {}) {
|
|
55
|
+
const keep = Math.max(0, Math.min(opts.keepPrefix ?? 3, secret.length));
|
|
56
|
+
const token = opts.token ?? '[REDACTED]';
|
|
57
|
+
return secret.slice(0, keep) + token;
|
|
58
|
+
}
|
|
59
|
+
function isAllowlisted(match, allowlist) {
|
|
60
|
+
if (!allowlist || allowlist.length === 0)
|
|
61
|
+
return false;
|
|
62
|
+
return allowlist.some((a) => (typeof a === 'string' ? match.includes(a) : a.test(match)));
|
|
63
|
+
}
|
|
64
|
+
/** Collect all rule + entropy hits across the text, deduped by offset. */
|
|
65
|
+
function collectHits(text, opts) {
|
|
66
|
+
const hits = [];
|
|
67
|
+
const claimed = []; // [start,end) ranges already taken
|
|
68
|
+
const overlaps = (start, end) => claimed.some(([s, e]) => start < e && end > s);
|
|
69
|
+
for (const rule of RULES) {
|
|
70
|
+
rule.regex.lastIndex = 0;
|
|
71
|
+
let m;
|
|
72
|
+
while ((m = rule.regex.exec(text)) !== null) {
|
|
73
|
+
const secret = rule.group != null ? m[rule.group] : m[0];
|
|
74
|
+
if (secret == null)
|
|
75
|
+
continue;
|
|
76
|
+
const start = rule.group != null ? m.index + m[0].indexOf(secret) : m.index;
|
|
77
|
+
const end = start + secret.length;
|
|
78
|
+
if (overlaps(start, end))
|
|
79
|
+
continue;
|
|
80
|
+
if (isAllowlisted(secret, opts.allowlist))
|
|
81
|
+
continue;
|
|
82
|
+
claimed.push([start, end]);
|
|
83
|
+
hits.push({ ruleId: rule.id, description: rule.description, index: start, match: secret });
|
|
84
|
+
if (m.index === rule.regex.lastIndex)
|
|
85
|
+
rule.regex.lastIndex++; // guard zero-width
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (opts.entropy !== false) {
|
|
89
|
+
const threshold = opts.entropyThreshold ?? 3.5;
|
|
90
|
+
ASSIGNMENT_RE.lastIndex = 0;
|
|
91
|
+
let m;
|
|
92
|
+
while ((m = ASSIGNMENT_RE.exec(text)) !== null) {
|
|
93
|
+
const value = m[1];
|
|
94
|
+
const start = m.index + m[0].indexOf(value);
|
|
95
|
+
const end = start + value.length;
|
|
96
|
+
if (overlaps(start, end))
|
|
97
|
+
continue;
|
|
98
|
+
if (isAllowlisted(value, opts.allowlist))
|
|
99
|
+
continue;
|
|
100
|
+
if (shannonEntropy(value) < threshold)
|
|
101
|
+
continue;
|
|
102
|
+
claimed.push([start, end]);
|
|
103
|
+
hits.push({ ruleId: 'high-entropy-assignment', description: 'High-entropy secret assignment', index: start, match: value });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return hits.sort((a, b) => a.index - b.index);
|
|
107
|
+
}
|
|
108
|
+
/** Map an absolute offset to 1-based {line, column}. */
|
|
109
|
+
function lineColOf(text, index) {
|
|
110
|
+
let line = 1;
|
|
111
|
+
let last = -1;
|
|
112
|
+
for (let i = 0; i < index; i++) {
|
|
113
|
+
if (text[i] === '\n') {
|
|
114
|
+
line++;
|
|
115
|
+
last = i;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { line, column: index - last };
|
|
119
|
+
}
|
|
120
|
+
/** Scan without rewriting — returns findings only (for --scan / CI gating). */
|
|
121
|
+
export function scanText(text, opts = {}) {
|
|
122
|
+
return collectHits(text, opts).map((h) => {
|
|
123
|
+
const { line, column } = lineColOf(text, h.index);
|
|
124
|
+
return { ruleId: h.ruleId, description: h.description, line, column, match: h.match, redacted: maskSecret(h.match, opts) };
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** Redact secrets in `text`, returning the rewritten output + findings. */
|
|
128
|
+
export function redactText(text, opts = {}) {
|
|
129
|
+
const hits = collectHits(text, opts);
|
|
130
|
+
const findings = [];
|
|
131
|
+
let out = '';
|
|
132
|
+
let cursor = 0;
|
|
133
|
+
for (const h of hits) {
|
|
134
|
+
const { line, column } = lineColOf(text, h.index);
|
|
135
|
+
const redacted = maskSecret(h.match, opts);
|
|
136
|
+
out += text.slice(cursor, h.index) + redacted;
|
|
137
|
+
cursor = h.index + h.match.length;
|
|
138
|
+
findings.push({ ruleId: h.ruleId, description: h.description, line, column, match: h.match, redacted });
|
|
139
|
+
}
|
|
140
|
+
out += text.slice(cursor);
|
|
141
|
+
return { output: out, findings };
|
|
142
|
+
}
|
|
143
|
+
/** One-line human summary for stderr. */
|
|
144
|
+
export function formatFindingsSummary(findings) {
|
|
145
|
+
if (findings.length === 0)
|
|
146
|
+
return 'redact: no secrets found';
|
|
147
|
+
const byRule = new Map();
|
|
148
|
+
for (const f of findings)
|
|
149
|
+
byRule.set(f.ruleId, (byRule.get(f.ruleId) ?? 0) + 1);
|
|
150
|
+
const parts = [...byRule.entries()].sort((a, b) => b[1] - a[1]).map(([r, n]) => `${r}:${n}`);
|
|
151
|
+
return `redact: ${findings.length} secret${findings.length === 1 ? '' : 's'} redacted (${parts.join(', ')})`;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=redact.js.map
|