@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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +197 -0
  3. package/dist/checks/auth-001-device-auth-disabled.d.ts +10 -0
  4. package/dist/checks/auth-001-device-auth-disabled.js +34 -0
  5. package/dist/checks/auth-002-insecure-fallback.d.ts +10 -0
  6. package/dist/checks/auth-002-insecure-fallback.js +34 -0
  7. package/dist/checks/auth-003-no-gateway-auth.d.ts +10 -0
  8. package/dist/checks/auth-003-no-gateway-auth.js +40 -0
  9. package/dist/checks/auth-004-public-trusted-proxies.d.ts +10 -0
  10. package/dist/checks/auth-004-public-trusted-proxies.js +37 -0
  11. package/dist/checks/auth-005-hooks-no-token.d.ts +10 -0
  12. package/dist/checks/auth-005-hooks-no-token.js +42 -0
  13. package/dist/checks/auth-006-pairing-exposed.d.ts +10 -0
  14. package/dist/checks/auth-006-pairing-exposed.js +46 -0
  15. package/dist/checks/auth-007-missing-trusted-proxies.d.ts +11 -0
  16. package/dist/checks/auth-007-missing-trusted-proxies.js +46 -0
  17. package/dist/checks/chan-001-open-dm.d.ts +10 -0
  18. package/dist/checks/chan-001-open-dm.js +48 -0
  19. package/dist/checks/chan-002-group-policy.d.ts +10 -0
  20. package/dist/checks/chan-002-group-policy.js +43 -0
  21. package/dist/checks/chan-003-no-mention.d.ts +10 -0
  22. package/dist/checks/chan-003-no-mention.js +45 -0
  23. package/dist/checks/chan-004-dm-isolation.d.ts +10 -0
  24. package/dist/checks/chan-004-dm-isolation.js +50 -0
  25. package/dist/checks/chan-005-verbose-groups.d.ts +10 -0
  26. package/dist/checks/chan-005-verbose-groups.js +53 -0
  27. package/dist/checks/disc-001-mdns-full.d.ts +10 -0
  28. package/dist/checks/disc-001-mdns-full.js +34 -0
  29. package/dist/checks/disc-002-mdns-enabled.d.ts +10 -0
  30. package/dist/checks/disc-002-mdns-enabled.js +35 -0
  31. package/dist/checks/exec-001-full-security.d.ts +10 -0
  32. package/dist/checks/exec-001-full-security.js +34 -0
  33. package/dist/checks/exec-002-sandbox-disabled.d.ts +10 -0
  34. package/dist/checks/exec-002-sandbox-disabled.js +34 -0
  35. package/dist/checks/exec-003-elevated-unrestricted.d.ts +10 -0
  36. package/dist/checks/exec-003-elevated-unrestricted.js +38 -0
  37. package/dist/checks/exec-004-approval-fallback.d.ts +10 -0
  38. package/dist/checks/exec-004-approval-fallback.js +50 -0
  39. package/dist/checks/exec-005-sandbox-non-main.d.ts +10 -0
  40. package/dist/checks/exec-005-sandbox-non-main.js +34 -0
  41. package/dist/checks/exec-006-cross-agent-sandbox.d.ts +10 -0
  42. package/dist/checks/exec-006-cross-agent-sandbox.js +34 -0
  43. package/dist/checks/exec-007-workspace-rw.d.ts +10 -0
  44. package/dist/checks/exec-007-workspace-rw.js +34 -0
  45. package/dist/checks/index.d.ts +16 -0
  46. package/dist/checks/index.js +94 -0
  47. package/dist/checks/loader.d.ts +38 -0
  48. package/dist/checks/loader.js +149 -0
  49. package/dist/checks/model-001-weak-model-tools.d.ts +10 -0
  50. package/dist/checks/model-001-weak-model-tools.js +68 -0
  51. package/dist/checks/net-001-gateway-binding.d.ts +10 -0
  52. package/dist/checks/net-001-gateway-binding.js +34 -0
  53. package/dist/checks/net-002-default-port.d.ts +10 -0
  54. package/dist/checks/net-002-default-port.js +35 -0
  55. package/dist/checks/net-003-tailnet-no-token.d.ts +10 -0
  56. package/dist/checks/net-003-tailnet-no-token.js +34 -0
  57. package/dist/checks/plug-001-no-allowlist.d.ts +10 -0
  58. package/dist/checks/plug-001-no-allowlist.js +52 -0
  59. package/dist/checks/plug-002-extensions-exposed.d.ts +10 -0
  60. package/dist/checks/plug-002-extensions-exposed.js +41 -0
  61. package/dist/checks/runner.d.ts +14 -0
  62. package/dist/checks/runner.js +72 -0
  63. package/dist/checks/schema.d.ts +54 -0
  64. package/dist/checks/schema.js +171 -0
  65. package/dist/checks/sec-001-hardcoded-keys.d.ts +10 -0
  66. package/dist/checks/sec-001-hardcoded-keys.js +34 -0
  67. package/dist/checks/sec-002-world-readable-config.d.ts +10 -0
  68. package/dist/checks/sec-002-world-readable-config.js +39 -0
  69. package/dist/checks/sec-003-credentials-exposed.d.ts +10 -0
  70. package/dist/checks/sec-003-credentials-exposed.js +41 -0
  71. package/dist/checks/sec-004-env-readable.d.ts +10 -0
  72. package/dist/checks/sec-004-env-readable.js +40 -0
  73. package/dist/checks/sec-005-transcripts-exposed.d.ts +11 -0
  74. package/dist/checks/sec-005-transcripts-exposed.js +62 -0
  75. package/dist/checks/sec-006-redaction-disabled.d.ts +10 -0
  76. package/dist/checks/sec-006-redaction-disabled.js +34 -0
  77. package/dist/checks/sec-007-no-redact-patterns.d.ts +10 -0
  78. package/dist/checks/sec-007-no-redact-patterns.js +39 -0
  79. package/dist/checks/sec-008-state-dir-permissions.d.ts +10 -0
  80. package/dist/checks/sec-008-state-dir-permissions.js +49 -0
  81. package/dist/checks/types.d.ts +45 -0
  82. package/dist/checks/types.js +2 -0
  83. package/dist/clawdit-output.schema.json +162 -0
  84. package/dist/cli.d.ts +23 -0
  85. package/dist/cli.js +132 -0
  86. package/dist/config.d.ts +22 -0
  87. package/dist/config.js +150 -0
  88. package/dist/formatter.d.ts +42 -0
  89. package/dist/formatter.js +233 -0
  90. package/dist/index.d.ts +3 -0
  91. package/dist/index.js +168 -0
  92. package/dist/utils.d.ts +46 -0
  93. package/dist/utils.js +146 -0
  94. package/package.json +48 -0
