openclaw-credential-vault 1.0.0-beta.1

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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +95 -0
  3. package/bin/linux-x64/openclaw-vault-resolver +0 -0
  4. package/bin/vault-setup.sh +163 -0
  5. package/dist/audit.d.ts +74 -0
  6. package/dist/audit.d.ts.map +1 -0
  7. package/dist/audit.js +238 -0
  8. package/dist/audit.js.map +1 -0
  9. package/dist/browser.d.ts +125 -0
  10. package/dist/browser.d.ts.map +1 -0
  11. package/dist/browser.js +326 -0
  12. package/dist/browser.js.map +1 -0
  13. package/dist/cli.d.ts +32 -0
  14. package/dist/cli.d.ts.map +1 -0
  15. package/dist/cli.js +1059 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/config.d.ts +60 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/config.js +261 -0
  20. package/dist/config.js.map +1 -0
  21. package/dist/crypto.d.ts +53 -0
  22. package/dist/crypto.d.ts.map +1 -0
  23. package/dist/crypto.js +300 -0
  24. package/dist/crypto.js.map +1 -0
  25. package/dist/guesser.d.ts +78 -0
  26. package/dist/guesser.d.ts.map +1 -0
  27. package/dist/guesser.js +361 -0
  28. package/dist/guesser.js.map +1 -0
  29. package/dist/index.d.ts +212 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +767 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/registry.d.ts +47 -0
  34. package/dist/registry.d.ts.map +1 -0
  35. package/dist/registry.js +296 -0
  36. package/dist/registry.js.map +1 -0
  37. package/dist/resolver.d.ts +37 -0
  38. package/dist/resolver.d.ts.map +1 -0
  39. package/dist/resolver.js +200 -0
  40. package/dist/resolver.js.map +1 -0
  41. package/dist/scrubber.d.ts +93 -0
  42. package/dist/scrubber.d.ts.map +1 -0
  43. package/dist/scrubber.js +304 -0
  44. package/dist/scrubber.js.map +1 -0
  45. package/dist/types.d.ts +235 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +6 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/vault-status.d.ts +15 -0
  50. package/dist/vault-status.d.ts.map +1 -0
  51. package/dist/vault-status.js +112 -0
  52. package/dist/vault-status.js.map +1 -0
  53. package/openclaw.plugin.json +23 -0
  54. package/package.json +60 -0
  55. package/tools/cookie-helper.html +216 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenClaw Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # OpenClaw Credential Vault
