@token-security/clawdit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +197 -0
- package/dist/checks/auth-001-device-auth-disabled.d.ts +10 -0
- package/dist/checks/auth-001-device-auth-disabled.js +34 -0
- package/dist/checks/auth-002-insecure-fallback.d.ts +10 -0
- package/dist/checks/auth-002-insecure-fallback.js +34 -0
- package/dist/checks/auth-003-no-gateway-auth.d.ts +10 -0
- package/dist/checks/auth-003-no-gateway-auth.js +40 -0
- package/dist/checks/auth-004-public-trusted-proxies.d.ts +10 -0
- package/dist/checks/auth-004-public-trusted-proxies.js +37 -0
- package/dist/checks/auth-005-hooks-no-token.d.ts +10 -0
- package/dist/checks/auth-005-hooks-no-token.js +42 -0
- package/dist/checks/auth-006-pairing-exposed.d.ts +10 -0
- package/dist/checks/auth-006-pairing-exposed.js +46 -0
- package/dist/checks/auth-007-missing-trusted-proxies.d.ts +11 -0
- package/dist/checks/auth-007-missing-trusted-proxies.js +46 -0
- package/dist/checks/chan-001-open-dm.d.ts +10 -0
- package/dist/checks/chan-001-open-dm.js +48 -0
- package/dist/checks/chan-002-group-policy.d.ts +10 -0
- package/dist/checks/chan-002-group-policy.js +43 -0
- package/dist/checks/chan-003-no-mention.d.ts +10 -0
- package/dist/checks/chan-003-no-mention.js +45 -0
- package/dist/checks/chan-004-dm-isolation.d.ts +10 -0
- package/dist/checks/chan-004-dm-isolation.js +50 -0
- package/dist/checks/chan-005-verbose-groups.d.ts +10 -0
- package/dist/checks/chan-005-verbose-groups.js +53 -0
- package/dist/checks/disc-001-mdns-full.d.ts +10 -0
- package/dist/checks/disc-001-mdns-full.js +34 -0
- package/dist/checks/disc-002-mdns-enabled.d.ts +10 -0
- package/dist/checks/disc-002-mdns-enabled.js +35 -0
- package/dist/checks/exec-001-full-security.d.ts +10 -0
- package/dist/checks/exec-001-full-security.js +34 -0
- package/dist/checks/exec-002-sandbox-disabled.d.ts +10 -0
- package/dist/checks/exec-002-sandbox-disabled.js +34 -0
- package/dist/checks/exec-003-elevated-unrestricted.d.ts +10 -0
- package/dist/checks/exec-003-elevated-unrestricted.js +38 -0
- package/dist/checks/exec-004-approval-fallback.d.ts +10 -0
- package/dist/checks/exec-004-approval-fallback.js +50 -0
- package/dist/checks/exec-005-sandbox-non-main.d.ts +10 -0
- package/dist/checks/exec-005-sandbox-non-main.js +34 -0
- package/dist/checks/exec-006-cross-agent-sandbox.d.ts +10 -0
- package/dist/checks/exec-006-cross-agent-sandbox.js +34 -0
- package/dist/checks/exec-007-workspace-rw.d.ts +10 -0
- package/dist/checks/exec-007-workspace-rw.js +34 -0
- package/dist/checks/index.d.ts +16 -0
- package/dist/checks/index.js +94 -0
- package/dist/checks/loader.d.ts +38 -0
- package/dist/checks/loader.js +149 -0
- package/dist/checks/model-001-weak-model-tools.d.ts +10 -0
- package/dist/checks/model-001-weak-model-tools.js +68 -0
- package/dist/checks/net-001-gateway-binding.d.ts +10 -0
- package/dist/checks/net-001-gateway-binding.js +34 -0
- package/dist/checks/net-002-default-port.d.ts +10 -0
- package/dist/checks/net-002-default-port.js +35 -0
- package/dist/checks/net-003-tailnet-no-token.d.ts +10 -0
- package/dist/checks/net-003-tailnet-no-token.js +34 -0
- package/dist/checks/plug-001-no-allowlist.d.ts +10 -0
- package/dist/checks/plug-001-no-allowlist.js +52 -0
- package/dist/checks/plug-002-extensions-exposed.d.ts +10 -0
- package/dist/checks/plug-002-extensions-exposed.js +41 -0
- package/dist/checks/runner.d.ts +14 -0
- package/dist/checks/runner.js +72 -0
- package/dist/checks/schema.d.ts +54 -0
- package/dist/checks/schema.js +171 -0
- package/dist/checks/sec-001-hardcoded-keys.d.ts +10 -0
- package/dist/checks/sec-001-hardcoded-keys.js +34 -0
- package/dist/checks/sec-002-world-readable-config.d.ts +10 -0
- package/dist/checks/sec-002-world-readable-config.js +39 -0
- package/dist/checks/sec-003-credentials-exposed.d.ts +10 -0
- package/dist/checks/sec-003-credentials-exposed.js +41 -0
- package/dist/checks/sec-004-env-readable.d.ts +10 -0
- package/dist/checks/sec-004-env-readable.js +40 -0
- package/dist/checks/sec-005-transcripts-exposed.d.ts +11 -0
- package/dist/checks/sec-005-transcripts-exposed.js +62 -0
- package/dist/checks/sec-006-redaction-disabled.d.ts +10 -0
- package/dist/checks/sec-006-redaction-disabled.js +34 -0
- package/dist/checks/sec-007-no-redact-patterns.d.ts +10 -0
- package/dist/checks/sec-007-no-redact-patterns.js +39 -0
- package/dist/checks/sec-008-state-dir-permissions.d.ts +10 -0
- package/dist/checks/sec-008-state-dir-permissions.js +49 -0
- package/dist/checks/types.d.ts +45 -0
- package/dist/checks/types.js +2 -0
- package/dist/clawdit-output.schema.json +162 -0
- package/dist/cli.d.ts +23 -0
- package/dist/cli.js +132 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +150 -0
- package/dist/formatter.d.ts +42 -0
- package/dist/formatter.js +233 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +168 -0
- package/dist/utils.d.ts +46 -0
- package/dist/utils.js +146 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Token Security
|
|
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,197 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/logo.png" alt="clawdit" width="400">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](package.json)
|
|
7
|
+
[](https://github.com/token-security/clawdit)
|
|
8
|
+
|
|
9
|
+
> Security audit tool for OpenClaw configurations. Find misconfigurations before attackers do.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Auto-discovery** - Finds config files in standard locations
|
|
14
|
+
- **35 security checks** - Across 8 categories (network, auth, execution, secrets, and more)
|
|
15
|
+
- **CI/CD ready** - JSON output with structured exit codes
|
|
16
|
+
- **Stdin support** - Pipe configs directly for scripting
|
|
17
|
+
- **Zero config** - Works out of the box
|
|
18
|
+
|
|
19
|
+
## Demo
|
|
20
|
+
|
|
21
|
+
<!-- PLACEHOLDER: Add a terminal GIF or screenshot showing clawdit in action -->
|
|
22
|
+
<!-- Tools like asciinema, terminalizer, or vhs can record terminal sessions -->
|
|
23
|
+
<!-- Example:  -->
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
$ clawdit
|
|
27
|
+
clawdit v0.1.0
|
|
28
|
+
|
|
29
|
+
Scanning: /home/user/.openclaw/openclaw.json
|
|
30
|
+
|
|
31
|
+
HIGH NET-001 Gateway binding to 0.0.0.0 exposes service to all interfaces
|
|
32
|
+
HIGH AUTH-002 No authentication configured for gateway
|
|
33
|
+
MED SEC-003 Config file has overly permissive permissions (0644)
|
|
34
|
+
|
|
35
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
36
|
+
Summary: 2 HIGH, 1 MEDIUM, 0 LOW (35 checks run)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install -g @token-security/clawdit
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or from source:
|
|
48
|
+
```bash
|
|
49
|
+
git clone https://github.com/token-security/clawdit.git
|
|
50
|
+
cd clawdit && npm install && npm run build
|
|
51
|
+
npm install -g .
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Uninstall
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm uninstall -g @token-security/clawdit
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Usage
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
clawdit # Auto-discover config and audit
|
|
64
|
+
clawdit /path/to/config.json # Audit specific file
|
|
65
|
+
clawdit - # Read from stdin
|
|
66
|
+
clawdit --format=json # JSON output (default when piped)
|
|
67
|
+
clawdit --list-checks # List all security checks
|
|
68
|
+
clawdit --severity=high # Only show HIGH findings
|
|
69
|
+
clawdit --help # Full options
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Security Checks
|
|
73
|
+
|
|
74
|
+
### Network (NET)
|
|
75
|
+
|
|
76
|
+
| ID | Severity | Description |
|
|
77
|
+
|----|----------|-------------|
|
|
78
|
+
| NET-001 | HIGH | Gateway bound to all interfaces |
|
|
79
|
+
| NET-002 | MEDIUM | Non-default gateway port |
|
|
80
|
+
| NET-003 | LOW | Gateway bound to tailnet without token auth |
|
|
81
|
+
|
|
82
|
+
### Authentication (AUTH)
|
|
83
|
+
|
|
84
|
+
| ID | Severity | Description |
|
|
85
|
+
|----|----------|-------------|
|
|
86
|
+
| AUTH-001 | HIGH | Device authentication disabled |
|
|
87
|
+
| AUTH-002 | HIGH | Insecure auth fallback enabled |
|
|
88
|
+
| AUTH-003 | HIGH | No gateway authentication configured |
|
|
89
|
+
| AUTH-004 | MEDIUM | Trusted proxies includes non-private IPs |
|
|
90
|
+
| AUTH-005 | MEDIUM | Hooks token not configured |
|
|
91
|
+
| AUTH-006 | MEDIUM | Node pairing credentials exposed |
|
|
92
|
+
| AUTH-007 | LOW | Missing trusted proxies configuration |
|
|
93
|
+
|
|
94
|
+
### Execution (EXEC)
|
|
95
|
+
|
|
96
|
+
| ID | Severity | Description |
|
|
97
|
+
|----|----------|-------------|
|
|
98
|
+
| EXEC-001 | HIGH | Exec security set to full |
|
|
99
|
+
| EXEC-002 | HIGH | Sandbox disabled for all sessions |
|
|
100
|
+
| EXEC-003 | HIGH | Elevated mode enabled without restrictions |
|
|
101
|
+
| EXEC-004 | MEDIUM | Exec approval fallback not set to deny |
|
|
102
|
+
| EXEC-005 | MEDIUM | Sandbox only protects non-main sessions |
|
|
103
|
+
| EXEC-006 | HIGH | Sandbox scope enables cross-agent access |
|
|
104
|
+
| EXEC-007 | MEDIUM | Workspace access too permissive |
|
|
105
|
+
|
|
106
|
+
### Secrets (SEC)
|
|
107
|
+
|
|
108
|
+
| ID | Severity | Description |
|
|
109
|
+
|----|----------|-------------|
|
|
110
|
+
| SEC-001 | HIGH | API keys hardcoded in config |
|
|
111
|
+
| SEC-002 | HIGH | Configuration file is world-readable |
|
|
112
|
+
| SEC-003 | HIGH | Credentials directory exposed |
|
|
113
|
+
| SEC-004 | MEDIUM | .env file is readable by other users |
|
|
114
|
+
| SEC-005 | MEDIUM | Session transcripts exposed |
|
|
115
|
+
| SEC-006 | LOW | Log redaction disabled |
|
|
116
|
+
| SEC-007 | LOW | No custom redact patterns |
|
|
117
|
+
| SEC-008 | MEDIUM | State directory has insecure permissions |
|
|
118
|
+
|
|
119
|
+
### Discovery (DISC)
|
|
120
|
+
|
|
121
|
+
| ID | Severity | Description |
|
|
122
|
+
|----|----------|-------------|
|
|
123
|
+
| DISC-001 | MEDIUM | mDNS full mode exposes system info |
|
|
124
|
+
| DISC-002 | LOW | mDNS enabled (information disclosure) |
|
|
125
|
+
|
|
126
|
+
### Channels (CHAN)
|
|
127
|
+
|
|
128
|
+
| ID | Severity | Description |
|
|
129
|
+
|----|----------|-------------|
|
|
130
|
+
| CHAN-001 | HIGH | Open DM policy without allowlist |
|
|
131
|
+
| CHAN-002 | MEDIUM | Group policy not set to allowlist |
|
|
132
|
+
| CHAN-003 | LOW | Require mention disabled in groups |
|
|
133
|
+
| CHAN-004 | MEDIUM | DM session isolation disabled |
|
|
134
|
+
| CHAN-005 | LOW | Verbose/reasoning enabled in groups |
|
|
135
|
+
|
|
136
|
+
### Model (MODEL)
|
|
137
|
+
|
|
138
|
+
| ID | Severity | Description |
|
|
139
|
+
|----|----------|-------------|
|
|
140
|
+
| MODEL-001 | LOW | Weak model with tools enabled |
|
|
141
|
+
|
|
142
|
+
### Plugins (PLUG)
|
|
143
|
+
|
|
144
|
+
| ID | Severity | Description |
|
|
145
|
+
|----|----------|-------------|
|
|
146
|
+
| PLUG-001 | MEDIUM | Plugins without explicit allowlist |
|
|
147
|
+
| PLUG-002 | MEDIUM | Plugin directory permissions exposed |
|
|
148
|
+
|
|
149
|
+
## Scripting
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Check exit code in scripts
|
|
153
|
+
clawdit config.json && echo "All clear" || echo "Issues found: $?"
|
|
154
|
+
|
|
155
|
+
# Parse JSON output
|
|
156
|
+
clawdit --format=json | jq '.findings[] | {id, severity, name}'
|
|
157
|
+
|
|
158
|
+
# Pipe config from another command
|
|
159
|
+
cat config.json | clawdit -
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Exit Codes
|
|
163
|
+
|
|
164
|
+
| Code | Meaning |
|
|
165
|
+
|------|---------|
|
|
166
|
+
| 0 | All checks passed |
|
|
167
|
+
| 1 | HIGH severity findings |
|
|
168
|
+
| 2 | MEDIUM severity findings |
|
|
169
|
+
| 3 | LOW severity findings |
|
|
170
|
+
| 10+ | Configuration/runtime errors |
|
|
171
|
+
|
|
172
|
+
## Versioning
|
|
173
|
+
|
|
174
|
+
This project follows [Semantic Versioning](https://semver.org/):
|
|
175
|
+
|
|
176
|
+
| Version Bump | When to Use | Example |
|
|
177
|
+
|--------------|-------------|---------|
|
|
178
|
+
| **MAJOR** (x.0.0) | Breaking changes: removed checks, changed exit codes, incompatible CLI flags | 1.0.0 → 2.0.0 |
|
|
179
|
+
| **MINOR** (0.x.0) | New features: new checks, new output formats, new CLI options | 0.1.0 → 0.2.0 |
|
|
180
|
+
| **PATCH** (0.0.x) | Bug fixes: fixed false positives, documentation updates | 0.1.0 → 0.1.1 |
|
|
181
|
+
|
|
182
|
+
To release a new version:
|
|
183
|
+
```bash
|
|
184
|
+
npm version patch|minor|major
|
|
185
|
+
git push --follow-tags
|
|
186
|
+
npm publish --access public
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Contributing
|
|
190
|
+
|
|
191
|
+
PRs welcome! Please run `npm test` before submitting.
|
|
192
|
+
|
|
193
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines and [CLAUDE.md](CLAUDE.md) for architecture details.
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
[MIT](LICENSE) - Token Security
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-001: Device authentication disabled
|
|
3
|
+
*
|
|
4
|
+
* Detects when device authentication is explicitly disabled, allowing
|
|
5
|
+
* unauthorized control of the gateway.
|
|
6
|
+
*/
|
|
7
|
+
import type { Check } from './types.js';
|
|
8
|
+
declare const check: Check;
|
|
9
|
+
export default check;
|
|
10
|
+
//# sourceMappingURL=auth-001-device-auth-disabled.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-001: Device authentication disabled
|
|
3
|
+
*
|
|
4
|
+
* Detects when device authentication is explicitly disabled, allowing
|
|
5
|
+
* unauthorized control of the gateway.
|
|
6
|
+
*/
|
|
7
|
+
import { getValueAtPath } from '../utils.js';
|
|
8
|
+
const check = {
|
|
9
|
+
id: 'AUTH-001',
|
|
10
|
+
severity: 'HIGH',
|
|
11
|
+
name: 'Device authentication disabled',
|
|
12
|
+
execute(ctx) {
|
|
13
|
+
const disabled = getValueAtPath(ctx.config, 'gateway.controlUi.dangerouslyDisableDeviceAuth');
|
|
14
|
+
if (disabled === true) {
|
|
15
|
+
return [{
|
|
16
|
+
id: 'AUTH-001',
|
|
17
|
+
severity: 'HIGH',
|
|
18
|
+
name: 'Device authentication disabled',
|
|
19
|
+
location: { file: ctx.configPath, path: 'gateway.controlUi.dangerouslyDisableDeviceAuth' },
|
|
20
|
+
currentValue: true,
|
|
21
|
+
expectedValue: 'false (or not set)',
|
|
22
|
+
risk: 'Attackers can bypass device identity verification, enabling unauthorized control of the gateway from any paired client.',
|
|
23
|
+
fix: {
|
|
24
|
+
description: 'Remove or set to false in config',
|
|
25
|
+
command: `jq 'del(.gateway.controlUi.dangerouslyDisableDeviceAuth)' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
|
|
26
|
+
},
|
|
27
|
+
references: ['https://docs.openclaw.ai/gateway/security#device-authentication'],
|
|
28
|
+
}];
|
|
29
|
+
}
|
|
30
|
+
return [];
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
export default check;
|
|
34
|
+
//# sourceMappingURL=auth-001-device-auth-disabled.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-002: Insecure auth fallback enabled
|
|
3
|
+
*
|
|
4
|
+
* Detects when insecure authentication fallback is enabled, allowing
|
|
5
|
+
* authentication to be bypassed.
|
|
6
|
+
*/
|
|
7
|
+
import type { Check } from './types.js';
|
|
8
|
+
declare const check: Check;
|
|
9
|
+
export default check;
|
|
10
|
+
//# sourceMappingURL=auth-002-insecure-fallback.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-002: Insecure auth fallback enabled
|
|
3
|
+
*
|
|
4
|
+
* Detects when insecure authentication fallback is enabled, allowing
|
|
5
|
+
* authentication to be bypassed.
|
|
6
|
+
*/
|
|
7
|
+
import { getValueAtPath } from '../utils.js';
|
|
8
|
+
const check = {
|
|
9
|
+
id: 'AUTH-002',
|
|
10
|
+
severity: 'HIGH',
|
|
11
|
+
name: 'Insecure auth fallback enabled',
|
|
12
|
+
execute(ctx) {
|
|
13
|
+
const insecure = getValueAtPath(ctx.config, 'gateway.controlUi.allowInsecureAuth');
|
|
14
|
+
if (insecure === true) {
|
|
15
|
+
return [{
|
|
16
|
+
id: 'AUTH-002',
|
|
17
|
+
severity: 'HIGH',
|
|
18
|
+
name: 'Insecure auth fallback enabled',
|
|
19
|
+
location: { file: ctx.configPath, path: 'gateway.controlUi.allowInsecureAuth' },
|
|
20
|
+
currentValue: true,
|
|
21
|
+
expectedValue: 'false (or not set)',
|
|
22
|
+
risk: 'Authentication can be bypassed using insecure fallback mechanisms, allowing unauthorized access.',
|
|
23
|
+
fix: {
|
|
24
|
+
description: 'Remove or set to false in config',
|
|
25
|
+
command: `jq 'del(.gateway.controlUi.allowInsecureAuth)' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
|
|
26
|
+
},
|
|
27
|
+
references: ['https://docs.openclaw.ai/gateway/security#authentication'],
|
|
28
|
+
}];
|
|
29
|
+
}
|
|
30
|
+
return [];
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
export default check;
|
|
34
|
+
//# sourceMappingURL=auth-002-insecure-fallback.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-003: No gateway authentication configured
|
|
3
|
+
*
|
|
4
|
+
* Detects when the gateway is network-accessible but has no authentication
|
|
5
|
+
* configured, allowing anyone to control it.
|
|
6
|
+
*/
|
|
7
|
+
import type { Check } from './types.js';
|
|
8
|
+
declare const check: Check;
|
|
9
|
+
export default check;
|
|
10
|
+
//# sourceMappingURL=auth-003-no-gateway-auth.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-003: No gateway authentication configured
|
|
3
|
+
*
|
|
4
|
+
* Detects when the gateway is network-accessible but has no authentication
|
|
5
|
+
* configured, allowing anyone to control it.
|
|
6
|
+
*/
|
|
7
|
+
import { getValueAtPath, hasGatewayAuth } from '../utils.js';
|
|
8
|
+
const check = {
|
|
9
|
+
id: 'AUTH-003',
|
|
10
|
+
severity: 'HIGH',
|
|
11
|
+
name: 'No gateway authentication configured',
|
|
12
|
+
execute(ctx) {
|
|
13
|
+
const bind = getValueAtPath(ctx.config, 'gateway.bind');
|
|
14
|
+
// Skip if gateway.bind is not configured (safe default)
|
|
15
|
+
if (bind === undefined)
|
|
16
|
+
return [];
|
|
17
|
+
// Only check if not bound to loopback
|
|
18
|
+
if (bind !== 'loopback' && bind !== '127.0.0.1' && bind !== 'localhost') {
|
|
19
|
+
if (!hasGatewayAuth(ctx.config)) {
|
|
20
|
+
return [{
|
|
21
|
+
id: 'AUTH-003',
|
|
22
|
+
severity: 'HIGH',
|
|
23
|
+
name: 'No gateway authentication configured',
|
|
24
|
+
location: { file: ctx.configPath, path: 'gateway.auth' },
|
|
25
|
+
currentValue: 'not configured',
|
|
26
|
+
expectedValue: 'gateway.auth.token or gateway.auth.password set',
|
|
27
|
+
risk: 'The gateway is network-accessible but has no authentication configured. Anyone who can reach the gateway can control it.',
|
|
28
|
+
fix: {
|
|
29
|
+
description: 'Configure authentication token',
|
|
30
|
+
command: `jq '.gateway.auth.token = "YOUR_SECRET_TOKEN"' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
|
|
31
|
+
},
|
|
32
|
+
references: ['https://docs.openclaw.ai/gateway/security#authentication'],
|
|
33
|
+
}];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return [];
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export default check;
|
|
40
|
+
//# sourceMappingURL=auth-003-no-gateway-auth.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-004: Trusted proxies includes non-private IPs
|
|
3
|
+
*
|
|
4
|
+
* Detects when the trusted proxies list contains public (non-RFC1918/RFC6598) IP addresses,
|
|
5
|
+
* which could allow IP spoofing attacks.
|
|
6
|
+
*/
|
|
7
|
+
import type { Check } from './types.js';
|
|
8
|
+
declare const check: Check;
|
|
9
|
+
export default check;
|
|
10
|
+
//# sourceMappingURL=auth-004-public-trusted-proxies.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-004: Trusted proxies includes non-private IPs
|
|
3
|
+
*
|
|
4
|
+
* Detects when the trusted proxies list contains public (non-RFC1918/RFC6598) IP addresses,
|
|
5
|
+
* which could allow IP spoofing attacks.
|
|
6
|
+
*/
|
|
7
|
+
import { getValueAtPath, isPrivateIP } from '../utils.js';
|
|
8
|
+
const check = {
|
|
9
|
+
id: 'AUTH-004',
|
|
10
|
+
severity: 'MEDIUM',
|
|
11
|
+
name: 'Trusted proxies includes non-private IPs',
|
|
12
|
+
execute(ctx) {
|
|
13
|
+
const trustedProxies = getValueAtPath(ctx.config, 'gateway.trustedProxies');
|
|
14
|
+
if (!Array.isArray(trustedProxies))
|
|
15
|
+
return [];
|
|
16
|
+
const publicIPs = trustedProxies.filter((ip) => typeof ip === 'string' && !isPrivateIP(ip));
|
|
17
|
+
if (publicIPs.length > 0) {
|
|
18
|
+
return [{
|
|
19
|
+
id: 'AUTH-004',
|
|
20
|
+
severity: 'MEDIUM',
|
|
21
|
+
name: 'Trusted proxies includes non-private IPs',
|
|
22
|
+
location: { file: ctx.configPath, path: 'gateway.trustedProxies' },
|
|
23
|
+
currentValue: publicIPs.join(', '),
|
|
24
|
+
expectedValue: 'Only private IPs (RFC1918/RFC6598)',
|
|
25
|
+
risk: `Public IP addresses in trusted proxies can be spoofed. Attackers could manipulate X-Forwarded-For headers to bypass IP-based access controls.`,
|
|
26
|
+
fix: {
|
|
27
|
+
description: 'Remove public IPs from trusted proxies',
|
|
28
|
+
command: `jq '.gateway.trustedProxies = [.gateway.trustedProxies[] | select(. | test("^(10\\\\.|172\\\\.(1[6-9]|2[0-9]|3[01])\\\\.|192\\\\.168\\\\.|127\\\\.|100\\\\.(6[4-9]|[7-9][0-9]|1[01][0-9]|12[0-7])\\\\.)") or . == "loopback" or . == "localhost")]' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
|
|
29
|
+
},
|
|
30
|
+
references: ['https://docs.openclaw.ai/gateway/security#trusted-proxies'],
|
|
31
|
+
}];
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
export default check;
|
|
37
|
+
//# sourceMappingURL=auth-004-public-trusted-proxies.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-005: Hooks token not configured
|
|
3
|
+
*
|
|
4
|
+
* Detects when hooks are enabled but no authentication token is configured,
|
|
5
|
+
* allowing unauthenticated webhook requests.
|
|
6
|
+
*/
|
|
7
|
+
import type { Check } from './types.js';
|
|
8
|
+
declare const check: Check;
|
|
9
|
+
export default check;
|
|
10
|
+
//# sourceMappingURL=auth-005-hooks-no-token.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-005: Hooks token not configured
|
|
3
|
+
*
|
|
4
|
+
* Detects when hooks are enabled but no authentication token is configured,
|
|
5
|
+
* allowing unauthenticated webhook requests.
|
|
6
|
+
*/
|
|
7
|
+
import { getValueAtPath } from '../utils.js';
|
|
8
|
+
const check = {
|
|
9
|
+
id: 'AUTH-005',
|
|
10
|
+
severity: 'MEDIUM',
|
|
11
|
+
name: 'Hooks token not configured',
|
|
12
|
+
execute(ctx) {
|
|
13
|
+
const hooks = getValueAtPath(ctx.config, 'hooks');
|
|
14
|
+
if (!hooks || typeof hooks !== 'object')
|
|
15
|
+
return [];
|
|
16
|
+
const hooksConfig = hooks;
|
|
17
|
+
const enabled = hooksConfig.enabled;
|
|
18
|
+
const token = hooksConfig.token;
|
|
19
|
+
// Check if hooks are enabled (explicit or implicit via having hook definitions)
|
|
20
|
+
const hasHookDefinitions = Object.keys(hooksConfig).some(key => key !== 'enabled' && key !== 'token' && typeof hooksConfig[key] === 'object');
|
|
21
|
+
const hooksEnabled = enabled === true || (enabled !== false && hasHookDefinitions);
|
|
22
|
+
if (hooksEnabled && (!token || (typeof token === 'string' && token.length === 0))) {
|
|
23
|
+
return [{
|
|
24
|
+
id: 'AUTH-005',
|
|
25
|
+
severity: 'MEDIUM',
|
|
26
|
+
name: 'Hooks token not configured',
|
|
27
|
+
location: { file: ctx.configPath, path: 'hooks.token' },
|
|
28
|
+
currentValue: token ?? 'not set',
|
|
29
|
+
expectedValue: 'A secure token string',
|
|
30
|
+
risk: 'Hooks are enabled without authentication. Attackers who discover the webhook endpoint can trigger arbitrary hook events.',
|
|
31
|
+
fix: {
|
|
32
|
+
description: 'Configure a secure token for webhook authentication',
|
|
33
|
+
command: `jq '.hooks.token = "your-secure-webhook-token"' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
|
|
34
|
+
},
|
|
35
|
+
references: ['https://docs.openclaw.ai/hooks/security#authentication'],
|
|
36
|
+
}];
|
|
37
|
+
}
|
|
38
|
+
return [];
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
export default check;
|
|
42
|
+
//# sourceMappingURL=auth-005-hooks-no-token.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-006: Node pairing credentials exposed
|
|
3
|
+
*
|
|
4
|
+
* Detects when ~/.openclaw/nodes/paired.json has permissions other than 600,
|
|
5
|
+
* potentially exposing node pairing credentials to other users.
|
|
6
|
+
*/
|
|
7
|
+
import type { Check } from './types.js';
|
|
8
|
+
declare const check: Check;
|
|
9
|
+
export default check;
|
|
10
|
+
//# sourceMappingURL=auth-006-pairing-exposed.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-006: Node pairing credentials exposed
|
|
3
|
+
*
|
|
4
|
+
* Detects when ~/.openclaw/nodes/paired.json has permissions other than 600,
|
|
5
|
+
* potentially exposing node pairing credentials to other users.
|
|
6
|
+
*/
|
|
7
|
+
import { getFileMode, formatMode } from '../utils.js';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { existsSync } from 'node:fs';
|
|
11
|
+
const check = {
|
|
12
|
+
id: 'AUTH-006',
|
|
13
|
+
severity: 'MEDIUM',
|
|
14
|
+
name: 'Node pairing credentials exposed',
|
|
15
|
+
execute(ctx) {
|
|
16
|
+
const pairedPath = join(homedir(), '.openclaw', 'nodes', 'paired.json');
|
|
17
|
+
// Skip if file doesn't exist
|
|
18
|
+
if (!existsSync(pairedPath))
|
|
19
|
+
return [];
|
|
20
|
+
const mode = getFileMode(pairedPath);
|
|
21
|
+
// Skip on Windows
|
|
22
|
+
if (mode === null)
|
|
23
|
+
return [];
|
|
24
|
+
// Check if group or other has any permissions
|
|
25
|
+
const groupOther = mode & 0o077;
|
|
26
|
+
if (groupOther > 0) {
|
|
27
|
+
return [{
|
|
28
|
+
id: 'AUTH-006',
|
|
29
|
+
severity: 'MEDIUM',
|
|
30
|
+
name: 'Node pairing credentials exposed',
|
|
31
|
+
location: { file: pairedPath, path: null },
|
|
32
|
+
currentValue: formatMode(mode),
|
|
33
|
+
expectedValue: '600',
|
|
34
|
+
risk: 'Node pairing credentials are readable by other users on the system. An attacker could use these credentials to impersonate paired nodes.',
|
|
35
|
+
fix: {
|
|
36
|
+
description: 'Restrict file permissions',
|
|
37
|
+
command: `chmod 600 ${pairedPath}`,
|
|
38
|
+
},
|
|
39
|
+
references: ['https://docs.openclaw.ai/nodes/security#pairing-credentials'],
|
|
40
|
+
}];
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
export default check;
|
|
46
|
+
//# sourceMappingURL=auth-006-pairing-exposed.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-007: Missing trusted proxies configuration
|
|
3
|
+
*
|
|
4
|
+
* Detects when gateway is bound to loopback but trustedProxies is not configured.
|
|
5
|
+
* This is a low-severity informational check because it only matters if the
|
|
6
|
+
* service is exposed through a reverse proxy.
|
|
7
|
+
*/
|
|
8
|
+
import type { Check } from './types.js';
|
|
9
|
+
declare const check: Check;
|
|
10
|
+
export default check;
|
|
11
|
+
//# sourceMappingURL=auth-007-missing-trusted-proxies.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTH-007: Missing trusted proxies configuration
|
|
3
|
+
*
|
|
4
|
+
* Detects when gateway is bound to loopback but trustedProxies is not configured.
|
|
5
|
+
* This is a low-severity informational check because it only matters if the
|
|
6
|
+
* service is exposed through a reverse proxy.
|
|
7
|
+
*/
|
|
8
|
+
import { getValueAtPath } from '../utils.js';
|
|
9
|
+
const check = {
|
|
10
|
+
id: 'AUTH-007',
|
|
11
|
+
severity: 'LOW',
|
|
12
|
+
name: 'Missing trusted proxies configuration',
|
|
13
|
+
execute(ctx) {
|
|
14
|
+
const bind = getValueAtPath(ctx.config, 'gateway.bind');
|
|
15
|
+
const trustedProxies = getValueAtPath(ctx.config, 'gateway.trustedProxies');
|
|
16
|
+
// Check if binding is loopback (or undefined, which defaults to loopback)
|
|
17
|
+
const isLoopback = bind === 'loopback' ||
|
|
18
|
+
bind === '127.0.0.1' ||
|
|
19
|
+
bind === 'localhost' ||
|
|
20
|
+
bind === undefined;
|
|
21
|
+
// Check if trusted proxies is configured
|
|
22
|
+
const hasProxies = Array.isArray(trustedProxies) && trustedProxies.length > 0;
|
|
23
|
+
if (isLoopback && !hasProxies) {
|
|
24
|
+
return [{
|
|
25
|
+
id: 'AUTH-007',
|
|
26
|
+
severity: 'LOW',
|
|
27
|
+
name: 'Missing trusted proxies configuration',
|
|
28
|
+
location: {
|
|
29
|
+
file: ctx.configPath,
|
|
30
|
+
path: 'gateway.trustedProxies',
|
|
31
|
+
},
|
|
32
|
+
currentValue: trustedProxies ?? '(not set)',
|
|
33
|
+
expectedValue: 'Array of trusted proxy IPs (if using reverse proxy)',
|
|
34
|
+
risk: 'If the Control UI is exposed through a reverse proxy without configuring trusted proxies, local-client checks can be spoofed via X-Forwarded-For headers.',
|
|
35
|
+
fix: {
|
|
36
|
+
description: 'Configure trusted proxy IPs if using a reverse proxy',
|
|
37
|
+
command: `jq '.gateway.trustedProxies = ["127.0.0.1"]' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
|
|
38
|
+
},
|
|
39
|
+
references: [],
|
|
40
|
+
}];
|
|
41
|
+
}
|
|
42
|
+
return [];
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
export default check;
|
|
46
|
+
//# sourceMappingURL=auth-007-missing-trusted-proxies.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CHAN-001: Open DM policy without allowlist
|
|
3
|
+
*
|
|
4
|
+
* Detects when a channel has open DM policy without a specific allowlist,
|
|
5
|
+
* allowing anyone to send direct messages to the bot.
|
|
6
|
+
*/
|
|
7
|
+
import type { Check } from './types.js';
|
|
8
|
+
declare const check: Check;
|
|
9
|
+
export default check;
|
|
10
|
+
//# sourceMappingURL=chan-001-open-dm.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CHAN-001: Open DM policy without allowlist
|
|
3
|
+
*
|
|
4
|
+
* Detects when a channel has open DM policy without a specific allowlist,
|
|
5
|
+
* allowing anyone to send direct messages to the bot.
|
|
6
|
+
*/
|
|
7
|
+
import { getValueAtPath } from '../utils.js';
|
|
8
|
+
const check = {
|
|
9
|
+
id: 'CHAN-001',
|
|
10
|
+
severity: 'HIGH',
|
|
11
|
+
name: 'Open DM policy without allowlist',
|
|
12
|
+
execute(ctx) {
|
|
13
|
+
const findings = [];
|
|
14
|
+
const channels = getValueAtPath(ctx.config, 'channels');
|
|
15
|
+
if (!channels || typeof channels !== 'object')
|
|
16
|
+
return [];
|
|
17
|
+
for (const [channelName, channelConfig] of Object.entries(channels)) {
|
|
18
|
+
if (!channelConfig || typeof channelConfig !== 'object')
|
|
19
|
+
continue;
|
|
20
|
+
const dmPolicy = channelConfig.dmPolicy;
|
|
21
|
+
const allowFrom = channelConfig.allowFrom;
|
|
22
|
+
if (dmPolicy === 'open') {
|
|
23
|
+
const hasAllowlist = Array.isArray(allowFrom) &&
|
|
24
|
+
allowFrom.length > 0 &&
|
|
25
|
+
!allowFrom.includes('*');
|
|
26
|
+
if (!hasAllowlist) {
|
|
27
|
+
findings.push({
|
|
28
|
+
id: 'CHAN-001',
|
|
29
|
+
severity: 'HIGH',
|
|
30
|
+
name: 'Open DM policy without allowlist',
|
|
31
|
+
location: { file: ctx.configPath, path: `channels.${channelName}.dmPolicy` },
|
|
32
|
+
currentValue: `dmPolicy: open, allowFrom: ${allowFrom ? JSON.stringify(allowFrom) : 'not set'}`,
|
|
33
|
+
expectedValue: 'dmPolicy: allowlist OR allowFrom with specific users',
|
|
34
|
+
risk: `Channel "${channelName}" accepts DMs from anyone. Attackers can send malicious prompts directly to the bot.`,
|
|
35
|
+
fix: {
|
|
36
|
+
description: 'Configure allowlist for DM policy',
|
|
37
|
+
command: `jq '.channels["${channelName}"].dmPolicy = "allowlist"' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
|
|
38
|
+
},
|
|
39
|
+
references: ['https://docs.openclaw.ai/channels/security#dm-policy'],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return findings;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
export default check;
|
|
48
|
+
//# sourceMappingURL=chan-001-open-dm.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CHAN-002: Group policy not set to allowlist
|
|
3
|
+
*
|
|
4
|
+
* Detects when a channel's groupPolicy is not set to 'allowlist',
|
|
5
|
+
* potentially allowing messages from unauthorized groups.
|
|
6
|
+
*/
|
|
7
|
+
import type { Check } from './types.js';
|
|
8
|
+
declare const check: Check;
|
|
9
|
+
export default check;
|
|
10
|
+
//# sourceMappingURL=chan-002-group-policy.d.ts.map
|