atabey 0.1.1-beta → 0.1.2
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 +144 -78
- package/dist/atabey/src/cli/commands/coverage.js +17 -12
- package/dist/atabey/src/cli/commands/coverage.js.map +1 -1
- package/dist/atabey/src/cli/commands/lint.d.ts +2 -1
- package/dist/atabey/src/cli/commands/lint.js +20 -1
- package/dist/atabey/src/cli/commands/lint.js.map +1 -1
- package/dist/atabey/src/cli/commands/mcp.js +56 -23
- package/dist/atabey/src/cli/commands/mcp.js.map +1 -1
- package/dist/atabey/src/cli/commands/orchestrate.d.ts +6 -6
- package/dist/shared/src/agent-identity.d.ts +1 -34
- package/dist/shared/src/agent-identity.js +1 -76
- package/dist/shared/src/agent-identity.js.map +1 -1
- package/dist/shared/src/api-catalog.d.ts +1 -57
- package/dist/shared/src/api-catalog.js +1 -253
- package/dist/shared/src/api-catalog.js.map +1 -1
- package/dist/shared/src/audit-chain.d.ts +1 -80
- package/dist/shared/src/audit-chain.js +1 -261
- package/dist/shared/src/audit-chain.js.map +1 -1
- package/dist/shared/src/audit.d.ts +1 -121
- package/dist/shared/src/audit.js +1 -321
- package/dist/shared/src/audit.js.map +1 -1
- package/dist/shared/src/constants.d.ts +1 -150
- package/dist/shared/src/constants.js +1 -211
- package/dist/shared/src/constants.js.map +1 -1
- package/dist/shared/src/database.d.ts +1 -6
- package/dist/shared/src/database.js +1 -15
- package/dist/shared/src/database.js.map +1 -1
- package/dist/shared/src/errors.d.ts +1 -46
- package/dist/shared/src/errors.js +1 -68
- package/dist/shared/src/errors.js.map +1 -1
- package/dist/shared/src/file-lock.d.ts +1 -93
- package/dist/shared/src/file-lock.js +1 -239
- package/dist/shared/src/file-lock.js.map +1 -1
- package/dist/shared/src/fs.d.ts +1 -9
- package/dist/shared/src/fs.js +1 -53
- package/dist/shared/src/fs.js.map +1 -1
- package/dist/shared/src/index.d.ts +2 -31
- package/dist/shared/src/index.js +2 -31
- package/dist/shared/src/index.js.map +1 -1
- package/dist/shared/src/job-queue.d.ts +1 -76
- package/dist/shared/src/job-queue.js +1 -199
- package/dist/shared/src/job-queue.js.map +1 -1
- package/dist/shared/src/lock.d.ts +1 -82
- package/dist/shared/src/lock.js +1 -158
- package/dist/shared/src/lock.js.map +1 -1
- package/dist/shared/src/logger.d.ts +1 -31
- package/dist/shared/src/logger.js +1 -152
- package/dist/shared/src/logger.js.map +1 -1
- package/dist/shared/src/memory-prune.d.ts +1 -18
- package/dist/shared/src/memory-prune.js +1 -47
- package/dist/shared/src/memory-prune.js.map +1 -1
- package/dist/shared/src/migrations.d.ts +1 -69
- package/dist/shared/src/migrations.js +1 -393
- package/dist/shared/src/migrations.js.map +1 -1
- package/dist/shared/src/modules/agent/agent-identity.d.ts +34 -0
- package/dist/shared/src/modules/agent/agent-identity.js +77 -0
- package/dist/shared/src/modules/agent/agent-identity.js.map +1 -0
- package/dist/shared/src/modules/agent/api-catalog.d.ts +57 -0
- package/dist/shared/src/modules/agent/api-catalog.js +254 -0
- package/dist/shared/src/modules/agent/api-catalog.js.map +1 -0
- package/dist/shared/src/modules/agent/index.d.ts +3 -0
- package/dist/shared/src/modules/agent/index.js +4 -0
- package/dist/shared/src/modules/agent/index.js.map +1 -0
- package/dist/shared/src/modules/agent/tokenizer.d.ts +54 -0
- package/dist/shared/src/modules/agent/tokenizer.js +272 -0
- package/dist/shared/src/modules/agent/tokenizer.js.map +1 -0
- package/dist/shared/src/modules/concurrency/file-lock.d.ts +93 -0
- package/dist/shared/src/modules/concurrency/file-lock.js +240 -0
- package/dist/shared/src/modules/concurrency/file-lock.js.map +1 -0
- package/dist/shared/src/modules/concurrency/index.d.ts +6 -0
- package/dist/shared/src/modules/concurrency/index.js +7 -0
- package/dist/shared/src/modules/concurrency/index.js.map +1 -0
- package/dist/shared/src/modules/concurrency/job-queue.d.ts +76 -0
- package/dist/shared/src/modules/concurrency/job-queue.js +200 -0
- package/dist/shared/src/modules/concurrency/job-queue.js.map +1 -0
- package/dist/shared/src/modules/concurrency/lock.d.ts +82 -0
- package/dist/shared/src/modules/concurrency/lock.js +159 -0
- package/dist/shared/src/modules/concurrency/lock.js.map +1 -0
- package/dist/shared/src/modules/concurrency/resilience.d.ts +84 -0
- package/dist/shared/src/modules/concurrency/resilience.js +217 -0
- package/dist/shared/src/modules/concurrency/resilience.js.map +1 -0
- package/dist/shared/src/modules/concurrency/worker-pool.d.ts +52 -0
- package/dist/shared/src/modules/concurrency/worker-pool.js +229 -0
- package/dist/shared/src/modules/concurrency/worker-pool.js.map +1 -0
- package/dist/shared/src/modules/concurrency/worker-thread.d.ts +21 -0
- package/dist/shared/src/modules/concurrency/worker-thread.js +67 -0
- package/dist/shared/src/modules/concurrency/worker-thread.js.map +1 -0
- package/dist/shared/src/modules/index.d.ts +14 -0
- package/dist/shared/src/modules/index.js +15 -0
- package/dist/shared/src/modules/index.js.map +1 -0
- package/dist/shared/src/modules/observability/audit-chain.d.ts +80 -0
- package/dist/shared/src/modules/observability/audit-chain.js +262 -0
- package/dist/shared/src/modules/observability/audit-chain.js.map +1 -0
- package/dist/shared/src/modules/observability/audit.d.ts +121 -0
- package/dist/shared/src/modules/observability/audit.js +322 -0
- package/dist/shared/src/modules/observability/audit.js.map +1 -0
- package/dist/shared/src/modules/observability/index.d.ts +4 -0
- package/dist/shared/src/modules/observability/index.js +5 -0
- package/dist/shared/src/modules/observability/index.js.map +1 -0
- package/dist/shared/src/modules/observability/logger.d.ts +31 -0
- package/dist/shared/src/modules/observability/logger.js +153 -0
- package/dist/shared/src/modules/observability/logger.js.map +1 -0
- package/dist/shared/src/modules/observability/otel.d.ts +39 -0
- package/dist/shared/src/modules/observability/otel.js +93 -0
- package/dist/shared/src/modules/observability/otel.js.map +1 -0
- package/dist/shared/src/modules/primitives/constants.d.ts +150 -0
- package/dist/shared/src/modules/primitives/constants.js +212 -0
- package/dist/shared/src/modules/primitives/constants.js.map +1 -0
- package/dist/shared/src/modules/primitives/errors.d.ts +46 -0
- package/dist/shared/src/modules/primitives/errors.js +69 -0
- package/dist/shared/src/modules/primitives/errors.js.map +1 -0
- package/dist/shared/src/modules/primitives/fs.d.ts +9 -0
- package/dist/shared/src/modules/primitives/fs.js +54 -0
- package/dist/shared/src/modules/primitives/fs.js.map +1 -0
- package/dist/shared/src/modules/primitives/index.d.ts +7 -0
- package/dist/shared/src/modules/primitives/index.js +8 -0
- package/dist/shared/src/modules/primitives/index.js.map +1 -0
- package/dist/shared/src/modules/primitives/outbound-http.d.ts +32 -0
- package/dist/shared/src/modules/primitives/outbound-http.js +88 -0
- package/dist/shared/src/modules/primitives/outbound-http.js.map +1 -0
- package/dist/shared/src/modules/primitives/string.d.ts +1 -0
- package/dist/shared/src/modules/primitives/string.js +30 -0
- package/dist/shared/src/modules/primitives/string.js.map +1 -0
- package/dist/shared/src/modules/primitives/time.d.ts +5 -0
- package/dist/shared/src/modules/primitives/time.js +35 -0
- package/dist/shared/src/modules/primitives/time.js.map +1 -0
- package/dist/shared/src/modules/primitives/types.d.ts +27 -0
- package/dist/shared/src/modules/primitives/types.js +17 -0
- package/dist/shared/src/modules/primitives/types.js.map +1 -0
- package/dist/shared/src/modules/sandbox/index.d.ts +3 -0
- package/dist/shared/src/modules/sandbox/index.js +4 -0
- package/dist/shared/src/modules/sandbox/index.js.map +1 -0
- package/dist/shared/src/modules/sandbox/sandbox-fs.d.ts +28 -0
- package/dist/shared/src/modules/sandbox/sandbox-fs.js +143 -0
- package/dist/shared/src/modules/sandbox/sandbox-fs.js.map +1 -0
- package/dist/shared/src/modules/sandbox/sandbox-runtime.d.ts +78 -0
- package/dist/shared/src/modules/sandbox/sandbox-runtime.js +282 -0
- package/dist/shared/src/modules/sandbox/sandbox-runtime.js.map +1 -0
- package/dist/shared/src/modules/sandbox/sandbox.d.ts +54 -0
- package/dist/shared/src/modules/sandbox/sandbox.js +179 -0
- package/dist/shared/src/modules/sandbox/sandbox.js.map +1 -0
- package/dist/shared/src/modules/security/index.d.ts +5 -0
- package/dist/shared/src/modules/security/index.js +6 -0
- package/dist/shared/src/modules/security/index.js.map +1 -0
- package/dist/shared/src/modules/security/pii.d.ts +70 -0
- package/dist/shared/src/modules/security/pii.js +276 -0
- package/dist/shared/src/modules/security/pii.js.map +1 -0
- package/dist/shared/src/modules/security/policy-gate.d.ts +42 -0
- package/dist/shared/src/modules/security/policy-gate.js +172 -0
- package/dist/shared/src/modules/security/policy-gate.js.map +1 -0
- package/dist/shared/src/modules/security/retention.d.ts +68 -0
- package/dist/shared/src/modules/security/retention.js +218 -0
- package/dist/shared/src/modules/security/retention.js.map +1 -0
- package/dist/shared/src/modules/security/secrets.d.ts +99 -0
- package/dist/shared/src/modules/security/secrets.js +371 -0
- package/dist/shared/src/modules/security/secrets.js.map +1 -0
- package/dist/shared/src/modules/security/tls.d.ts +87 -0
- package/dist/shared/src/modules/security/tls.js +154 -0
- package/dist/shared/src/modules/security/tls.js.map +1 -0
- package/dist/shared/src/modules/storage/database.d.ts +6 -0
- package/dist/shared/src/modules/storage/database.js +16 -0
- package/dist/shared/src/modules/storage/database.js.map +1 -0
- package/dist/shared/src/modules/storage/index.d.ts +4 -0
- package/dist/shared/src/modules/storage/index.js +5 -0
- package/dist/shared/src/modules/storage/index.js.map +1 -0
- package/dist/shared/src/modules/storage/memory-prune.d.ts +18 -0
- package/dist/shared/src/modules/storage/memory-prune.js +48 -0
- package/dist/shared/src/modules/storage/memory-prune.js.map +1 -0
- package/dist/shared/src/modules/storage/migrations.d.ts +69 -0
- package/dist/shared/src/modules/storage/migrations.js +394 -0
- package/dist/shared/src/modules/storage/migrations.js.map +1 -0
- package/dist/shared/src/modules/storage/storage.d.ts +341 -0
- package/dist/shared/src/modules/storage/storage.js +1004 -0
- package/dist/shared/src/modules/storage/storage.js.map +1 -0
- package/dist/shared/src/otel.d.ts +1 -39
- package/dist/shared/src/otel.js +1 -92
- package/dist/shared/src/otel.js.map +1 -1
- package/dist/shared/src/outbound-http.d.ts +1 -32
- package/dist/shared/src/outbound-http.js +1 -87
- package/dist/shared/src/outbound-http.js.map +1 -1
- package/dist/shared/src/pii.d.ts +1 -70
- package/dist/shared/src/pii.js +1 -275
- package/dist/shared/src/pii.js.map +1 -1
- package/dist/shared/src/policy-gate.d.ts +1 -42
- package/dist/shared/src/policy-gate.js +1 -171
- package/dist/shared/src/policy-gate.js.map +1 -1
- package/dist/shared/src/resilience.d.ts +1 -84
- package/dist/shared/src/resilience.js +1 -216
- package/dist/shared/src/resilience.js.map +1 -1
- package/dist/shared/src/retention.d.ts +1 -68
- package/dist/shared/src/retention.js +1 -217
- package/dist/shared/src/retention.js.map +1 -1
- package/dist/shared/src/sandbox-fs.d.ts +1 -28
- package/dist/shared/src/sandbox-fs.js +1 -142
- package/dist/shared/src/sandbox-fs.js.map +1 -1
- package/dist/shared/src/sandbox-runtime.d.ts +1 -78
- package/dist/shared/src/sandbox-runtime.js +1 -281
- package/dist/shared/src/sandbox-runtime.js.map +1 -1
- package/dist/shared/src/sandbox.d.ts +1 -54
- package/dist/shared/src/sandbox.js +1 -178
- package/dist/shared/src/sandbox.js.map +1 -1
- package/dist/shared/src/secrets.d.ts +1 -99
- package/dist/shared/src/secrets.js +1 -370
- package/dist/shared/src/secrets.js.map +1 -1
- package/dist/shared/src/storage.d.ts +1 -341
- package/dist/shared/src/storage.js +1 -1003
- package/dist/shared/src/storage.js.map +1 -1
- package/dist/shared/src/string.d.ts +1 -1
- package/dist/shared/src/string.js +1 -29
- package/dist/shared/src/string.js.map +1 -1
- package/dist/shared/src/time.d.ts +1 -5
- package/dist/shared/src/time.js +1 -34
- package/dist/shared/src/time.js.map +1 -1
- package/dist/shared/src/tls.d.ts +1 -87
- package/dist/shared/src/tls.js +1 -153
- package/dist/shared/src/tls.js.map +1 -1
- package/dist/shared/src/tokenizer.d.ts +1 -54
- package/dist/shared/src/tokenizer.js +1 -271
- package/dist/shared/src/tokenizer.js.map +1 -1
- package/dist/shared/src/types.d.ts +1 -27
- package/dist/shared/src/types.js +1 -16
- package/dist/shared/src/types.js.map +1 -1
- package/dist/shared/src/worker-pool.d.ts +1 -52
- package/dist/shared/src/worker-pool.js +1 -228
- package/dist/shared/src/worker-pool.js.map +1 -1
- package/dist/shared/src/worker-thread.d.ts +1 -21
- package/dist/shared/src/worker-thread.js +1 -66
- package/dist/shared/src/worker-thread.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,179 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* OS-level subprocess isolation via uid/gid (no Docker required).
|
|
3
|
-
*
|
|
4
|
-
* On Unix, child_process.spawn accepts `uid` and `gid`. When configured,
|
|
5
|
-
* agent-triggered shell commands run as a restricted system user so that
|
|
6
|
-
* even a hallucinated destructive command hits EACCES / Permission Denied
|
|
7
|
-
* on protected paths.
|
|
8
|
-
*
|
|
9
|
-
* Windows: uid/gid are ignored; isolation is a no-op (allow-list still applies).
|
|
10
|
-
*
|
|
11
|
-
* Configuration (any of):
|
|
12
|
-
* ATABEY_SANDBOX_UID=501
|
|
13
|
-
* ATABEY_SANDBOX_GID=20
|
|
14
|
-
* ATABEY_SANDBOX_USER=atabey-sandbox (resolved via `id -u` / `id -g` when possible)
|
|
15
|
-
* ATABEY_SANDBOX_ENABLED=true|false (default: true when uid/gid resolvable)
|
|
16
|
-
*/
|
|
17
|
-
import { spawn } from "child_process";
|
|
18
|
-
import { execFileSync } from "child_process";
|
|
19
|
-
import os from "os";
|
|
20
|
-
let cachedIdentity = null;
|
|
21
|
-
function parseUint(value) {
|
|
22
|
-
if (value === undefined || value === "")
|
|
23
|
-
return undefined;
|
|
24
|
-
const n = Number.parseInt(value, 10);
|
|
25
|
-
if (!Number.isFinite(n) || n < 0)
|
|
26
|
-
return undefined;
|
|
27
|
-
return n;
|
|
28
|
-
}
|
|
29
|
-
function resolveUserIds(username) {
|
|
30
|
-
if (process.platform === "win32")
|
|
31
|
-
return {};
|
|
32
|
-
try {
|
|
33
|
-
const uidRaw = execFileSync("id", ["-u", username], {
|
|
34
|
-
encoding: "utf8",
|
|
35
|
-
timeout: 2000,
|
|
36
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
37
|
-
}).trim();
|
|
38
|
-
const gidRaw = execFileSync("id", ["-g", username], {
|
|
39
|
-
encoding: "utf8",
|
|
40
|
-
timeout: 2000,
|
|
41
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
42
|
-
}).trim();
|
|
43
|
-
return {
|
|
44
|
-
uid: parseUint(uidRaw),
|
|
45
|
-
gid: parseUint(gidRaw),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return {};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Resolve sandbox identity from env (cached).
|
|
54
|
-
* Call `clearSandboxIdentityCache()` in tests after env changes.
|
|
55
|
-
*/
|
|
56
|
-
export function resolveSandboxIdentity() {
|
|
57
|
-
if (cachedIdentity)
|
|
58
|
-
return cachedIdentity;
|
|
59
|
-
if (process.platform === "win32") {
|
|
60
|
-
cachedIdentity = {
|
|
61
|
-
enabled: false,
|
|
62
|
-
reason: "uid/gid isolation is not available on Windows",
|
|
63
|
-
};
|
|
64
|
-
return cachedIdentity;
|
|
65
|
-
}
|
|
66
|
-
const explicitOff = process.env.ATABEY_SANDBOX_ENABLED === "false" ||
|
|
67
|
-
process.env.ATABEY_SANDBOX_ENABLED === "0";
|
|
68
|
-
if (explicitOff) {
|
|
69
|
-
cachedIdentity = { enabled: false, reason: "ATABEY_SANDBOX_ENABLED=false" };
|
|
70
|
-
return cachedIdentity;
|
|
71
|
-
}
|
|
72
|
-
let uid = parseUint(process.env.ATABEY_SANDBOX_UID);
|
|
73
|
-
let gid = parseUint(process.env.ATABEY_SANDBOX_GID);
|
|
74
|
-
const user = process.env.ATABEY_SANDBOX_USER?.trim() || undefined;
|
|
75
|
-
if (user && (uid === undefined || gid === undefined)) {
|
|
76
|
-
const resolved = resolveUserIds(user);
|
|
77
|
-
uid = uid ?? resolved.uid;
|
|
78
|
-
gid = gid ?? resolved.gid;
|
|
79
|
-
}
|
|
80
|
-
// Never drop privileges to root (0) — that would be a no-op or worse
|
|
81
|
-
if (uid === 0) {
|
|
82
|
-
cachedIdentity = {
|
|
83
|
-
enabled: false,
|
|
84
|
-
reason: "ATABEY_SANDBOX_UID=0 (root) is refused",
|
|
85
|
-
uid,
|
|
86
|
-
gid,
|
|
87
|
-
user,
|
|
88
|
-
};
|
|
89
|
-
return cachedIdentity;
|
|
90
|
-
}
|
|
91
|
-
if (uid === undefined) {
|
|
92
|
-
// Optional: if current process is already non-root and no sandbox user
|
|
93
|
-
// configured, we leave sandbox disabled rather than inventing a user.
|
|
94
|
-
cachedIdentity = {
|
|
95
|
-
enabled: false,
|
|
96
|
-
reason: "No ATABEY_SANDBOX_UID / ATABEY_SANDBOX_USER configured — shell runs as current user",
|
|
97
|
-
user,
|
|
98
|
-
};
|
|
99
|
-
return cachedIdentity;
|
|
100
|
-
}
|
|
101
|
-
// Dropping privileges only works if we are root (or have CAP_SETUID).
|
|
102
|
-
// If we are a normal user with a different target uid, spawn will fail —
|
|
103
|
-
// detect and warn via reason, still pass options (caller sees Permission error).
|
|
104
|
-
const canSetUid = typeof process.getuid === "function" && process.getuid() === 0;
|
|
105
|
-
if (!canSetUid && typeof process.getuid === "function" && process.getuid() !== uid) {
|
|
106
|
-
cachedIdentity = {
|
|
107
|
-
enabled: false,
|
|
108
|
-
uid,
|
|
109
|
-
gid,
|
|
110
|
-
user,
|
|
111
|
-
reason: `Sandbox uid=${uid} requested but process uid=${process.getuid()} is not root; ` +
|
|
112
|
-
`setuid will fail. Run MCP as root with a dedicated sandbox user, or unset sandbox env.`,
|
|
113
|
-
};
|
|
114
|
-
return cachedIdentity;
|
|
115
|
-
}
|
|
116
|
-
cachedIdentity = {
|
|
117
|
-
enabled: true,
|
|
118
|
-
uid,
|
|
119
|
-
gid,
|
|
120
|
-
user,
|
|
121
|
-
};
|
|
122
|
-
return cachedIdentity;
|
|
123
|
-
}
|
|
124
|
-
export function clearSandboxIdentityCache() {
|
|
125
|
-
cachedIdentity = null;
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Build spawn options with uid/gid when sandbox is active.
|
|
129
|
-
*/
|
|
130
|
-
export function applySandboxToSpawnOptions(options = {}) {
|
|
131
|
-
const { disableSandbox, ...rest } = options;
|
|
132
|
-
if (disableSandbox)
|
|
133
|
-
return { ...rest };
|
|
134
|
-
const identity = resolveSandboxIdentity();
|
|
135
|
-
if (!identity.enabled || identity.uid === undefined) {
|
|
136
|
-
return { ...rest };
|
|
137
|
-
}
|
|
138
|
-
return {
|
|
139
|
-
...rest,
|
|
140
|
-
uid: identity.uid,
|
|
141
|
-
...(identity.gid !== undefined ? { gid: identity.gid } : {}),
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* spawn() wrapper that applies OS sandbox identity when configured.
|
|
146
|
-
*/
|
|
147
|
-
export function sandboxSpawn(command, args, options = {}) {
|
|
148
|
-
const opts = applySandboxToSpawnOptions(options);
|
|
149
|
-
return spawn(command, args, opts);
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Human-readable status for CLI diagnostics.
|
|
153
|
-
*/
|
|
154
|
-
export function getSandboxStatus() {
|
|
155
|
-
return {
|
|
156
|
-
platform: process.platform,
|
|
157
|
-
identity: resolveSandboxIdentity(),
|
|
158
|
-
processUid: typeof process.getuid === "function" ? process.getuid() : null,
|
|
159
|
-
processGid: typeof process.getgid === "function" ? process.getgid() : null,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
/** Default restricted paths agents should not touch even with shell allow-list. */
|
|
163
|
-
export const SANDBOX_PROTECTED_HINTS = [
|
|
164
|
-
"/etc",
|
|
165
|
-
"/usr",
|
|
166
|
-
"/bin",
|
|
167
|
-
"/sbin",
|
|
168
|
-
"/System",
|
|
169
|
-
pathHomeDotSsh(),
|
|
170
|
-
];
|
|
171
|
-
function pathHomeDotSsh() {
|
|
172
|
-
try {
|
|
173
|
-
return `${os.homedir()}/.ssh`;
|
|
174
|
-
}
|
|
175
|
-
catch {
|
|
176
|
-
return "~/.ssh";
|
|
177
|
-
}
|
|
178
|
-
}
|
|
1
|
+
export * from "./modules/sandbox/sandbox.js";
|
|
179
2
|
//# sourceMappingURL=sandbox.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../../../shared/src/sandbox.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../../../../shared/src/sandbox.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC"}
|
|
@@ -1,99 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Secret Management & Leak Guard (Phase 1.5)
|
|
3
|
-
*
|
|
4
|
-
* Provides:
|
|
5
|
-
* 1. Encrypted local secret vault (.atabey/secrets.vault.json) using AES-256-GCM
|
|
6
|
-
* with per-secret random salt (HKDF-SHA256) and 96-bit IV.
|
|
7
|
-
* 2. Subprocess environment scrubber (strips sensitive keys before spawning child processes).
|
|
8
|
-
* 3. Secret leak scanner (detects API keys, cloud credentials, private keys, and vault values).
|
|
9
|
-
*/
|
|
10
|
-
export interface VaultEntry {
|
|
11
|
-
/** Base64-encoded 16-byte HKDF salt */
|
|
12
|
-
salt: string;
|
|
13
|
-
/** Base64-encoded 12-byte GCM initialization vector */
|
|
14
|
-
iv: string;
|
|
15
|
-
/** Base64-encoded 16-byte GCM authentication tag */
|
|
16
|
-
tag: string;
|
|
17
|
-
/** Base64-encoded ciphertext */
|
|
18
|
-
ciphertext: string;
|
|
19
|
-
/** ISO-8601 creation timestamp */
|
|
20
|
-
createdAt: string;
|
|
21
|
-
/** ISO-8601 last update timestamp */
|
|
22
|
-
updatedAt: string;
|
|
23
|
-
/** Optional non-sensitive description */
|
|
24
|
-
description?: string;
|
|
25
|
-
}
|
|
26
|
-
export interface VaultFile {
|
|
27
|
-
version: 1;
|
|
28
|
-
entries: Record<string, VaultEntry>;
|
|
29
|
-
}
|
|
30
|
-
export interface SecretMetadata {
|
|
31
|
-
name: string;
|
|
32
|
-
createdAt: string;
|
|
33
|
-
updatedAt: string;
|
|
34
|
-
description?: string;
|
|
35
|
-
}
|
|
36
|
-
export interface SecretFinding {
|
|
37
|
-
ruleId: string;
|
|
38
|
-
description: string;
|
|
39
|
-
matchPreview: string;
|
|
40
|
-
index: number;
|
|
41
|
-
}
|
|
42
|
-
export declare class SecretManager {
|
|
43
|
-
private static resolveVaultDir;
|
|
44
|
-
private static resolveMasterKey;
|
|
45
|
-
private static vaultFilePath;
|
|
46
|
-
private static readVault;
|
|
47
|
-
private static writeVault;
|
|
48
|
-
/**
|
|
49
|
-
* Encrypt and store a secret in the local AES-256-GCM vault.
|
|
50
|
-
*/
|
|
51
|
-
static setSecret(name: string, value: string, options?: {
|
|
52
|
-
description?: string;
|
|
53
|
-
vaultDir?: string;
|
|
54
|
-
}): void;
|
|
55
|
-
/**
|
|
56
|
-
* Retrieve and decrypt a secret by name.
|
|
57
|
-
* Checks process.env[name] fallback only when allowEnvFallback is true.
|
|
58
|
-
*/
|
|
59
|
-
static getSecret(name: string, options?: {
|
|
60
|
-
vaultDir?: string;
|
|
61
|
-
allowEnvFallback?: boolean;
|
|
62
|
-
}): string | null;
|
|
63
|
-
/**
|
|
64
|
-
* Delete a secret from the vault.
|
|
65
|
-
*/
|
|
66
|
-
static deleteSecret(name: string, options?: {
|
|
67
|
-
vaultDir?: string;
|
|
68
|
-
}): boolean;
|
|
69
|
-
/**
|
|
70
|
-
* List stored secret metadata without exposing plaintext values.
|
|
71
|
-
*/
|
|
72
|
-
static listSecrets(options?: {
|
|
73
|
-
vaultDir?: string;
|
|
74
|
-
}): SecretMetadata[];
|
|
75
|
-
/**
|
|
76
|
-
* Re-encrypt all vault entries under a new 32-byte master key.
|
|
77
|
-
*
|
|
78
|
-
* Safe ordering: decrypt everything first, build the new vault in memory,
|
|
79
|
-
* then atomically swap. The previous key is kept as `master.key.bak` for
|
|
80
|
-
* the duration of the swap and restored on failure, so a mid-rotation error
|
|
81
|
-
* cannot make the vault permanently undecryptable.
|
|
82
|
-
*/
|
|
83
|
-
static rotateMasterKey(newKeyMaterial: string | Buffer, options?: {
|
|
84
|
-
vaultDir?: string;
|
|
85
|
-
}): number;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Scrub sensitive credentials from an environment object before passing to a child process.
|
|
89
|
-
*/
|
|
90
|
-
export declare function scrubEnvForSubprocess(env?: NodeJS.ProcessEnv, extraAllowedKeys?: string[]): NodeJS.ProcessEnv;
|
|
91
|
-
/**
|
|
92
|
-
* Scan arbitrary text (command output, file content, tool arguments) for leaked secrets.
|
|
93
|
-
*/
|
|
94
|
-
export declare function scanForSecrets(text: string): SecretFinding[];
|
|
95
|
-
/**
|
|
96
|
-
* Redact leaked secrets from text before it is returned to an AI/LLM.
|
|
97
|
-
* Complements PII masking with cloud-credential/private-key patterns.
|
|
98
|
-
*/
|
|
99
|
-
export declare function redactSecrets(text: string): string;
|
|
1
|
+
export * from "./modules/security/secrets.js";
|
|
@@ -1,371 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
* Secret Management & Leak Guard (Phase 1.5)
|
|
3
|
-
*
|
|
4
|
-
* Provides:
|
|
5
|
-
* 1. Encrypted local secret vault (.atabey/secrets.vault.json) using AES-256-GCM
|
|
6
|
-
* with per-secret random salt (HKDF-SHA256) and 96-bit IV.
|
|
7
|
-
* 2. Subprocess environment scrubber (strips sensitive keys before spawning child processes).
|
|
8
|
-
* 3. Secret leak scanner (detects API keys, cloud credentials, private keys, and vault values).
|
|
9
|
-
*/
|
|
10
|
-
import crypto from "crypto";
|
|
11
|
-
import fs from "fs";
|
|
12
|
-
import path from "path";
|
|
13
|
-
import { resolveFrameworkDir } from "./storage.js";
|
|
14
|
-
/** Env var names or patterns that must never leak into untrusted subprocesses. */
|
|
15
|
-
const SENSITIVE_ENV_PATTERNS = [
|
|
16
|
-
/_KEY$/i,
|
|
17
|
-
/_KEYS$/i,
|
|
18
|
-
/_SECRET/i,
|
|
19
|
-
/_TOKEN$/i,
|
|
20
|
-
/_PASSWORD$/i,
|
|
21
|
-
/_PASSWD$/i,
|
|
22
|
-
/(^|_)PWD$/i,
|
|
23
|
-
/^PGPASSWORD$/i,
|
|
24
|
-
/_CREDENTIAL/i,
|
|
25
|
-
/_PRIVATE/i,
|
|
26
|
-
/_PASSPHRASE$/i,
|
|
27
|
-
/ACCESS_KEY/i,
|
|
28
|
-
/_APIKEY$/i,
|
|
29
|
-
/_DSN$/i,
|
|
30
|
-
/(^|_)AUTH$/i,
|
|
31
|
-
/_SALT$/i,
|
|
32
|
-
/(^|_)SECRETKEY/i,
|
|
33
|
-
/^ATABEY_AUDIT_HMAC_KEY$/i,
|
|
34
|
-
/^ATABEY_MASTER_KEY$/i,
|
|
35
|
-
/^MCP_AUTH_TOKEN$/i,
|
|
36
|
-
/^MCP_AUTH_USERS$/i,
|
|
37
|
-
/^DATABASE_URL$/i,
|
|
38
|
-
/^REDIS_URL$/i,
|
|
39
|
-
/^OPENAI_API_KEY$/i,
|
|
40
|
-
/^ANTHROPIC_API_KEY$/i,
|
|
41
|
-
/^GEMINI_API_KEY$/i,
|
|
42
|
-
/^GOOGLE_API_KEY$/i,
|
|
43
|
-
/^AWS_SECRET_ACCESS_KEY$/i,
|
|
44
|
-
/^AWS_SESSION_TOKEN$/i,
|
|
45
|
-
/^GITHUB_TOKEN$/i,
|
|
46
|
-
/^GH_TOKEN$/i,
|
|
47
|
-
/^GITLAB_TOKEN$/i,
|
|
48
|
-
/^NPM_TOKEN$/i,
|
|
49
|
-
/^SLACK_BOT_TOKEN$/i,
|
|
50
|
-
/^STRIPE_SECRET_KEY$/i,
|
|
51
|
-
];
|
|
52
|
-
const SECRET_LEAK_PATTERNS = [
|
|
53
|
-
{
|
|
54
|
-
id: "aws-access-key",
|
|
55
|
-
description: "AWS Access Key ID",
|
|
56
|
-
regex: /\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b/g,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
id: "github-pat",
|
|
60
|
-
description: "GitHub Personal Access Token",
|
|
61
|
-
regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,255}\b|\bgithub_pat_[A-Za-z0-9_]{80,255}\b/g,
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
id: "openai-anthropic-key",
|
|
65
|
-
description: "OpenAI / Anthropic API Key",
|
|
66
|
-
regex: /\bsk-(?:proj-|ant-api\d{2}-|live-|test-)?[A-Za-z0-9_-]{20,200}\b/g,
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
id: "google-api-key",
|
|
70
|
-
description: "Google Cloud / Gemini API Key",
|
|
71
|
-
regex: /\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
id: "slack-token",
|
|
75
|
-
description: "Slack API Token",
|
|
76
|
-
regex: /\bxox[baprs]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*\b/g,
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
id: "private-key-pem",
|
|
80
|
-
description: "PEM Private Key Block",
|
|
81
|
-
regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY-----/g,
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
id: "stripe-secret",
|
|
85
|
-
description: "Stripe Live/Test Secret Key",
|
|
86
|
-
regex: /\b(?:sk|rk)_(?:live|test)_[0-9a-zA-Z]{24,99}\b/g,
|
|
87
|
-
},
|
|
88
|
-
];
|
|
89
|
-
function deriveSubkey(masterKey, salt) {
|
|
90
|
-
return Buffer.from(crypto.hkdfSync("sha256", masterKey, salt, Buffer.from("atabey-vault-v1", "utf8"), 32));
|
|
91
|
-
}
|
|
92
|
-
function redactPreview(match) {
|
|
93
|
-
if (match.length <= 8)
|
|
94
|
-
return "***";
|
|
95
|
-
return `${match.slice(0, 4)}...${match.slice(-3)}`;
|
|
96
|
-
}
|
|
97
|
-
export class SecretManager {
|
|
98
|
-
static resolveVaultDir(customDir) {
|
|
99
|
-
if (customDir)
|
|
100
|
-
return customDir;
|
|
101
|
-
const envDir = process.env.ATABEY_TEST_DIR || process.env.ATABEY_FRAMEWORK_DIR;
|
|
102
|
-
if (envDir && envDir.trim())
|
|
103
|
-
return envDir.trim();
|
|
104
|
-
return resolveFrameworkDir();
|
|
105
|
-
}
|
|
106
|
-
static resolveMasterKey(vaultDir) {
|
|
107
|
-
const envKey = process.env.ATABEY_MASTER_KEY?.trim();
|
|
108
|
-
if (envKey) {
|
|
109
|
-
return crypto.createHash("sha256").update(envKey, "utf8").digest();
|
|
110
|
-
}
|
|
111
|
-
const keyPath = path.join(vaultDir, "master.key");
|
|
112
|
-
if (fs.existsSync(keyPath)) {
|
|
113
|
-
const raw = fs.readFileSync(keyPath);
|
|
114
|
-
if (raw.length === 32)
|
|
115
|
-
return raw;
|
|
116
|
-
return crypto.createHash("sha256").update(raw).digest();
|
|
117
|
-
}
|
|
118
|
-
fs.mkdirSync(vaultDir, { recursive: true });
|
|
119
|
-
const generated = crypto.randomBytes(32);
|
|
120
|
-
fs.writeFileSync(keyPath, generated, { mode: 0o600 });
|
|
121
|
-
return generated;
|
|
122
|
-
}
|
|
123
|
-
static vaultFilePath(vaultDir) {
|
|
124
|
-
return path.join(vaultDir, "secrets.vault.json");
|
|
125
|
-
}
|
|
126
|
-
static readVault(vaultDir) {
|
|
127
|
-
const filePath = this.vaultFilePath(vaultDir);
|
|
128
|
-
if (!fs.existsSync(filePath)) {
|
|
129
|
-
return { version: 1, entries: {} };
|
|
130
|
-
}
|
|
131
|
-
const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
132
|
-
if (!raw || raw.version !== 1 || typeof raw.entries !== "object" || raw.entries === null) {
|
|
133
|
-
throw new Error("Invalid or unsupported secrets.vault.json format.");
|
|
134
|
-
}
|
|
135
|
-
return raw;
|
|
136
|
-
}
|
|
137
|
-
static writeVault(vaultDir, vault) {
|
|
138
|
-
fs.mkdirSync(vaultDir, { recursive: true });
|
|
139
|
-
const filePath = this.vaultFilePath(vaultDir);
|
|
140
|
-
const tmpPath = `${filePath}.tmp.${process.pid}`;
|
|
141
|
-
fs.writeFileSync(tmpPath, JSON.stringify(vault, null, 2), { mode: 0o600 });
|
|
142
|
-
fs.renameSync(tmpPath, filePath);
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Encrypt and store a secret in the local AES-256-GCM vault.
|
|
146
|
-
*/
|
|
147
|
-
static setSecret(name, value, options = {}) {
|
|
148
|
-
const keyName = name.trim();
|
|
149
|
-
if (!keyName || !/^[A-Za-z_][A-Za-z0-9_.-]{0,127}$/.test(keyName)) {
|
|
150
|
-
throw new Error(`Invalid secret name "${name}". Use alphanumeric, '_', '.', or '-' characters.`);
|
|
151
|
-
}
|
|
152
|
-
const vaultDir = this.resolveVaultDir(options.vaultDir);
|
|
153
|
-
const masterKey = this.resolveMasterKey(vaultDir);
|
|
154
|
-
const salt = crypto.randomBytes(16);
|
|
155
|
-
const iv = crypto.randomBytes(12);
|
|
156
|
-
const subkey = deriveSubkey(masterKey, salt);
|
|
157
|
-
const cipher = crypto.createCipheriv("aes-256-gcm", subkey, iv);
|
|
158
|
-
cipher.setAAD(Buffer.from(keyName, "utf8"));
|
|
159
|
-
const encrypted = Buffer.concat([cipher.update(value, "utf8"), cipher.final()]);
|
|
160
|
-
const tag = cipher.getAuthTag();
|
|
161
|
-
const vault = this.readVault(vaultDir);
|
|
162
|
-
const now = new Date().toISOString();
|
|
163
|
-
const existing = vault.entries[keyName];
|
|
164
|
-
vault.entries[keyName] = {
|
|
165
|
-
salt: salt.toString("base64"),
|
|
166
|
-
iv: iv.toString("base64"),
|
|
167
|
-
tag: tag.toString("base64"),
|
|
168
|
-
ciphertext: encrypted.toString("base64"),
|
|
169
|
-
createdAt: existing?.createdAt ?? now,
|
|
170
|
-
updatedAt: now,
|
|
171
|
-
description: options.description ?? existing?.description,
|
|
172
|
-
};
|
|
173
|
-
this.writeVault(vaultDir, vault);
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Retrieve and decrypt a secret by name.
|
|
177
|
-
* Checks process.env[name] fallback only when allowEnvFallback is true.
|
|
178
|
-
*/
|
|
179
|
-
static getSecret(name, options = {}) {
|
|
180
|
-
const keyName = name.trim();
|
|
181
|
-
const vaultDir = this.resolveVaultDir(options.vaultDir);
|
|
182
|
-
const vault = this.readVault(vaultDir);
|
|
183
|
-
const entry = vault.entries[keyName];
|
|
184
|
-
if (!entry) {
|
|
185
|
-
if (options.allowEnvFallback && process.env[keyName] !== undefined) {
|
|
186
|
-
return process.env[keyName] ?? null;
|
|
187
|
-
}
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
const masterKey = this.resolveMasterKey(vaultDir);
|
|
191
|
-
const salt = Buffer.from(entry.salt, "base64");
|
|
192
|
-
const iv = Buffer.from(entry.iv, "base64");
|
|
193
|
-
const tag = Buffer.from(entry.tag, "base64");
|
|
194
|
-
const ciphertext = Buffer.from(entry.ciphertext, "base64");
|
|
195
|
-
const subkey = deriveSubkey(masterKey, salt);
|
|
196
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", subkey, iv);
|
|
197
|
-
decipher.setAAD(Buffer.from(keyName, "utf8"));
|
|
198
|
-
decipher.setAuthTag(tag);
|
|
199
|
-
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
200
|
-
return decrypted.toString("utf8");
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Delete a secret from the vault.
|
|
204
|
-
*/
|
|
205
|
-
static deleteSecret(name, options = {}) {
|
|
206
|
-
const keyName = name.trim();
|
|
207
|
-
const vaultDir = this.resolveVaultDir(options.vaultDir);
|
|
208
|
-
const vault = this.readVault(vaultDir);
|
|
209
|
-
if (!(keyName in vault.entries))
|
|
210
|
-
return false;
|
|
211
|
-
delete vault.entries[keyName];
|
|
212
|
-
this.writeVault(vaultDir, vault);
|
|
213
|
-
return true;
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* List stored secret metadata without exposing plaintext values.
|
|
217
|
-
*/
|
|
218
|
-
static listSecrets(options = {}) {
|
|
219
|
-
const vaultDir = this.resolveVaultDir(options.vaultDir);
|
|
220
|
-
const vault = this.readVault(vaultDir);
|
|
221
|
-
return Object.entries(vault.entries).map(([name, entry]) => ({
|
|
222
|
-
name,
|
|
223
|
-
createdAt: entry.createdAt,
|
|
224
|
-
updatedAt: entry.updatedAt,
|
|
225
|
-
description: entry.description,
|
|
226
|
-
}));
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Re-encrypt all vault entries under a new 32-byte master key.
|
|
230
|
-
*
|
|
231
|
-
* Safe ordering: decrypt everything first, build the new vault in memory,
|
|
232
|
-
* then atomically swap. The previous key is kept as `master.key.bak` for
|
|
233
|
-
* the duration of the swap and restored on failure, so a mid-rotation error
|
|
234
|
-
* cannot make the vault permanently undecryptable.
|
|
235
|
-
*/
|
|
236
|
-
static rotateMasterKey(newKeyMaterial, options = {}) {
|
|
237
|
-
const vaultDir = this.resolveVaultDir(options.vaultDir);
|
|
238
|
-
if (process.env.ATABEY_MASTER_KEY?.trim()) {
|
|
239
|
-
throw new Error("[VAULT] ATABEY_MASTER_KEY is authoritative; file-based rotation is disabled while it is set. " +
|
|
240
|
-
"Rotate by changing ATABEY_MASTER_KEY and re-running `atabey secrets set` for each secret.");
|
|
241
|
-
}
|
|
242
|
-
const vault = this.readVault(vaultDir);
|
|
243
|
-
const names = Object.keys(vault.entries);
|
|
244
|
-
if (names.length === 0)
|
|
245
|
-
return 0;
|
|
246
|
-
// 1. Decrypt everything with the CURRENT key (no writes yet).
|
|
247
|
-
const decrypted = names.map((name) => {
|
|
248
|
-
const value = this.getSecret(name, { vaultDir });
|
|
249
|
-
if (value === null) {
|
|
250
|
-
throw new Error(`Failed to decrypt secret "${name}" during key rotation.`);
|
|
251
|
-
}
|
|
252
|
-
return {
|
|
253
|
-
name,
|
|
254
|
-
value,
|
|
255
|
-
description: vault.entries[name].description,
|
|
256
|
-
createdAt: vault.entries[name].createdAt,
|
|
257
|
-
};
|
|
258
|
-
});
|
|
259
|
-
const newMasterKey = typeof newKeyMaterial === "string"
|
|
260
|
-
? crypto.createHash("sha256").update(newKeyMaterial, "utf8").digest()
|
|
261
|
-
: newKeyMaterial.length === 32
|
|
262
|
-
? Buffer.from(newKeyMaterial)
|
|
263
|
-
: crypto.createHash("sha256").update(newKeyMaterial).digest();
|
|
264
|
-
// 2. Build the new vault in memory, encrypted with the new key.
|
|
265
|
-
const newEntries = {};
|
|
266
|
-
const now = new Date().toISOString();
|
|
267
|
-
for (const item of decrypted) {
|
|
268
|
-
const salt = crypto.randomBytes(16);
|
|
269
|
-
const iv = crypto.randomBytes(12);
|
|
270
|
-
const subkey = deriveSubkey(newMasterKey, salt);
|
|
271
|
-
const cipher = crypto.createCipheriv("aes-256-gcm", subkey, iv);
|
|
272
|
-
cipher.setAAD(Buffer.from(item.name, "utf8"));
|
|
273
|
-
const encrypted = Buffer.concat([cipher.update(item.value, "utf8"), cipher.final()]);
|
|
274
|
-
newEntries[item.name] = {
|
|
275
|
-
salt: salt.toString("base64"),
|
|
276
|
-
iv: iv.toString("base64"),
|
|
277
|
-
tag: cipher.getAuthTag().toString("base64"),
|
|
278
|
-
ciphertext: encrypted.toString("base64"),
|
|
279
|
-
createdAt: item.createdAt,
|
|
280
|
-
updatedAt: now,
|
|
281
|
-
description: item.description,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
// 3. Atomically swap key + vault with rollback protection.
|
|
285
|
-
fs.mkdirSync(vaultDir, { recursive: true });
|
|
286
|
-
const keyPath = path.join(vaultDir, "master.key");
|
|
287
|
-
const backupPath = `${keyPath}.bak`;
|
|
288
|
-
const hadKey = fs.existsSync(keyPath);
|
|
289
|
-
const oldKey = hadKey ? fs.readFileSync(keyPath) : null;
|
|
290
|
-
try {
|
|
291
|
-
if (oldKey)
|
|
292
|
-
fs.writeFileSync(backupPath, oldKey, { mode: 0o600 });
|
|
293
|
-
fs.writeFileSync(keyPath, newMasterKey, { mode: 0o600 });
|
|
294
|
-
this.writeVault(vaultDir, { version: 1, entries: newEntries });
|
|
295
|
-
if (fs.existsSync(backupPath))
|
|
296
|
-
fs.unlinkSync(backupPath);
|
|
297
|
-
}
|
|
298
|
-
catch (err) {
|
|
299
|
-
// Restore the previous key so the (unchanged) old vault stays readable.
|
|
300
|
-
try {
|
|
301
|
-
if (oldKey)
|
|
302
|
-
fs.writeFileSync(keyPath, oldKey, { mode: 0o600 });
|
|
303
|
-
else if (fs.existsSync(keyPath))
|
|
304
|
-
fs.unlinkSync(keyPath);
|
|
305
|
-
if (fs.existsSync(backupPath))
|
|
306
|
-
fs.unlinkSync(backupPath);
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
/* best-effort rollback */
|
|
310
|
-
}
|
|
311
|
-
throw err;
|
|
312
|
-
}
|
|
313
|
-
return decrypted.length;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Scrub sensitive credentials from an environment object before passing to a child process.
|
|
318
|
-
*/
|
|
319
|
-
export function scrubEnvForSubprocess(env = process.env, extraAllowedKeys = []) {
|
|
320
|
-
const allowed = new Set(extraAllowedKeys.map((k) => k.toUpperCase()));
|
|
321
|
-
const clean = {};
|
|
322
|
-
for (const [key, value] of Object.entries(env)) {
|
|
323
|
-
if (value === undefined)
|
|
324
|
-
continue;
|
|
325
|
-
if (allowed.has(key.toUpperCase())) {
|
|
326
|
-
clean[key] = value;
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
const isSensitive = SENSITIVE_ENV_PATTERNS.some((re) => re.test(key));
|
|
330
|
-
if (!isSensitive) {
|
|
331
|
-
clean[key] = value;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return clean;
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Scan arbitrary text (command output, file content, tool arguments) for leaked secrets.
|
|
338
|
-
*/
|
|
339
|
-
export function scanForSecrets(text) {
|
|
340
|
-
if (!text)
|
|
341
|
-
return [];
|
|
342
|
-
const findings = [];
|
|
343
|
-
for (const pattern of SECRET_LEAK_PATTERNS) {
|
|
344
|
-
pattern.regex.lastIndex = 0;
|
|
345
|
-
let match;
|
|
346
|
-
while ((match = pattern.regex.exec(text)) !== null) {
|
|
347
|
-
findings.push({
|
|
348
|
-
ruleId: pattern.id,
|
|
349
|
-
description: pattern.description,
|
|
350
|
-
matchPreview: redactPreview(match[0]),
|
|
351
|
-
index: match.index,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return findings;
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* Redact leaked secrets from text before it is returned to an AI/LLM.
|
|
359
|
-
* Complements PII masking with cloud-credential/private-key patterns.
|
|
360
|
-
*/
|
|
361
|
-
export function redactSecrets(text) {
|
|
362
|
-
if (!text)
|
|
363
|
-
return text;
|
|
364
|
-
let out = text;
|
|
365
|
-
for (const pattern of SECRET_LEAK_PATTERNS) {
|
|
366
|
-
const flags = pattern.regex.flags.includes("g") ? pattern.regex.flags : `${pattern.regex.flags}g`;
|
|
367
|
-
out = out.replace(new RegExp(pattern.regex.source, flags), `[REDACTED:${pattern.id}]`);
|
|
368
|
-
}
|
|
369
|
-
return out;
|
|
370
|
-
}
|
|
1
|
+
export * from "./modules/security/secrets.js";
|
|
371
2
|
//# sourceMappingURL=secrets.js.map
|