localant 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/LICENSE +21 -0
- package/README.md +290 -0
- package/SECURITY.md +87 -0
- package/examples/skills/hello-world/CHANGELOG.md +4 -0
- package/examples/skills/hello-world/LICENSE +1 -0
- package/examples/skills/hello-world/README.md +20 -0
- package/examples/skills/hello-world/examples/example.json +1 -0
- package/examples/skills/hello-world/package.json +9 -0
- package/examples/skills/hello-world/skill.json +32 -0
- package/examples/skills/hello-world/src/index.ts +19 -0
- package/examples/skills/hello-world/tests/index.test.ts +19 -0
- package/package.json +63 -0
- package/packages/cli/dist/bin.d.ts +3 -0
- package/packages/cli/dist/bin.d.ts.map +1 -0
- package/packages/cli/dist/bin.js +261 -0
- package/packages/cli/dist/bin.js.map +1 -0
- package/packages/cli/dist/doctor.d.ts +3 -0
- package/packages/cli/dist/doctor.d.ts.map +1 -0
- package/packages/cli/dist/doctor.js +35 -0
- package/packages/cli/dist/doctor.js.map +1 -0
- package/packages/cli/dist/index.d.ts +3 -0
- package/packages/cli/dist/index.d.ts.map +1 -0
- package/packages/cli/dist/index.js +3 -0
- package/packages/cli/dist/index.js.map +1 -0
- package/packages/cli/dist/runtime.d.ts +11 -0
- package/packages/cli/dist/runtime.d.ts.map +1 -0
- package/packages/cli/dist/runtime.js +82 -0
- package/packages/cli/dist/runtime.js.map +1 -0
- package/packages/cli/dist/util.d.ts +18 -0
- package/packages/cli/dist/util.d.ts.map +1 -0
- package/packages/cli/dist/util.js +47 -0
- package/packages/cli/dist/util.js.map +1 -0
- package/packages/cli/package.json +19 -0
- package/packages/dashboard/dist/index.d.ts +10 -0
- package/packages/dashboard/dist/index.d.ts.map +1 -0
- package/packages/dashboard/dist/index.js +178 -0
- package/packages/dashboard/dist/index.js.map +1 -0
- package/packages/dashboard/package.json +10 -0
- package/packages/gateway/dist/gateway.d.ts +85 -0
- package/packages/gateway/dist/gateway.d.ts.map +1 -0
- package/packages/gateway/dist/gateway.js +234 -0
- package/packages/gateway/dist/gateway.js.map +1 -0
- package/packages/gateway/dist/index.d.ts +13 -0
- package/packages/gateway/dist/index.d.ts.map +1 -0
- package/packages/gateway/dist/index.js +16 -0
- package/packages/gateway/dist/index.js.map +1 -0
- package/packages/gateway/dist/managers/coding-agent-manager.d.ts +71 -0
- package/packages/gateway/dist/managers/coding-agent-manager.d.ts.map +1 -0
- package/packages/gateway/dist/managers/coding-agent-manager.js +179 -0
- package/packages/gateway/dist/managers/coding-agent-manager.js.map +1 -0
- package/packages/gateway/dist/managers/fs-manager.d.ts +63 -0
- package/packages/gateway/dist/managers/fs-manager.d.ts.map +1 -0
- package/packages/gateway/dist/managers/fs-manager.js +229 -0
- package/packages/gateway/dist/managers/fs-manager.js.map +1 -0
- package/packages/gateway/dist/managers/git-manager.d.ts +21 -0
- package/packages/gateway/dist/managers/git-manager.d.ts.map +1 -0
- package/packages/gateway/dist/managers/git-manager.js +67 -0
- package/packages/gateway/dist/managers/git-manager.js.map +1 -0
- package/packages/gateway/dist/managers/mcp-bridge.d.ts +26 -0
- package/packages/gateway/dist/managers/mcp-bridge.d.ts.map +1 -0
- package/packages/gateway/dist/managers/mcp-bridge.js +92 -0
- package/packages/gateway/dist/managers/mcp-bridge.js.map +1 -0
- package/packages/gateway/dist/managers/project-registry.d.ts +17 -0
- package/packages/gateway/dist/managers/project-registry.d.ts.map +1 -0
- package/packages/gateway/dist/managers/project-registry.js +90 -0
- package/packages/gateway/dist/managers/project-registry.js.map +1 -0
- package/packages/gateway/dist/managers/shell-manager.d.ts +48 -0
- package/packages/gateway/dist/managers/shell-manager.d.ts.map +1 -0
- package/packages/gateway/dist/managers/shell-manager.js +132 -0
- package/packages/gateway/dist/managers/shell-manager.js.map +1 -0
- package/packages/gateway/dist/managers/skill-runtime.d.ts +37 -0
- package/packages/gateway/dist/managers/skill-runtime.d.ts.map +1 -0
- package/packages/gateway/dist/managers/skill-runtime.js +310 -0
- package/packages/gateway/dist/managers/skill-runtime.js.map +1 -0
- package/packages/gateway/dist/managers/tunnel-manager.d.ts +23 -0
- package/packages/gateway/dist/managers/tunnel-manager.d.ts.map +1 -0
- package/packages/gateway/dist/managers/tunnel-manager.js +106 -0
- package/packages/gateway/dist/managers/tunnel-manager.js.map +1 -0
- package/packages/gateway/dist/registry.d.ts +28 -0
- package/packages/gateway/dist/registry.d.ts.map +1 -0
- package/packages/gateway/dist/registry.js +20 -0
- package/packages/gateway/dist/registry.js.map +1 -0
- package/packages/gateway/dist/security/command-guard.d.ts +35 -0
- package/packages/gateway/dist/security/command-guard.d.ts.map +1 -0
- package/packages/gateway/dist/security/command-guard.js +105 -0
- package/packages/gateway/dist/security/command-guard.js.map +1 -0
- package/packages/gateway/dist/security/path-guard.d.ts +31 -0
- package/packages/gateway/dist/security/path-guard.d.ts.map +1 -0
- package/packages/gateway/dist/security/path-guard.js +101 -0
- package/packages/gateway/dist/security/path-guard.js.map +1 -0
- package/packages/gateway/dist/skill-runner.d.ts +2 -0
- package/packages/gateway/dist/skill-runner.d.ts.map +1 -0
- package/packages/gateway/dist/skill-runner.js +38 -0
- package/packages/gateway/dist/skill-runner.js.map +1 -0
- package/packages/gateway/dist/stores/approval-store.d.ts +34 -0
- package/packages/gateway/dist/stores/approval-store.d.ts.map +1 -0
- package/packages/gateway/dist/stores/approval-store.js +108 -0
- package/packages/gateway/dist/stores/approval-store.js.map +1 -0
- package/packages/gateway/dist/stores/audit-log.d.ts +23 -0
- package/packages/gateway/dist/stores/audit-log.d.ts.map +1 -0
- package/packages/gateway/dist/stores/audit-log.js +70 -0
- package/packages/gateway/dist/stores/audit-log.js.map +1 -0
- package/packages/gateway/dist/stores/config-store.d.ts +14 -0
- package/packages/gateway/dist/stores/config-store.d.ts.map +1 -0
- package/packages/gateway/dist/stores/config-store.js +57 -0
- package/packages/gateway/dist/stores/config-store.js.map +1 -0
- package/packages/gateway/dist/stores/secret-vault.d.ts +23 -0
- package/packages/gateway/dist/stores/secret-vault.d.ts.map +1 -0
- package/packages/gateway/dist/stores/secret-vault.js +74 -0
- package/packages/gateway/dist/stores/secret-vault.js.map +1 -0
- package/packages/gateway/dist/tools/adapters.d.ts +8 -0
- package/packages/gateway/dist/tools/adapters.d.ts.map +1 -0
- package/packages/gateway/dist/tools/adapters.js +178 -0
- package/packages/gateway/dist/tools/adapters.js.map +1 -0
- package/packages/gateway/dist/tools/adb.d.ts +3 -0
- package/packages/gateway/dist/tools/adb.d.ts.map +1 -0
- package/packages/gateway/dist/tools/adb.js +60 -0
- package/packages/gateway/dist/tools/adb.js.map +1 -0
- package/packages/gateway/dist/tools/article.d.ts +3 -0
- package/packages/gateway/dist/tools/article.d.ts.map +1 -0
- package/packages/gateway/dist/tools/article.js +230 -0
- package/packages/gateway/dist/tools/article.js.map +1 -0
- package/packages/gateway/dist/tools/audit-approval.d.ts +4 -0
- package/packages/gateway/dist/tools/audit-approval.d.ts.map +1 -0
- package/packages/gateway/dist/tools/audit-approval.js +64 -0
- package/packages/gateway/dist/tools/audit-approval.js.map +1 -0
- package/packages/gateway/dist/tools/browser.d.ts +3 -0
- package/packages/gateway/dist/tools/browser.d.ts.map +1 -0
- package/packages/gateway/dist/tools/browser.js +55 -0
- package/packages/gateway/dist/tools/browser.js.map +1 -0
- package/packages/gateway/dist/tools/coding-agent.d.ts +3 -0
- package/packages/gateway/dist/tools/coding-agent.d.ts.map +1 -0
- package/packages/gateway/dist/tools/coding-agent.js +103 -0
- package/packages/gateway/dist/tools/coding-agent.js.map +1 -0
- package/packages/gateway/dist/tools/filesystem.d.ts +3 -0
- package/packages/gateway/dist/tools/filesystem.d.ts.map +1 -0
- package/packages/gateway/dist/tools/filesystem.js +141 -0
- package/packages/gateway/dist/tools/filesystem.js.map +1 -0
- package/packages/gateway/dist/tools/git.d.ts +3 -0
- package/packages/gateway/dist/tools/git.d.ts.map +1 -0
- package/packages/gateway/dist/tools/git.js +92 -0
- package/packages/gateway/dist/tools/git.js.map +1 -0
- package/packages/gateway/dist/tools/index.d.ts +4 -0
- package/packages/gateway/dist/tools/index.d.ts.map +1 -0
- package/packages/gateway/dist/tools/index.js +29 -0
- package/packages/gateway/dist/tools/index.js.map +1 -0
- package/packages/gateway/dist/tools/project.d.ts +3 -0
- package/packages/gateway/dist/tools/project.d.ts.map +1 -0
- package/packages/gateway/dist/tools/project.js +86 -0
- package/packages/gateway/dist/tools/project.js.map +1 -0
- package/packages/gateway/dist/tools/shell.d.ts +3 -0
- package/packages/gateway/dist/tools/shell.d.ts.map +1 -0
- package/packages/gateway/dist/tools/shell.js +98 -0
- package/packages/gateway/dist/tools/shell.js.map +1 -0
- package/packages/gateway/dist/tools/skill.d.ts +3 -0
- package/packages/gateway/dist/tools/skill.d.ts.map +1 -0
- package/packages/gateway/dist/tools/skill.js +231 -0
- package/packages/gateway/dist/tools/skill.js.map +1 -0
- package/packages/gateway/dist/tools/system.d.ts +3 -0
- package/packages/gateway/dist/tools/system.d.ts.map +1 -0
- package/packages/gateway/dist/tools/system.js +78 -0
- package/packages/gateway/dist/tools/system.js.map +1 -0
- package/packages/gateway/dist/util/exec.d.ts +21 -0
- package/packages/gateway/dist/util/exec.d.ts.map +1 -0
- package/packages/gateway/dist/util/exec.js +50 -0
- package/packages/gateway/dist/util/exec.js.map +1 -0
- package/packages/gateway/package.json +18 -0
- package/packages/mcp/dist/http-server.d.ts +16 -0
- package/packages/mcp/dist/http-server.d.ts.map +1 -0
- package/packages/mcp/dist/http-server.js +138 -0
- package/packages/mcp/dist/http-server.js.map +1 -0
- package/packages/mcp/dist/index.d.ts +4 -0
- package/packages/mcp/dist/index.d.ts.map +1 -0
- package/packages/mcp/dist/index.js +3 -0
- package/packages/mcp/dist/index.js.map +1 -0
- package/packages/mcp/dist/mcp-server.d.ts +9 -0
- package/packages/mcp/dist/mcp-server.d.ts.map +1 -0
- package/packages/mcp/dist/mcp-server.js +26 -0
- package/packages/mcp/dist/mcp-server.js.map +1 -0
- package/packages/mcp/package.json +18 -0
- package/packages/shared/dist/config.d.ts +314 -0
- package/packages/shared/dist/config.d.ts.map +1 -0
- package/packages/shared/dist/config.js +146 -0
- package/packages/shared/dist/config.js.map +1 -0
- package/packages/shared/dist/index.d.ts +8 -0
- package/packages/shared/dist/index.d.ts.map +1 -0
- package/packages/shared/dist/index.js +8 -0
- package/packages/shared/dist/index.js.map +1 -0
- package/packages/shared/dist/logger.d.ts +8 -0
- package/packages/shared/dist/logger.d.ts.map +1 -0
- package/packages/shared/dist/logger.js +26 -0
- package/packages/shared/dist/logger.js.map +1 -0
- package/packages/shared/dist/net.d.ts +10 -0
- package/packages/shared/dist/net.d.ts.map +1 -0
- package/packages/shared/dist/net.js +35 -0
- package/packages/shared/dist/net.js.map +1 -0
- package/packages/shared/dist/paths.d.ts +30 -0
- package/packages/shared/dist/paths.d.ts.map +1 -0
- package/packages/shared/dist/paths.js +70 -0
- package/packages/shared/dist/paths.js.map +1 -0
- package/packages/shared/dist/redaction.d.ts +15 -0
- package/packages/shared/dist/redaction.d.ts.map +1 -0
- package/packages/shared/dist/redaction.js +58 -0
- package/packages/shared/dist/redaction.js.map +1 -0
- package/packages/shared/dist/risk.d.ts +23 -0
- package/packages/shared/dist/risk.d.ts.map +1 -0
- package/packages/shared/dist/risk.js +28 -0
- package/packages/shared/dist/risk.js.map +1 -0
- package/packages/shared/dist/types.d.ts +94 -0
- package/packages/shared/dist/types.d.ts.map +1 -0
- package/packages/shared/dist/types.js +2 -0
- package/packages/shared/dist/types.js.map +1 -0
- package/packages/shared/package.json +13 -0
- package/packages/skill-sdk/dist/index.d.ts +36 -0
- package/packages/skill-sdk/dist/index.d.ts.map +1 -0
- package/packages/skill-sdk/dist/index.js +20 -0
- package/packages/skill-sdk/dist/index.js.map +1 -0
- package/packages/skill-sdk/package.json +14 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export class CommandRejectedError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "CommandRejectedError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Tokenize a command for safety inspection. We split on shell control
|
|
9
|
+
* operators so that `a && rm -rf /` is inspected as two segments, defeating
|
|
10
|
+
* naive "starts with allowed command" bypasses.
|
|
11
|
+
*/
|
|
12
|
+
export function parseCommand(input) {
|
|
13
|
+
const normalized = input.trim().replace(/\s+/g, " ");
|
|
14
|
+
// Split on shell operators that chain/redirect commands.
|
|
15
|
+
const segments = normalized.split(/(?:\|\||&&|;|\||&|>|<|`|\$\()/g);
|
|
16
|
+
const tokens = [];
|
|
17
|
+
for (const seg of segments) {
|
|
18
|
+
for (const word of seg.trim().split(" ")) {
|
|
19
|
+
if (word)
|
|
20
|
+
tokens.push(word.toLowerCase());
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { normalized, tokens };
|
|
24
|
+
}
|
|
25
|
+
/** Detect characters/sequences indicating command substitution or chaining. */
|
|
26
|
+
function hasDangerousMetachars(input) {
|
|
27
|
+
// Backticks, $(...) command substitution, and process substitution.
|
|
28
|
+
return /`|\$\(|\$\{|<\(|>\(/.test(input);
|
|
29
|
+
}
|
|
30
|
+
export class CommandGuard {
|
|
31
|
+
allowedCommands;
|
|
32
|
+
blockedTokens;
|
|
33
|
+
constructor(allowedCommands, blockedTokens) {
|
|
34
|
+
this.allowedCommands = allowedCommands;
|
|
35
|
+
this.blockedTokens = blockedTokens;
|
|
36
|
+
}
|
|
37
|
+
setAllowed(commands) {
|
|
38
|
+
this.allowedCommands = commands;
|
|
39
|
+
}
|
|
40
|
+
setBlocked(tokens) {
|
|
41
|
+
this.blockedTokens = tokens;
|
|
42
|
+
}
|
|
43
|
+
allowed() {
|
|
44
|
+
return [...this.allowedCommands];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validate a command against the allowlist and blocklist. Throws on any
|
|
48
|
+
* rejection. Returns the normalized command on success.
|
|
49
|
+
*
|
|
50
|
+
* A command is allowed only if its normalized form *starts with* one of the
|
|
51
|
+
* allowlisted command prefixes AND contains no blocked tokens, no dangerous
|
|
52
|
+
* chaining/substitution metacharacters, and no piped second program.
|
|
53
|
+
*/
|
|
54
|
+
assertAllowed(input) {
|
|
55
|
+
const { normalized, tokens } = parseCommand(input);
|
|
56
|
+
if (!normalized)
|
|
57
|
+
throw new CommandRejectedError("Empty command.");
|
|
58
|
+
if (hasDangerousMetachars(input)) {
|
|
59
|
+
throw new CommandRejectedError("Command rejected: command substitution / process substitution is not allowed.");
|
|
60
|
+
}
|
|
61
|
+
// Reject chaining operators outright for the allowed-command runner.
|
|
62
|
+
if (/(\|\||&&|;|\||&|>|<)/.test(input)) {
|
|
63
|
+
throw new CommandRejectedError("Command rejected: pipelines, redirection and chaining are not allowed.");
|
|
64
|
+
}
|
|
65
|
+
// Blocked tokens anywhere.
|
|
66
|
+
const blocked = this.blockedTokens.map((t) => t.toLowerCase());
|
|
67
|
+
for (const tok of tokens) {
|
|
68
|
+
if (blocked.includes(tok)) {
|
|
69
|
+
throw new CommandRejectedError(`Command rejected: '${tok}' is a blocked command.`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Special-case destructive flag combos that are not single tokens.
|
|
73
|
+
if (/\brm\b/.test(normalized) && /-[a-z]*r[a-z]*f|-[a-z]*f[a-z]*r|--recursive|--force/i.test(normalized)) {
|
|
74
|
+
throw new CommandRejectedError("Command rejected: recursive/forced 'rm' is blocked.");
|
|
75
|
+
}
|
|
76
|
+
if (/\bchmod\b\s+777/.test(normalized)) {
|
|
77
|
+
throw new CommandRejectedError("Command rejected: 'chmod 777' is blocked.");
|
|
78
|
+
}
|
|
79
|
+
// Must match an allowlisted prefix.
|
|
80
|
+
const ok = this.allowedCommands.some((cmd) => {
|
|
81
|
+
const c = cmd.trim().replace(/\s+/g, " ").toLowerCase();
|
|
82
|
+
const n = normalized.toLowerCase();
|
|
83
|
+
return n === c || n.startsWith(c + " ");
|
|
84
|
+
});
|
|
85
|
+
if (!ok) {
|
|
86
|
+
throw new CommandRejectedError(`Command rejected: '${normalized}' is not in the allowed command list. Use shell_request_command_approval to request it.`);
|
|
87
|
+
}
|
|
88
|
+
return normalized;
|
|
89
|
+
}
|
|
90
|
+
/** Looser check for an *approved* arbitrary command — still blocks the hard blocklist. */
|
|
91
|
+
assertNotBlocked(input) {
|
|
92
|
+
const { normalized, tokens } = parseCommand(input);
|
|
93
|
+
const blocked = this.blockedTokens.map((t) => t.toLowerCase());
|
|
94
|
+
for (const tok of tokens) {
|
|
95
|
+
if (blocked.includes(tok)) {
|
|
96
|
+
throw new CommandRejectedError(`Command rejected: '${tok}' is a blocked command even after approval.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (/\brm\b/.test(normalized) && /-[a-z]*r[a-z]*f/i.test(normalized)) {
|
|
100
|
+
throw new CommandRejectedError("Command rejected: 'rm -rf' is blocked even after approval.");
|
|
101
|
+
}
|
|
102
|
+
return normalized;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=command-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-guard.js","sourceRoot":"","sources":["../../src/security/command-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare class PathAccessError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Resolves and validates a requested path against the allowed directories and
|
|
6
|
+
* the sensitive blocklist. Defends against:
|
|
7
|
+
* - path traversal (`../`)
|
|
8
|
+
* - symlink traversal (realpath of every existing ancestor is re-checked)
|
|
9
|
+
* - access to sensitive system/credential paths
|
|
10
|
+
*/
|
|
11
|
+
export declare class PathGuard {
|
|
12
|
+
private allowedDirs;
|
|
13
|
+
private readonly blocklist;
|
|
14
|
+
constructor(allowedDirs: string[]);
|
|
15
|
+
setAllowedDirectories(dirs: string[]): void;
|
|
16
|
+
allowed(): string[];
|
|
17
|
+
private inBlocklist;
|
|
18
|
+
private inAllowlist;
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the realpath of the deepest existing ancestor and re-check it.
|
|
21
|
+
* This catches a symlink anywhere along the path that points outside the
|
|
22
|
+
* allowlist (e.g. `~/Projects/evil -> /etc`).
|
|
23
|
+
*/
|
|
24
|
+
private resolveRealAncestor;
|
|
25
|
+
/**
|
|
26
|
+
* Validate a path for the requested mode. Returns the normalized absolute
|
|
27
|
+
* path. Throws PathAccessError on any violation.
|
|
28
|
+
*/
|
|
29
|
+
assertAccess(requested: string, mode: "read" | "write"): string;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=path-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-guard.d.ts","sourceRoot":"","sources":["../../src/security/path-guard.ts"],"names":[],"mappings":"AAKA,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAkBD;;;;;;GAMG;AACH,qBAAa,SAAS;IAGR,OAAO,CAAC,WAAW;IAF/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAW;gBAEjB,WAAW,EAAE,MAAM,EAAE;IAIzC,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAI3C,OAAO,IAAI,MAAM,EAAE;IAInB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,WAAW;IAInB;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAmB3B;;;OAGG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM;CAwBhE"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { sensitiveBlocklist } from "@localant/shared";
|
|
5
|
+
export class PathAccessError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "PathAccessError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function expandHome(p) {
|
|
12
|
+
if (p === "~")
|
|
13
|
+
return os.homedir();
|
|
14
|
+
if (p.startsWith("~/") || p.startsWith("~\\"))
|
|
15
|
+
return path.join(os.homedir(), p.slice(2));
|
|
16
|
+
return p;
|
|
17
|
+
}
|
|
18
|
+
function normalizeAbs(p) {
|
|
19
|
+
return path.resolve(expandHome(p));
|
|
20
|
+
}
|
|
21
|
+
/** True if `child` is `parent` or nested under it (prefix check on segments). */
|
|
22
|
+
function isWithin(parent, child) {
|
|
23
|
+
const rel = path.relative(parent, child);
|
|
24
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolves and validates a requested path against the allowed directories and
|
|
28
|
+
* the sensitive blocklist. Defends against:
|
|
29
|
+
* - path traversal (`../`)
|
|
30
|
+
* - symlink traversal (realpath of every existing ancestor is re-checked)
|
|
31
|
+
* - access to sensitive system/credential paths
|
|
32
|
+
*/
|
|
33
|
+
export class PathGuard {
|
|
34
|
+
allowedDirs;
|
|
35
|
+
blocklist;
|
|
36
|
+
constructor(allowedDirs) {
|
|
37
|
+
this.allowedDirs = allowedDirs;
|
|
38
|
+
this.blocklist = sensitiveBlocklist().map(normalizeAbs);
|
|
39
|
+
}
|
|
40
|
+
setAllowedDirectories(dirs) {
|
|
41
|
+
this.allowedDirs = dirs;
|
|
42
|
+
}
|
|
43
|
+
allowed() {
|
|
44
|
+
return this.allowedDirs.map(normalizeAbs);
|
|
45
|
+
}
|
|
46
|
+
inBlocklist(resolved) {
|
|
47
|
+
return this.blocklist.some((b) => isWithin(b, resolved));
|
|
48
|
+
}
|
|
49
|
+
inAllowlist(resolved) {
|
|
50
|
+
return this.allowed().some((dir) => isWithin(dir, resolved));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve the realpath of the deepest existing ancestor and re-check it.
|
|
54
|
+
* This catches a symlink anywhere along the path that points outside the
|
|
55
|
+
* allowlist (e.g. `~/Projects/evil -> /etc`).
|
|
56
|
+
*/
|
|
57
|
+
resolveRealAncestor(resolved) {
|
|
58
|
+
let current = resolved;
|
|
59
|
+
const tail = [];
|
|
60
|
+
// Walk up until we find a path that exists on disk.
|
|
61
|
+
while (!fs.existsSync(current)) {
|
|
62
|
+
const parent = path.dirname(current);
|
|
63
|
+
if (parent === current)
|
|
64
|
+
break;
|
|
65
|
+
tail.unshift(path.basename(current));
|
|
66
|
+
current = parent;
|
|
67
|
+
}
|
|
68
|
+
let real;
|
|
69
|
+
try {
|
|
70
|
+
real = fs.realpathSync(current);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
real = current;
|
|
74
|
+
}
|
|
75
|
+
return tail.length ? path.join(real, ...tail) : real;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Validate a path for the requested mode. Returns the normalized absolute
|
|
79
|
+
* path. Throws PathAccessError on any violation.
|
|
80
|
+
*/
|
|
81
|
+
assertAccess(requested, mode) {
|
|
82
|
+
const resolved = normalizeAbs(requested);
|
|
83
|
+
if (this.inBlocklist(resolved)) {
|
|
84
|
+
throw new PathAccessError(`Access denied: '${requested}' is in the sensitive blocklist.`);
|
|
85
|
+
}
|
|
86
|
+
if (!this.inAllowlist(resolved)) {
|
|
87
|
+
throw new PathAccessError(`Access denied: '${requested}' is outside the allowed directories. Add it with fs_add_allowed_directory.`);
|
|
88
|
+
}
|
|
89
|
+
// Re-check after resolving symlinks on existing ancestors.
|
|
90
|
+
const real = this.resolveRealAncestor(resolved);
|
|
91
|
+
if (this.inBlocklist(real)) {
|
|
92
|
+
throw new PathAccessError(`Access denied: '${requested}' resolves (via symlink) into a sensitive path.`);
|
|
93
|
+
}
|
|
94
|
+
if (!this.inAllowlist(real)) {
|
|
95
|
+
throw new PathAccessError(`Access denied: '${requested}' resolves (via symlink) outside allowed directories.`);
|
|
96
|
+
}
|
|
97
|
+
void mode; // mode-specific policy is enforced by callers / permission engine.
|
|
98
|
+
return resolved;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=path-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-guard.js","sourceRoot":"","sources":["../../src/security/path-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-runner.d.ts","sourceRoot":"","sources":["../src/skill-runner.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolated skill execution entry point. Reads a JSON payload from stdin,
|
|
3
|
+
* dynamically imports the skill entry (Node strips TS types on v22.18+),
|
|
4
|
+
* validates input against the tool's Zod schema, runs the handler with a
|
|
5
|
+
* restricted context, and prints a single JSON result line to stdout.
|
|
6
|
+
*
|
|
7
|
+
* The skill only receives the secret values it is permitted to access; the
|
|
8
|
+
* vault itself is never exposed to the subprocess.
|
|
9
|
+
*/
|
|
10
|
+
import { pathToFileURL } from "node:url";
|
|
11
|
+
async function readStdin() {
|
|
12
|
+
const chunks = [];
|
|
13
|
+
for await (const chunk of process.stdin)
|
|
14
|
+
chunks.push(chunk);
|
|
15
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
16
|
+
}
|
|
17
|
+
async function main() {
|
|
18
|
+
const payload = JSON.parse(await readStdin());
|
|
19
|
+
const mod = await import(pathToFileURL(payload.entry).href);
|
|
20
|
+
const skill = mod.default;
|
|
21
|
+
if (!skill || !skill.tools || !skill.tools[payload.tool]) {
|
|
22
|
+
throw new Error(`Tool '${payload.tool}' not found in skill.`);
|
|
23
|
+
}
|
|
24
|
+
const tool = skill.tools[payload.tool];
|
|
25
|
+
const parsed = tool.inputSchema ? tool.inputSchema.parse(payload.input) : payload.input;
|
|
26
|
+
const ctx = {
|
|
27
|
+
getSecret: async (name) => payload.secrets[name],
|
|
28
|
+
workspaceDir: payload.workspaceDir,
|
|
29
|
+
log: (msg) => process.stderr.write(`[skill] ${msg}\n`),
|
|
30
|
+
};
|
|
31
|
+
const result = await tool.handler(parsed, ctx);
|
|
32
|
+
process.stdout.write(JSON.stringify({ ok: true, result }) + "\n");
|
|
33
|
+
}
|
|
34
|
+
main().catch((err) => {
|
|
35
|
+
process.stdout.write(JSON.stringify({ ok: false, error: err instanceof Error ? err.message : String(err) }) + "\n");
|
|
36
|
+
process.exitCode = 1;
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=skill-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-runner.js","sourceRoot":"","sources":["../src/skill-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAUzC,KAAK,UAAU,SAAS;IACtB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK;QAAE,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;IACtE,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,SAAS,EAAE,CAAY,CAAC;IACzD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC;IAC1B,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CAAC,SAAS,OAAO,CAAC,IAAI,uBAAuB,CAAC,CAAC;IAChE,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IACxF,MAAM,GAAG,GAAG;QACV,SAAS,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;QACxD,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,GAAG,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC;KAC/D,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;AACpE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IACpH,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AppPaths, ApprovalRequest, RiskLevel } from "@localant/shared";
|
|
2
|
+
/**
|
|
3
|
+
* Persistent approval queue. Approvals can be granted via the CLI, the
|
|
4
|
+
* dashboard, or MCP approval tools — ChatGPT-side confirmation is never
|
|
5
|
+
* trusted alone.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ApprovalStore {
|
|
8
|
+
private readonly file;
|
|
9
|
+
/** session id -> set of tool names approved for the whole session. */
|
|
10
|
+
private readonly sessionGrants;
|
|
11
|
+
constructor(paths: AppPaths);
|
|
12
|
+
private read;
|
|
13
|
+
private write;
|
|
14
|
+
create(input: {
|
|
15
|
+
tool: string;
|
|
16
|
+
risk: RiskLevel;
|
|
17
|
+
requirement: "single" | "double";
|
|
18
|
+
reason: string;
|
|
19
|
+
summary: string;
|
|
20
|
+
caller: string;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
}): ApprovalRequest;
|
|
23
|
+
get(id: string): ApprovalRequest | undefined;
|
|
24
|
+
listPending(): ApprovalRequest[];
|
|
25
|
+
list(limit?: number): ApprovalRequest[];
|
|
26
|
+
approve(id: string, scope?: "once" | "session"): ApprovalRequest | undefined;
|
|
27
|
+
deny(id: string): ApprovalRequest | undefined;
|
|
28
|
+
hasSessionGrant(sessionId: string | undefined, tool: string): boolean;
|
|
29
|
+
/** Find an approved-but-unconsumed once-approval for a tool. */
|
|
30
|
+
findApprovedForTool(tool: string): ApprovalRequest | undefined;
|
|
31
|
+
/** Mark a once-approval as consumed so it cannot be reused. */
|
|
32
|
+
consume(id: string): void;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=approval-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-store.d.ts","sourceRoot":"","sources":["../../src/stores/approval-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7E;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;gBAEpD,KAAK,EAAE,QAAQ;IAI3B,OAAO,CAAC,IAAI;IAQZ,OAAO,CAAC,KAAK;IAIb,MAAM,CAAC,KAAK,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,SAAS,CAAC;QAChB,WAAW,EAAE,QAAQ,GAAG,QAAQ,CAAC;QACjC,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,eAAe;IAcnB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAI5C,WAAW,IAAI,eAAe,EAAE;IAIhC,IAAI,CAAC,KAAK,SAAM,GAAG,eAAe,EAAE;IAIpC,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,GAAG,SAAkB,GAAG,eAAe,GAAG,SAAS;IA0BpF,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAc7C,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO;IAKrE,gEAAgE;IAChE,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAI9D,+DAA+D;IAC/D,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;CAO1B"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
/**
|
|
4
|
+
* Persistent approval queue. Approvals can be granted via the CLI, the
|
|
5
|
+
* dashboard, or MCP approval tools — ChatGPT-side confirmation is never
|
|
6
|
+
* trusted alone.
|
|
7
|
+
*/
|
|
8
|
+
export class ApprovalStore {
|
|
9
|
+
file;
|
|
10
|
+
/** session id -> set of tool names approved for the whole session. */
|
|
11
|
+
sessionGrants = new Map();
|
|
12
|
+
constructor(paths) {
|
|
13
|
+
this.file = paths.approvalsFile;
|
|
14
|
+
}
|
|
15
|
+
read() {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(fs.readFileSync(this.file, "utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
write(items) {
|
|
24
|
+
fs.writeFileSync(this.file, JSON.stringify(items, null, 2), { mode: 0o600 });
|
|
25
|
+
}
|
|
26
|
+
create(input) {
|
|
27
|
+
const req = {
|
|
28
|
+
id: nanoid(10),
|
|
29
|
+
createdAt: new Date().toISOString(),
|
|
30
|
+
status: "pending",
|
|
31
|
+
approvalsGiven: 0,
|
|
32
|
+
...input,
|
|
33
|
+
};
|
|
34
|
+
const items = this.read();
|
|
35
|
+
items.push(req);
|
|
36
|
+
this.write(items);
|
|
37
|
+
return req;
|
|
38
|
+
}
|
|
39
|
+
get(id) {
|
|
40
|
+
return this.read().find((r) => r.id === id);
|
|
41
|
+
}
|
|
42
|
+
listPending() {
|
|
43
|
+
return this.read().filter((r) => r.status === "pending");
|
|
44
|
+
}
|
|
45
|
+
list(limit = 100) {
|
|
46
|
+
return this.read().slice(-limit).reverse();
|
|
47
|
+
}
|
|
48
|
+
approve(id, scope = "once") {
|
|
49
|
+
const items = this.read();
|
|
50
|
+
const idx = items.findIndex((r) => r.id === id);
|
|
51
|
+
if (idx === -1)
|
|
52
|
+
return undefined;
|
|
53
|
+
const req = items[idx];
|
|
54
|
+
if (req.status !== "pending")
|
|
55
|
+
return req;
|
|
56
|
+
const given = req.approvalsGiven + 1;
|
|
57
|
+
const needed = req.requirement === "double" ? 2 : 1;
|
|
58
|
+
const updated = {
|
|
59
|
+
...req,
|
|
60
|
+
approvalsGiven: given,
|
|
61
|
+
scope,
|
|
62
|
+
...(given >= needed
|
|
63
|
+
? { status: "approved", resolvedAt: new Date().toISOString() }
|
|
64
|
+
: {}),
|
|
65
|
+
};
|
|
66
|
+
items[idx] = updated;
|
|
67
|
+
this.write(items);
|
|
68
|
+
if (updated.status === "approved" && scope === "session" && req.sessionId) {
|
|
69
|
+
const set = this.sessionGrants.get(req.sessionId) ?? new Set();
|
|
70
|
+
set.add(req.tool);
|
|
71
|
+
this.sessionGrants.set(req.sessionId, set);
|
|
72
|
+
}
|
|
73
|
+
return updated;
|
|
74
|
+
}
|
|
75
|
+
deny(id) {
|
|
76
|
+
const items = this.read();
|
|
77
|
+
const idx = items.findIndex((r) => r.id === id);
|
|
78
|
+
if (idx === -1)
|
|
79
|
+
return undefined;
|
|
80
|
+
const updated = {
|
|
81
|
+
...items[idx],
|
|
82
|
+
status: "denied",
|
|
83
|
+
resolvedAt: new Date().toISOString(),
|
|
84
|
+
};
|
|
85
|
+
items[idx] = updated;
|
|
86
|
+
this.write(items);
|
|
87
|
+
return updated;
|
|
88
|
+
}
|
|
89
|
+
hasSessionGrant(sessionId, tool) {
|
|
90
|
+
if (!sessionId)
|
|
91
|
+
return false;
|
|
92
|
+
return this.sessionGrants.get(sessionId)?.has(tool) ?? false;
|
|
93
|
+
}
|
|
94
|
+
/** Find an approved-but-unconsumed once-approval for a tool. */
|
|
95
|
+
findApprovedForTool(tool) {
|
|
96
|
+
return this.read().find((r) => r.tool === tool && r.status === "approved" && r.scope !== "session");
|
|
97
|
+
}
|
|
98
|
+
/** Mark a once-approval as consumed so it cannot be reused. */
|
|
99
|
+
consume(id) {
|
|
100
|
+
const items = this.read();
|
|
101
|
+
const idx = items.findIndex((r) => r.id === id);
|
|
102
|
+
if (idx === -1)
|
|
103
|
+
return;
|
|
104
|
+
items[idx] = { ...items[idx], status: "expired", resolvedAt: new Date().toISOString() };
|
|
105
|
+
this.write(items);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=approval-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-store.js","sourceRoot":"","sources":["../../src/stores/approval-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type AppPaths, type AuditEntry, type RiskLevel } from "@localant/shared";
|
|
2
|
+
/** Append-only audit log backed by a JSONL file. Secrets are redacted. */
|
|
3
|
+
export declare class AuditLog {
|
|
4
|
+
private readonly file;
|
|
5
|
+
private secretsProvider;
|
|
6
|
+
constructor(paths: AppPaths);
|
|
7
|
+
setSecretsProvider(fn: () => string[]): void;
|
|
8
|
+
record(entry: {
|
|
9
|
+
tool: string;
|
|
10
|
+
caller: string;
|
|
11
|
+
risk: RiskLevel;
|
|
12
|
+
input: unknown;
|
|
13
|
+
output: unknown;
|
|
14
|
+
approval: AuditEntry["approval"];
|
|
15
|
+
durationMs: number;
|
|
16
|
+
error?: string;
|
|
17
|
+
}): AuditEntry;
|
|
18
|
+
list(limit?: number, offset?: number): AuditEntry[];
|
|
19
|
+
get(id: string): AuditEntry | undefined;
|
|
20
|
+
search(query: string, limit?: number): AuditEntry[];
|
|
21
|
+
readAll(): AuditEntry[];
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=audit-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-log.d.ts","sourceRoot":"","sources":["../../src/stores/audit-log.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEpG,0EAA0E;AAC1E,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,eAAe,CAA4B;gBAEvC,KAAK,EAAE,QAAQ;IAI3B,kBAAkB,CAAC,EAAE,EAAE,MAAM,MAAM,EAAE,GAAG,IAAI;IAI5C,MAAM,CAAC,KAAK,EAAE;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,SAAS,CAAC;QAChB,KAAK,EAAE,OAAO,CAAC;QACf,MAAM,EAAE,OAAO,CAAC;QAChB,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;QACjC,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,UAAU;IAkBd,IAAI,CAAC,KAAK,SAAM,EAAE,MAAM,SAAI,GAAG,UAAU,EAAE;IAK3C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIvC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAM,GAAG,UAAU,EAAE;IAahD,OAAO,IAAI,UAAU,EAAE;CAWxB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import { redact, truncate } from "@localant/shared";
|
|
4
|
+
/** Append-only audit log backed by a JSONL file. Secrets are redacted. */
|
|
5
|
+
export class AuditLog {
|
|
6
|
+
file;
|
|
7
|
+
secretsProvider = () => [];
|
|
8
|
+
constructor(paths) {
|
|
9
|
+
this.file = paths.auditLog;
|
|
10
|
+
}
|
|
11
|
+
setSecretsProvider(fn) {
|
|
12
|
+
this.secretsProvider = fn;
|
|
13
|
+
}
|
|
14
|
+
record(entry) {
|
|
15
|
+
const secrets = this.secretsProvider();
|
|
16
|
+
const full = {
|
|
17
|
+
id: nanoid(12),
|
|
18
|
+
timestamp: new Date().toISOString(),
|
|
19
|
+
tool: entry.tool,
|
|
20
|
+
caller: entry.caller,
|
|
21
|
+
risk: entry.risk,
|
|
22
|
+
inputSummary: truncate(redact(safeStringify(entry.input), secrets), 500),
|
|
23
|
+
outputSummary: truncate(redact(safeStringify(entry.output), secrets), 500),
|
|
24
|
+
approval: entry.approval,
|
|
25
|
+
durationMs: entry.durationMs,
|
|
26
|
+
...(entry.error ? { error: truncate(redact(entry.error, secrets), 500) } : {}),
|
|
27
|
+
};
|
|
28
|
+
fs.appendFileSync(this.file, JSON.stringify(full) + "\n");
|
|
29
|
+
return full;
|
|
30
|
+
}
|
|
31
|
+
list(limit = 100, offset = 0) {
|
|
32
|
+
const all = this.readAll();
|
|
33
|
+
return all.slice(Math.max(0, all.length - offset - limit), all.length - offset).reverse();
|
|
34
|
+
}
|
|
35
|
+
get(id) {
|
|
36
|
+
return this.readAll().find((e) => e.id === id);
|
|
37
|
+
}
|
|
38
|
+
search(query, limit = 100) {
|
|
39
|
+
const q = query.toLowerCase();
|
|
40
|
+
return this.readAll()
|
|
41
|
+
.filter((e) => e.tool.toLowerCase().includes(q) ||
|
|
42
|
+
e.inputSummary.toLowerCase().includes(q) ||
|
|
43
|
+
(e.error ?? "").toLowerCase().includes(q))
|
|
44
|
+
.reverse()
|
|
45
|
+
.slice(0, limit);
|
|
46
|
+
}
|
|
47
|
+
readAll() {
|
|
48
|
+
try {
|
|
49
|
+
return fs
|
|
50
|
+
.readFileSync(this.file, "utf8")
|
|
51
|
+
.split("\n")
|
|
52
|
+
.filter((l) => l.trim())
|
|
53
|
+
.map((l) => JSON.parse(l));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function safeStringify(value) {
|
|
61
|
+
if (typeof value === "string")
|
|
62
|
+
return value;
|
|
63
|
+
try {
|
|
64
|
+
return JSON.stringify(value);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return String(value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=audit-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-log.js","sourceRoot":"","sources":["../../src/stores/audit-log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAkD,MAAM,kBAAkB,CAAC;AAEpG,0EAA0E;AAC1E,MAAM,OAAO,QAAQ;IACF,IAAI,CAAS;IACtB,eAAe,GAAmB,GAAG,EAAE,CAAC,EAAE,CAAC;IAEnD,YAAY,KAAe;QACzB,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,QAAQ,CAAC;IAC7B,CAAC;IAED,kBAAkB,CAAC,EAAkB;QACnC,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;IAC5B,CAAC;IAED,MAAM,CAAC,KASN;QACC,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACvC,MAAM,IAAI,GAAe;YACvB,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;YACd,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC;YACxE,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC;YAC1E,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC/E,CAAC;QACF,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC,KAAK,GAAG,GAAG,EAAE,MAAM,GAAG,CAAC;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC3B,OAAO,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC;IAC5F,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,CAAC,KAAa,EAAE,KAAK,GAAG,GAAG;QAC/B,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC,OAAO,EAAE;aAClB,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YAChC,CAAC,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YACxC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAC5C;aACA,OAAO,EAAE;aACT,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,OAAO;QACL,IAAI,CAAC;YACH,OAAO,EAAE;iBACN,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;iBAC/B,KAAK,CAAC,IAAI,CAAC;iBACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAe,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;CACF;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Config, type AppPaths } from "@localant/shared";
|
|
2
|
+
/** Loads, persists and initializes on-disk configuration and identity files. */
|
|
3
|
+
export declare class ConfigStore {
|
|
4
|
+
readonly paths: AppPaths;
|
|
5
|
+
constructor(base?: string);
|
|
6
|
+
/** Create config dir tree and default files if missing. Idempotent. */
|
|
7
|
+
ensureInitialized(): void;
|
|
8
|
+
load(): Config;
|
|
9
|
+
save(config: Config): Config;
|
|
10
|
+
/** Immutably merge a partial patch into config and persist. */
|
|
11
|
+
update(patch: Partial<Config>): Config;
|
|
12
|
+
getToken(): string;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=config-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-store.d.ts","sourceRoot":"","sources":["../../src/stores/config-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,MAAM,EAA2B,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAErG,gFAAgF;AAChF,qBAAa,WAAW;IACtB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;gBAEb,IAAI,CAAC,EAAE,MAAM;IAIzB,uEAAuE;IACvE,iBAAiB,IAAI,IAAI;IAuBzB,IAAI,IAAI,MAAM;IASd,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAM5B,+DAA+D;IAC/D,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM;IAKtC,QAAQ,IAAI,MAAM;CAGnB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
import { ConfigSchema, defaultConfig, appPaths } from "@localant/shared";
|
|
5
|
+
/** Loads, persists and initializes on-disk configuration and identity files. */
|
|
6
|
+
export class ConfigStore {
|
|
7
|
+
paths;
|
|
8
|
+
constructor(base) {
|
|
9
|
+
this.paths = appPaths(base);
|
|
10
|
+
}
|
|
11
|
+
/** Create config dir tree and default files if missing. Idempotent. */
|
|
12
|
+
ensureInitialized() {
|
|
13
|
+
const p = this.paths;
|
|
14
|
+
for (const dir of [p.root, path.dirname(p.auditLog), p.skillsDir, p.backupsDir, p.workspaceDir, p.logsDir]) {
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
if (!fs.existsSync(p.configFile)) {
|
|
18
|
+
this.save(defaultConfig());
|
|
19
|
+
}
|
|
20
|
+
if (!fs.existsSync(p.tokenFile)) {
|
|
21
|
+
const token = crypto.randomBytes(32).toString("base64url");
|
|
22
|
+
fs.writeFileSync(p.tokenFile, token, { mode: 0o600 });
|
|
23
|
+
}
|
|
24
|
+
if (!fs.existsSync(p.secretsFile)) {
|
|
25
|
+
fs.writeFileSync(p.secretsFile, JSON.stringify({}), { mode: 0o600 });
|
|
26
|
+
}
|
|
27
|
+
if (!fs.existsSync(p.approvalsFile)) {
|
|
28
|
+
fs.writeFileSync(p.approvalsFile, JSON.stringify([]), { mode: 0o600 });
|
|
29
|
+
}
|
|
30
|
+
if (!fs.existsSync(p.auditLog)) {
|
|
31
|
+
fs.writeFileSync(p.auditLog, "", { mode: 0o600 });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
load() {
|
|
35
|
+
try {
|
|
36
|
+
const raw = JSON.parse(fs.readFileSync(this.paths.configFile, "utf8"));
|
|
37
|
+
return ConfigSchema.parse(raw);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return defaultConfig();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
save(config) {
|
|
44
|
+
const parsed = ConfigSchema.parse(config);
|
|
45
|
+
fs.writeFileSync(this.paths.configFile, JSON.stringify(parsed, null, 2), { mode: 0o600 });
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
/** Immutably merge a partial patch into config and persist. */
|
|
49
|
+
update(patch) {
|
|
50
|
+
const current = this.load();
|
|
51
|
+
return this.save({ ...current, ...patch });
|
|
52
|
+
}
|
|
53
|
+
getToken() {
|
|
54
|
+
return fs.readFileSync(this.paths.tokenFile, "utf8").trim();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=config-store.js.map
|