@@ -0,0 +1,34 @@
1
+ /**
2
+ * EXEC-007: Workspace access too permissive
3
+ *
4
+ * Detects when workspaceAccess is set to 'rw' (read-write),
5
+ * giving agents full write access to the workspace.
6
+ */
7
+ import { getValueAtPath } from '../utils.js';
8
+ const check = {
9
+ id: 'EXEC-007',
10
+ severity: 'MEDIUM',
11
+ name: 'Workspace access too permissive',
12
+ execute(ctx) {
13
+ const workspaceAccess = getValueAtPath(ctx.config, 'workspaceAccess');
14
+ if (workspaceAccess === 'rw') {
15
+ return [{
16
+ id: 'EXEC-007',
17
+ severity: 'MEDIUM',
18
+ name: 'Workspace access too permissive',
19
+ location: { file: ctx.configPath, path: 'workspaceAccess' },
20
+ currentValue: workspaceAccess,
21
+ expectedValue: 'ro (read-only) or not set',
22
+ risk: 'Agents have read-write access to the workspace. A compromised or misbehaving agent could modify or delete files in the workspace.',
23
+ fix: {
24
+ description: 'Set workspace access to read-only',
25
+ command: `jq '.workspaceAccess = "ro"' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
26
+ },
27
+ references: ['https://docs.openclaw.ai/workspace/security#access-modes'],
28
+ }];
29
+ }
30
+ return [];
31
+ },
32
+ };
33
+ export default check;
34
+ //# sourceMappingURL=exec-007-workspace-rw.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Security checks index
3
+ *
4
+ * Exports all security checks for use by the runner.
5
+ * Checks are imported from individual files for modularity.
6
+ */
7
+ import type { Check } from './types.js';
8
+ export type { Check, Finding, CheckContext, Severity, Location, Fix, Summary, AuditResult } from './types.js';
9
+ export { runChecks, getExitCode, type RunChecksOptions } from './runner.js';
10
+ export { loadChecks, loadChecksWithErrors, discoverCheckFiles } from './loader.js';
11
+ export { validateCheck, validateCheckId, validateFilename, assertValidCheck, VALID_CATEGORIES, VALID_SEVERITIES, type ValidationError, type ValidationResult, } from './schema.js';
12
+ /**
13
+ * All security checks, sorted by ID
14
+ */
15
+ export declare const checks: Check[];
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Security checks index
3
+ *
4
+ * Exports all security checks for use by the runner.
5
+ * Checks are imported from individual files for modularity.
6
+ */
7
+ export { runChecks, getExitCode } from './runner.js';
8
+ export { loadChecks, loadChecksWithErrors, discoverCheckFiles } from './loader.js';
9
+ export { validateCheck, validateCheckId, validateFilename, assertValidCheck, VALID_CATEGORIES, VALID_SEVERITIES, } from './schema.js';
10
+ // Import individual checks
11
+ import net001 from './net-001-gateway-binding.js';
12
+ import net002 from './net-002-default-port.js';
13
+ import net003 from './net-003-tailnet-no-token.js';
14
+ import auth001 from './auth-001-device-auth-disabled.js';
15
+ import auth002 from './auth-002-insecure-fallback.js';
16
+ import auth003 from './auth-003-no-gateway-auth.js';
17
+ import auth004 from './auth-004-public-trusted-proxies.js';
18
+ import auth005 from './auth-005-hooks-no-token.js';
19
+ import auth006 from './auth-006-pairing-exposed.js';
20
+ import auth007 from './auth-007-missing-trusted-proxies.js';
21
+ import exec001 from './exec-001-full-security.js';
22
+ import exec002 from './exec-002-sandbox-disabled.js';
23
+ import exec003 from './exec-003-elevated-unrestricted.js';
24
+ import exec004 from './exec-004-approval-fallback.js';
25
+ import exec005 from './exec-005-sandbox-non-main.js';
26
+ import exec006 from './exec-006-cross-agent-sandbox.js';
27
+ import exec007 from './exec-007-workspace-rw.js';
28
+ import sec001 from './sec-001-hardcoded-keys.js';
29
+ import sec002 from './sec-002-world-readable-config.js';
30
+ import sec003 from './sec-003-credentials-exposed.js';
31
+ import sec004 from './sec-004-env-readable.js';
32
+ import sec005 from './sec-005-transcripts-exposed.js';
33
+ import sec006 from './sec-006-redaction-disabled.js';
34
+ import sec007 from './sec-007-no-redact-patterns.js';
35
+ import sec008 from './sec-008-state-dir-permissions.js';
36
+ import disc001 from './disc-001-mdns-full.js';
37
+ import disc002 from './disc-002-mdns-enabled.js';
38
+ import chan001 from './chan-001-open-dm.js';
39
+ import chan002 from './chan-002-group-policy.js';
40
+ import chan003 from './chan-003-no-mention.js';
41
+ import chan004 from './chan-004-dm-isolation.js';
42
+ import chan005 from './chan-005-verbose-groups.js';
43
+ import model001 from './model-001-weak-model-tools.js';
44
+ import plug001 from './plug-001-no-allowlist.js';
45
+ import plug002 from './plug-002-extensions-exposed.js';
46
+ /**
47
+ * All security checks, sorted by ID
48
+ */
49
+ export const checks = [
50
+ // Network checks
51
+ net001,
52
+ net002,
53
+ net003,
54
+ // Authentication checks
55
+ auth001,
56
+ auth002,
57
+ auth003,
58
+ auth004,
59
+ auth005,
60
+ auth006,
61
+ auth007,
62
+ // Execution security checks
63
+ exec001,
64
+ exec002,
65
+ exec003,
66
+ exec004,
67
+ exec005,
68
+ exec006,
69
+ exec007,
70
+ // Secrets and credentials checks
71
+ sec001,
72
+ sec002,
73
+ sec003,
74
+ sec004,
75
+ sec005,
76
+ sec006,
77
+ sec007,
78
+ sec008,
79
+ // Discovery checks
80
+ disc001,
81
+ disc002,
82
+ // Channel checks
83
+ chan001,
84
+ chan002,
85
+ chan003,
86
+ chan004,
87
+ chan005,
88
+ // Model checks
89
+ model001,
90
+ // Plugin checks
91
+ plug001,
92
+ plug002,
93
+ ];
94
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Check auto-discovery and loading
3
+ *
4
+ * Discovers all check files matching the pattern {category}-{nnn}-{desc}.ts
5
+ * and loads them as Check objects. Validates each check using the schema.
6
+ */
7
+ import type { Check } from './types.js';
8
+ /**
9
+ * Discover all check files in the checks directory
10
+ * Returns filenames sorted alphabetically
11
+ */
12
+ export declare function discoverCheckFiles(): string[];
13
+ /**
14
+ * Sort checks by category and number
15
+ */
16
+ export declare function sortChecks(checks: Check[]): Check[];
17
+ /**
18
+ * Load all checks from the checks directory
19
+ *
20
+ * This is the async version that dynamically imports check files.
21
+ * For sync usage, import checks directly from index.ts.
22
+ *
23
+ * @returns Array of validated Check objects, sorted by ID
24
+ */
25
+ export declare function loadChecks(): Promise<Check[]>;
26
+ /**
27
+ * Load checks with detailed error reporting
28
+ *
29
+ * @returns Object with loaded checks and any errors encountered
30
+ */
31
+ export declare function loadChecksWithErrors(): Promise<{
32
+ checks: Check[];
33
+ errors: Array<{
34
+ file: string;
35
+ error: string;
36
+ }>;
37
+ }>;
38
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Check auto-discovery and loading
3
+ *
4
+ * Discovers all check files matching the pattern {category}-{nnn}-{desc}.ts
5
+ * and loads them as Check objects. Validates each check using the schema.
6
+ */
7
+ import { readdirSync } from 'node:fs';
8
+ import { dirname, join } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { assertValidCheck, getCategoryFromId, getNumberFromId } from './schema.js';
11
+ /**
12
+ * Pattern for check files: category-number-description.ts
13
+ * Examples: net-001-gateway-binding.ts, auth-002-insecure-fallback.ts
14
+ */
15
+ const CHECK_FILE_PATTERN = /^([a-z]+)-(\d{3})-[a-z0-9-]+\.ts$/;
16
+ /**
17
+ * Files to exclude from check discovery
18
+ */
19
+ const EXCLUDE_FILES = [
20
+ 'index.ts',
21
+ 'types.ts',
22
+ 'runner.ts',
23
+ 'loader.ts',
24
+ 'schema.ts',
25
+ '_template.ts',
26
+ ];
27
+ /**
28
+ * File patterns to exclude (tests, templates)
29
+ */
30
+ const EXCLUDE_PATTERNS = [
31
+ /\.test\.ts$/, // Test files
32
+ /^_/, // Files starting with underscore
33
+ ];
34
+ /**
35
+ * Check if a filename should be excluded from loading
36
+ */
37
+ function shouldExclude(filename) {
38
+ if (EXCLUDE_FILES.includes(filename))
39
+ return true;
40
+ for (const pattern of EXCLUDE_PATTERNS) {
41
+ if (pattern.test(filename))
42
+ return true;
43
+ }
44
+ return false;
45
+ }
46
+ /**
47
+ * Check if a filename matches the check file pattern
48
+ */
49
+ function isCheckFile(filename) {
50
+ if (shouldExclude(filename))
51
+ return false;
52
+ return CHECK_FILE_PATTERN.test(filename);
53
+ }
54
+ /**
55
+ * Get the directory where checks are located
56
+ */
57
+ function getChecksDir() {
58
+ const __filename = fileURLToPath(import.meta.url);
59
+ return dirname(__filename);
60
+ }
61
+ /**
62
+ * Discover all check files in the checks directory
63
+ * Returns filenames sorted alphabetically
64
+ */
65
+ export function discoverCheckFiles() {
66
+ const checksDir = getChecksDir();
67
+ try {
68
+ const files = readdirSync(checksDir);
69
+ return files
70
+ .filter(isCheckFile)
71
+ .sort((a, b) => a.localeCompare(b));
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ }
77
+ /**
78
+ * Sort checks by category and number
79
+ */
80
+ export function sortChecks(checks) {
81
+ return [...checks].sort((a, b) => {
82
+ const catA = getCategoryFromId(a.id) ?? '';
83
+ const catB = getCategoryFromId(b.id) ?? '';
84
+ if (catA !== catB)
85
+ return catA.localeCompare(catB);
86
+ const numA = getNumberFromId(a.id) ?? '';
87
+ const numB = getNumberFromId(b.id) ?? '';
88
+ return numA.localeCompare(numB);
89
+ });
90
+ }
91
+ /**
92
+ * Load a single check from a file
93
+ */
94
+ async function loadCheckFile(filename) {
95
+ const checksDir = getChecksDir();
96
+ const filePath = join(checksDir, filename);
97
+ // Import expects .js extension for compiled files
98
+ const jsPath = filePath.replace(/\.ts$/, '.js');
99
+ try {
100
+ const module = await import(jsPath);
101
+ const check = module.default;
102
+ // Validate the loaded check
103
+ assertValidCheck(check, filename);
104
+ return check;
105
+ }
106
+ catch (error) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ throw new Error(`Failed to load check from ${filename}: ${message}`);
109
+ }
110
+ }
111
+ /**
112
+ * Load all checks from the checks directory
113
+ *
114
+ * This is the async version that dynamically imports check files.
115
+ * For sync usage, import checks directly from index.ts.
116
+ *
117
+ * @returns Array of validated Check objects, sorted by ID
118
+ */
119
+ export async function loadChecks() {
120
+ const files = discoverCheckFiles();
121
+ const checks = [];
122
+ for (const file of files) {
123
+ const check = await loadCheckFile(file);
124
+ checks.push(check);
125
+ }
126
+ return sortChecks(checks);
127
+ }
128
+ /**
129
+ * Load checks with detailed error reporting
130
+ *
131
+ * @returns Object with loaded checks and any errors encountered
132
+ */
133
+ export async function loadChecksWithErrors() {
134
+ const files = discoverCheckFiles();
135
+ const checks = [];
136
+ const errors = [];
137
+ for (const file of files) {
138
+ try {
139
+ const check = await loadCheckFile(file);
140
+ checks.push(check);
141
+ }
142
+ catch (error) {
143
+ const message = error instanceof Error ? error.message : String(error);
144
+ errors.push({ file, error: message });
145
+ }
146
+ }
147
+ return { checks: sortChecks(checks), errors };
148
+ }
149
+ //# sourceMappingURL=loader.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * MODEL-001: Weak model with tools enabled
3
+ *
4
+ * Detects when a weak-tier model is configured with tool execution enabled,
5
+ * which may not have adequate safety guardrails for tool use.
6
+ */
7
+ import type { Check } from './types.js';
8
+ declare const check: Check;
9
+ export default check;
10
+ //# sourceMappingURL=model-001-weak-model-tools.d.ts.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * MODEL-001: Weak model with tools enabled
3
+ *
4
+ * Detects when a weak-tier model is configured with tool execution enabled,
5
+ * which may not have adequate safety guardrails for tool use.
6
+ */
7
+ import { getValueAtPath } from '../utils.js';
8
+ /**
9
+ * Models considered "weak tier" - smaller models with potentially
10
+ * less robust safety guardrails for tool use.
11
+ */
12
+ const WEAK_TIER_MODELS = [
13
+ 'claude-instant',
14
+ 'claude-instant-1',
15
+ 'claude-instant-1.2',
16
+ 'gpt-3.5-turbo',
17
+ 'gpt-3.5-turbo-16k',
18
+ 'gpt-3.5-turbo-instruct',
19
+ 'text-davinci-003',
20
+ 'text-davinci-002',
21
+ 'mistral-tiny',
22
+ 'mistral-small',
23
+ 'open-mistral-7b',
24
+ 'open-mixtral-8x7b',
25
+ 'llama-2-7b',
26
+ 'llama-2-13b',
27
+ 'codellama-7b',
28
+ 'codellama-13b',
29
+ ];
30
+ const check = {
31
+ id: 'MODEL-001',
32
+ severity: 'LOW',
33
+ name: 'Weak model with tools enabled',
34
+ execute(ctx) {
35
+ const model = getValueAtPath(ctx.config, 'model');
36
+ const toolsExec = getValueAtPath(ctx.config, 'tools.exec');
37
+ if (!model)
38
+ return [];
39
+ // Check if model is weak tier (case-insensitive, supports prefixes)
40
+ const modelLower = model.toLowerCase();
41
+ const isWeakModel = WEAK_TIER_MODELS.some(weak => modelLower === weak.toLowerCase() ||
42
+ modelLower.startsWith(weak.toLowerCase() + '-'));
43
+ // Check if tools.exec is enabled
44
+ const toolsEnabled = toolsExec !== undefined &&
45
+ toolsExec !== false &&
46
+ toolsExec !== 'off' &&
47
+ toolsExec !== 'disabled';
48
+ if (isWeakModel && toolsEnabled) {
49
+ return [{
50
+ id: 'MODEL-001',
51
+ severity: 'LOW',
52
+ name: 'Weak model with tools enabled',
53
+ location: { file: ctx.configPath, path: 'model' },
54
+ currentValue: `model: ${model}, tools.exec: ${toolsExec}`,
55
+ expectedValue: 'A stronger model (claude-3-*, gpt-4-*) when tools are enabled',
56
+ risk: `Smaller models like "${model}" may have weaker safety guardrails for tool execution. This increases the risk of unintended or harmful tool invocations.`,
57
+ fix: {
58
+ description: 'Use a stronger model when tool execution is enabled',
59
+ command: `jq '.model = "claude-3-sonnet-20240229"' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
60
+ },
61
+ references: ['https://docs.openclaw.ai/models/security#tool-safety'],
62
+ }];
63
+ }
64
+ return [];
65
+ },
66
+ };
67
+ export default check;
68
+ //# sourceMappingURL=model-001-weak-model-tools.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * NET-001: Gateway bound to all interfaces
3
+ *
4
+ * Detects when the gateway is bound to 0.0.0.0 or lan without authentication,
5
+ * exposing it to any network interface.
6
+ */
7
+ import type { Check } from './types.js';
8
+ declare const check: Check;
9
+ export default check;
10
+ //# sourceMappingURL=net-001-gateway-binding.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * NET-001: Gateway bound to all interfaces
3
+ *
4
+ * Detects when the gateway is bound to 0.0.0.0 or lan without authentication,
5
+ * exposing it to any network interface.
6
+ */
7
+ import { getValueAtPath, hasGatewayAuth } from '../utils.js';
8
+ const check = {
9
+ id: 'NET-001',
10
+ severity: 'HIGH',
11
+ name: 'Gateway bound to all interfaces',
12
+ execute(ctx) {
13
+ const bind = getValueAtPath(ctx.config, 'gateway.bind');
14
+ if ((bind === '0.0.0.0' || bind === 'lan') && !hasGatewayAuth(ctx.config)) {
15
+ return [{
16
+ id: 'NET-001',
17
+ severity: 'HIGH',
18
+ name: 'Gateway bound to all interfaces',
19
+ location: { file: ctx.configPath, path: 'gateway.bind' },
20
+ currentValue: bind,
21
+ expectedValue: 'loopback (or configure gateway.auth.token)',
22
+ risk: 'The gateway is accessible from any network interface without authentication. Attackers on the same network can connect and control OpenClaw.',
23
+ fix: {
24
+ description: 'Bind to loopback only or configure authentication',
25
+ command: `jq '.gateway.bind = "loopback"' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
26
+ },
27
+ references: ['https://docs.openclaw.ai/gateway/security#network-binding'],
28
+ }];
29
+ }
30
+ return [];
31
+ },
32
+ };
33
+ export default check;
34
+ //# sourceMappingURL=net-001-gateway-binding.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * NET-002: Non-default gateway port
3
+ *
4
+ * Detects when the gateway is using the default port 18789,
5
+ * which is well-known and makes the service easier to discover.
6
+ */
7
+ import type { Check } from './types.js';
8
+ declare const check: Check;
9
+ export default check;
10
+ //# sourceMappingURL=net-002-default-port.d.ts.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * NET-002: Non-default gateway port
3
+ *
4
+ * Detects when the gateway is using the default port 18789,
5
+ * which is well-known and makes the service easier to discover.
6
+ */
7
+ import { getValueAtPath } from '../utils.js';
8
+ const check = {
9
+ id: 'NET-002',
10
+ severity: 'MEDIUM',
11
+ name: 'Non-default gateway port',
12
+ execute(ctx) {
13
+ const port = getValueAtPath(ctx.config, 'gateway.port');
14
+ // Default port is 18789 - flag if using default
15
+ if (port === 18789 || port === undefined) {
16
+ return [{
17
+ id: 'NET-002',
18
+ severity: 'MEDIUM',
19
+ name: 'Non-default gateway port',
20
+ location: { file: ctx.configPath, path: 'gateway.port' },
21
+ currentValue: port ?? 18789,
22
+ expectedValue: 'Non-default port (not 18789)',
23
+ risk: 'Using the default port 18789 makes the gateway easier to discover through port scanning. Attackers commonly scan for well-known service ports.',
24
+ fix: {
25
+ description: 'Configure a non-default port',
26
+ command: `jq '.gateway.port = 28789' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
27
+ },
28
+ references: ['https://docs.openclaw.ai/gateway/security#port-configuration'],
29
+ }];
30
+ }
31
+ return [];
32
+ },
33
+ };
34
+ export default check;
35
+ //# sourceMappingURL=net-002-default-port.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * NET-003: Gateway bound to tailnet without token auth
3
+ *
4
+ * Detects when the gateway is bound to a Tailscale network (tailnet)
5
+ * without token authentication, relying solely on Tailscale's network security.
6
+ */
7
+ import type { Check } from './types.js';
8
+ declare const check: Check;
9
+ export default check;
10
+ //# sourceMappingURL=net-003-tailnet-no-token.d.ts.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * NET-003: Gateway bound to tailnet without token auth
3
+ *
4
+ * Detects when the gateway is bound to a Tailscale network (tailnet)
5
+ * without token authentication, relying solely on Tailscale's network security.
6
+ */
7
+ import { getValueAtPath, hasGatewayAuth } from '../utils.js';
8
+ const check = {
9
+ id: 'NET-003',
10
+ severity: 'LOW',
11
+ name: 'Gateway bound to tailnet without token auth',
12
+ execute(ctx) {
13
+ const bind = getValueAtPath(ctx.config, 'gateway.bind');
14
+ if (bind === 'tailnet' && !hasGatewayAuth(ctx.config)) {
15
+ return [{
16
+ id: 'NET-003',
17
+ severity: 'LOW',
18
+ name: 'Gateway bound to tailnet without token auth',
19
+ location: { file: ctx.configPath, path: 'gateway.bind' },
20
+ currentValue: bind,
21
+ expectedValue: 'tailnet with gateway.auth.token configured',
22
+ risk: 'While Tailscale provides network-level security, adding token auth provides defense-in-depth. Compromised tailnet devices could access the gateway.',
23
+ fix: {
24
+ description: 'Configure token authentication for additional security',
25
+ command: `jq '.gateway.auth.token = "your-secure-token"' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
26
+ },
27
+ references: ['https://docs.openclaw.ai/gateway/security#tailnet'],
28
+ }];
29
+ }
30
+ return [];
31
+ },
32
+ };
33
+ export default check;
34
+ //# sourceMappingURL=net-003-tailnet-no-token.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * PLUG-001: Plugins without explicit allowlist
3
+ *
4
+ * Detects when extensions are installed but no plugin allowlist is configured,
5
+ * meaning any extension in the directory will be loaded without explicit approval.
6
+ */
7
+ import type { Check } from './types.js';
8
+ declare const check: Check;
9
+ export default check;
10
+ //# sourceMappingURL=plug-001-no-allowlist.d.ts.map
@@ -0,0 +1,52 @@
1
+ /**
2
+ * PLUG-001: Plugins without explicit allowlist
3
+ *
4
+ * Detects when extensions are installed but no plugin allowlist is configured,
5
+ * meaning any extension in the directory will be loaded without explicit approval.
6
+ */
7
+ import { getValueAtPath } from '../utils.js';
8
+ import { existsSync, readdirSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+ const check = {
11
+ id: 'PLUG-001',
12
+ severity: 'MEDIUM',
13
+ name: 'Plugins without explicit allowlist',
14
+ execute(ctx) {
15
+ const extensionsDir = join(ctx.configDir, 'extensions');
16
+ // Skip if extensions directory doesn't exist
17
+ if (!existsSync(extensionsDir))
18
+ return [];
19
+ // Check if directory has any contents
20
+ let hasExtensions = false;
21
+ try {
22
+ const entries = readdirSync(extensionsDir);
23
+ hasExtensions = entries.length > 0;
24
+ }
25
+ catch {
26
+ return []; // Can't read dir, skip
27
+ }
28
+ if (!hasExtensions)
29
+ return [];
30
+ // Extensions exist - check if plugins.allow is configured
31
+ const pluginsAllow = getValueAtPath(ctx.config, 'plugins.allow');
32
+ if (pluginsAllow === undefined) {
33
+ return [{
34
+ id: 'PLUG-001',
35
+ severity: 'MEDIUM',
36
+ name: 'Plugins without explicit allowlist',
37
+ location: { file: ctx.configPath, path: 'plugins.allow' },
38
+ currentValue: 'not set',
39
+ expectedValue: 'Array of allowed plugin names',
40
+ risk: 'Extensions are installed but no allowlist is configured. Any extension in the directory will be loaded without explicit approval.',
41
+ fix: {
42
+ description: 'Configure an explicit plugin allowlist',
43
+ command: `jq '.plugins.allow = ["plugin-name"]' ${ctx.configPath} > tmp.json && mv tmp.json ${ctx.configPath}`,
44
+ },
45
+ references: ['https://docs.openclaw.ai/plugins/security'],
46
+ }];
47
+ }
48
+ return [];
49
+ },
50
+ };
51
+ export default check;
52
+ //# sourceMappingURL=plug-001-no-allowlist.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * PLUG-002: Plugin directory permissions exposed
3
+ *
4
+ * Detects when the extensions directory has permissions that allow other users
5
+ * on the system to access it, potentially installing malicious plugins.
6
+ */
7
+ import type { Check } from './types.js';
8
+ declare const check: Check;
9
+ export default check;
10
+ //# sourceMappingURL=plug-002-extensions-exposed.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * PLUG-002: Plugin directory permissions exposed
3
+ *
4
+ * Detects when the extensions directory has permissions that allow other users
5
+ * on the system to access it, potentially installing malicious plugins.
6
+ */
7
+ import { getFileMode, formatMode } from '../utils.js';
8
+ import { join } from 'node:path';
9
+ const check = {
10
+ id: 'PLUG-002',
11
+ severity: 'MEDIUM',
12
+ name: 'Plugin directory permissions exposed',
13
+ execute(ctx) {
14
+ const extensionsDir = join(ctx.configDir, 'extensions');
15
+ const mode = getFileMode(extensionsDir);
16
+ // Skip on Windows or if dir doesn't exist
17
+ if (mode === null)
18
+ return [];
19
+ // Check if group or other has any permissions
20
+ const groupOther = mode & 0o077;
21
+ if (groupOther > 0) {
22
+ return [{
23
+ id: 'PLUG-002',
24
+ severity: 'MEDIUM',
25
+ name: 'Plugin directory permissions exposed',
26
+ location: { file: extensionsDir, path: null },
27
+ currentValue: formatMode(mode),
28
+ expectedValue: '700',
29
+ risk: 'Other users on the system can access the extensions directory, potentially installing malicious plugins or modifying existing ones.',
30
+ fix: {
31
+ description: 'Restrict directory permissions',
32
+ command: `chmod 700 ${extensionsDir}`,
33
+ },
34
+ references: ['https://docs.openclaw.ai/plugins/security'],
35
+ }];
36
+ }
37
+ return [];
38
+ },
39
+ };
40
+ export default check;
41
+ //# sourceMappingURL=plug-002-extensions-exposed.js.map