2
+
3
+ Encrypted credential management for [OpenClaw](https://openclaw.ai). Keeps API keys, tokens, and passwords out of the AI agent's context window — where they could be exfiltrated, leaked into transcripts, or exposed through tool output.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ curl -fsSL https://raw.githubusercontent.com/karanuppal/openclaw-credential-vault/main/install.sh | bash
9
+ ```
10
+
11
+ This installs the plugin, creates a dedicated system user for credential isolation, and configures everything. You'll be prompted for sudo.
12
+
13
+ **Can't use sudo?** Run `openclaw vault init` for inline-only mode — credentials are still encrypted at rest, just without OS-level user separation.
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ # Add a credential (auto-detects format)
19
+ openclaw vault add github --key "ghp_your_token_here"
20
+
21
+ # Verify it works
22
+ openclaw vault test github
23
+
24
+ # Add more
25
+ openclaw vault add stripe --key "sk_live_..."
26
+ openclaw vault add amazon --type browser-password --domain .amazon.com --key "p@ssw0rd"
27
+ ```
28
+
29
+ That's it. The agent can now use `gh`, call Stripe APIs, and log into Amazon — without ever seeing the credentials.
30
+
31
+ ## How It Works
32
+
33
+ ```
34
+ Agent runs "gh pr list"
35
+
36
+ before_tool_call hook matches "gh" → decrypts credential → injects GH_TOKEN into subprocess
37
+
38
+ gh runs with the token, returns PR listings
39
+
40
+ Subprocess exits — credential dies with it
41
+
42
+ Output scrubbed for credential patterns (3 layers: regex + literal + env-var)
43
+
44
+ Agent sees clean PR listings — no credential anywhere in context
45
+ ```
46
+
47
+ **Encryption:** AES-256-GCM with Argon2id key derivation (64 MiB memory, 3 iterations). Each credential is a separate `.enc` file.
48
+
49
+ **Isolation:** The Rust resolver binary runs as a dedicated `openclaw-vault` system user via setuid. The agent process can't read the credential files — it gets "Permission denied." The plugin and resolver use protocol versioning to detect mismatches after updates — if `npm update` delivers a new version, the plugin surfaces actionable fix instructions (`sudo bash vault-setup.sh`) until the installed binary is updated.
50
+
51
+ **Scrubbing:** Three redundant layers catch credentials in tool output, file writes, outbound messages, and session transcripts. Patterns for GitHub, Stripe, Gumroad, OpenAI, and Anthropic tokens ship built-in.
52
+
53
+ ## Commands
54
+
55
+ | Command | Description |
56
+ |---------|-------------|
57
+ | `vault init` | Initialize vault (creates directory, shows setup instructions) |
58
+ | `vault add <tool> --key <cred>` | Add a credential (auto-detects format) |
59
+ | `vault add <tool> --type browser-password --domain <d> --key <p>` | Add a domain-pinned browser password |
60
+ | `vault add <tool> --type browser-cookie --domain <d>` | Add browser cookies (paste JSON or Netscape format) |
61
+ | `vault list` | Show all stored credentials and status |
62
+ | `vault show <tool>` | Show credential details and config |
63
+ | `vault test <tool>` | Verify injection and scrubbing work |
64
+ | `vault rotate <tool> --key <new>` | Rotate a credential |
65
+ | `vault rotate --check` | Show overdue rotations |
66
+ | `vault rotate --all` | Emergency mass rotation walkthrough |
67
+ | `vault remove <tool>` | Remove credential (keeps scrub patterns) |
68
+ | `vault remove <tool> --purge` | Fully remove credential + config |
69
+ | `vault audit` | Security audit (permissions, rotation, config) |
70
+ | `vault logs` | View audit log (credential access + scrubbing) |
71
+ | `vault logs --stats` | Aggregate access/scrub statistics |
72
+ | `vault logs --tool <name> --last <duration>` | Filter by tool and time |
73
+ | `vault logs --json` | Raw JSONL output |
74
+
75
+ ## Platform Support
76
+
77
+ | Platform | Status |
78
+ |----------|--------|
79
+ | Linux x64 | Full support (inline + resolver) |
80
+ | Linux arm64 | Inline mode only (resolver binary coming soon) |
81
+ | macOS | Inline mode only |
82
+
83
+ **Sandbox mode:** Not yet tested with OpenClaw's Docker sandbox. The vault hooks run in the gateway process (not inside the sandbox container), so they should work in theory, but no end-to-end verification has been done. See [Specification](docs/SPEC.md#sandbox-compatibility) for details.
84
+
85
+ ## Learn More
86
+
87
+ - **[Architecture](docs/ARCHITECTURE.md)** — Component map, Mermaid diagrams, hook pipeline, Rust resolver deep dive
88
+ - **[Threat Model](docs/THREAT-MODEL.md)** — What we defend against, what we don't, and design trade-offs
89
+ - **[Specification](docs/SPEC.md)** — Encryption scheme, hook behavior, CLI reference, config schemas
90
+ - **[Testing](docs/TESTING.md)** — 576 tests across 29 files: unit, integration, adversarial, performance, resolver versioning
91
+ - **[Security Audit](docs/SECURITY-AUDIT.md)** — Validation methodology, results, bugs found and fixed
92
+
93
+ ## License
94
+
95
+ MIT
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # OpenClaw Credential Vault — OS-Level Isolation Setup
4
+ #
5
+ # This script sets up the Rust resolver binary with setuid permissions
6
+ # for OS-level credential isolation. Must be run as root.
7
+ #
8
+ # Usage:
9
+ # sudo bash /path/to/vault-setup.sh
10
+ #
11
+ # What it does:
12
+ # 1. Creates 'openclaw-vault' system user (no login, no home directory)
13
+ # 2. Installs the resolver binary to /usr/local/bin/ with setuid
14
+ # 3. Creates /var/lib/openclaw-vault/ with restricted permissions
15
+ # 4. Migrates credential files from user vault to system vault
16
+ # 5. Updates vault config to use binary resolver mode
17
+ #
18
+ set -euo pipefail
19
+
20
+ # ── Require root ──
21
+ if [ "$(id -u)" -ne 0 ]; then
22
+ echo "Error: This script must be run as root."
23
+ echo "Usage: sudo bash $0"
24
+ exit 1
25
+ fi
26
+
27
+ # ── Resolve paths ──
28
+ # OPENCLAW_VAULT_DIR can be overridden; defaults to ~user/.openclaw/vault
29
+ # OPENCLAW_USER is the non-root user who owns the vault
30
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
31
+
32
+ # Auto-detect the calling user (the one who ran sudo)
33
+ if [ -n "${SUDO_USER:-}" ]; then
34
+ OPENCLAW_USER="$SUDO_USER"
35
+ elif [ -n "${OPENCLAW_USER:-}" ]; then
36
+ OPENCLAW_USER="$OPENCLAW_USER"
37
+ else
38
+ echo "Error: Cannot determine the OpenClaw user."
39
+ echo "Run with sudo (which sets SUDO_USER) or set OPENCLAW_USER."
40
+ exit 1
41
+ fi
42
+
43
+ USER_HOME=$(eval echo "~${OPENCLAW_USER}")
44
+ VAULT_DIR="${OPENCLAW_VAULT_DIR:-${USER_HOME}/.openclaw/vault}"
45
+ SYSTEM_VAULT_DIR="/var/lib/openclaw-vault"
46
+ DEST_BINARY="/usr/local/bin/openclaw-vault-resolver"
47
+
48
+ # ── Find the resolver binary ──
49
+ # Search order: bin/<platform>-<arch>/ in package, then dev build paths
50
+ ARCH="$(uname -m)"
51
+ case "$ARCH" in
52
+ x86_64) ARCH_DIR="linux-x64" ;;
53
+ aarch64) ARCH_DIR="linux-arm64" ;;
54
+ *) ARCH_DIR="linux-${ARCH}" ;;
55
+ esac
56
+
57
+ SOURCE_BINARY=""
58
+ SEARCH_PATHS=(
59
+ "${SCRIPT_DIR}/${ARCH_DIR}/openclaw-vault-resolver"
60
+ "${SCRIPT_DIR}/../resolver/target/release/openclaw-vault-resolver"
61
+ "${SCRIPT_DIR}/../resolver/target/x86_64-unknown-linux-musl/release/openclaw-vault-resolver"
62
+ )
63
+
64
+ for p in "${SEARCH_PATHS[@]}"; do
65
+ if [ -f "$p" ]; then
66
+ SOURCE_BINARY="$p"
67
+ break
68
+ fi
69
+ done
70
+
71
+ if [ -z "$SOURCE_BINARY" ]; then
72
+ echo "Error: Resolver binary not found for ${ARCH_DIR}."
73
+ echo "Searched:"
74
+ for p in "${SEARCH_PATHS[@]}"; do
75
+ echo " $p"
76
+ done
77
+ exit 1
78
+ fi
79
+
80
+ echo "OpenClaw Credential Vault — OS-Level Isolation Setup"
81
+ echo "===================================================="
82
+ echo ""
83
+ echo " User: ${OPENCLAW_USER}"
84
+ echo " Vault dir: ${VAULT_DIR}"
85
+ echo " System dir: ${SYSTEM_VAULT_DIR}"
86
+ echo " Binary: ${SOURCE_BINARY}"
87
+ echo ""
88
+
89
+ # ── Step 1: Create system user ──
90
+ if id openclaw-vault &>/dev/null; then
91
+ echo "✓ System user 'openclaw-vault' already exists"
92
+ else
93
+ useradd --system --no-create-home --shell /usr/sbin/nologin openclaw-vault
94
+ echo "✓ Created system user 'openclaw-vault'"
95
+ fi
96
+
97
+ # ── Step 2: Install resolver binary ──
98
+ cp "$SOURCE_BINARY" "$DEST_BINARY"
99
+ chown openclaw-vault:openclaw-vault "$DEST_BINARY"
100
+ chmod u+s,a+rx "$DEST_BINARY"
101
+ echo "✓ Resolver binary installed: ${DEST_BINARY} (setuid openclaw-vault)"
102
+
103
+ # ── Step 3: Create system vault directory ──
104
+ mkdir -p "$SYSTEM_VAULT_DIR"
105
+ chown openclaw-vault:openclaw-vault "$SYSTEM_VAULT_DIR"
106
+ chmod 700 "$SYSTEM_VAULT_DIR"
107
+ echo "✓ System vault directory: ${SYSTEM_VAULT_DIR}"
108
+
109
+ # ── Step 4: Migrate credential files ──
110
+ MIGRATED=0
111
+ if [ -d "$VAULT_DIR" ]; then
112
+ for f in "$VAULT_DIR"/*.enc "$VAULT_DIR"/.vault-meta.json; do
113
+ [ -f "$f" ] || continue
114
+ BASENAME="$(basename "$f")"
115
+ cp "$f" "${SYSTEM_VAULT_DIR}/${BASENAME}"
116
+ chown openclaw-vault:openclaw-vault "${SYSTEM_VAULT_DIR}/${BASENAME}"
117
+ chmod 600 "${SYSTEM_VAULT_DIR}/${BASENAME}"
118
+ MIGRATED=$((MIGRATED + 1))
119
+ done
120
+ fi
121
+
122
+ if [ "$MIGRATED" -gt 0 ]; then
123
+ echo "✓ Migrated ${MIGRATED} file(s) to ${SYSTEM_VAULT_DIR}"
124
+ else
125
+ echo "ℹ No credential files to migrate"
126
+ fi
127
+
128
+ # ── Step 5: Initialize vault if not already done ──
129
+ TOOLS_YAML="${VAULT_DIR}/tools.yaml"
130
+ if [ ! -f "$TOOLS_YAML" ]; then
131
+ echo "Initializing vault..."
132
+ mkdir -p "$VAULT_DIR"
133
+ cat > "$TOOLS_YAML" <<EOF
134
+ version: 1
135
+ masterKeyMode: machine
136
+ resolverMode: binary
137
+ tools: {}
138
+ EOF
139
+ # Create .vault-meta.json
140
+ cat > "${VAULT_DIR}/.vault-meta.json" <<EOF
141
+ {
142
+ "createdAt": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)",
143
+ "installTimestamp": "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)",
144
+ "masterKeyMode": "machine"
145
+ }
146
+ EOF
147
+ chmod 600 "$TOOLS_YAML" "${VAULT_DIR}/.vault-meta.json"
148
+ chown "${OPENCLAW_USER}:${OPENCLAW_USER}" "$VAULT_DIR" "$TOOLS_YAML" "${VAULT_DIR}/.vault-meta.json"
149
+ echo "✓ Vault initialized at ${VAULT_DIR}"
150
+ else
151
+ # Update resolverMode to binary
152
+ if grep -q "resolverMode:" "$TOOLS_YAML"; then
153
+ sed -i 's/resolverMode:.*/resolverMode: binary/' "$TOOLS_YAML"
154
+ else
155
+ echo "resolverMode: binary" >> "$TOOLS_YAML"
156
+ fi
157
+ chown "${OPENCLAW_USER}:${OPENCLAW_USER}" "$TOOLS_YAML"
158
+ echo '✓ Config updated: resolverMode = "binary"'
159
+ fi
160
+
161
+ echo ""
162
+ echo "✓ Setup complete — credentials are now isolated behind OS-user separation."
163
+ echo " Run 'openclaw doctor fix' to restart the gateway with the new configuration."
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Audit logging: append-only JSONL audit log for credential access and scrubbing events.
3
+ *
4
+ * Storage: ~/.openclaw/vault/audit.log (JSONL, one event per line)
5
+ * Permissions: 0600 (Phase 1), owned by openclaw-vault (Phase 2)
6
+ */
7
+ import { AuditEvent } from "./types.js";
8
+ /**
9
+ * Get the audit log file path.
10
+ */
11
+ export declare function getAuditLogPath(vaultDir?: string): string;
12
+ export declare function writeAuditEvent(event: AuditEvent, vaultDir?: string): void;
13
+ /**
14
+ * Log a credential access event.
15
+ */
16
+ export declare function logCredentialAccess(params: {
17
+ tool: string;
18
+ credential: string;
19
+ injectionType: string;
20
+ command: string;
21
+ sessionKey?: string;
22
+ durationMs: number;
23
+ success: boolean;
24
+ }, vaultDir?: string): void;
25
+ /**
26
+ * Log a scrubbing event.
27
+ */
28
+ export declare function logScrubEvent(params: {
29
+ hook: string;
30
+ credential: string;
31
+ pattern: string;
32
+ replacements: number;
33
+ sessionKey?: string;
34
+ }, vaultDir?: string): void;
35
+ /**
36
+ * Log a compaction event.
37
+ */
38
+ export declare function logCompactionEvent(params: {
39
+ sessionKey?: string;
40
+ scrubbingActive: boolean;
41
+ }, vaultDir?: string): void;
42
+ /** Filter options for reading audit log */
43
+ export interface AuditLogFilter {
44
+ tool?: string;
45
+ type?: string;
46
+ last?: string;
47
+ limit?: number;
48
+ }
49
+ /**
50
+ * Parse a duration string like "24h", "7d", "30m" into milliseconds.
51
+ */
52
+ export declare function parseDuration(duration: string): number;
53
+ /**
54
+ * Read audit log events with optional filters.
55
+ */
56
+ export declare function readAuditLog(filter?: AuditLogFilter, vaultDir?: string): AuditEvent[];
57
+ /** Aggregate stats from the audit log */
58
+ export interface AuditStats {
59
+ totalEvents: number;
60
+ credentialAccesses: number;
61
+ scrubEvents: number;
62
+ compactionEvents: number;
63
+ byTool: Record<string, {
64
+ accesses: number;
65
+ scrubs: number;
66
+ lastAccess?: string;
67
+ }>;
68
+ byHook: Record<string, number>;
69
+ }
70
+ /**
71
+ * Compute aggregate stats from the audit log.
72
+ */
73
+ export declare function computeAuditStats(vaultDir?: string): AuditStats;
74
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAGxC;;GAEG;AACH,wBAAgB,eAAe,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAGzD;AAQD,wBAAgB,eAAe,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAuB1E;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAY1B;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAU1B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;CAC1B,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAO1B;AAED,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAatD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,cAAmB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE,CA+CzF;AAED,yCAAyC;AACzC,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,UAAU,CA4C/D"}
package/dist/audit.js ADDED
@@ -0,0 +1,238 @@
1
+ "use strict";
2
+ /**
3
+ * Audit logging: append-only JSONL audit log for credential access and scrubbing events.
4
+ *
5
+ * Storage: ~/.openclaw/vault/audit.log (JSONL, one event per line)
6
+ * Permissions: 0600 (Phase 1), owned by openclaw-vault (Phase 2)
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.getAuditLogPath = getAuditLogPath;
43
+ exports.writeAuditEvent = writeAuditEvent;
44
+ exports.logCredentialAccess = logCredentialAccess;
45
+ exports.logScrubEvent = logScrubEvent;
46
+ exports.logCompactionEvent = logCompactionEvent;
47
+ exports.parseDuration = parseDuration;
48
+ exports.readAuditLog = readAuditLog;
49
+ exports.computeAuditStats = computeAuditStats;
50
+ const fs = __importStar(require("node:fs"));
51
+ const path = __importStar(require("node:path"));
52
+ const config_js_1 = require("./config.js");
53
+ /**
54
+ * Get the audit log file path.
55
+ */
56
+ function getAuditLogPath(vaultDir) {
57
+ const dir = vaultDir ?? (0, config_js_1.getVaultDir)();
58
+ return path.join(dir, "audit.log");
59
+ }
60
+ /**
61
+ * Append an audit event to the log file (JSONL, append-only).
62
+ */
63
+ /** Max audit log size before rotation: 5 MB */
64
+ const MAX_AUDIT_LOG_BYTES = 5 * 1024 * 1024;
65
+ function writeAuditEvent(event, vaultDir) {
66
+ const logPath = getAuditLogPath(vaultDir);
67
+ const dir = path.dirname(logPath);
68
+ // Ensure directory exists
69
+ if (!fs.existsSync(dir)) {
70
+ fs.mkdirSync(dir, { recursive: true });
71
+ }
72
+ // Rotate if log exceeds max size (keep one backup)
73
+ try {
74
+ if (fs.existsSync(logPath)) {
75
+ const stat = fs.statSync(logPath);
76
+ if (stat.size > MAX_AUDIT_LOG_BYTES) {
77
+ const backupPath = logPath + ".1";
78
+ try {
79
+ fs.unlinkSync(backupPath);
80
+ }
81
+ catch { /* no old backup */ }
82
+ fs.renameSync(logPath, backupPath);
83
+ }
84
+ }
85
+ }
86
+ catch { /* non-fatal: continue writing to current log */ }
87
+ const line = JSON.stringify(event) + "\n";
88
+ fs.appendFileSync(logPath, line, { mode: 0o600 });
89
+ }
90
+ /**
91
+ * Log a credential access event.
92
+ */
93
+ function logCredentialAccess(params, vaultDir) {
94
+ writeAuditEvent({
95
+ type: "credential_access",
96
+ timestamp: new Date().toISOString(),
97
+ tool: params.tool,
98
+ credential: params.credential,
99
+ injectionType: params.injectionType,
100
+ command: params.command,
101
+ sessionKey: params.sessionKey ?? "unknown",
102
+ durationMs: params.durationMs,
103
+ success: params.success,
104
+ }, vaultDir);
105
+ }
106
+ /**
107
+ * Log a scrubbing event.
108
+ */
109
+ function logScrubEvent(params, vaultDir) {
110
+ writeAuditEvent({
111
+ type: "scrub",
112
+ timestamp: new Date().toISOString(),
113
+ hook: params.hook,
114
+ credential: params.credential,
115
+ pattern: params.pattern,
116
+ replacements: params.replacements,
117
+ sessionKey: params.sessionKey ?? "unknown",
118
+ }, vaultDir);
119
+ }
120
+ /**
121
+ * Log a compaction event.
122
+ */
123
+ function logCompactionEvent(params, vaultDir) {
124
+ writeAuditEvent({
125
+ type: "compaction",
126
+ timestamp: new Date().toISOString(),
127
+ sessionKey: params.sessionKey ?? "unknown",
128
+ scrubbingActive: params.scrubbingActive,
129
+ }, vaultDir);
130
+ }
131
+ /**
132
+ * Parse a duration string like "24h", "7d", "30m" into milliseconds.
133
+ */
134
+ function parseDuration(duration) {
135
+ const match = duration.match(/^(\d+)(m|h|d)$/);
136
+ if (!match)
137
+ throw new Error(`Invalid duration format: ${duration} (use e.g., 24h, 7d, 30m)`);
138
+ const value = parseInt(match[1], 10);
139
+ const unit = match[2];
140
+ switch (unit) {
141
+ case "m": return value * 60 * 1000;
142
+ case "h": return value * 60 * 60 * 1000;
143
+ case "d": return value * 24 * 60 * 60 * 1000;
144
+ default: throw new Error(`Unknown duration unit: ${unit}`);
145
+ }
146
+ }
147
+ /**
148
+ * Read audit log events with optional filters.
149
+ */
150
+ function readAuditLog(filter = {}, vaultDir) {
151
+ const logPath = getAuditLogPath(vaultDir);
152
+ if (!fs.existsSync(logPath)) {
153
+ return [];
154
+ }
155
+ const content = fs.readFileSync(logPath, "utf8");
156
+ const lines = content.trim().split("\n").filter(Boolean);
157
+ let events = [];
158
+ for (const line of lines) {
159
+ try {
160
+ events.push(JSON.parse(line));
161
+ }
162
+ catch {
163
+ // Skip malformed lines
164
+ }
165
+ }
166
+ // Apply type filter
167
+ if (filter.type) {
168
+ events = events.filter((e) => e.type === filter.type);
169
+ }
170
+ // Apply tool filter
171
+ if (filter.tool) {
172
+ events = events.filter((e) => {
173
+ if (e.type === "credential_access")
174
+ return e.tool === filter.tool || e.credential === filter.tool;
175
+ if (e.type === "scrub")
176
+ return e.credential === filter.tool;
177
+ return false;
178
+ });
179
+ }
180
+ // Apply time filter
181
+ if (filter.last) {
182
+ const ms = parseDuration(filter.last);
183
+ const cutoff = Date.now() - ms;
184
+ events = events.filter((e) => new Date(e.timestamp).getTime() >= cutoff);
185
+ }
186
+ // Apply limit (take last N)
187
+ const limit = filter.limit ?? 50;
188
+ if (events.length > limit) {
189
+ events = events.slice(-limit);
190
+ }
191
+ return events;
192
+ }
193
+ /**
194
+ * Compute aggregate stats from the audit log.
195
+ */
196
+ function computeAuditStats(vaultDir) {
197
+ const events = readAuditLog({ limit: Infinity }, vaultDir);
198
+ const stats = {
199
+ totalEvents: events.length,
200
+ credentialAccesses: 0,
201
+ scrubEvents: 0,
202
+ compactionEvents: 0,
203
+ byTool: {},
204
+ byHook: {},
205
+ };
206
+ for (const event of events) {
207
+ switch (event.type) {
208
+ case "credential_access": {
209
+ stats.credentialAccesses++;
210
+ const tool = event.credential;
211
+ if (!stats.byTool[tool]) {
212
+ stats.byTool[tool] = { accesses: 0, scrubs: 0 };
213
+ }
214
+ stats.byTool[tool].accesses++;
215
+ stats.byTool[tool].lastAccess = event.timestamp;
216
+ break;
217
+ }
218
+ case "scrub": {
219
+ stats.scrubEvents++;
220
+ const tool = event.credential;
221
+ if (!stats.byTool[tool]) {
222
+ stats.byTool[tool] = { accesses: 0, scrubs: 0 };
223
+ }
224
+ stats.byTool[tool].scrubs++;
225
+ if (!stats.byHook[event.hook]) {
226
+ stats.byHook[event.hook] = 0;
227
+ }
228
+ stats.byHook[event.hook]++;
229
+ break;
230
+ }
231
+ case "compaction":
232
+ stats.compactionEvents++;
233
+ break;
234
+ }
235
+ }
236
+ return stats;
237
+ }
238
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}