@idevelopers/agentlock 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/.claude-plugin/marketplace.json +21 -0
- package/.claude-plugin/plugin.json +11 -0
- package/.mcp.json +11 -0
- package/README.md +114 -0
- package/agentlock.policy.example.json +84 -0
- package/codex-plugin/plugin.json +24 -0
- package/commands/check.md +6 -0
- package/commands/guard.md +6 -0
- package/commands/scan.md +6 -0
- package/dist/audit/log.js +32 -0
- package/dist/config/policy.js +47 -0
- package/dist/config/schema.js +51 -0
- package/dist/enforcement/classify.js +51 -0
- package/dist/enforcement/evaluator.js +126 -0
- package/dist/enforcement/generate_policy.js +73 -0
- package/dist/enforcement/match.js +28 -0
- package/dist/enforcement/runner.js +66 -0
- package/dist/enforcement/secrets.js +38 -0
- package/dist/index.js +17 -0
- package/dist/license/constants.js +2 -0
- package/dist/license/gate.js +6 -0
- package/dist/license/validator.js +187 -0
- package/dist/scan/environment.js +100 -0
- package/dist/server.js +14 -0
- package/dist/tools/activate_license.js +24 -0
- package/dist/tools/audit_log.js +22 -0
- package/dist/tools/check_action.js +44 -0
- package/dist/tools/generate_policy.js +19 -0
- package/dist/tools/response.js +10 -0
- package/dist/tools/run_guarded_command.js +68 -0
- package/dist/tools/scan_environment.js +16 -0
- package/dist/util/paths.js +35 -0
- package/dist/util/redact.js +28 -0
- package/package.json +49 -0
- package/skills/agentlock-workflow/SKILL.md +17 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function matchesAny(value, patterns) {
|
|
2
|
+
return patterns.some((pattern) => matchesPattern(value, pattern));
|
|
3
|
+
}
|
|
4
|
+
export function matchesPattern(value, pattern) {
|
|
5
|
+
const normalizedValue = normalize(value);
|
|
6
|
+
const normalizedPattern = normalize(pattern);
|
|
7
|
+
if (normalizedPattern === "**" || normalizedPattern === "*") {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (!normalizedPattern.includes("*")) {
|
|
11
|
+
return (normalizedValue === normalizedPattern ||
|
|
12
|
+
normalizedValue.startsWith(`${normalizedPattern}/`) ||
|
|
13
|
+
normalizedValue.startsWith(`${normalizedPattern} `));
|
|
14
|
+
}
|
|
15
|
+
return globToRegExp(normalizedPattern).test(normalizedValue);
|
|
16
|
+
}
|
|
17
|
+
function globToRegExp(pattern) {
|
|
18
|
+
const marker = "\u0000";
|
|
19
|
+
const escaped = pattern
|
|
20
|
+
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
|
21
|
+
.replace(/\*\*/g, marker)
|
|
22
|
+
.replace(/\*/g, "[^/ ]*")
|
|
23
|
+
.replace(new RegExp(marker, "g"), ".*");
|
|
24
|
+
return new RegExp(`^${escaped}$`);
|
|
25
|
+
}
|
|
26
|
+
function normalize(value) {
|
|
27
|
+
return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/");
|
|
28
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { tailRedacted } from "../util/redact.js";
|
|
4
|
+
export async function runCommand(command, args, cwd, timeoutMs) {
|
|
5
|
+
const startedAt = Date.now();
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const child = spawn(command, args, {
|
|
8
|
+
cwd,
|
|
9
|
+
shell: false,
|
|
10
|
+
env: sanitizedEnv(cwd),
|
|
11
|
+
});
|
|
12
|
+
let stdout = "";
|
|
13
|
+
let stderr = "";
|
|
14
|
+
let timedOut = false;
|
|
15
|
+
const timer = setTimeout(() => {
|
|
16
|
+
timedOut = true;
|
|
17
|
+
child.kill("SIGTERM");
|
|
18
|
+
}, timeoutMs);
|
|
19
|
+
child.stdout.on("data", (chunk) => {
|
|
20
|
+
stdout += chunk.toString("utf8");
|
|
21
|
+
stdout = stdout.slice(-8192);
|
|
22
|
+
});
|
|
23
|
+
child.stderr.on("data", (chunk) => {
|
|
24
|
+
stderr += chunk.toString("utf8");
|
|
25
|
+
stderr = stderr.slice(-8192);
|
|
26
|
+
});
|
|
27
|
+
child.on("error", (error) => {
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
resolve({
|
|
30
|
+
status: "error",
|
|
31
|
+
exitCode: null,
|
|
32
|
+
durationMs: Date.now() - startedAt,
|
|
33
|
+
stdoutTail: tailRedacted(stdout),
|
|
34
|
+
stderrTail: tailRedacted(`${stderr}\n${error.message}`),
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
child.on("close", (code) => {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
resolve({
|
|
40
|
+
status: timedOut ? "timeout" : code === 0 ? "pass" : "fail",
|
|
41
|
+
exitCode: code,
|
|
42
|
+
durationMs: Date.now() - startedAt,
|
|
43
|
+
stdoutTail: tailRedacted(stdout),
|
|
44
|
+
stderrTail: tailRedacted(stderr),
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function sanitizedEnv(cwd) {
|
|
50
|
+
const allowed = new Set(["PATH", "HOME", "TMPDIR", "TEMP", "TMP", "LANG", "LC_ALL", "SHELL"]);
|
|
51
|
+
const env = {};
|
|
52
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
53
|
+
if (!value) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (allowed.has(key)) {
|
|
57
|
+
env[key] = value;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (!/(key|token|secret|password|credential|authorization|cookie|polar|agentlock|openai|anthropic|github|npm|aws|gcp|google|azure|cloudflare)/i.test(key)) {
|
|
61
|
+
env[key] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
env.PATH = [path.join(cwd, "node_modules", ".bin"), env.PATH].filter(Boolean).join(path.delimiter);
|
|
65
|
+
return env;
|
|
66
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const SECRET_PATH_PATTERNS = [
|
|
3
|
+
".env",
|
|
4
|
+
".env.*",
|
|
5
|
+
"**/.env",
|
|
6
|
+
"**/.env.*",
|
|
7
|
+
"**/.npmrc",
|
|
8
|
+
"~/.npmrc",
|
|
9
|
+
"~/.aws/**",
|
|
10
|
+
"~/.ssh/**",
|
|
11
|
+
"~/.config/gcloud/**",
|
|
12
|
+
"~/.docker/config.json",
|
|
13
|
+
"~/.kube/config",
|
|
14
|
+
"**/*secret*",
|
|
15
|
+
"**/*credential*",
|
|
16
|
+
"**/*token*",
|
|
17
|
+
"**/Cookies",
|
|
18
|
+
];
|
|
19
|
+
export function defaultSecretPathPatterns() {
|
|
20
|
+
return [...SECRET_PATH_PATTERNS];
|
|
21
|
+
}
|
|
22
|
+
export function isLikelySecretPath(inputPath) {
|
|
23
|
+
const normalized = inputPath.replaceAll("\\", "/").toLowerCase();
|
|
24
|
+
const base = path.basename(normalized);
|
|
25
|
+
return (base === ".env" ||
|
|
26
|
+
base.startsWith(".env.") ||
|
|
27
|
+
base === ".npmrc" ||
|
|
28
|
+
base.includes("id_rsa") ||
|
|
29
|
+
base.includes("id_ed25519") ||
|
|
30
|
+
normalized.includes("/.ssh/") ||
|
|
31
|
+
normalized.includes("/.aws/") ||
|
|
32
|
+
normalized.includes("/.config/gcloud/") ||
|
|
33
|
+
normalized.includes("/.kube/config") ||
|
|
34
|
+
normalized.includes("credential") ||
|
|
35
|
+
normalized.includes("secret") ||
|
|
36
|
+
normalized.includes("token") ||
|
|
37
|
+
normalized.endsWith("/cookies"));
|
|
38
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { registerTools } from "./server.js";
|
|
5
|
+
async function main() {
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "agentlock",
|
|
8
|
+
version: "0.1.0",
|
|
9
|
+
});
|
|
10
|
+
registerTools(server);
|
|
11
|
+
const transport = new StdioServerTransport();
|
|
12
|
+
await server.connect(transport);
|
|
13
|
+
}
|
|
14
|
+
main().catch((error) => {
|
|
15
|
+
console.error(error);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { AGENTLOCK_CHECKOUT_URL } from "./constants.js";
|
|
2
|
+
export function requirePro(feature, tier) {
|
|
3
|
+
if (tier === "free") {
|
|
4
|
+
throw new Error(`Pro license required: ${feature}. Buy a license at ${process.env.AGENTLOCK_CHECKOUT_URL ?? AGENTLOCK_CHECKOUT_URL}, then activate it with agentlock_activate_license.`);
|
|
5
|
+
}
|
|
6
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Polar } from "@polar-sh/sdk";
|
|
4
|
+
import { licenseCachePath } from "../util/paths.js";
|
|
5
|
+
import { AGENTLOCK_POLAR_ORGANIZATION_ID } from "./constants.js";
|
|
6
|
+
const OFFLINE_GRACE_MS = 72 * 60 * 60 * 1000;
|
|
7
|
+
export class PolarLicenseValidator {
|
|
8
|
+
options;
|
|
9
|
+
cachePath;
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.options = options;
|
|
12
|
+
this.cachePath = options.cachePath ?? licenseCachePath();
|
|
13
|
+
}
|
|
14
|
+
async verify() {
|
|
15
|
+
const cache = await readLicenseCache(this.cachePath);
|
|
16
|
+
if (!cache.key) {
|
|
17
|
+
return { tier: "free" };
|
|
18
|
+
}
|
|
19
|
+
const organizationId = this.options.organizationId ??
|
|
20
|
+
process.env.POLAR_ORGANIZATION_ID ??
|
|
21
|
+
defaultOrganizationId();
|
|
22
|
+
if (!organizationId) {
|
|
23
|
+
return cachedWithinGrace(cache) ?? { tier: "free" };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const polar = new Polar({
|
|
27
|
+
server: process.env.POLAR_ENVIRONMENT === "sandbox" ? "sandbox" : "production",
|
|
28
|
+
});
|
|
29
|
+
const activationId = cache.activationId ??
|
|
30
|
+
(await activateLicenseKey(polar, cache.key, organizationId, this.cachePath, cache));
|
|
31
|
+
const validated = await polar.customerPortal.licenseKeys.validate({
|
|
32
|
+
key: cache.key,
|
|
33
|
+
organizationId,
|
|
34
|
+
activationId,
|
|
35
|
+
}, {
|
|
36
|
+
timeoutMs: 10000,
|
|
37
|
+
});
|
|
38
|
+
if (!isGrantedLicense(validated)) {
|
|
39
|
+
return { tier: "free" };
|
|
40
|
+
}
|
|
41
|
+
const verification = verificationFromPolarResponse(validated);
|
|
42
|
+
await writeLicenseCache(this.cachePath, {
|
|
43
|
+
...cache,
|
|
44
|
+
activationId,
|
|
45
|
+
...verification,
|
|
46
|
+
validatedAt: new Date().toISOString(),
|
|
47
|
+
});
|
|
48
|
+
return verification;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (isNetworkError(error)) {
|
|
52
|
+
return cachedWithinGrace(cache) ?? { tier: "free" };
|
|
53
|
+
}
|
|
54
|
+
return { tier: "free" };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function storeLicenseKey(key, cachePath = licenseCachePath()) {
|
|
59
|
+
const existing = await readLicenseCache(cachePath);
|
|
60
|
+
if (existing.key === key) {
|
|
61
|
+
await writeLicenseCache(cachePath, {
|
|
62
|
+
...existing,
|
|
63
|
+
key,
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await writeLicenseCache(cachePath, { key });
|
|
68
|
+
}
|
|
69
|
+
function defaultOrganizationId() {
|
|
70
|
+
if (process.env.POLAR_ENVIRONMENT === "sandbox") {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
return process.env.AGENTLOCK_POLAR_ORGANIZATION_ID ?? AGENTLOCK_POLAR_ORGANIZATION_ID;
|
|
74
|
+
}
|
|
75
|
+
async function activateLicenseKey(polar, key, organizationId, cachePath, cache) {
|
|
76
|
+
const activation = await polar.customerPortal.licenseKeys.activate({
|
|
77
|
+
key,
|
|
78
|
+
organizationId,
|
|
79
|
+
label: process.env.AGENTLOCK_ACTIVATION_LABEL ?? "agentlock-local",
|
|
80
|
+
meta: {
|
|
81
|
+
app: "agentlock",
|
|
82
|
+
},
|
|
83
|
+
}, {
|
|
84
|
+
timeoutMs: 10000,
|
|
85
|
+
});
|
|
86
|
+
await writeLicenseCache(cachePath, {
|
|
87
|
+
...cache,
|
|
88
|
+
activationId: activation.id,
|
|
89
|
+
});
|
|
90
|
+
return activation.id;
|
|
91
|
+
}
|
|
92
|
+
async function readLicenseCache(cachePath) {
|
|
93
|
+
try {
|
|
94
|
+
const raw = await readFile(cachePath, "utf8");
|
|
95
|
+
return JSON.parse(raw);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function writeLicenseCache(cachePath, cache) {
|
|
105
|
+
await mkdir(path.dirname(cachePath), { recursive: true });
|
|
106
|
+
await writeFile(cachePath, JSON.stringify(cache, null, 2), "utf8");
|
|
107
|
+
}
|
|
108
|
+
function cachedWithinGrace(cache) {
|
|
109
|
+
if (!cache.tier || cache.tier === "free" || !cache.validatedAt) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
const validatedAt = new Date(cache.validatedAt).getTime();
|
|
113
|
+
if (Number.isNaN(validatedAt) || Date.now() - validatedAt > OFFLINE_GRACE_MS) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
tier: cache.tier,
|
|
118
|
+
validUntil: cache.validUntil,
|
|
119
|
+
seats: cache.seats,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function isGrantedLicense(response) {
|
|
123
|
+
const record = asRecord(response);
|
|
124
|
+
if (record["valid"] === false) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
const status = record["status"];
|
|
128
|
+
if (typeof status === "string" && status !== "granted") {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
function verificationFromPolarResponse(response) {
|
|
134
|
+
const record = asRecord(response);
|
|
135
|
+
const tier = inferTier(record);
|
|
136
|
+
const expiresAt = record["expiresAt"];
|
|
137
|
+
const limitActivations = numberValue(record["limitActivations"]);
|
|
138
|
+
return {
|
|
139
|
+
tier,
|
|
140
|
+
validUntil: expiresAt instanceof Date
|
|
141
|
+
? expiresAt.toISOString()
|
|
142
|
+
: typeof expiresAt === "string"
|
|
143
|
+
? expiresAt
|
|
144
|
+
: undefined,
|
|
145
|
+
seats: tier === "team" ? limitActivations ?? undefined : undefined,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function inferTier(record) {
|
|
149
|
+
const explicitTier = nestedString(record, ["metadata", "tier"]) ??
|
|
150
|
+
nestedString(record, ["benefit", "metadata", "tier"]) ??
|
|
151
|
+
nestedString(record, ["product", "metadata", "tier"]);
|
|
152
|
+
if (explicitTier === "team") {
|
|
153
|
+
return "team";
|
|
154
|
+
}
|
|
155
|
+
if (explicitTier === "pro") {
|
|
156
|
+
return "pro";
|
|
157
|
+
}
|
|
158
|
+
const limitActivations = numberValue(record["limitActivations"]);
|
|
159
|
+
if (limitActivations && limitActivations > 1) {
|
|
160
|
+
return "team";
|
|
161
|
+
}
|
|
162
|
+
return "pro";
|
|
163
|
+
}
|
|
164
|
+
function nestedString(record, pathParts) {
|
|
165
|
+
let current = record;
|
|
166
|
+
for (const part of pathParts) {
|
|
167
|
+
current = asRecord(current)[part];
|
|
168
|
+
}
|
|
169
|
+
return typeof current === "string" ? current.toLowerCase() : undefined;
|
|
170
|
+
}
|
|
171
|
+
function numberValue(value) {
|
|
172
|
+
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
173
|
+
}
|
|
174
|
+
function asRecord(value) {
|
|
175
|
+
return typeof value === "object" && value !== null
|
|
176
|
+
? value
|
|
177
|
+
: {};
|
|
178
|
+
}
|
|
179
|
+
function isNetworkError(error) {
|
|
180
|
+
const record = asRecord(error);
|
|
181
|
+
const name = String(record["name"] ?? "");
|
|
182
|
+
const message = String(record["message"] ?? "");
|
|
183
|
+
return /(connection|network|timeout|abort|econn|enotfound|etimedout)/i.test(`${name} ${message}`);
|
|
184
|
+
}
|
|
185
|
+
function isNodeError(error) {
|
|
186
|
+
return error instanceof Error && "code" in error;
|
|
187
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { access, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { redactValue } from "../util/redact.js";
|
|
5
|
+
const repoSecretFiles = [
|
|
6
|
+
".env",
|
|
7
|
+
".env.local",
|
|
8
|
+
".env.production",
|
|
9
|
+
".npmrc",
|
|
10
|
+
".mcp.json",
|
|
11
|
+
".cursor/mcp.json",
|
|
12
|
+
".claude/settings.local.json",
|
|
13
|
+
];
|
|
14
|
+
const homeSecretFiles = [
|
|
15
|
+
"~/.aws/credentials",
|
|
16
|
+
"~/.aws/config",
|
|
17
|
+
"~/.config/gcloud/application_default_credentials.json",
|
|
18
|
+
"~/.docker/config.json",
|
|
19
|
+
"~/.kube/config",
|
|
20
|
+
"~/.npmrc",
|
|
21
|
+
"~/.ssh/id_rsa",
|
|
22
|
+
"~/.ssh/id_ed25519",
|
|
23
|
+
"~/Library/Application Support/Google/Chrome/Default/Cookies",
|
|
24
|
+
];
|
|
25
|
+
export async function scanEnvironment(root, includeHome) {
|
|
26
|
+
const resolvedRoot = path.resolve(root);
|
|
27
|
+
const risks = [];
|
|
28
|
+
for (const file of repoSecretFiles) {
|
|
29
|
+
await addIfExists(risks, path.join(resolvedRoot, file), file, "repo-secret");
|
|
30
|
+
}
|
|
31
|
+
if (includeHome) {
|
|
32
|
+
for (const file of homeSecretFiles) {
|
|
33
|
+
await addIfExists(risks, expandHome(file), file, "home-secret");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
await scanPackageScripts(risks, resolvedRoot);
|
|
37
|
+
return redactValue({
|
|
38
|
+
root: resolvedRoot,
|
|
39
|
+
scannedAt: new Date().toISOString(),
|
|
40
|
+
riskCount: risks.length,
|
|
41
|
+
risks,
|
|
42
|
+
notes: [
|
|
43
|
+
"AgentLock reports file presence and risky configuration surfaces only.",
|
|
44
|
+
"Secret values are never printed by the scanner.",
|
|
45
|
+
],
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async function addIfExists(risks, absolutePath, displayPath, category) {
|
|
49
|
+
try {
|
|
50
|
+
const fileStat = await stat(absolutePath);
|
|
51
|
+
if (!fileStat.isFile()) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
risks.push({
|
|
59
|
+
category,
|
|
60
|
+
severity: severityForPath(displayPath),
|
|
61
|
+
path: displayPath,
|
|
62
|
+
message: `${displayPath} exists and may be accessible to local AI agents or MCP tools.`,
|
|
63
|
+
recommendation: "Deny this path in agentlock.policy.json unless the current task explicitly needs it.",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function scanPackageScripts(risks, root) {
|
|
67
|
+
const packagePath = path.join(root, "package.json");
|
|
68
|
+
try {
|
|
69
|
+
await access(packagePath);
|
|
70
|
+
const raw = await readFile(packagePath, "utf8");
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
const scripts = parsed.scripts ?? {};
|
|
73
|
+
for (const [name, command] of Object.entries(scripts)) {
|
|
74
|
+
if (/\b(publish|deploy|push|terraform apply|prisma migrate reset)\b/i.test(command)) {
|
|
75
|
+
risks.push({
|
|
76
|
+
category: "dangerous-script",
|
|
77
|
+
severity: "medium",
|
|
78
|
+
path: `package.json#scripts.${name}`,
|
|
79
|
+
message: `Script '${name}' appears to run a publish, deploy, push, or database migration action.`,
|
|
80
|
+
recommendation: "Require explicit approval before an agent runs this script.",
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function severityForPath(displayPath) {
|
|
90
|
+
if (displayPath.includes(".ssh") || displayPath.includes("Cookies") || displayPath.includes("credentials")) {
|
|
91
|
+
return "high";
|
|
92
|
+
}
|
|
93
|
+
if (displayPath.includes(".env") || displayPath.includes(".npmrc")) {
|
|
94
|
+
return "high";
|
|
95
|
+
}
|
|
96
|
+
return "medium";
|
|
97
|
+
}
|
|
98
|
+
function expandHome(inputPath) {
|
|
99
|
+
return inputPath.startsWith("~/") ? path.join(os.homedir(), inputPath.slice(2)) : inputPath;
|
|
100
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { registerActivateLicenseTool } from "./tools/activate_license.js";
|
|
2
|
+
import { registerAuditLogTool } from "./tools/audit_log.js";
|
|
3
|
+
import { registerCheckActionTool } from "./tools/check_action.js";
|
|
4
|
+
import { registerGeneratePolicyTool } from "./tools/generate_policy.js";
|
|
5
|
+
import { registerRunGuardedCommandTool } from "./tools/run_guarded_command.js";
|
|
6
|
+
import { registerScanEnvironmentTool } from "./tools/scan_environment.js";
|
|
7
|
+
export function registerTools(server) {
|
|
8
|
+
registerScanEnvironmentTool(server);
|
|
9
|
+
registerGeneratePolicyTool(server);
|
|
10
|
+
registerCheckActionTool(server);
|
|
11
|
+
registerRunGuardedCommandTool(server);
|
|
12
|
+
registerAuditLogTool(server);
|
|
13
|
+
registerActivateLicenseTool(server);
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AGENTLOCK_CHECKOUT_URL } from "../license/constants.js";
|
|
3
|
+
import { PolarLicenseValidator, storeLicenseKey, } from "../license/validator.js";
|
|
4
|
+
import { jsonToolResult } from "./response.js";
|
|
5
|
+
const activateLicenseInputSchema = {
|
|
6
|
+
key: z.string().min(1),
|
|
7
|
+
};
|
|
8
|
+
export function registerActivateLicenseTool(server) {
|
|
9
|
+
server.registerTool("agentlock_activate_license", {
|
|
10
|
+
title: "Activate AgentLock License",
|
|
11
|
+
description: "Store and verify a Polar license key.",
|
|
12
|
+
inputSchema: activateLicenseInputSchema,
|
|
13
|
+
}, async ({ key }) => {
|
|
14
|
+
await storeLicenseKey(key);
|
|
15
|
+
const verification = await new PolarLicenseValidator().verify();
|
|
16
|
+
return jsonToolResult({
|
|
17
|
+
tier: verification.tier,
|
|
18
|
+
validUntil: verification.validUntil,
|
|
19
|
+
checkoutUrl: verification.tier === "free"
|
|
20
|
+
? process.env.AGENTLOCK_CHECKOUT_URL ?? AGENTLOCK_CHECKOUT_URL
|
|
21
|
+
: undefined,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readAuditEntries } from "../audit/log.js";
|
|
3
|
+
import { requirePro } from "../license/gate.js";
|
|
4
|
+
import { PolarLicenseValidator } from "../license/validator.js";
|
|
5
|
+
import { jsonToolResult } from "./response.js";
|
|
6
|
+
const auditLogInputSchema = {
|
|
7
|
+
limit: z.number().int().positive().max(200).default(50),
|
|
8
|
+
};
|
|
9
|
+
export function registerAuditLogTool(server) {
|
|
10
|
+
server.registerTool("agentlock_audit_log", {
|
|
11
|
+
title: "Read AgentLock Audit Log",
|
|
12
|
+
description: "Read recent redacted local AgentLock audit entries.",
|
|
13
|
+
inputSchema: auditLogInputSchema,
|
|
14
|
+
}, async ({ limit }) => {
|
|
15
|
+
const license = await new PolarLicenseValidator().verify();
|
|
16
|
+
requirePro("agentlock_audit_log", license.tier);
|
|
17
|
+
return jsonToolResult({
|
|
18
|
+
tier: license.tier,
|
|
19
|
+
entries: await readAuditEntries(limit),
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { appendAuditEntry } from "../audit/log.js";
|
|
3
|
+
import { loadPolicy } from "../config/policy.js";
|
|
4
|
+
import { actionTypeSchema } from "../config/schema.js";
|
|
5
|
+
import { evaluateAction } from "../enforcement/evaluator.js";
|
|
6
|
+
import { requirePro } from "../license/gate.js";
|
|
7
|
+
import { PolarLicenseValidator } from "../license/validator.js";
|
|
8
|
+
import { jsonToolResult } from "./response.js";
|
|
9
|
+
const checkActionInputSchema = {
|
|
10
|
+
type: actionTypeSchema,
|
|
11
|
+
capsuleId: z.string().min(1).optional(),
|
|
12
|
+
path: z.string().min(1).optional(),
|
|
13
|
+
command: z.string().min(1).optional(),
|
|
14
|
+
args: z.array(z.string()).default([]),
|
|
15
|
+
domain: z.string().min(1).optional(),
|
|
16
|
+
mcpServer: z.string().min(1).optional(),
|
|
17
|
+
mcpTool: z.string().min(1).optional(),
|
|
18
|
+
approved: z.boolean().default(false),
|
|
19
|
+
};
|
|
20
|
+
export function registerCheckActionTool(server) {
|
|
21
|
+
server.registerTool("agentlock_check_action", {
|
|
22
|
+
title: "Check AgentLock Action",
|
|
23
|
+
description: "Evaluate a file, command, network, or MCP action against the active AgentLock policy.",
|
|
24
|
+
inputSchema: checkActionInputSchema,
|
|
25
|
+
}, async (input) => {
|
|
26
|
+
const license = await new PolarLicenseValidator().verify();
|
|
27
|
+
requirePro("agentlock_check_action", license.tier);
|
|
28
|
+
const loaded = await loadPolicy(process.cwd());
|
|
29
|
+
const result = evaluateAction(loaded.policy, input, process.cwd());
|
|
30
|
+
await appendAuditEntry({
|
|
31
|
+
timestamp: new Date().toISOString(),
|
|
32
|
+
tool: "agentlock_check_action",
|
|
33
|
+
decision: result.decision,
|
|
34
|
+
action: input,
|
|
35
|
+
result,
|
|
36
|
+
});
|
|
37
|
+
return jsonToolResult({
|
|
38
|
+
...result,
|
|
39
|
+
policySource: loaded.source,
|
|
40
|
+
policyPath: loaded.path,
|
|
41
|
+
tier: license.tier,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { generatePolicy } from "../enforcement/generate_policy.js";
|
|
3
|
+
import { jsonToolResult } from "./response.js";
|
|
4
|
+
const generatePolicyInputSchema = {
|
|
5
|
+
mode: z.enum(["strict", "balanced", "relaxed"]).default("balanced"),
|
|
6
|
+
};
|
|
7
|
+
export function registerGeneratePolicyTool(server) {
|
|
8
|
+
server.registerTool("agentlock_generate_policy", {
|
|
9
|
+
title: "Generate AgentLock Policy",
|
|
10
|
+
description: "Generate a starter agentlock.policy.json permission capsule.",
|
|
11
|
+
inputSchema: generatePolicyInputSchema,
|
|
12
|
+
}, async ({ mode }) => {
|
|
13
|
+
const policy = generatePolicy({ mode: mode });
|
|
14
|
+
return jsonToolResult({
|
|
15
|
+
recommendedPath: "agentlock.policy.json",
|
|
16
|
+
policy,
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { appendAuditEntry } from "../audit/log.js";
|
|
3
|
+
import { loadPolicy } from "../config/policy.js";
|
|
4
|
+
import { evaluateAction } from "../enforcement/evaluator.js";
|
|
5
|
+
import { runCommand } from "../enforcement/runner.js";
|
|
6
|
+
import { requirePro } from "../license/gate.js";
|
|
7
|
+
import { PolarLicenseValidator } from "../license/validator.js";
|
|
8
|
+
import { jsonToolResult } from "./response.js";
|
|
9
|
+
const runGuardedCommandInputSchema = {
|
|
10
|
+
command: z.string().min(1),
|
|
11
|
+
args: z.array(z.string()).default([]),
|
|
12
|
+
cwd: z.string().min(1).optional(),
|
|
13
|
+
capsuleId: z.string().min(1).optional(),
|
|
14
|
+
approved: z.boolean().default(false),
|
|
15
|
+
timeoutMs: z.number().int().positive().max(600000).default(120000),
|
|
16
|
+
};
|
|
17
|
+
export function registerRunGuardedCommandTool(server) {
|
|
18
|
+
server.registerTool("agentlock_run_guarded_command", {
|
|
19
|
+
title: "Run AgentLock Guarded Command",
|
|
20
|
+
description: "Run a local command only after AgentLock policy allows it.",
|
|
21
|
+
inputSchema: runGuardedCommandInputSchema,
|
|
22
|
+
}, async ({ command, args, cwd, capsuleId, approved, timeoutMs }) => {
|
|
23
|
+
const license = await new PolarLicenseValidator().verify();
|
|
24
|
+
requirePro("agentlock_run_guarded_command", license.tier);
|
|
25
|
+
const runCwd = cwd ?? process.cwd();
|
|
26
|
+
const loaded = await loadPolicy(process.cwd());
|
|
27
|
+
const action = {
|
|
28
|
+
type: "command",
|
|
29
|
+
command,
|
|
30
|
+
args,
|
|
31
|
+
capsuleId,
|
|
32
|
+
approved,
|
|
33
|
+
};
|
|
34
|
+
const evaluation = evaluateAction(loaded.policy, action, runCwd);
|
|
35
|
+
if (evaluation.decision !== "allow") {
|
|
36
|
+
await appendAuditEntry({
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
tool: "agentlock_run_guarded_command",
|
|
39
|
+
decision: evaluation.decision,
|
|
40
|
+
action,
|
|
41
|
+
result: evaluation,
|
|
42
|
+
});
|
|
43
|
+
return jsonToolResult({
|
|
44
|
+
ran: false,
|
|
45
|
+
evaluation,
|
|
46
|
+
policySource: loaded.source,
|
|
47
|
+
policyPath: loaded.path,
|
|
48
|
+
tier: license.tier,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const result = await runCommand(command, args, runCwd, timeoutMs);
|
|
52
|
+
await appendAuditEntry({
|
|
53
|
+
timestamp: new Date().toISOString(),
|
|
54
|
+
tool: "agentlock_run_guarded_command",
|
|
55
|
+
decision: evaluation.decision,
|
|
56
|
+
action,
|
|
57
|
+
result,
|
|
58
|
+
});
|
|
59
|
+
return jsonToolResult({
|
|
60
|
+
ran: true,
|
|
61
|
+
evaluation,
|
|
62
|
+
commandResult: result,
|
|
63
|
+
policySource: loaded.source,
|
|
64
|
+
policyPath: loaded.path,
|
|
65
|
+
tier: license.tier,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { scanEnvironment } from "../scan/environment.js";
|
|
3
|
+
import { jsonToolResult } from "./response.js";
|
|
4
|
+
const scanEnvironmentInputSchema = {
|
|
5
|
+
root: z.string().min(1).optional(),
|
|
6
|
+
includeHome: z.boolean().default(true),
|
|
7
|
+
};
|
|
8
|
+
export function registerScanEnvironmentTool(server) {
|
|
9
|
+
server.registerTool("agentlock_scan_environment", {
|
|
10
|
+
title: "Scan AgentLock Environment",
|
|
11
|
+
description: "Scan local AI-agent, MCP, and secret-adjacent surfaces without printing secret values.",
|
|
12
|
+
inputSchema: scanEnvironmentInputSchema,
|
|
13
|
+
}, async ({ root, includeHome }) => {
|
|
14
|
+
return jsonToolResult(await scanEnvironment(root ?? process.cwd(), includeHome));
|
|
15
|
+
});
|
|
16
|
+
}
|