feat(doctor): audit config + state permissions
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Doctor: check config/state permissions and offer to tighten them. — thanks @steipete
|
||||||
- Doctor/Daemon: audit supervisor configs, add --repair/--force flows, surface service config audits in daemon status, and document user vs system services. — thanks @steipete
|
- Doctor/Daemon: audit supervisor configs, add --repair/--force flows, surface service config audits in daemon status, and document user vs system services. — thanks @steipete
|
||||||
- Daemon: align generated systemd unit with docs for network-online + restart delay. (#479) — thanks @azade-c
|
- Daemon: align generated systemd unit with docs for network-online + restart delay. (#479) — thanks @azade-c
|
||||||
- Outbound: default Telegram account selection for config-only tokens; remove heartbeat-specific accountId handling. (follow-up #516) — thanks @YuriNachos
|
- Outbound: default Telegram account selection for config-only tokens; remove heartbeat-specific accountId handling. (follow-up #516) — thanks @YuriNachos
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ cat ~/.clawdbot/clawdbot.json
|
|||||||
- Legacy config migration and normalization.
|
- Legacy config migration and normalization.
|
||||||
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
|
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
|
||||||
- State integrity and permissions checks (sessions, transcripts, state dir).
|
- State integrity and permissions checks (sessions, transcripts, state dir).
|
||||||
|
- Config file permission checks (chmod 600) when running locally.
|
||||||
- Legacy workspace dir detection (`~/clawdis`, `~/clawdbot`).
|
- Legacy workspace dir detection (`~/clawdis`, `~/clawdbot`).
|
||||||
- Sandbox image repair when sandboxing is enabled.
|
- Sandbox image repair when sandboxing is enabled.
|
||||||
- Legacy service migration and extra gateway detection.
|
- Legacy service migration and extra gateway detection.
|
||||||
@@ -129,6 +130,8 @@ Doctor checks:
|
|||||||
split between installs).
|
split between installs).
|
||||||
- **Remote mode reminder**: if `gateway.mode=remote`, doctor reminds you to run
|
- **Remote mode reminder**: if `gateway.mode=remote`, doctor reminds you to run
|
||||||
it on the remote host (the state lives there).
|
it on the remote host (the state lives there).
|
||||||
|
- **Config file permissions**: warns if `~/.clawdbot/clawdbot.json` is
|
||||||
|
group/world readable and offers to tighten to `600`.
|
||||||
|
|
||||||
### 5) Sandbox image repair
|
### 5) Sandbox image repair
|
||||||
When sandboxing is enabled, doctor checks Docker images and offers to build or
|
When sandboxing is enabled, doctor checks Docker images and offers to build or
|
||||||
|
|||||||
@@ -95,6 +95,14 @@ This is social engineering 101. Create distrust, encourage snooping.
|
|||||||
|
|
||||||
## Configuration Hardening (examples)
|
## Configuration Hardening (examples)
|
||||||
|
|
||||||
|
### 0) File permissions
|
||||||
|
|
||||||
|
Keep config + state private on the gateway host:
|
||||||
|
- `~/.clawdbot/clawdbot.json`: `600` (user read/write only)
|
||||||
|
- `~/.clawdbot`: `700` (user only)
|
||||||
|
|
||||||
|
`clawdbot doctor` can warn and offer to tighten these permissions.
|
||||||
|
|
||||||
### 1) DMs: pairing by default
|
### 1) DMs: pairing by default
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ function findOtherStateDirs(stateDir: string): string[] {
|
|||||||
export async function noteStateIntegrity(
|
export async function noteStateIntegrity(
|
||||||
cfg: ClawdbotConfig,
|
cfg: ClawdbotConfig,
|
||||||
prompter: DoctorPrompterLike,
|
prompter: DoctorPrompterLike,
|
||||||
|
configPath?: string,
|
||||||
) {
|
) {
|
||||||
const warnings: string[] = [];
|
const warnings: string[] = [];
|
||||||
const changes: string[] = [];
|
const changes: string[] = [];
|
||||||
@@ -186,6 +187,49 @@ export async function noteStateIntegrity(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (stateDirExists && process.platform !== "win32") {
|
||||||
|
try {
|
||||||
|
const stat = fs.statSync(stateDir);
|
||||||
|
if ((stat.mode & 0o077) !== 0) {
|
||||||
|
warnings.push(
|
||||||
|
`- State directory permissions are too open (${stateDir}). Recommend chmod 700.`,
|
||||||
|
);
|
||||||
|
const tighten = await prompter.confirmSkipInNonInteractive({
|
||||||
|
message: `Tighten permissions on ${stateDir} to 700?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (tighten) {
|
||||||
|
fs.chmodSync(stateDir, 0o700);
|
||||||
|
changes.push(`- Tightened permissions on ${stateDir} to 700`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
warnings.push(`- Failed to read ${stateDir} permissions: ${String(err)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (configPath && existsFile(configPath) && process.platform !== "win32") {
|
||||||
|
try {
|
||||||
|
const stat = fs.statSync(configPath);
|
||||||
|
if ((stat.mode & 0o077) !== 0) {
|
||||||
|
warnings.push(
|
||||||
|
`- Config file is group/world readable (${configPath}). Recommend chmod 600.`,
|
||||||
|
);
|
||||||
|
const tighten = await prompter.confirmSkipInNonInteractive({
|
||||||
|
message: `Tighten permissions on ${configPath} to 600?`,
|
||||||
|
initialValue: true,
|
||||||
|
});
|
||||||
|
if (tighten) {
|
||||||
|
fs.chmodSync(configPath, 0o600);
|
||||||
|
changes.push(`- Tightened permissions on ${configPath} to 600`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
warnings.push(
|
||||||
|
`- Failed to read config permissions (${configPath}): ${String(err)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (stateDirExists) {
|
if (stateDirExists) {
|
||||||
const dirCandidates = new Map<string, string>();
|
const dirCandidates = new Map<string, string>();
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ export async function doctorCommand(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await noteStateIntegrity(cfg, prompter);
|
await noteStateIntegrity(cfg, prompter, snapshot.path ?? CONFIG_PATH_CLAWDBOT);
|
||||||
|
|
||||||
cfg = await maybeRepairSandboxImages(cfg, runtime, prompter);
|
cfg = await maybeRepairSandboxImages(cfg, runtime, prompter);
|
||||||
noteSandboxScopeWarnings(cfg);
|
noteSandboxScopeWarnings(cfg);
|
||||||
|
|||||||
Reference in New Issue
Block a user