@hanhnd/agent-kit 1.0.8 → 1.0.9
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 +1 -0
- package/dist/kit-server.js +96 -16
- package/dist/tools/agent.js +13 -3
- package/dist/tools/config.d.ts +16 -0
- package/dist/tools/config.js +62 -0
- package/dist/tools/security.d.ts +4 -0
- package/dist/tools/security.js +29 -16
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
| `/ak:research [topic]` | Research a topic |
|
|
16
16
|
| `/ak:review-pr [PR URL]` | Review a pull request |
|
|
17
17
|
| `/ak:review` | Review uncommitted local changes |
|
|
18
|
+
| `/ak:debate [subject]` | Run adversarial debate (Gilfoyle vs Dinesh vs Judge) |
|
|
18
19
|
| `/ak:ticket [ID]` | Fetch a Jira ticket and plan from it |
|
|
19
20
|
| `/ak:git` | Git commit, branch, and PR workflow |
|
|
20
21
|
| `/ak:init` | Create the project overview file |
|
package/dist/kit-server.js
CHANGED
|
@@ -20998,6 +20998,62 @@ function getWorkspaceRoot() {
|
|
|
20998
20998
|
return process.env.WORKSPACE_DIR || process.env.CLAUDE_WORKSPACE || process.env.GEMINI_WORKSPACE || process.env.INIT_CWD || process.env.PWD || process.cwd();
|
|
20999
20999
|
}
|
|
21000
21000
|
|
|
21001
|
+
// src/tools/config.ts
|
|
21002
|
+
var FORBIDDEN_FILES = [
|
|
21003
|
+
".env",
|
|
21004
|
+
".bashrc",
|
|
21005
|
+
".zshrc",
|
|
21006
|
+
".profile",
|
|
21007
|
+
".bash_profile",
|
|
21008
|
+
".bash_history",
|
|
21009
|
+
".zsh_history",
|
|
21010
|
+
"config.mjs",
|
|
21011
|
+
".npmrc",
|
|
21012
|
+
".yarnrc",
|
|
21013
|
+
".netrc",
|
|
21014
|
+
".gitconfig"
|
|
21015
|
+
];
|
|
21016
|
+
var FORBIDDEN_PATTERNS = [
|
|
21017
|
+
/^\.env$/i,
|
|
21018
|
+
// .env, .Env — case-insensitive exact
|
|
21019
|
+
/^\.env[^a-z]/i,
|
|
21020
|
+
// .env_bak, .env-prod, .env.local
|
|
21021
|
+
/^id_rsa/i,
|
|
21022
|
+
// SSH private keys
|
|
21023
|
+
/^id_ed25519/i,
|
|
21024
|
+
/^id_ecdsa/i,
|
|
21025
|
+
/\.pem$/i,
|
|
21026
|
+
// Certificates
|
|
21027
|
+
/credentials\.json$/i,
|
|
21028
|
+
/secrets\.json$/i,
|
|
21029
|
+
/secret\.json$/i
|
|
21030
|
+
];
|
|
21031
|
+
var FORBIDDEN_DIRS = [
|
|
21032
|
+
".git",
|
|
21033
|
+
".ssh",
|
|
21034
|
+
".aws",
|
|
21035
|
+
".kube",
|
|
21036
|
+
".gnupg",
|
|
21037
|
+
".docker"
|
|
21038
|
+
];
|
|
21039
|
+
var SAFE_ENV_VARS = [
|
|
21040
|
+
"PATH",
|
|
21041
|
+
"HOME",
|
|
21042
|
+
"USER",
|
|
21043
|
+
"SHELL",
|
|
21044
|
+
"TERM",
|
|
21045
|
+
"LANG",
|
|
21046
|
+
"LC_ALL",
|
|
21047
|
+
"LC_CTYPE",
|
|
21048
|
+
"TMPDIR",
|
|
21049
|
+
"TMP",
|
|
21050
|
+
"TEMP",
|
|
21051
|
+
"NODE_ENV",
|
|
21052
|
+
"GEMINI_WORKSPACE",
|
|
21053
|
+
"KIT_AGENT_TIMEOUT",
|
|
21054
|
+
"GEMINI_KIT_GIT_TIMEOUT"
|
|
21055
|
+
];
|
|
21056
|
+
|
|
21001
21057
|
// src/tools/core.ts
|
|
21002
21058
|
function registerCoreTools(server2) {
|
|
21003
21059
|
server2.tool(
|
|
@@ -21043,27 +21099,24 @@ import * as path2 from "path";
|
|
|
21043
21099
|
function sanitize(input) {
|
|
21044
21100
|
return String(input).replace(/[;&|`$<>\\]/g, "").trim().slice(0, 500);
|
|
21045
21101
|
}
|
|
21046
|
-
var FORBIDDEN_FILES = [
|
|
21047
|
-
".env",
|
|
21048
|
-
".bashrc",
|
|
21049
|
-
".zshrc",
|
|
21050
|
-
".profile",
|
|
21051
|
-
".bash_profile",
|
|
21052
|
-
".bash_history",
|
|
21053
|
-
".zsh_history",
|
|
21054
|
-
"config.mjs"
|
|
21055
|
-
// Some projects use this for sensitive config
|
|
21056
|
-
];
|
|
21057
21102
|
function validatePath(filePath, baseDir = process.cwd()) {
|
|
21058
21103
|
const resolved = path2.resolve(baseDir, filePath);
|
|
21059
21104
|
const root = path2.resolve(baseDir);
|
|
21060
21105
|
if (resolved !== root && !resolved.startsWith(root + path2.sep)) {
|
|
21061
21106
|
throw new Error(`Path traversal detected: ${filePath}`);
|
|
21062
21107
|
}
|
|
21108
|
+
const segments = resolved.split(path2.sep);
|
|
21109
|
+
for (const segment of segments) {
|
|
21110
|
+
if (FORBIDDEN_DIRS.some((d) => segment.toLowerCase() === d.toLowerCase())) {
|
|
21111
|
+
throw new Error(`Access to sensitive directory is FORBIDDEN: ${segment}`);
|
|
21112
|
+
}
|
|
21113
|
+
}
|
|
21063
21114
|
const fileName = path2.basename(resolved);
|
|
21064
|
-
|
|
21065
|
-
(
|
|
21066
|
-
)
|
|
21115
|
+
const isExactForbidden = FORBIDDEN_FILES.some(
|
|
21116
|
+
(f) => fileName.toLowerCase() === f.toLowerCase()
|
|
21117
|
+
);
|
|
21118
|
+
const isPatternForbidden = FORBIDDEN_PATTERNS.some((re) => re.test(fileName));
|
|
21119
|
+
if (isExactForbidden || isPatternForbidden) {
|
|
21067
21120
|
throw new Error(`Access to sensitive file is FORBIDDEN: ${fileName}`);
|
|
21068
21121
|
}
|
|
21069
21122
|
return resolved;
|
|
@@ -21083,6 +21136,25 @@ function commandExists(cmd) {
|
|
|
21083
21136
|
return false;
|
|
21084
21137
|
}
|
|
21085
21138
|
}
|
|
21139
|
+
function sanitizeOutput(text) {
|
|
21140
|
+
const SECRET_PATTERNS = [
|
|
21141
|
+
/(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}/g,
|
|
21142
|
+
// GitHub tokens
|
|
21143
|
+
/SK-[A-Za-z0-9]{40,}/g,
|
|
21144
|
+
// OpenAI keys
|
|
21145
|
+
/AKIA[0-9A-Z]{16}/g,
|
|
21146
|
+
// AWS access keys
|
|
21147
|
+
/xox[baprs]-[A-Za-z0-9-]{10,}/g,
|
|
21148
|
+
// Slack tokens
|
|
21149
|
+
/[Aa]tlassian[_\-]?[Aa][Pp][Ii][_\-]?[Tt]oken[^\s]*\s*[:=]\s*\S+/g
|
|
21150
|
+
// Atlassian token patterns
|
|
21151
|
+
];
|
|
21152
|
+
let sanitized = text;
|
|
21153
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
21154
|
+
sanitized = sanitized.replace(pattern, "[REDACTED]");
|
|
21155
|
+
}
|
|
21156
|
+
return sanitized;
|
|
21157
|
+
}
|
|
21086
21158
|
|
|
21087
21159
|
// src/tools/agent.ts
|
|
21088
21160
|
var AGENT_TIMEOUT = parseInt(process.env.KIT_AGENT_TIMEOUT || "300000", 10);
|
|
@@ -21162,9 +21234,16 @@ function registerAgentTools(server2) {
|
|
|
21162
21234
|
const slug = taskSlug(task);
|
|
21163
21235
|
const { logPath, logStream } = initAgentLog(workspaceRoot, slug, usedAgent);
|
|
21164
21236
|
const jobId = randomUUID();
|
|
21237
|
+
const sanitizedEnv = {};
|
|
21238
|
+
for (const key of SAFE_ENV_VARS) {
|
|
21239
|
+
if (process.env[key] !== void 0) {
|
|
21240
|
+
sanitizedEnv[key] = process.env[key];
|
|
21241
|
+
}
|
|
21242
|
+
}
|
|
21243
|
+
sanitizedEnv.GEMINI_WORKSPACE = workspaceRoot;
|
|
21165
21244
|
const child = spawn(usedAgent, agentArgs, {
|
|
21166
21245
|
cwd: workspaceRoot,
|
|
21167
|
-
env:
|
|
21246
|
+
env: sanitizedEnv
|
|
21168
21247
|
});
|
|
21169
21248
|
const job = {
|
|
21170
21249
|
id: jobId,
|
|
@@ -21195,6 +21274,7 @@ function registerAgentTools(server2) {
|
|
|
21195
21274
|
child.on("error", reject);
|
|
21196
21275
|
});
|
|
21197
21276
|
const output = job.chunks.join("");
|
|
21277
|
+
const safeOutput = sanitizeOutput(output);
|
|
21198
21278
|
return {
|
|
21199
21279
|
content: [
|
|
21200
21280
|
{
|
|
@@ -21204,7 +21284,7 @@ function registerAgentTools(server2) {
|
|
|
21204
21284
|
agent: usedAgent,
|
|
21205
21285
|
status: fallbackReason ? "fallback" : "success",
|
|
21206
21286
|
log: logPath,
|
|
21207
|
-
output,
|
|
21287
|
+
output: safeOutput,
|
|
21208
21288
|
...fallbackReason && { fallback_reason: fallbackReason }
|
|
21209
21289
|
},
|
|
21210
21290
|
null,
|
package/dist/tools/agent.js
CHANGED
|
@@ -8,7 +8,8 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { getWorkspaceRoot } from '../utils.js';
|
|
11
|
-
import { commandExists, validatePath } from './security.js';
|
|
11
|
+
import { commandExists, validatePath, sanitizeOutput } from './security.js';
|
|
12
|
+
import { SAFE_ENV_VARS } from './config.js';
|
|
12
13
|
// Configurable timeout via environment variable (default: 5 minutes)
|
|
13
14
|
const AGENT_TIMEOUT = parseInt(process.env.KIT_AGENT_TIMEOUT || '300000', 10);
|
|
14
15
|
const jobRegistry = new Map();
|
|
@@ -109,9 +110,17 @@ export function registerAgentTools(server) {
|
|
|
109
110
|
const slug = taskSlug(task);
|
|
110
111
|
const { logPath, logStream } = initAgentLog(workspaceRoot, slug, usedAgent);
|
|
111
112
|
const jobId = randomUUID();
|
|
113
|
+
// Build sanitized environment: only SAFE_ENV_VARS + GEMINI_WORKSPACE
|
|
114
|
+
const sanitizedEnv = {};
|
|
115
|
+
for (const key of SAFE_ENV_VARS) {
|
|
116
|
+
if (process.env[key] !== undefined) {
|
|
117
|
+
sanitizedEnv[key] = process.env[key];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
sanitizedEnv.GEMINI_WORKSPACE = workspaceRoot;
|
|
112
121
|
const child = spawn(usedAgent, agentArgs, {
|
|
113
122
|
cwd: workspaceRoot,
|
|
114
|
-
env:
|
|
123
|
+
env: sanitizedEnv,
|
|
115
124
|
});
|
|
116
125
|
const job = {
|
|
117
126
|
id: jobId,
|
|
@@ -144,6 +153,7 @@ export function registerAgentTools(server) {
|
|
|
144
153
|
child.on('error', reject);
|
|
145
154
|
});
|
|
146
155
|
const output = job.chunks.join('');
|
|
156
|
+
const safeOutput = sanitizeOutput(output);
|
|
147
157
|
return {
|
|
148
158
|
content: [
|
|
149
159
|
{
|
|
@@ -152,7 +162,7 @@ export function registerAgentTools(server) {
|
|
|
152
162
|
agent: usedAgent,
|
|
153
163
|
status: fallbackReason ? 'fallback' : 'success',
|
|
154
164
|
log: logPath,
|
|
155
|
-
output,
|
|
165
|
+
output: safeOutput,
|
|
156
166
|
...(fallbackReason && { fallback_reason: fallbackReason }),
|
|
157
167
|
}, null, 2),
|
|
158
168
|
},
|
package/dist/tools/config.d.ts
CHANGED
|
@@ -23,3 +23,19 @@ export declare function getFileExtensions(projectDir: string): string[];
|
|
|
23
23
|
* Load project settings from .gemini/settings.json
|
|
24
24
|
*/
|
|
25
25
|
export declare function loadProjectSettings(projectDir: string): ProjectSettings;
|
|
26
|
+
/**
|
|
27
|
+
* Sensitive files that agents are FORBIDDEN from reading/writing.
|
|
28
|
+
*/
|
|
29
|
+
export declare const FORBIDDEN_FILES: string[];
|
|
30
|
+
/**
|
|
31
|
+
* Regex patterns (case-insensitive) matched against path.basename.
|
|
32
|
+
*/
|
|
33
|
+
export declare const FORBIDDEN_PATTERNS: RegExp[];
|
|
34
|
+
/**
|
|
35
|
+
* Directory name segments that are always blocked.
|
|
36
|
+
*/
|
|
37
|
+
export declare const FORBIDDEN_DIRS: string[];
|
|
38
|
+
/**
|
|
39
|
+
* Environment variables safe to pass to child processes.
|
|
40
|
+
*/
|
|
41
|
+
export declare const SAFE_ENV_VARS: string[];
|
package/dist/tools/config.js
CHANGED
|
@@ -58,3 +58,65 @@ export function loadProjectSettings(projectDir) {
|
|
|
58
58
|
}
|
|
59
59
|
return {};
|
|
60
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Sensitive files that agents are FORBIDDEN from reading/writing.
|
|
63
|
+
*/
|
|
64
|
+
export const FORBIDDEN_FILES = [
|
|
65
|
+
'.env',
|
|
66
|
+
'.bashrc',
|
|
67
|
+
'.zshrc',
|
|
68
|
+
'.profile',
|
|
69
|
+
'.bash_profile',
|
|
70
|
+
'.bash_history',
|
|
71
|
+
'.zsh_history',
|
|
72
|
+
'config.mjs',
|
|
73
|
+
'.npmrc',
|
|
74
|
+
'.yarnrc',
|
|
75
|
+
'.netrc',
|
|
76
|
+
'.gitconfig',
|
|
77
|
+
];
|
|
78
|
+
/**
|
|
79
|
+
* Regex patterns (case-insensitive) matched against path.basename.
|
|
80
|
+
*/
|
|
81
|
+
export const FORBIDDEN_PATTERNS = [
|
|
82
|
+
/^\.env$/i, // .env, .Env — case-insensitive exact
|
|
83
|
+
/^\.env[^a-z]/i, // .env_bak, .env-prod, .env.local
|
|
84
|
+
/^id_rsa/i, // SSH private keys
|
|
85
|
+
/^id_ed25519/i,
|
|
86
|
+
/^id_ecdsa/i,
|
|
87
|
+
/\.pem$/i, // Certificates
|
|
88
|
+
/credentials\.json$/i,
|
|
89
|
+
/secrets\.json$/i,
|
|
90
|
+
/secret\.json$/i,
|
|
91
|
+
];
|
|
92
|
+
/**
|
|
93
|
+
* Directory name segments that are always blocked.
|
|
94
|
+
*/
|
|
95
|
+
export const FORBIDDEN_DIRS = [
|
|
96
|
+
'.git',
|
|
97
|
+
'.ssh',
|
|
98
|
+
'.aws',
|
|
99
|
+
'.kube',
|
|
100
|
+
'.gnupg',
|
|
101
|
+
'.docker',
|
|
102
|
+
];
|
|
103
|
+
/**
|
|
104
|
+
* Environment variables safe to pass to child processes.
|
|
105
|
+
*/
|
|
106
|
+
export const SAFE_ENV_VARS = [
|
|
107
|
+
'PATH',
|
|
108
|
+
'HOME',
|
|
109
|
+
'USER',
|
|
110
|
+
'SHELL',
|
|
111
|
+
'TERM',
|
|
112
|
+
'LANG',
|
|
113
|
+
'LC_ALL',
|
|
114
|
+
'LC_CTYPE',
|
|
115
|
+
'TMPDIR',
|
|
116
|
+
'TMP',
|
|
117
|
+
'TEMP',
|
|
118
|
+
'NODE_ENV',
|
|
119
|
+
'GEMINI_WORKSPACE',
|
|
120
|
+
'KIT_AGENT_TIMEOUT',
|
|
121
|
+
'GEMINI_KIT_GIT_TIMEOUT',
|
|
122
|
+
];
|
package/dist/tools/security.d.ts
CHANGED
|
@@ -38,3 +38,7 @@ export declare function commandExists(cmd: string): boolean;
|
|
|
38
38
|
* Uses queue-based approach to prevent stack overflow on deep directories
|
|
39
39
|
*/
|
|
40
40
|
export declare function findFilesAsync(dir: string, extensions: string[], maxFiles: number, excludeDirs?: string[]): Promise<string[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Sanitize output text by redacting sensitive tokens (API keys, etc.)
|
|
43
|
+
*/
|
|
44
|
+
export declare function sanitizeOutput(text: string): string;
|
package/dist/tools/security.js
CHANGED
|
@@ -6,6 +6,7 @@ import { execFileSync } from 'child_process';
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { getWorkspaceRoot } from '../utils.js';
|
|
9
|
+
import { FORBIDDEN_FILES, FORBIDDEN_PATTERNS, FORBIDDEN_DIRS } from './config.js';
|
|
9
10
|
/**
|
|
10
11
|
* Sanitize string for safe use with execFileSync
|
|
11
12
|
* Only removes dangerous shell operators - safe chars like !?#* are allowed
|
|
@@ -24,20 +25,6 @@ export function sanitize(input) {
|
|
|
24
25
|
.trim()
|
|
25
26
|
.slice(0, 500); // Limit length
|
|
26
27
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Sensitive files that agents are FORBIDDEN from reading/writing.
|
|
29
|
-
* Includes environment variables, shell configs, and system profiles.
|
|
30
|
-
*/
|
|
31
|
-
const FORBIDDEN_FILES = [
|
|
32
|
-
'.env',
|
|
33
|
-
'.bashrc',
|
|
34
|
-
'.zshrc',
|
|
35
|
-
'.profile',
|
|
36
|
-
'.bash_profile',
|
|
37
|
-
'.bash_history',
|
|
38
|
-
'.zsh_history',
|
|
39
|
-
'config.mjs', // Some projects use this for sensitive config
|
|
40
|
-
];
|
|
41
28
|
/**
|
|
42
29
|
* Validate file path to prevent path traversal attacks and access to forbidden files.
|
|
43
30
|
* Uses stricter path.sep check to prevent prefix matching flaws.
|
|
@@ -49,9 +36,18 @@ export function validatePath(filePath, baseDir = process.cwd()) {
|
|
|
49
36
|
if (resolved !== root && !resolved.startsWith(root + path.sep)) {
|
|
50
37
|
throw new Error(`Path traversal detected: ${filePath}`);
|
|
51
38
|
}
|
|
52
|
-
// 2.
|
|
39
|
+
// 2. Directory segment check
|
|
40
|
+
const segments = resolved.split(path.sep);
|
|
41
|
+
for (const segment of segments) {
|
|
42
|
+
if (FORBIDDEN_DIRS.some((d) => segment.toLowerCase() === d.toLowerCase())) {
|
|
43
|
+
throw new Error(`Access to sensitive directory is FORBIDDEN: ${segment}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// 3. Forbidden files check (two-stage)
|
|
53
47
|
const fileName = path.basename(resolved);
|
|
54
|
-
|
|
48
|
+
const isExactForbidden = FORBIDDEN_FILES.some((f) => fileName.toLowerCase() === f.toLowerCase());
|
|
49
|
+
const isPatternForbidden = FORBIDDEN_PATTERNS.some((re) => re.test(fileName));
|
|
50
|
+
if (isExactForbidden || isPatternForbidden) {
|
|
55
51
|
throw new Error(`Access to sensitive file is FORBIDDEN: ${fileName}`);
|
|
56
52
|
}
|
|
57
53
|
return resolved;
|
|
@@ -147,3 +143,20 @@ export async function findFilesAsync(dir, extensions, maxFiles, excludeDirs = ['
|
|
|
147
143
|
}
|
|
148
144
|
return results;
|
|
149
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Sanitize output text by redacting sensitive tokens (API keys, etc.)
|
|
148
|
+
*/
|
|
149
|
+
export function sanitizeOutput(text) {
|
|
150
|
+
const SECRET_PATTERNS = [
|
|
151
|
+
/(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}/g, // GitHub tokens
|
|
152
|
+
/SK-[A-Za-z0-9]{40,}/g, // OpenAI keys
|
|
153
|
+
/AKIA[0-9A-Z]{16}/g, // AWS access keys
|
|
154
|
+
/xox[baprs]-[A-Za-z0-9-]{10,}/g, // Slack tokens
|
|
155
|
+
/[Aa]tlassian[_\-]?[Aa][Pp][Ii][_\-]?[Tt]oken[^\s]*\s*[:=]\s*\S+/g, // Atlassian token patterns
|
|
156
|
+
];
|
|
157
|
+
let sanitized = text;
|
|
158
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
159
|
+
sanitized = sanitized.replace(pattern, '[REDACTED]');
|
|
160
|
+
}
|
|
161
|
+
return sanitized;
|
|
162
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanhnd/agent-kit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Super Engineer - Team of AI Agents for software development (Claude Code Plugin)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/kit-server.js",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"prepublishOnly": "npm run build",
|
|
22
22
|
"build": "tsc && npm run bundle",
|
|
23
23
|
"bundle": "esbuild src/kit-server.ts --bundle --platform=node --format=esm --outfile=dist/kit-server.js --external:node:* --banner:js=\"import { createRequire } from 'module'; const require = createRequire(import.meta.url);\"",
|
|
24
|
-
"dev": "tsc --watch"
|
|
24
|
+
"dev": "tsc --watch",
|
|
25
|
+
"test": "node --test tests/*.test.js"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"@modelcontextprotocol/sdk": "^1.13.1",
|