@hiro-c/agent-gate 1.0.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/AGENTS.md +76 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/dist/adapters/Adapter.d.ts +32 -0
- package/dist/adapters/Adapter.d.ts.map +1 -0
- package/dist/adapters/Adapter.js +2 -0
- package/dist/adapters/claude-code/adapter.d.ts +3 -0
- package/dist/adapters/claude-code/adapter.d.ts.map +1 -0
- package/dist/adapters/claude-code/adapter.js +45 -0
- package/dist/adapters/claude-code/transcript.d.ts +16 -0
- package/dist/adapters/claude-code/transcript.d.ts.map +1 -0
- package/dist/adapters/claude-code/transcript.js +104 -0
- package/dist/adapters/cursor/adapter.d.ts +3 -0
- package/dist/adapters/cursor/adapter.d.ts.map +1 -0
- package/dist/adapters/cursor/adapter.js +89 -0
- package/dist/adapters/index.d.ts +8 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +20 -0
- package/dist/cli/agent-gate.d.ts +13 -0
- package/dist/cli/agent-gate.d.ts.map +1 -0
- package/dist/cli/agent-gate.js +226 -0
- package/dist/cli/installer.d.ts +21 -0
- package/dist/cli/installer.d.ts.map +1 -0
- package/dist/cli/installer.js +71 -0
- package/dist/collector/collectClaudeMd.d.ts +3 -0
- package/dist/collector/collectClaudeMd.d.ts.map +1 -0
- package/dist/collector/collectClaudeMd.js +87 -0
- package/dist/collector/collectRuleSources.d.ts +3 -0
- package/dist/collector/collectRuleSources.d.ts.map +1 -0
- package/dist/collector/collectRuleSources.js +151 -0
- package/dist/config/AgentGateConfig.d.ts +18 -0
- package/dist/config/AgentGateConfig.d.ts.map +1 -0
- package/dist/config/AgentGateConfig.js +34 -0
- package/dist/config/Config.d.ts +26 -0
- package/dist/config/Config.d.ts.map +1 -0
- package/dist/config/Config.js +25 -0
- package/dist/config/PluginConfigLoader.d.ts +3 -0
- package/dist/config/PluginConfigLoader.d.ts.map +1 -0
- package/dist/config/PluginConfigLoader.js +85 -0
- package/dist/config/defineConfig.d.ts +23 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/defineConfig.js +10 -0
- package/dist/contracts/schemas/hookDataSchema.d.ts +7 -0
- package/dist/contracts/schemas/hookDataSchema.d.ts.map +1 -0
- package/dist/contracts/schemas/hookDataSchema.js +9 -0
- package/dist/contracts/types/Action.d.ts +23 -0
- package/dist/contracts/types/Action.d.ts.map +1 -0
- package/dist/contracts/types/Action.js +2 -0
- package/dist/contracts/types/ClaudeMdFile.d.ts +5 -0
- package/dist/contracts/types/ClaudeMdFile.d.ts.map +1 -0
- package/dist/contracts/types/ClaudeMdFile.js +2 -0
- package/dist/contracts/types/HookData.d.ts +6 -0
- package/dist/contracts/types/HookData.d.ts.map +1 -0
- package/dist/contracts/types/HookData.js +2 -0
- package/dist/contracts/types/ModelClient.d.ts +4 -0
- package/dist/contracts/types/ModelClient.d.ts.map +1 -0
- package/dist/contracts/types/ModelClient.js +2 -0
- package/dist/contracts/types/RuleSource.d.ts +7 -0
- package/dist/contracts/types/RuleSource.d.ts.map +1 -0
- package/dist/contracts/types/RuleSource.js +2 -0
- package/dist/contracts/types/SessionContext.d.ts +25 -0
- package/dist/contracts/types/SessionContext.d.ts.map +1 -0
- package/dist/contracts/types/SessionContext.js +2 -0
- package/dist/contracts/types/ValidationResult.d.ts +5 -0
- package/dist/contracts/types/ValidationResult.d.ts.map +1 -0
- package/dist/contracts/types/ValidationResult.js +2 -0
- package/dist/daemon/client.d.ts +17 -0
- package/dist/daemon/client.d.ts.map +1 -0
- package/dist/daemon/client.js +59 -0
- package/dist/daemon/protocol.d.ts +17 -0
- package/dist/daemon/protocol.d.ts.map +1 -0
- package/dist/daemon/protocol.js +8 -0
- package/dist/daemon/server.d.ts +27 -0
- package/dist/daemon/server.d.ts.map +1 -0
- package/dist/daemon/server.js +100 -0
- package/dist/deterministic/defaultRules.d.ts +11 -0
- package/dist/deterministic/defaultRules.d.ts.map +1 -0
- package/dist/deterministic/defaultRules.js +33 -0
- package/dist/deterministic/engine.d.ts +11 -0
- package/dist/deterministic/engine.d.ts.map +1 -0
- package/dist/deterministic/engine.js +12 -0
- package/dist/deterministic/factories.d.ts +20 -0
- package/dist/deterministic/factories.d.ts.map +1 -0
- package/dist/deterministic/factories.js +56 -0
- package/dist/deterministic/rules/preventBashSecretWrite.d.ts +3 -0
- package/dist/deterministic/rules/preventBashSecretWrite.d.ts.map +1 -0
- package/dist/deterministic/rules/preventBashSecretWrite.js +75 -0
- package/dist/deterministic/rules/preventForcePushMain.d.ts +7 -0
- package/dist/deterministic/rules/preventForcePushMain.d.ts.map +1 -0
- package/dist/deterministic/rules/preventForcePushMain.js +85 -0
- package/dist/deterministic/rules/preventRmRfRoot.d.ts +3 -0
- package/dist/deterministic/rules/preventRmRfRoot.d.ts.map +1 -0
- package/dist/deterministic/rules/preventRmRfRoot.js +68 -0
- package/dist/deterministic/rules/preventSecretFileWrite.d.ts +7 -0
- package/dist/deterministic/rules/preventSecretFileWrite.d.ts.map +1 -0
- package/dist/deterministic/rules/preventSecretFileWrite.js +55 -0
- package/dist/deterministic/rules/preventSystemPathWrite.d.ts +3 -0
- package/dist/deterministic/rules/preventSystemPathWrite.d.ts.map +1 -0
- package/dist/deterministic/rules/preventSystemPathWrite.js +38 -0
- package/dist/deterministic/types.d.ts +20 -0
- package/dist/deterministic/types.d.ts.map +1 -0
- package/dist/deterministic/types.js +2 -0
- package/dist/doctor/findings.d.ts +15 -0
- package/dist/doctor/findings.d.ts.map +1 -0
- package/dist/doctor/findings.js +2 -0
- package/dist/doctor/formatFindings.d.ts +3 -0
- package/dist/doctor/formatFindings.d.ts.map +1 -0
- package/dist/doctor/formatFindings.js +37 -0
- package/dist/doctor/lintRuleSources.d.ts +4 -0
- package/dist/doctor/lintRuleSources.d.ts.map +1 -0
- package/dist/doctor/lintRuleSources.js +87 -0
- package/dist/hooks/processHookData.d.ts +37 -0
- package/dist/hooks/processHookData.d.ts.map +1 -0
- package/dist/hooks/processHookData.js +181 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/observability/decisionLogger.d.ts +15 -0
- package/dist/observability/decisionLogger.d.ts.map +1 -0
- package/dist/observability/decisionLogger.js +20 -0
- package/dist/observability/eventBus.d.ts +15 -0
- package/dist/observability/eventBus.d.ts.map +1 -0
- package/dist/observability/eventBus.js +33 -0
- package/dist/observability/sinks/JsonlFileSink.d.ts +13 -0
- package/dist/observability/sinks/JsonlFileSink.d.ts.map +1 -0
- package/dist/observability/sinks/JsonlFileSink.js +36 -0
- package/dist/observability/sinks/Sink.d.ts +46 -0
- package/dist/observability/sinks/Sink.d.ts.map +1 -0
- package/dist/observability/sinks/Sink.js +2 -0
- package/dist/observability/stats.d.ts +14 -0
- package/dist/observability/stats.d.ts.map +1 -0
- package/dist/observability/stats.js +78 -0
- package/dist/validation/models/AnthropicApi.d.ts +9 -0
- package/dist/validation/models/AnthropicApi.d.ts.map +1 -0
- package/dist/validation/models/AnthropicApi.js +44 -0
- package/dist/validation/models/ClaudeCli.d.ts +10 -0
- package/dist/validation/models/ClaudeCli.d.ts.map +1 -0
- package/dist/validation/models/ClaudeCli.js +64 -0
- package/dist/validation/models/CompositeModelClient.d.ts +20 -0
- package/dist/validation/models/CompositeModelClient.d.ts.map +1 -0
- package/dist/validation/models/CompositeModelClient.js +53 -0
- package/dist/validation/prompts/context.d.ts +3 -0
- package/dist/validation/prompts/context.d.ts.map +1 -0
- package/dist/validation/prompts/context.js +29 -0
- package/dist/validation/prompts/response.d.ts +2 -0
- package/dist/validation/prompts/response.d.ts.map +1 -0
- package/dist/validation/prompts/response.js +20 -0
- package/dist/validation/prompts/system-prompt.d.ts +7 -0
- package/dist/validation/prompts/system-prompt.d.ts.map +1 -0
- package/dist/validation/prompts/system-prompt.js +69 -0
- package/dist/validation/validator.d.ts +5 -0
- package/dist/validation/validator.d.ts.map +1 -0
- package/dist/validation/validator.js +98 -0
- package/package.json +67 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runDeterministicRules = runDeterministicRules;
|
|
4
|
+
function runDeterministicRules(toolName, toolInput, rules, ctx) {
|
|
5
|
+
for (const rule of rules) {
|
|
6
|
+
const verdict = rule.check(toolName, toolInput, ctx);
|
|
7
|
+
if (verdict.kind === 'block') {
|
|
8
|
+
return { kind: 'block', reason: verdict.reason, ruleId: rule.id };
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return { kind: 'allow' };
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DeterministicRule } from './types';
|
|
2
|
+
export interface ForbidCommandPatternOptions {
|
|
3
|
+
id: string;
|
|
4
|
+
match: RegExp;
|
|
5
|
+
reason: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function forbidCommandPattern(opts: ForbidCommandPatternOptions): DeterministicRule;
|
|
8
|
+
export interface ForbidContentPatternOptions {
|
|
9
|
+
id: string;
|
|
10
|
+
match: RegExp;
|
|
11
|
+
reason: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function forbidContentPattern(opts: ForbidContentPatternOptions): DeterministicRule;
|
|
14
|
+
export interface ForbidFilePathPatternOptions {
|
|
15
|
+
id: string;
|
|
16
|
+
match: RegExp;
|
|
17
|
+
reason: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function forbidFilePathPattern(opts: ForbidFilePathPatternOptions): DeterministicRule;
|
|
20
|
+
//# sourceMappingURL=factories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factories.d.ts","sourceRoot":"","sources":["../../src/deterministic/factories.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAe,MAAM,SAAS,CAAA;AAIxD,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,2BAA2B,GAChC,iBAAiB,CAWnB;AAED,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,2BAA2B,GAChC,iBAAiB,CAkBnB;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,4BAA4B,GACjC,iBAAiB,CAWnB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.forbidCommandPattern = forbidCommandPattern;
|
|
4
|
+
exports.forbidContentPattern = forbidContentPattern;
|
|
5
|
+
exports.forbidFilePathPattern = forbidFilePathPattern;
|
|
6
|
+
const WRITE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit']);
|
|
7
|
+
function forbidCommandPattern(opts) {
|
|
8
|
+
return {
|
|
9
|
+
id: opts.id,
|
|
10
|
+
check(toolName, toolInput) {
|
|
11
|
+
if (toolName !== 'Bash')
|
|
12
|
+
return { kind: 'allow' };
|
|
13
|
+
const command = toolInput.command;
|
|
14
|
+
if (typeof command !== 'string')
|
|
15
|
+
return { kind: 'allow' };
|
|
16
|
+
if (!opts.match.test(command))
|
|
17
|
+
return { kind: 'allow' };
|
|
18
|
+
return { kind: 'block', reason: opts.reason };
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function forbidContentPattern(opts) {
|
|
23
|
+
return {
|
|
24
|
+
id: opts.id,
|
|
25
|
+
check(toolName, toolInput) {
|
|
26
|
+
if (!WRITE_TOOLS.has(toolName))
|
|
27
|
+
return { kind: 'allow' };
|
|
28
|
+
const candidates = [
|
|
29
|
+
toolInput.content,
|
|
30
|
+
toolInput.new_string,
|
|
31
|
+
toolInput.new_content,
|
|
32
|
+
];
|
|
33
|
+
for (const c of candidates) {
|
|
34
|
+
if (typeof c === 'string' && opts.match.test(c)) {
|
|
35
|
+
return { kind: 'block', reason: opts.reason };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return { kind: 'allow' };
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function forbidFilePathPattern(opts) {
|
|
43
|
+
return {
|
|
44
|
+
id: opts.id,
|
|
45
|
+
check(toolName, toolInput) {
|
|
46
|
+
if (!WRITE_TOOLS.has(toolName))
|
|
47
|
+
return { kind: 'allow' };
|
|
48
|
+
const filePath = toolInput.file_path;
|
|
49
|
+
if (typeof filePath !== 'string')
|
|
50
|
+
return { kind: 'allow' };
|
|
51
|
+
if (!opts.match.test(filePath))
|
|
52
|
+
return { kind: 'allow' };
|
|
53
|
+
return { kind: 'block', reason: opts.reason };
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preventBashSecretWrite.d.ts","sourceRoot":"","sources":["../../../src/deterministic/rules/preventBashSecretWrite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAe,MAAM,UAAU,CAAA;AAwDzD,eAAO,MAAM,sBAAsB,EAAE,iBAkBpC,CAAA"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preventBashSecretWrite = void 0;
|
|
4
|
+
const TEMPLATE_SUFFIXES = ['.example', '.sample', '.template', '.dist'];
|
|
5
|
+
function basename(path) {
|
|
6
|
+
const cleaned = path.replace(/[\s'"]+$/, '');
|
|
7
|
+
const idx = cleaned.lastIndexOf('/');
|
|
8
|
+
return idx >= 0 ? cleaned.slice(idx + 1) : cleaned;
|
|
9
|
+
}
|
|
10
|
+
function isTemplate(path) {
|
|
11
|
+
return TEMPLATE_SUFFIXES.some((s) => path.endsWith(s));
|
|
12
|
+
}
|
|
13
|
+
function isSecretTargetPath(rawPath) {
|
|
14
|
+
const cleaned = rawPath.replace(/^['"]|['"]$/g, '').trim();
|
|
15
|
+
if (isTemplate(cleaned))
|
|
16
|
+
return false;
|
|
17
|
+
const name = basename(cleaned);
|
|
18
|
+
if (name === '.env' || name.startsWith('.env.'))
|
|
19
|
+
return true;
|
|
20
|
+
if (cleaned.includes('/.ssh/'))
|
|
21
|
+
return true;
|
|
22
|
+
if (/\/\.aws\/(credentials|config)$/.test(cleaned))
|
|
23
|
+
return true;
|
|
24
|
+
if (/\.(pem|key)$/.test(name))
|
|
25
|
+
return true;
|
|
26
|
+
if (/^id_(rsa|ed25519|ecdsa|dsa)$/.test(name))
|
|
27
|
+
return true;
|
|
28
|
+
if (name === '.netrc')
|
|
29
|
+
return true;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extracts files that the command writes to via redirect (>, >>) or `tee`.
|
|
34
|
+
* Conservative: matches the next non-flag token after the operator/word.
|
|
35
|
+
*/
|
|
36
|
+
function extractWriteTargets(command) {
|
|
37
|
+
const targets = [];
|
|
38
|
+
// Redirect operators: >, >>, &>, &>>, 1>, 2> etc.
|
|
39
|
+
const redirectRe = /[12&]?>>?\s*([^\s;|&<>]+)/g;
|
|
40
|
+
for (const m of command.matchAll(redirectRe)) {
|
|
41
|
+
if (m[1])
|
|
42
|
+
targets.push(m[1]);
|
|
43
|
+
}
|
|
44
|
+
// tee [-a] FILE [FILE ...]
|
|
45
|
+
const teeRe = /\btee\b(?:\s+-[A-Za-z]+)*\s+([^\s;|&<>]+(?:\s+[^\s;|&<>]+)*)/g;
|
|
46
|
+
for (const m of command.matchAll(teeRe)) {
|
|
47
|
+
if (m[1]) {
|
|
48
|
+
for (const f of m[1].split(/\s+/)) {
|
|
49
|
+
if (f && !f.startsWith('-'))
|
|
50
|
+
targets.push(f);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return targets;
|
|
55
|
+
}
|
|
56
|
+
exports.preventBashSecretWrite = {
|
|
57
|
+
id: 'prevent-bash-secret-write',
|
|
58
|
+
check(toolName, toolInput) {
|
|
59
|
+
if (toolName !== 'Bash')
|
|
60
|
+
return { kind: 'allow' };
|
|
61
|
+
const command = toolInput.command;
|
|
62
|
+
if (typeof command !== 'string')
|
|
63
|
+
return { kind: 'allow' };
|
|
64
|
+
const targets = extractWriteTargets(command);
|
|
65
|
+
for (const target of targets) {
|
|
66
|
+
if (isSecretTargetPath(target)) {
|
|
67
|
+
return {
|
|
68
|
+
kind: 'block',
|
|
69
|
+
reason: `Refusing to write to a likely secret/credential file via shell redirect: ${target}. If this is intentional, run the command manually outside of the agent.`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { kind: 'allow' };
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DeterministicRule } from '../types';
|
|
2
|
+
export interface PreventForcePushMainOptions {
|
|
3
|
+
protectedBranches?: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function preventForcePushMainWith(opts?: PreventForcePushMainOptions): DeterministicRule;
|
|
6
|
+
export declare const preventForcePushMain: DeterministicRule;
|
|
7
|
+
//# sourceMappingURL=preventForcePushMain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preventForcePushMain.d.ts","sourceRoot":"","sources":["../../../src/deterministic/rules/preventForcePushMain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAe,MAAM,UAAU,CAAA;AA0DzD,MAAM,WAAW,2BAA2B;IAC1C,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC7B;AAED,wBAAgB,wBAAwB,CACtC,IAAI,CAAC,EAAE,2BAA2B,GACjC,iBAAiB,CA2BnB;AAED,eAAO,MAAM,oBAAoB,EAAE,iBAA8C,CAAA"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preventForcePushMain = void 0;
|
|
4
|
+
exports.preventForcePushMainWith = preventForcePushMainWith;
|
|
5
|
+
const DEFAULT_PROTECTED_BRANCHES = [
|
|
6
|
+
'main',
|
|
7
|
+
'master',
|
|
8
|
+
'develop',
|
|
9
|
+
'development',
|
|
10
|
+
'production',
|
|
11
|
+
'prod',
|
|
12
|
+
'release',
|
|
13
|
+
'stable',
|
|
14
|
+
];
|
|
15
|
+
function isGitPush(tokens) {
|
|
16
|
+
const gitIdx = tokens.indexOf('git');
|
|
17
|
+
if (gitIdx === -1)
|
|
18
|
+
return false;
|
|
19
|
+
return tokens[gitIdx + 1] === 'push';
|
|
20
|
+
}
|
|
21
|
+
function hasForceFlag(tokens) {
|
|
22
|
+
for (const token of tokens) {
|
|
23
|
+
if (token === '--force-with-lease')
|
|
24
|
+
continue;
|
|
25
|
+
if (token.startsWith('--force-with-lease='))
|
|
26
|
+
continue;
|
|
27
|
+
if (token === '--force' || token === '-f')
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function targetsProtectedBranch(tokens, protectedSet) {
|
|
33
|
+
for (const token of tokens) {
|
|
34
|
+
if (token.startsWith('+')) {
|
|
35
|
+
const branch = token.slice(1).split(':').pop() ?? '';
|
|
36
|
+
if (protectedSet.has(branch))
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (protectedSet.has(token))
|
|
40
|
+
return true;
|
|
41
|
+
if (token.includes(':')) {
|
|
42
|
+
const dest = token.split(':').pop() ?? '';
|
|
43
|
+
if (protectedSet.has(dest))
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
function hasPlusPrefixedProtected(tokens, protectedSet) {
|
|
50
|
+
for (const token of tokens) {
|
|
51
|
+
if (!token.startsWith('+'))
|
|
52
|
+
continue;
|
|
53
|
+
const branch = token.slice(1).split(':').pop() ?? '';
|
|
54
|
+
if (protectedSet.has(branch))
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
function preventForcePushMainWith(opts) {
|
|
60
|
+
const protectedSet = new Set(opts?.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES);
|
|
61
|
+
return {
|
|
62
|
+
id: 'prevent-force-push-main',
|
|
63
|
+
check(toolName, toolInput) {
|
|
64
|
+
if (toolName !== 'Bash')
|
|
65
|
+
return { kind: 'allow' };
|
|
66
|
+
const command = toolInput.command;
|
|
67
|
+
if (typeof command !== 'string')
|
|
68
|
+
return { kind: 'allow' };
|
|
69
|
+
const tokens = command.trim().split(/\s+/);
|
|
70
|
+
if (!isGitPush(tokens))
|
|
71
|
+
return { kind: 'allow' };
|
|
72
|
+
const force = hasForceFlag(tokens);
|
|
73
|
+
const plusForce = hasPlusPrefixedProtected(tokens, protectedSet);
|
|
74
|
+
if (!force && !plusForce)
|
|
75
|
+
return { kind: 'allow' };
|
|
76
|
+
if (!targetsProtectedBranch(tokens, protectedSet))
|
|
77
|
+
return { kind: 'allow' };
|
|
78
|
+
return {
|
|
79
|
+
kind: 'block',
|
|
80
|
+
reason: 'Force push to a protected branch is blocked. Use --force-with-lease if you really need to overwrite history, or push to a feature branch instead.',
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
exports.preventForcePushMain = preventForcePushMainWith();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preventRmRfRoot.d.ts","sourceRoot":"","sources":["../../../src/deterministic/rules/preventRmRfRoot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAe,MAAM,UAAU,CAAA;AA+CzD,eAAO,MAAM,eAAe,EAAE,iBAmB7B,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preventRmRfRoot = void 0;
|
|
4
|
+
const CATASTROPHIC_TARGETS = new Set([
|
|
5
|
+
'/',
|
|
6
|
+
'$HOME',
|
|
7
|
+
'${HOME}',
|
|
8
|
+
'"$HOME"',
|
|
9
|
+
"'$HOME'",
|
|
10
|
+
'~',
|
|
11
|
+
'~/',
|
|
12
|
+
'/etc',
|
|
13
|
+
'/usr',
|
|
14
|
+
'/var',
|
|
15
|
+
'/bin',
|
|
16
|
+
'/sbin',
|
|
17
|
+
'/boot',
|
|
18
|
+
'/lib',
|
|
19
|
+
'/lib64',
|
|
20
|
+
'/opt',
|
|
21
|
+
'/Users',
|
|
22
|
+
'/home',
|
|
23
|
+
'/root',
|
|
24
|
+
'/private',
|
|
25
|
+
'/System',
|
|
26
|
+
'/Library',
|
|
27
|
+
'/Applications',
|
|
28
|
+
]);
|
|
29
|
+
function stripSudoPrefix(command) {
|
|
30
|
+
return command.replace(/^\s*sudo(\s+-[A-Za-z]+)*\s+/, '');
|
|
31
|
+
}
|
|
32
|
+
function isRecursiveForceRm(command) {
|
|
33
|
+
const trimmed = stripSudoPrefix(command).trim();
|
|
34
|
+
if (!/^rm\b/.test(trimmed))
|
|
35
|
+
return false;
|
|
36
|
+
const padded = ' ' + trimmed + ' ';
|
|
37
|
+
if (/\s--recursive\b/.test(padded))
|
|
38
|
+
return true;
|
|
39
|
+
// Short flag form: any flag cluster containing r/R (case-insensitive)
|
|
40
|
+
return /\s-[A-Za-z]*[rR][A-Za-z]*\s/.test(padded);
|
|
41
|
+
}
|
|
42
|
+
function extractTargets(command) {
|
|
43
|
+
const trimmed = stripSudoPrefix(command).trim();
|
|
44
|
+
const tokens = trimmed.split(/\s+/);
|
|
45
|
+
return tokens.slice(1).filter((t) => !t.startsWith('-'));
|
|
46
|
+
}
|
|
47
|
+
exports.preventRmRfRoot = {
|
|
48
|
+
id: 'prevent-rm-rf-root',
|
|
49
|
+
check(toolName, toolInput) {
|
|
50
|
+
if (toolName !== 'Bash')
|
|
51
|
+
return { kind: 'allow' };
|
|
52
|
+
const command = toolInput.command;
|
|
53
|
+
if (typeof command !== 'string')
|
|
54
|
+
return { kind: 'allow' };
|
|
55
|
+
if (!isRecursiveForceRm(command))
|
|
56
|
+
return { kind: 'allow' };
|
|
57
|
+
const targets = extractTargets(command);
|
|
58
|
+
for (const target of targets) {
|
|
59
|
+
if (CATASTROPHIC_TARGETS.has(target)) {
|
|
60
|
+
return {
|
|
61
|
+
kind: 'block',
|
|
62
|
+
reason: `Refusing to run recursive rm on a catastrophic path: ${target}. If this is genuinely intended, run the command manually outside of the agent.`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { kind: 'allow' };
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DeterministicRule } from '../types';
|
|
2
|
+
export interface PreventSecretFileWriteOptions {
|
|
3
|
+
extraSecretPathPrefixes?: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function preventSecretFileWriteWith(opts?: PreventSecretFileWriteOptions): DeterministicRule;
|
|
6
|
+
export declare const preventSecretFileWrite: DeterministicRule;
|
|
7
|
+
//# sourceMappingURL=preventSecretFileWrite.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preventSecretFileWrite.d.ts","sourceRoot":"","sources":["../../../src/deterministic/rules/preventSecretFileWrite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAe,MAAM,UAAU,CAAA;AA8BzD,MAAM,WAAW,6BAA6B;IAC5C,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAA;CACnC;AAED,wBAAgB,0BAA0B,CACxC,IAAI,CAAC,EAAE,6BAA6B,GACnC,iBAAiB,CAoBnB;AAED,eAAO,MAAM,sBAAsB,EAAE,iBACP,CAAA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preventSecretFileWrite = void 0;
|
|
4
|
+
exports.preventSecretFileWriteWith = preventSecretFileWriteWith;
|
|
5
|
+
const TOOLS_THAT_WRITE = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit']);
|
|
6
|
+
const TEMPLATE_SUFFIXES = ['.example', '.sample', '.template', '.dist'];
|
|
7
|
+
function basename(path) {
|
|
8
|
+
const idx = path.lastIndexOf('/');
|
|
9
|
+
return idx >= 0 ? path.slice(idx + 1) : path;
|
|
10
|
+
}
|
|
11
|
+
function isTemplate(filePath) {
|
|
12
|
+
return TEMPLATE_SUFFIXES.some((suffix) => filePath.endsWith(suffix));
|
|
13
|
+
}
|
|
14
|
+
function isBuiltInSecretPath(filePath) {
|
|
15
|
+
if (isTemplate(filePath))
|
|
16
|
+
return false;
|
|
17
|
+
const name = basename(filePath);
|
|
18
|
+
if (name === '.env' || name.startsWith('.env.'))
|
|
19
|
+
return true;
|
|
20
|
+
if (filePath.includes('/.ssh/'))
|
|
21
|
+
return true;
|
|
22
|
+
if (/\/\.aws\/(credentials|config)$/.test(filePath))
|
|
23
|
+
return true;
|
|
24
|
+
if (/\.(pem|key)$/.test(name))
|
|
25
|
+
return true;
|
|
26
|
+
if (/^id_(rsa|ed25519|ecdsa|dsa)$/.test(name))
|
|
27
|
+
return true;
|
|
28
|
+
if (name === '.netrc')
|
|
29
|
+
return true;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function preventSecretFileWriteWith(opts) {
|
|
33
|
+
const extras = opts?.extraSecretPathPrefixes ?? [];
|
|
34
|
+
return {
|
|
35
|
+
id: 'prevent-secret-file-write',
|
|
36
|
+
check(toolName, toolInput) {
|
|
37
|
+
if (!TOOLS_THAT_WRITE.has(toolName))
|
|
38
|
+
return { kind: 'allow' };
|
|
39
|
+
const filePath = toolInput.file_path;
|
|
40
|
+
if (typeof filePath !== 'string')
|
|
41
|
+
return { kind: 'allow' };
|
|
42
|
+
if (isTemplate(filePath))
|
|
43
|
+
return { kind: 'allow' };
|
|
44
|
+
const builtIn = isBuiltInSecretPath(filePath);
|
|
45
|
+
const extra = extras.some((prefix) => filePath.includes(prefix));
|
|
46
|
+
if (!builtIn && !extra)
|
|
47
|
+
return { kind: 'allow' };
|
|
48
|
+
return {
|
|
49
|
+
kind: 'block',
|
|
50
|
+
reason: `Refusing to write to a likely secret/credential file: ${filePath}. If this is intentional, edit the file outside of the agent.`,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
exports.preventSecretFileWrite = preventSecretFileWriteWith();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preventSystemPathWrite.d.ts","sourceRoot":"","sources":["../../../src/deterministic/rules/preventSystemPathWrite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAe,MAAM,UAAU,CAAA;AAwBzD,eAAO,MAAM,sBAAsB,EAAE,iBAapC,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.preventSystemPathWrite = void 0;
|
|
4
|
+
const TOOLS_THAT_WRITE = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit']);
|
|
5
|
+
const SYSTEM_PREFIXES = [
|
|
6
|
+
'/etc/',
|
|
7
|
+
'/usr/',
|
|
8
|
+
'/var/',
|
|
9
|
+
'/bin/',
|
|
10
|
+
'/sbin/',
|
|
11
|
+
'/boot/',
|
|
12
|
+
'/lib/',
|
|
13
|
+
'/lib64/',
|
|
14
|
+
'/opt/',
|
|
15
|
+
'/System/',
|
|
16
|
+
'/Library/',
|
|
17
|
+
'/private/etc/',
|
|
18
|
+
'/private/var/',
|
|
19
|
+
];
|
|
20
|
+
function startsWithSystemPath(filePath) {
|
|
21
|
+
return SYSTEM_PREFIXES.some((prefix) => filePath.startsWith(prefix));
|
|
22
|
+
}
|
|
23
|
+
exports.preventSystemPathWrite = {
|
|
24
|
+
id: 'prevent-system-path-write',
|
|
25
|
+
check(toolName, toolInput) {
|
|
26
|
+
if (!TOOLS_THAT_WRITE.has(toolName))
|
|
27
|
+
return { kind: 'allow' };
|
|
28
|
+
const filePath = toolInput.file_path;
|
|
29
|
+
if (typeof filePath !== 'string')
|
|
30
|
+
return { kind: 'allow' };
|
|
31
|
+
if (!startsWithSystemPath(filePath))
|
|
32
|
+
return { kind: 'allow' };
|
|
33
|
+
return {
|
|
34
|
+
kind: 'block',
|
|
35
|
+
reason: `Refusing to modify a system path: ${filePath}. Operations against /etc, /usr, /System, /Library and similar locations belong outside of the agent.`,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SessionContext } from '../contracts/types/SessionContext';
|
|
2
|
+
export type RuleVerdict = {
|
|
3
|
+
kind: 'allow';
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'block';
|
|
6
|
+
reason: string;
|
|
7
|
+
};
|
|
8
|
+
export interface DeterministicRule {
|
|
9
|
+
id: string;
|
|
10
|
+
/**
|
|
11
|
+
* Decide whether to block this tool operation.
|
|
12
|
+
*
|
|
13
|
+
* @param toolName - normalized tool name (e.g. "Bash", "Edit", "Write").
|
|
14
|
+
* @param toolInput - normalized tool input fields.
|
|
15
|
+
* @param ctx - optional session context with history and project paths.
|
|
16
|
+
* Existing rules ignore this argument; new rules can read it.
|
|
17
|
+
*/
|
|
18
|
+
check(toolName: string, toolInput: Record<string, unknown>, ctx?: SessionContext): RuleVerdict;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/deterministic/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAA;AAElE,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAErC,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV;;;;;;;OAOG;IACH,KAAK,CACH,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,GAAG,CAAC,EAAE,cAAc,GACnB,WAAW,CAAA;CACf"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RuleSourceKind } from '../contracts/types/RuleSource';
|
|
2
|
+
export type Severity = 'info' | 'warning' | 'error';
|
|
3
|
+
export type FindingCode = 'empty-file' | 'ambiguous-modifier' | 'no-concrete-rules';
|
|
4
|
+
export interface Finding {
|
|
5
|
+
ruleSourcePath: string;
|
|
6
|
+
ruleSourceKind: RuleSourceKind;
|
|
7
|
+
severity: Severity;
|
|
8
|
+
code: FindingCode;
|
|
9
|
+
message: string;
|
|
10
|
+
/** 1-indexed line number when the finding is line-specific. */
|
|
11
|
+
line?: number;
|
|
12
|
+
/** Small excerpt of the offending text for human review. */
|
|
13
|
+
excerpt?: string;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=findings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.d.ts","sourceRoot":"","sources":["../../src/doctor/findings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAE9D,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAA;AAEnD,MAAM,MAAM,WAAW,GACnB,YAAY,GACZ,oBAAoB,GACpB,mBAAmB,CAAA;AAEvB,MAAM,WAAW,OAAO;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,cAAc,CAAA;IAC9B,QAAQ,EAAE,QAAQ,CAAA;IAClB,IAAI,EAAE,WAAW,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatFindings.d.ts","sourceRoot":"","sources":["../../src/doctor/formatFindings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAiBpC,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAuB1D"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatFindings = formatFindings;
|
|
4
|
+
const SEVERITY_LABEL = {
|
|
5
|
+
info: 'info',
|
|
6
|
+
warning: 'warning',
|
|
7
|
+
error: 'error',
|
|
8
|
+
};
|
|
9
|
+
function groupByPath(findings) {
|
|
10
|
+
const out = {};
|
|
11
|
+
for (const f of findings) {
|
|
12
|
+
if (!out[f.ruleSourcePath])
|
|
13
|
+
out[f.ruleSourcePath] = [];
|
|
14
|
+
out[f.ruleSourcePath].push(f);
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
function formatFindings(findings) {
|
|
19
|
+
if (findings.length === 0) {
|
|
20
|
+
return 'No issues found. 0 findings.';
|
|
21
|
+
}
|
|
22
|
+
const lines = [];
|
|
23
|
+
const grouped = groupByPath(findings);
|
|
24
|
+
for (const path of Object.keys(grouped).sort()) {
|
|
25
|
+
lines.push(path);
|
|
26
|
+
for (const f of grouped[path]) {
|
|
27
|
+
const loc = f.line !== undefined ? ` (line ${f.line})` : '';
|
|
28
|
+
const excerpt = f.excerpt !== undefined && f.excerpt.length > 0
|
|
29
|
+
? `\n > ${f.excerpt}`
|
|
30
|
+
: '';
|
|
31
|
+
lines.push(` [${SEVERITY_LABEL[f.severity]}] ${f.code}${loc}: ${f.message}${excerpt}`);
|
|
32
|
+
}
|
|
33
|
+
lines.push('');
|
|
34
|
+
}
|
|
35
|
+
lines.push(`${findings.length} finding${findings.length === 1 ? '' : 's'}.`);
|
|
36
|
+
return lines.join('\n');
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lintRuleSources.d.ts","sourceRoot":"","sources":["../../src/doctor/lintRuleSources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAwDpC,wBAAgB,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,CAyChE"}
|