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.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/bin/linux-x64/openclaw-vault-resolver +0 -0
- package/bin/vault-setup.sh +163 -0
- package/dist/audit.d.ts +74 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +238 -0
- package/dist/audit.js.map +1 -0
- package/dist/browser.d.ts +125 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +326 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +32 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1059 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +60 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +261 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto.d.ts +53 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +300 -0
- package/dist/crypto.js.map +1 -0
- package/dist/guesser.d.ts +78 -0
- package/dist/guesser.d.ts.map +1 -0
- package/dist/guesser.js +361 -0
- package/dist/guesser.js.map +1 -0
- package/dist/index.d.ts +212 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +767 -0
- package/dist/index.js.map +1 -0
- package/dist/registry.d.ts +47 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +296 -0
- package/dist/registry.js.map +1 -0
- package/dist/resolver.d.ts +37 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +200 -0
- package/dist/resolver.js.map +1 -0
- package/dist/scrubber.d.ts +93 -0
- package/dist/scrubber.d.ts.map +1 -0
- package/dist/scrubber.js +304 -0
- package/dist/scrubber.js.map +1 -0
- package/dist/types.d.ts +235 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/vault-status.d.ts +15 -0
- package/dist/vault-status.d.ts.map +1 -0
- package/dist/vault-status.js +112 -0
- package/dist/vault-status.js.map +1 -0
- package/openclaw.plugin.json +23 -0
- package/package.json +60 -0
- 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
|
|
Binary file
|
|
@@ -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."
|
package/dist/audit.d.ts
ADDED
|
@@ -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":";AAAA;;;;;GAKG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUH,0CAGC;AAQD,0CAuBC;AAKD,kDAoBC;AAKD,sCAgBC;AAKD,gDAUC;AAaD,sCAaC;AAKD,oCA+CC;AAeD,8CA4CC;AAhPD,4CAA8B;AAC9B,gDAAkC;AAElC,2CAA0C;AAE1C;;GAEG;AACH,SAAgB,eAAe,CAAC,QAAiB;IAC/C,MAAM,GAAG,GAAG,QAAQ,IAAI,IAAA,uBAAW,GAAE,CAAC;IACtC,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;AACrC,CAAC;AAED;;GAEG;AACH,+CAA+C;AAC/C,MAAM,mBAAmB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE5C,SAAgB,eAAe,CAAC,KAAiB,EAAE,QAAiB;IAClE,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAElC,0BAA0B;IAC1B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,mDAAmD;IACnD,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAClC,IAAI,IAAI,CAAC,IAAI,GAAG,mBAAmB,EAAE,CAAC;gBACpC,MAAM,UAAU,GAAG,OAAO,GAAG,IAAI,CAAC;gBAClC,IAAI,CAAC;oBAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,CAAC;gBAChE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAC,gDAAgD,CAAC,CAAC;IAE5D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAC1C,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACpD,CAAC;AAED;;GAEG;AACH,SAAgB,mBAAmB,CAAC,MAQnC,EAAE,QAAiB;IAClB,eAAe,CAAC;QACd,IAAI,EAAE,mBAAmB;QACzB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,SAAS;QAC1C,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,EAAE,QAAQ,CAAC,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAgB,aAAa,CAAC,MAM7B,EAAE,QAAiB;IAClB,eAAe,CAAC;QACd,IAAI,EAAE,OAAO;QACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,SAAS;KAC3C,EAAE,QAAQ,CAAC,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB,CAAC,MAGlC,EAAE,QAAiB;IAClB,eAAe,CAAC;QACd,IAAI,EAAE,YAAY;QAClB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,SAAS;QAC1C,eAAe,EAAE,MAAM,CAAC,eAAe;KACxC,EAAE,QAAQ,CAAC,CAAC;AACf,CAAC;AAUD;;GAEG;AACH,SAAgB,aAAa,CAAC,QAAgB;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC/C,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,2BAA2B,CAAC,CAAC;IAE7F,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAEtB,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,GAAG,CAAC,CAAC,OAAO,KAAK,GAAG,EAAE,GAAG,IAAI,CAAC;QACnC,KAAK,GAAG,CAAC,CAAC,OAAO,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QACxC,KAAK,GAAG,CAAC,CAAC,OAAO,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAC7C,OAAO,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,SAAyB,EAAE,EAAE,QAAiB;IACzE,MAAM,OAAO,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAE1C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEzD,IAAI,MAAM,GAAiB,EAAE,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAe,CAAC,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC;IACxD,CAAC;IAED,oBAAoB;IACpB,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YAC3B,IAAI,CAAC,CAAC,IAAI,KAAK,mBAAmB;gBAAE,OAAO,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,IAAI,CAAC;YAClG,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;gBAAE,OAAO,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,IAAI,CAAC;YAC5D,OAAO,KAAK,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;IAED,oBAAoB;IACpB,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,MAAM,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;QAC/B,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,IAAI,MAAM,CAAC,CAAC;IAC3E,CAAC;IAED,4BAA4B;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IACjC,IAAI,MAAM,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC;QAC1B,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAYD;;GAEG;AACH,SAAgB,iBAAiB,CAAC,QAAiB;IACjD,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC;IAE3D,MAAM,KAAK,GAAe;QACxB,WAAW,EAAE,MAAM,CAAC,MAAM;QAC1B,kBAAkB,EAAE,CAAC;QACrB,WAAW,EAAE,CAAC;QACd,gBAAgB,EAAE,CAAC;QACnB,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,EAAE;KACX,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,mBAAmB,CAAC,CAAC,CAAC;gBACzB,KAAK,CAAC,kBAAkB,EAAE,CAAC;gBAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC;gBAC9B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;oBACxB,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;gBAClD,CAAC;gBACD,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAC9B,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC;gBAChD,MAAM;YACR,CAAC;YACD,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,KAAK,CAAC,WAAW,EAAE,CAAC;gBACpB,MAAM,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC;gBAC9B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;oBACxB,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;gBAClD,CAAC;gBACD,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;gBAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC9B,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC/B,CAAC;gBACD,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3B,MAAM;YACR,CAAC;YACD,KAAK,YAAY;gBACf,KAAK,CAAC,gBAAgB,EAAE,CAAC;gBACzB,MAAM;QACV,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|